@aztec/sequencer-client 0.0.1-commit.f650c0a5c → 0.0.1-commit.f7ea82942

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 (65) hide show
  1. package/dest/client/sequencer-client.d.ts +3 -1
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +3 -0
  4. package/dest/config.d.ts +1 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +8 -11
  7. package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
  8. package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
  9. package/dest/global_variable_builder/fee_predictor.js +128 -0
  10. package/dest/global_variable_builder/fee_provider.d.ts +21 -0
  11. package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
  12. package/dest/global_variable_builder/fee_provider.js +58 -0
  13. package/dest/global_variable_builder/global_builder.d.ts +4 -9
  14. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  15. package/dest/global_variable_builder/global_builder.js +3 -41
  16. package/dest/global_variable_builder/index.d.ts +3 -1
  17. package/dest/global_variable_builder/index.d.ts.map +1 -1
  18. package/dest/global_variable_builder/index.js +2 -0
  19. package/dest/publisher/config.d.ts +1 -1
  20. package/dest/publisher/config.d.ts.map +1 -1
  21. package/dest/publisher/config.js +2 -2
  22. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +3 -4
  23. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -1
  24. package/dest/publisher/sequencer-publisher.d.ts +10 -28
  25. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  26. package/dest/publisher/sequencer-publisher.js +24 -43
  27. package/dest/sequencer/chain_state_overrides.d.ts +25 -0
  28. package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
  29. package/dest/sequencer/chain_state_overrides.js +39 -0
  30. package/dest/sequencer/checkpoint_proposal_job.d.ts +24 -21
  31. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  32. package/dest/sequencer/checkpoint_proposal_job.js +263 -120
  33. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
  34. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
  35. package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
  36. package/dest/sequencer/events.d.ts +6 -1
  37. package/dest/sequencer/events.d.ts.map +1 -1
  38. package/dest/sequencer/metrics.d.ts +7 -10
  39. package/dest/sequencer/metrics.d.ts.map +1 -1
  40. package/dest/sequencer/metrics.js +27 -20
  41. package/dest/sequencer/sequencer.d.ts +5 -2
  42. package/dest/sequencer/sequencer.d.ts.map +1 -1
  43. package/dest/sequencer/sequencer.js +15 -11
  44. package/dest/sequencer/timetable.d.ts +11 -1
  45. package/dest/sequencer/timetable.d.ts.map +1 -1
  46. package/dest/sequencer/timetable.js +42 -43
  47. package/package.json +27 -27
  48. package/src/client/sequencer-client.ts +5 -0
  49. package/src/config.ts +8 -11
  50. package/src/global_variable_builder/README.md +44 -0
  51. package/src/global_variable_builder/fee_predictor.ts +172 -0
  52. package/src/global_variable_builder/fee_provider.ts +75 -0
  53. package/src/global_variable_builder/global_builder.ts +7 -54
  54. package/src/global_variable_builder/index.ts +2 -0
  55. package/src/publisher/config.ts +6 -4
  56. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +3 -1
  57. package/src/publisher/sequencer-publisher.ts +49 -83
  58. package/src/sequencer/README.md +9 -8
  59. package/src/sequencer/chain_state_overrides.ts +87 -0
  60. package/src/sequencer/checkpoint_proposal_job.ts +328 -162
  61. package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
  62. package/src/sequencer/events.ts +5 -0
  63. package/src/sequencer/metrics.ts +35 -25
  64. package/src/sequencer/sequencer.ts +22 -11
  65. package/src/sequencer/timetable.ts +50 -49
@@ -1,16 +1,18 @@
1
- import { RollupContract } from '@aztec/ethereum/contracts';
1
+ import {
2
+ RollupContract,
3
+ type SimulationOverridesPlan,
4
+ buildSimulationOverridesStateOverride,
5
+ } from '@aztec/ethereum/contracts';
2
6
  import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
3
7
  import type { ViemPublicClient } from '@aztec/ethereum/types';
4
8
  import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
5
9
  import { Fr } from '@aztec/foundation/curves/bn254';
6
10
  import type { EthAddress } from '@aztec/foundation/eth-address';
7
- import { createLogger } from '@aztec/foundation/log';
8
11
  import type { DateProvider } from '@aztec/foundation/timer';
9
12
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
10
13
  import { type L1RollupConstants, getNextL1SlotTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
11
14
  import { GasFees } from '@aztec/stdlib/gas';
12
15
  import type {
13
- BuildCheckpointGlobalVariablesOpts,
14
16
  CheckpointGlobalVariables,
15
17
  GlobalVariableBuilder as GlobalVariableBuilderInterface,
16
18
  } from '@aztec/stdlib/tx';
@@ -27,10 +29,6 @@ export type GlobalVariableBuilderConfig = {
27
29
  * Simple global variables builder.
28
30
  */
29
31
  export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
30
- private log = createLogger('sequencer:global_variable_builder');
31
- private currentMinFees: Promise<GasFees> = Promise.resolve(new GasFees(0, 0));
32
- private currentL1BlockNumber: bigint | undefined = undefined;
33
-
34
32
  private readonly rollupContract: RollupContract;
35
33
  private readonly ethereumSlotDuration: number;
36
34
  private readonly aztecSlotDuration: number;
@@ -54,40 +52,6 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
54
52
  this.rollupContract = new RollupContract(this.publicClient, config.l1Contracts.rollupAddress);
55
53
  }
56
54
 
57
- /**
58
- * Computes the "current" min fees, e.g., the price that you currently should pay to get include in the next block
59
- * @returns Min fees for the next block
60
- */
61
- private async computeCurrentMinFees(): Promise<GasFees> {
62
- // Since this might be called in the middle of a slot where a block might have been published,
63
- // we need to fetch the last block written, and estimate the earliest timestamp for the next block.
64
- // The timestamp of that last block will act as a lower bound for the next block.
65
-
66
- const lastCheckpoint = await this.rollupContract.getPendingCheckpoint();
67
- const earliestTimestamp = await this.rollupContract.getTimestampForSlot(
68
- SlotNumber.fromBigInt(BigInt(lastCheckpoint.slotNumber) + 1n),
69
- );
70
- const nextEthTimestamp = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
71
- l1GenesisTime: this.l1GenesisTime,
72
- ethereumSlotDuration: this.ethereumSlotDuration,
73
- });
74
- const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp;
75
-
76
- return new GasFees(0, await this.rollupContract.getManaMinFeeAt(timestamp, true));
77
- }
78
-
79
- public async getCurrentMinFees(): Promise<GasFees> {
80
- // Get the current block number
81
- const blockNumber = await this.publicClient.getBlockNumber();
82
-
83
- // If the L1 block number has changed then chain a new promise to get the current min fees
84
- if (this.currentL1BlockNumber === undefined || blockNumber > this.currentL1BlockNumber) {
85
- this.currentL1BlockNumber = blockNumber;
86
- this.currentMinFees = this.currentMinFees.then(() => this.computeCurrentMinFees());
87
- }
88
- return this.currentMinFees;
89
- }
90
-
91
55
  /**
92
56
  * Simple builder of global variables.
93
57
  * @param blockNumber - The block number to build global variables for.
@@ -120,7 +84,7 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
120
84
  coinbase: EthAddress,
121
85
  feeRecipient: AztecAddress,
122
86
  slotNumber: SlotNumber,
123
- opts?: BuildCheckpointGlobalVariablesOpts,
87
+ simulationOverridesPlan?: SimulationOverridesPlan,
124
88
  ): Promise<CheckpointGlobalVariables> {
125
89
  const { chainId, version } = this;
126
90
 
@@ -129,18 +93,7 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
129
93
  l1GenesisTime: this.l1GenesisTime,
130
94
  });
131
95
 
132
- // When pipelining, force the proposed checkpoint number and fee header to the parent so that
133
- // the fee computation matches what L1 will see when the previous pipelined checkpoint has landed.
134
- const pendingNumberOverride = await this.rollupContract.makePendingCheckpointNumberOverride(
135
- opts?.forcePendingCheckpointNumber,
136
- );
137
- const feeHeaderOverride = opts?.forceProposedFeeHeader
138
- ? await this.rollupContract.makeFeeHeaderOverride(
139
- opts.forceProposedFeeHeader.checkpointNumber,
140
- opts.forceProposedFeeHeader.feeHeader,
141
- )
142
- : [];
143
- const stateOverride = RollupContract.mergeStateOverrides(pendingNumberOverride, feeHeaderOverride);
96
+ const stateOverride = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
144
97
  const gasFees = new GasFees(0, await this.rollupContract.getManaMinFeeAt(timestamp, true, stateOverride));
145
98
 
146
99
  return { chainId, version, slotNumber, timestamp, coinbase, feeRecipient, gasFees };
@@ -1 +1,3 @@
1
+ export { FeeProviderImpl } from './fee_provider.js';
1
2
  export { GlobalVariableBuilder, type GlobalVariableBuilderConfig } from './global_builder.js';
3
+ export { FeePredictor } from './fee_predictor.js';
@@ -120,7 +120,8 @@ export const proverTxSenderConfigMappings: ConfigMappingsType<Omit<ProverTxSende
120
120
  proverPublisherPrivateKeys: {
121
121
  env: `PROVER_PUBLISHER_PRIVATE_KEYS`,
122
122
  description: 'The private keys to be used by the prover publisher.',
123
- parseEnv: (val: string) => val.split(',').map(key => new SecretValue(`0x${key.replace('0x', '')}`)),
123
+ parseEnv: (val: string) =>
124
+ val.split(',').map(key => new SecretValue(`0x${key.replace('0x', '')}` as `0x${string}`)),
124
125
  defaultValue: [],
125
126
  fallback: [`PROVER_PUBLISHER_PRIVATE_KEY`],
126
127
  },
@@ -137,7 +138,8 @@ export const sequencerTxSenderConfigMappings: ConfigMappingsType<Omit<SequencerT
137
138
  sequencerPublisherPrivateKeys: {
138
139
  env: `SEQ_PUBLISHER_PRIVATE_KEYS`,
139
140
  description: 'The private keys to be used by the sequencer publisher.',
140
- parseEnv: (val: string) => val.split(',').map(key => new SecretValue(`0x${key.replace('0x', '')}`)),
141
+ parseEnv: (val: string) =>
142
+ val.split(',').map(key => new SecretValue(`0x${key.replace('0x', '')}` as `0x${string}`)),
141
143
  defaultValue: [],
142
144
  fallback: [`SEQ_PUBLISHER_PRIVATE_KEY`],
143
145
  },
@@ -166,7 +168,7 @@ export const sequencerPublisherConfigMappings: ConfigMappingsType<SequencerPubli
166
168
  sequencerPublisherForwarderAddress: {
167
169
  env: `SEQ_PUBLISHER_FORWARDER_ADDRESS`,
168
170
  description: 'Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only)',
169
- parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
171
+ parseEnv: (val: string) => EthAddress.fromString(val),
170
172
  },
171
173
  l1TxFailedStore: {
172
174
  env: 'L1_TX_FAILED_STORE',
@@ -192,7 +194,7 @@ export const proverPublisherConfigMappings: ConfigMappingsType<ProverPublisherCo
192
194
  proverPublisherForwarderAddress: {
193
195
  env: `PROVER_PUBLISHER_FORWARDER_ADDRESS`,
194
196
  description: 'Address of the forwarder contract to wrap all L1 transactions through (for testing purposes only)',
195
- parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
197
+ parseEnv: (val: string) => EthAddress.fromString(val),
196
198
  },
197
199
  ...publisherFundingConfigMappings,
198
200
  };
@@ -1,7 +1,9 @@
1
+ import type { Branded } from '@aztec/foundation/branded-types';
2
+
1
3
  import type { Hex } from 'viem';
2
4
 
3
5
  /** URI pointing to a stored failed L1 transaction. */
4
- export type FailedL1TxUri = string & { __brand: 'FailedL1TxUri' };
6
+ export type FailedL1TxUri = Branded<string, 'FailedL1TxUri'>;
5
7
 
6
8
  /** A failed L1 transaction captured for debugging and replay. */
7
9
  export type FailedL1Tx = {
@@ -4,15 +4,17 @@ import type { EpochCache } from '@aztec/epoch-cache';
4
4
  import type { L1ContractsConfig } from '@aztec/ethereum/config';
5
5
  import {
6
6
  FeeAssetPriceOracle,
7
- type FeeHeader,
8
7
  type GovernanceProposerContract,
9
8
  type IEmpireBase,
10
9
  MULTI_CALL_3_ADDRESS,
11
10
  Multicall3,
12
11
  RollupContract,
12
+ SimulationOverridesBuilder,
13
+ type SimulationOverridesPlan,
13
14
  type SlashingProposerContract,
14
15
  type ViemCommitteeAttestations,
15
16
  type ViemHeader,
17
+ buildSimulationOverridesStateOverride,
16
18
  } from '@aztec/ethereum/contracts';
17
19
  import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
18
20
  import {
@@ -26,7 +28,6 @@ import {
26
28
  } from '@aztec/ethereum/l1-tx-utils';
27
29
  import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
28
30
  import { sumBigint } from '@aztec/foundation/bigint';
29
- import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
30
31
  import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
31
32
  import { trimmedBytesLength } from '@aztec/foundation/buffer';
32
33
  import { pick } from '@aztec/foundation/collection';
@@ -50,7 +51,6 @@ import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from
50
51
 
51
52
  import {
52
53
  type Hex,
53
- type StateOverride,
54
54
  type TransactionReceipt,
55
55
  type TypedDataDefinition,
56
56
  encodeFunctionData,
@@ -119,6 +119,11 @@ export type InvalidateCheckpointRequest = {
119
119
  lastArchive: Fr;
120
120
  };
121
121
 
122
+ type EnqueueProposeCheckpointOpts = {
123
+ txTimeoutAt?: Date;
124
+ simulationOverridesPlan?: SimulationOverridesPlan;
125
+ };
126
+
122
127
  interface RequestWithExpiry {
123
128
  action: Action;
124
129
  request: L1TxRequest;
@@ -662,27 +667,21 @@ export class SequencerPublisher {
662
667
  * @param tipArchive - The archive to check
663
668
  * @returns The slot and block number if it is possible to propose, undefined otherwise
664
669
  */
665
- public canProposeAt(
666
- tipArchive: Fr,
667
- msgSender: EthAddress,
668
- opts: {
669
- forcePendingCheckpointNumber?: CheckpointNumber;
670
- forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
671
- pipelined?: boolean;
672
- } = {},
673
- ) {
670
+ public async canProposeAt(tipArchive: Fr, msgSender: EthAddress, simulationOverridesPlan?: SimulationOverridesPlan) {
674
671
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
675
672
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
676
673
 
677
- const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
674
+ const pipelined = this.epochCache.isProposerPipeliningEnabled();
678
675
  const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
679
676
  const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
680
677
 
681
678
  return this.rollupContract
682
- .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
683
- forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
684
- forceArchive: opts.forceArchive,
685
- })
679
+ .canProposeAt(
680
+ tipArchive.toBuffer(),
681
+ msgSender.toString(),
682
+ nextL1SlotTs,
683
+ await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan),
684
+ )
686
685
  .catch(err => {
687
686
  if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
688
687
  this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
@@ -704,7 +703,7 @@ export class SequencerPublisher {
704
703
  @trackSpan('SequencerPublisher.validateBlockHeader')
705
704
  public async validateBlockHeader(
706
705
  header: CheckpointHeader,
707
- opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
706
+ simulationOverridesPlan?: SimulationOverridesPlan,
708
707
  ): Promise<void> {
709
708
  const flags = { ignoreDA: true, ignoreSignatures: true };
710
709
 
@@ -719,9 +718,7 @@ export class SequencerPublisher {
719
718
  ] as const;
720
719
 
721
720
  const ts = this.getSimulationTimestamp(header.slotNumber);
722
- const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
723
- opts?.forcePendingCheckpointNumber,
724
- );
721
+ const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
725
722
  let balance = 0n;
726
723
  if (this.config.fishermanMode) {
727
724
  // In fisherman mode, we can't know where the proposer is publishing from
@@ -882,10 +879,7 @@ export class SequencerPublisher {
882
879
  checkpoint: Checkpoint,
883
880
  attestationsAndSigners: CommitteeAttestationsAndSigners,
884
881
  attestationsAndSignersSignature: Signature,
885
- options: {
886
- forcePendingCheckpointNumber?: CheckpointNumber;
887
- forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
888
- },
882
+ simulationOverridesPlan?: SimulationOverridesPlan,
889
883
  ): Promise<void> {
890
884
  const blobFields = checkpoint.toBlobFields();
891
885
  const blobs = await getBlobsPerL1Block(blobFields);
@@ -905,7 +899,7 @@ export class SequencerPublisher {
905
899
  blobInput,
906
900
  ] as const;
907
901
 
908
- await this.simulateProposeTx(args, options);
902
+ await this.simulateProposeTx(args, simulationOverridesPlan);
909
903
  }
910
904
 
911
905
  private async enqueueCastSignalHelper(
@@ -1154,11 +1148,7 @@ export class SequencerPublisher {
1154
1148
  checkpoint: Checkpoint,
1155
1149
  attestationsAndSigners: CommitteeAttestationsAndSigners,
1156
1150
  attestationsAndSignersSignature: Signature,
1157
- opts: {
1158
- txTimeoutAt?: Date;
1159
- forcePendingCheckpointNumber?: CheckpointNumber;
1160
- forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
1161
- } = {},
1151
+ opts: EnqueueProposeCheckpointOpts = {},
1162
1152
  ): Promise<void> {
1163
1153
  const checkpointHeader = checkpoint.header;
1164
1154
 
@@ -1174,6 +1164,10 @@ export class SequencerPublisher {
1174
1164
  feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
1175
1165
  };
1176
1166
 
1167
+ const simulationOverridesPlan = SimulationOverridesBuilder.from(opts.simulationOverridesPlan)
1168
+ .withoutBlobCheck()
1169
+ .build();
1170
+
1177
1171
  try {
1178
1172
  // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
1179
1173
  // This means that we can avoid the simulation issues in later checks.
@@ -1184,13 +1178,13 @@ export class SequencerPublisher {
1184
1178
  checkpoint,
1185
1179
  attestationsAndSigners,
1186
1180
  attestationsAndSignersSignature,
1187
- opts,
1181
+ simulationOverridesPlan,
1188
1182
  );
1189
1183
  } catch (err: any) {
1190
1184
  this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
1191
1185
  ...checkpoint.getStats(),
1192
1186
  slotNumber: checkpoint.header.slotNumber,
1193
- forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
1187
+ simulationOverridesPlan,
1194
1188
  });
1195
1189
  throw err;
1196
1190
  }
@@ -1205,16 +1199,22 @@ export class SequencerPublisher {
1205
1199
  checkpoint,
1206
1200
  attestationsAndSigners,
1207
1201
  attestationsAndSignersSignature,
1208
- {
1209
- // Forcing pending checkpoint number is included its required if an invalidation request is included
1210
- forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
1211
- },
1202
+ simulationOverridesPlan,
1212
1203
  );
1213
1204
  };
1214
1205
  }
1215
1206
 
1216
- this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
1217
- await this.addProposeTx(checkpoint, proposeTxArgs, opts, preCheck);
1207
+ this.log.verbose(`Enqueuing checkpoint propose transaction`, {
1208
+ ...checkpoint.toCheckpointInfo(),
1209
+ txTimeoutAt: opts.txTimeoutAt,
1210
+ simulationOverridesPlan,
1211
+ });
1212
+ await this.addProposeTx(
1213
+ checkpoint,
1214
+ proposeTxArgs,
1215
+ { txTimeoutAt: opts.txTimeoutAt, simulationOverridesPlan },
1216
+ preCheck,
1217
+ );
1218
1218
  }
1219
1219
 
1220
1220
  public enqueueInvalidateCheckpoint(
@@ -1344,10 +1344,7 @@ export class SequencerPublisher {
1344
1344
  this.l1TxUtils.restart();
1345
1345
  }
1346
1346
 
1347
- private async prepareProposeTx(
1348
- encodedData: L1ProcessArgs,
1349
- options: { forcePendingCheckpointNumber?: CheckpointNumber },
1350
- ) {
1347
+ private async prepareProposeTx(encodedData: L1ProcessArgs, simulationOverridesPlan?: SimulationOverridesPlan) {
1351
1348
  const kzg = Blob.getViemKzgInstance();
1352
1349
  const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
1353
1350
  this.log.debug('Validating blob input', { blobInput });
@@ -1418,7 +1415,7 @@ export class SequencerPublisher {
1418
1415
  blobInput,
1419
1416
  ] as const;
1420
1417
 
1421
- const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
1418
+ const { rollupData, simulationResult } = await this.simulateProposeTx(args, simulationOverridesPlan);
1422
1419
 
1423
1420
  return { args, blobEvaluationGas, rollupData, simulationResult };
1424
1421
  }
@@ -1442,10 +1439,7 @@ export class SequencerPublisher {
1442
1439
  ViemSignature,
1443
1440
  `0x${string}`,
1444
1441
  ],
1445
- options: {
1446
- forcePendingCheckpointNumber?: CheckpointNumber;
1447
- forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
1448
- },
1442
+ simulationOverridesPlan?: SimulationOverridesPlan,
1449
1443
  ) {
1450
1444
  const rollupData = encodeFunctionData({
1451
1445
  abi: RollupAbi,
@@ -1453,34 +1447,7 @@ export class SequencerPublisher {
1453
1447
  args,
1454
1448
  });
1455
1449
 
1456
- // override the proposed checkpoint number if requested
1457
- const forcePendingCheckpointNumberStateDiff = (
1458
- options.forcePendingCheckpointNumber !== undefined
1459
- ? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
1460
- : []
1461
- ).flatMap(override => override.stateDiff ?? []);
1462
-
1463
- // override the fee header for a specific checkpoint number if requested (used when pipelining)
1464
- const forceProposedFeeHeaderStateDiff = (
1465
- options.forceProposedFeeHeader !== undefined
1466
- ? await this.rollupContract.makeFeeHeaderOverride(
1467
- options.forceProposedFeeHeader.checkpointNumber,
1468
- options.forceProposedFeeHeader.feeHeader,
1469
- )
1470
- : []
1471
- ).flatMap(override => override.stateDiff ?? []);
1472
-
1473
- const stateOverrides: StateOverride = [
1474
- {
1475
- address: this.rollupContract.address,
1476
- // @note we override checkBlob to false since blobs are not part simulate()
1477
- stateDiff: [
1478
- { slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
1479
- ...forcePendingCheckpointNumberStateDiff,
1480
- ...forceProposedFeeHeaderStateDiff,
1481
- ],
1482
- },
1483
- ];
1450
+ const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
1484
1451
  // In fisherman mode, simulate as the proposer but with sufficient balance
1485
1452
  if (this.proposerAddressForSimulation) {
1486
1453
  stateOverrides.push({
@@ -1545,17 +1512,16 @@ export class SequencerPublisher {
1545
1512
  private async addProposeTx(
1546
1513
  checkpoint: Checkpoint,
1547
1514
  encodedData: L1ProcessArgs,
1548
- opts: {
1549
- txTimeoutAt?: Date;
1550
- forcePendingCheckpointNumber?: CheckpointNumber;
1551
- forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
1552
- } = {},
1515
+ opts: EnqueueProposeCheckpointOpts = {},
1553
1516
  preCheck?: () => Promise<void>,
1554
1517
  ): Promise<void> {
1555
1518
  const slot = checkpoint.header.slotNumber;
1556
1519
  const timer = new Timer();
1557
1520
  const kzg = Blob.getViemKzgInstance();
1558
- const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
1521
+ const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
1522
+ encodedData,
1523
+ opts.simulationOverridesPlan,
1524
+ );
1559
1525
  const startBlock = await this.l1TxUtils.getBlockNumber();
1560
1526
  const gasLimit = this.l1TxUtils.bumpGasLimit(
1561
1527
  BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
@@ -1578,7 +1544,7 @@ export class SequencerPublisher {
1578
1544
  data: rollupData,
1579
1545
  },
1580
1546
  lastValidL2Slot: checkpoint.header.slotNumber,
1581
- gasConfig: { ...opts, gasLimit },
1547
+ gasConfig: { txTimeoutAt: opts.txTimeoutAt, gasLimit },
1582
1548
  preCheck,
1583
1549
  blobConfig: {
1584
1550
  blobs: encodedData.blobs.map(b => b.data),
@@ -88,7 +88,8 @@ checkpointFinalizationTime = propagationTime
88
88
  timeReservedAtEnd (normal mode) = blockDuration (last sub-slot for reexecution)
89
89
  + checkpointFinalizationTime
90
90
 
91
- timeReservedAtEnd (pipelining) = blockDuration (last sub-slot for reexecution only)
91
+ timeReservedAtEnd (pipelining) = assembleTime
92
+ + propagationTime (proposal must reach validators before the slot flips)
92
93
 
93
94
  timeAvailableForBlocks = slotDuration - initializationOffset - timeReservedAtEnd
94
95
 
@@ -109,9 +110,9 @@ This means:
109
110
 
110
111
  **The same slot with proposer pipelining enabled:**
111
112
  ```
112
- timeReservedAtEnd = 8s
113
- timeAvailableForBlocks = 72s - 2s - 8s = 62s
114
- numberOfBlocks = floor(62s / 8s) = 7 blocks
113
+ timeReservedAtEnd = 1s + 2s = 3s
114
+ timeAvailableForBlocks = 72s - 2s - 3s = 67s
115
+ numberOfBlocks = floor(67s / 8s) = 8 blocks
115
116
  ```
116
117
 
117
118
  The extra two block opportunities come from not charging the current slot for checkpoint finalization and L1 publishing.
@@ -128,8 +129,8 @@ It helps to think in terms of two different slots:
128
129
  So the work is split like this:
129
130
 
130
131
  - **During slot N-1**: Initialization, block building, and last-block re-execution
131
- - **Near the end of slot N-1**: The checkpoint proposal is broadcast and validators attest to checkpoint N.
132
- - **During slot N**: The proposer collects signatures, and the checkpoint is submitted to L1
132
+ - **Near the end of slot N-1**: The checkpoint proposal is broadcast so validators can start the last re-execution as slot `N` begins.
133
+ - **During slot N**: Validators finish re-executing, send attestations, the proposer collects them, and the checkpoint is submitted to L1 before slot `N` reaches its publish cutoff
133
134
 
134
135
  In other words, pipelining does not mean "do everything for slot N earlier". It specifically moves **block production and block re-execution** earlier, while **checkpoint proposal, attestation gathering, and L1 submission** remain aligned with slot `N`.
135
136
 
@@ -142,7 +143,7 @@ Slot 11 (wall clock):
142
143
  - Collect checkpoint 12 attestations
143
144
 
144
145
  Slot 12 (target/submission slot):
145
- - Collect remaining checkpoint 12 attestations
146
+ - Collect attestations for checkpoint 12 until slot 12 reaches its L1 publish cutoff
146
147
  - Submit checkpoint 12 to L1
147
148
  ```
148
149
 
@@ -534,7 +535,7 @@ The sequencer transitions through these states during a slot:
534
535
  | **WAITING_UNTIL_NEXT_BLOCK** | Until next sub-slot start | Sleep between blocks to maintain intervals |
535
536
  | **ASSEMBLING_CHECKPOINT** | assembleTime (1s) | Assemble final checkpoint |
536
537
  | **COLLECTING_ATTESTATIONS** | Until L1 publish deadline | Wait for validator signatures |
537
- | **PUBLISHING_CHECKPOINT** | Until slot end | Submit to L1 |
538
+ | **PUBLISHING_CHECKPOINT** | Until L1 publish deadline | Submit to L1 |
538
539
 
539
540
  ## Complete Example: 72-Second Slot with 8-Second Sub-Slots
540
541
 
@@ -0,0 +1,87 @@
1
+ import { RollupContract, SimulationOverridesBuilder, type SimulationOverridesPlan } from '@aztec/ethereum/contracts';
2
+ import { CheckpointNumber } from '@aztec/foundation/branded-types';
3
+ import type { Fr } from '@aztec/foundation/curves/bn254';
4
+ import type { Logger } from '@aztec/foundation/log';
5
+ import type { ProposedCheckpointData } from '@aztec/stdlib/checkpoint';
6
+
7
+ type PipelinedParentSimulationOverridesPlanInput = {
8
+ checkpointNumber: CheckpointNumber;
9
+ proposedCheckpointData?: ProposedCheckpointData;
10
+ rollup: RollupContract;
11
+ log: Logger;
12
+ };
13
+
14
+ type SubmissionSimulationOverridesPlanInput = {
15
+ pipelinedParentPlan?: SimulationOverridesPlan;
16
+ invalidateToPendingCheckpointNumber?: CheckpointNumber;
17
+ lastArchiveRoot: Fr;
18
+ pipeliningEnabled: boolean;
19
+ };
20
+
21
+ /** Builds the simulated parent checkpoint view used while constructing a pipelined proposal. */
22
+ export async function buildPipelinedParentSimulationOverridesPlan(
23
+ input: PipelinedParentSimulationOverridesPlanInput,
24
+ ): Promise<SimulationOverridesPlan | undefined> {
25
+ const parentCheckpointNumber = CheckpointNumber(input.checkpointNumber - 1);
26
+ const builder = new SimulationOverridesBuilder().forPendingCheckpoint(parentCheckpointNumber);
27
+
28
+ const pendingFeeHeader = await computePipelinedParentFeeHeader(input);
29
+ if (pendingFeeHeader) {
30
+ builder.withPendingFeeHeader(pendingFeeHeader);
31
+ }
32
+
33
+ return builder.build();
34
+ }
35
+
36
+ /** Builds the simulated chain view used when validating and enqueueing checkpoint submission. */
37
+ export function buildSubmissionSimulationOverridesPlan(
38
+ input: SubmissionSimulationOverridesPlanInput,
39
+ ): SimulationOverridesPlan | undefined {
40
+ const pendingCheckpointNumber =
41
+ input.invalidateToPendingCheckpointNumber ?? input.pipelinedParentPlan?.pendingCheckpointNumber;
42
+
43
+ const builder = SimulationOverridesBuilder.from(input.pipelinedParentPlan).forPendingCheckpoint(
44
+ pendingCheckpointNumber,
45
+ );
46
+
47
+ if (input.pipeliningEnabled && pendingCheckpointNumber !== undefined) {
48
+ builder.withPendingArchive(input.lastArchiveRoot);
49
+ }
50
+
51
+ return builder.build();
52
+ }
53
+
54
+ /** Derives the pending parent fee header used during pipelined proposal simulation. */
55
+ export async function computePipelinedParentFeeHeader(input: PipelinedParentSimulationOverridesPlanInput) {
56
+ if (!input.proposedCheckpointData || input.checkpointNumber < 2) {
57
+ return undefined;
58
+ }
59
+
60
+ const grandparentCheckpointNumber = CheckpointNumber(input.checkpointNumber - 2);
61
+
62
+ try {
63
+ const [grandparentCheckpoint, manaTarget] = await Promise.all([
64
+ input.rollup.getCheckpoint(grandparentCheckpointNumber),
65
+ input.rollup.getManaTarget(),
66
+ ]);
67
+
68
+ if (!grandparentCheckpoint?.feeHeader) {
69
+ input.log.error(
70
+ `Grandparent checkpoint or feeHeader missing for checkpoint ${grandparentCheckpointNumber.toString()}`,
71
+ );
72
+ return undefined;
73
+ }
74
+
75
+ return RollupContract.computeChildFeeHeader(
76
+ grandparentCheckpoint.feeHeader,
77
+ input.proposedCheckpointData.totalManaUsed,
78
+ input.proposedCheckpointData.feeAssetPriceModifier,
79
+ manaTarget,
80
+ );
81
+ } catch (err) {
82
+ input.log.error(
83
+ `Failed to derive pipelined parent fee header for checkpoint ${grandparentCheckpointNumber.toString()}: ${err}`,
84
+ );
85
+ return undefined;
86
+ }
87
+ }