@aztec/sequencer-client 0.0.1-commit.0c875d939 → 0.0.1-commit.0dc957cde

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 (99) hide show
  1. package/dest/client/sequencer-client.d.ts +17 -7
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +63 -30
  4. package/dest/config.d.ts +26 -6
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +49 -29
  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 +15 -16
  14. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  15. package/dest/global_variable_builder/global_builder.js +16 -50
  16. package/dest/global_variable_builder/index.d.ts +4 -2
  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 +47 -17
  20. package/dest/publisher/config.d.ts.map +1 -1
  21. package/dest/publisher/config.js +121 -42
  22. package/dest/publisher/index.d.ts +2 -1
  23. package/dest/publisher/index.d.ts.map +1 -1
  24. package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
  25. package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
  26. package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
  27. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +58 -0
  28. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
  29. package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
  30. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
  31. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
  32. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
  33. package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
  34. package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
  35. package/dest/publisher/l1_tx_failed_store/index.js +2 -0
  36. package/dest/publisher/sequencer-publisher-factory.d.ts +11 -5
  37. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  38. package/dest/publisher/sequencer-publisher-factory.js +27 -3
  39. package/dest/publisher/sequencer-publisher.d.ts +68 -48
  40. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  41. package/dest/publisher/sequencer-publisher.js +419 -135
  42. package/dest/sequencer/chain_state_overrides.d.ts +25 -0
  43. package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
  44. package/dest/sequencer/chain_state_overrides.js +39 -0
  45. package/dest/sequencer/checkpoint_proposal_job.d.ts +40 -10
  46. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  47. package/dest/sequencer/checkpoint_proposal_job.js +522 -212
  48. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
  49. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
  50. package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
  51. package/dest/sequencer/checkpoint_voter.d.ts +1 -2
  52. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  53. package/dest/sequencer/checkpoint_voter.js +2 -5
  54. package/dest/sequencer/events.d.ts +7 -1
  55. package/dest/sequencer/events.d.ts.map +1 -1
  56. package/dest/sequencer/metrics.d.ts +18 -8
  57. package/dest/sequencer/metrics.d.ts.map +1 -1
  58. package/dest/sequencer/metrics.js +85 -21
  59. package/dest/sequencer/sequencer.d.ts +43 -17
  60. package/dest/sequencer/sequencer.d.ts.map +1 -1
  61. package/dest/sequencer/sequencer.js +158 -97
  62. package/dest/sequencer/timetable.d.ts +17 -3
  63. package/dest/sequencer/timetable.d.ts.map +1 -1
  64. package/dest/sequencer/timetable.js +51 -43
  65. package/dest/sequencer/types.d.ts +2 -2
  66. package/dest/sequencer/types.d.ts.map +1 -1
  67. package/dest/test/index.d.ts +3 -5
  68. package/dest/test/index.d.ts.map +1 -1
  69. package/dest/test/mock_checkpoint_builder.d.ts +7 -9
  70. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  71. package/dest/test/mock_checkpoint_builder.js +39 -30
  72. package/package.json +27 -28
  73. package/src/client/sequencer-client.ts +81 -30
  74. package/src/config.ts +61 -35
  75. package/src/global_variable_builder/README.md +44 -0
  76. package/src/global_variable_builder/fee_predictor.ts +172 -0
  77. package/src/global_variable_builder/fee_provider.ts +75 -0
  78. package/src/global_variable_builder/global_builder.ts +27 -63
  79. package/src/global_variable_builder/index.ts +3 -1
  80. package/src/publisher/config.ts +157 -45
  81. package/src/publisher/index.ts +3 -0
  82. package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
  83. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +57 -0
  84. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
  85. package/src/publisher/l1_tx_failed_store/index.ts +3 -0
  86. package/src/publisher/sequencer-publisher-factory.ts +38 -9
  87. package/src/publisher/sequencer-publisher.ts +472 -193
  88. package/src/sequencer/README.md +83 -13
  89. package/src/sequencer/chain_state_overrides.ts +87 -0
  90. package/src/sequencer/checkpoint_proposal_job.ts +665 -234
  91. package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
  92. package/src/sequencer/checkpoint_voter.ts +1 -12
  93. package/src/sequencer/events.ts +6 -1
  94. package/src/sequencer/metrics.ts +100 -26
  95. package/src/sequencer/sequencer.ts +227 -109
  96. package/src/sequencer/timetable.ts +64 -52
  97. package/src/sequencer/types.ts +1 -1
  98. package/src/test/index.ts +2 -4
  99. package/src/test/mock_checkpoint_builder.ts +51 -48
@@ -1,9 +1,11 @@
1
- import { createLogger } from '@aztec/aztec.js/log';
1
+ import type { Logger } from '@aztec/foundation/log';
2
2
  import {
3
3
  CHECKPOINT_ASSEMBLE_TIME,
4
4
  CHECKPOINT_INITIALIZATION_TIME,
5
+ type CheckpointTiming,
5
6
  DEFAULT_P2P_PROPAGATION_TIME,
6
7
  MIN_EXECUTION_TIME,
8
+ createCheckpointTimingModel,
7
9
  } from '@aztec/stdlib/timetable';
8
10
 
9
11
  import { SequencerTooSlowError } from './errors.js';
@@ -11,6 +13,8 @@ import type { SequencerMetrics } from './metrics.js';
11
13
  import { SequencerState } from './utils.js';
12
14
 
13
15
  export class SequencerTimetable {
16
+ private readonly checkpointTiming: CheckpointTiming;
17
+
14
18
  /**
15
19
  * How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
16
20
  * assuming an execution time equal to `minExecutionTime`, subtracted from the slot duration. This means that, if the proposer
@@ -70,6 +74,15 @@ export class SequencerTimetable {
70
74
  /** Maximum number of blocks that can be built in this slot configuration */
71
75
  public readonly maxNumberOfBlocks: number;
72
76
 
77
+ /** Whether pipelining is enabled (checkpoint finalization deferred to next slot). */
78
+ public readonly pipelining: boolean;
79
+
80
+ /**
81
+ * How far into the target slot attestation collection can extend when pipelining.
82
+ * Covers validator re-execution (one block duration) plus one-way attestation return.
83
+ */
84
+ public readonly pipeliningAttestationGracePeriod: number;
85
+
73
86
  constructor(
74
87
  opts: {
75
88
  ethereumSlotDuration: number;
@@ -78,61 +91,41 @@ export class SequencerTimetable {
78
91
  p2pPropagationTime?: number;
79
92
  blockDurationMs?: number;
80
93
  enforce: boolean;
94
+ pipelining?: boolean;
81
95
  },
82
96
  private readonly metrics?: SequencerMetrics,
83
- private readonly log = createLogger('sequencer:timetable'),
97
+ private readonly log?: Logger,
84
98
  ) {
85
99
  this.ethereumSlotDuration = opts.ethereumSlotDuration;
86
100
  this.aztecSlotDuration = opts.aztecSlotDuration;
87
101
  this.l1PublishingTime = opts.l1PublishingTime;
88
- this.p2pPropagationTime = opts.p2pPropagationTime ?? DEFAULT_P2P_PROPAGATION_TIME;
89
102
  this.blockDuration = opts.blockDurationMs ? opts.blockDurationMs / 1000 : undefined;
90
103
  this.enforce = opts.enforce;
104
+ this.pipelining = opts.pipelining ?? false;
105
+
106
+ this.checkpointTiming = createCheckpointTimingModel({
107
+ aztecSlotDuration: this.aztecSlotDuration,
108
+ ethereumSlotDuration: this.ethereumSlotDuration,
109
+ blockDuration: this.blockDuration,
110
+ l1PublishingTime: this.l1PublishingTime,
111
+ p2pPropagationTime: opts.p2pPropagationTime ?? DEFAULT_P2P_PROPAGATION_TIME,
112
+ pipelining: this.pipelining,
113
+ });
91
114
 
92
- // Assume zero-cost propagation time and faster runs in test environments where L1 slot duration is shortened
93
- if (this.ethereumSlotDuration < 8) {
94
- this.p2pPropagationTime = 0;
95
- this.checkpointAssembleTime = 0.5;
96
- this.checkpointInitializationTime = 0.5;
97
- this.minExecutionTime = 1;
98
- }
99
-
100
- // Min execution time cannot be less than the block duration if set
101
- if (this.blockDuration !== undefined && this.minExecutionTime > this.blockDuration) {
102
- this.minExecutionTime = this.blockDuration;
103
- }
115
+ this.p2pPropagationTime = this.checkpointTiming.p2pPropagationTime;
116
+ this.checkpointAssembleTime = this.checkpointTiming.checkpointAssembleTime;
117
+ this.checkpointInitializationTime = this.checkpointTiming.checkpointInitializationTime;
118
+ this.minExecutionTime = this.checkpointTiming.minExecutionTime;
104
119
 
105
120
  // Calculate initialization offset - estimate of time needed for sync + proposer check
106
121
  // This is the baseline for all sub-slot deadlines
107
- this.initializationOffset = this.checkpointInitializationTime;
108
-
109
- // Calculate total checkpoint finalization time (assembly + attestations + L1 publishing)
110
- this.checkpointFinalizationTime =
111
- this.checkpointAssembleTime +
112
- this.p2pPropagationTime * 2 + // Round-trip propagation
113
- this.l1PublishingTime; // L1 publishing
114
-
115
- // Calculate maximum number of blocks that fit in this slot
116
- if (!this.blockDuration) {
117
- this.maxNumberOfBlocks = 1; // Single block per slot
118
- } else {
119
- const timeReservedAtEnd =
120
- this.blockDuration + // Last sub-slot for validator re-execution
121
- this.checkpointFinalizationTime; // Checkpoint finalization
122
- const timeAvailableForBlocks = this.aztecSlotDuration - this.initializationOffset - timeReservedAtEnd;
123
- this.maxNumberOfBlocks = Math.floor(timeAvailableForBlocks / this.blockDuration);
124
- }
122
+ this.initializationOffset = this.checkpointTiming.checkpointInitializationTime;
123
+ this.checkpointFinalizationTime = this.checkpointTiming.checkpointFinalizationTime;
124
+ this.pipeliningAttestationGracePeriod = this.checkpointTiming.pipeliningAttestationGracePeriod;
125
+ this.maxNumberOfBlocks = this.checkpointTiming.calculateMaxBlocksPerSlot();
126
+ this.initializeDeadline = this.checkpointTiming.initializeDeadline;
125
127
 
126
- // Minimum work to do within a slot for building a block with the minimum time for execution and publishing its checkpoint
127
- const minWorkToDo =
128
- this.initializationOffset +
129
- this.minExecutionTime * 2 + // Execution and reexecution
130
- this.checkpointFinalizationTime;
131
-
132
- const initializeDeadline = this.aztecSlotDuration - minWorkToDo;
133
- this.initializeDeadline = initializeDeadline;
134
-
135
- this.log.verbose(
128
+ this.log?.info(
136
129
  `Sequencer timetable initialized with ${this.maxNumberOfBlocks} blocks per slot (${this.enforce ? 'enforced' : 'not enforced'})`,
137
130
  {
138
131
  ethereumSlotDuration: this.ethereumSlotDuration,
@@ -144,19 +137,37 @@ export class SequencerTimetable {
144
137
  blockAssembleTime: this.checkpointAssembleTime,
145
138
  initializeDeadline: this.initializeDeadline,
146
139
  enforce: this.enforce,
147
- minWorkToDo,
140
+ pipelining: this.pipelining,
141
+ pipeliningAttestationGracePeriod: this.pipeliningAttestationGracePeriod,
142
+ minWorkToDo: this.checkpointTiming.minimumBuildSlotWork,
148
143
  blockDuration: this.blockDuration,
149
144
  maxNumberOfBlocks: this.maxNumberOfBlocks,
150
145
  },
151
146
  );
152
147
 
153
- if (initializeDeadline <= 0) {
148
+ if (this.initializeDeadline <= 0) {
154
149
  throw new Error(
155
- `Block proposal initialize deadline cannot be negative (got ${initializeDeadline} from total time needed ${minWorkToDo} and a slot duration of ${this.aztecSlotDuration}).`,
150
+ `Block proposal initialize deadline cannot be negative (got ${this.initializeDeadline} from total time needed ${this.checkpointTiming.minimumBuildSlotWork} and a slot duration of ${this.aztecSlotDuration}).`,
156
151
  );
157
152
  }
158
153
  }
159
154
 
155
+ public getCheckpointAssemblyDeadline(): number {
156
+ return this.checkpointTiming.checkpointAssemblyDeadline;
157
+ }
158
+
159
+ public getCheckpointAttestationDeadline(): number {
160
+ return this.checkpointTiming.checkpointAttestationDeadline;
161
+ }
162
+
163
+ public getCheckpointAttestationStartDeadline(): number {
164
+ return this.checkpointTiming.checkpointAttestationStartDeadline;
165
+ }
166
+
167
+ public getCheckpointPublishingDeadline(): number {
168
+ return this.checkpointTiming.checkpointPublishingDeadline;
169
+ }
170
+
160
171
  public getMaxAllowedTime(
161
172
  state: Extract<SequencerState, SequencerState.STOPPED | SequencerState.IDLE | SequencerState.SYNCHRONIZING>,
162
173
  ): undefined;
@@ -179,10 +190,11 @@ export class SequencerTimetable {
179
190
  case SequencerState.WAITING_UNTIL_NEXT_BLOCK:
180
191
  return this.initializeDeadline + this.checkpointInitializationTime;
181
192
  case SequencerState.ASSEMBLING_CHECKPOINT:
193
+ return this.getCheckpointAssemblyDeadline();
182
194
  case SequencerState.COLLECTING_ATTESTATIONS:
183
- return this.aztecSlotDuration - this.l1PublishingTime - 2 * this.p2pPropagationTime;
195
+ return this.getCheckpointAttestationStartDeadline();
184
196
  case SequencerState.PUBLISHING_CHECKPOINT:
185
- return this.aztecSlotDuration - this.l1PublishingTime;
197
+ return this.getCheckpointPublishingDeadline();
186
198
  default: {
187
199
  const _exhaustiveCheck: never = state;
188
200
  throw new Error(`Unexpected state: ${state}`);
@@ -206,7 +218,7 @@ export class SequencerTimetable {
206
218
  }
207
219
 
208
220
  this.metrics?.recordStateTransitionBufferMs(Math.floor(bufferSeconds * 1000), newState);
209
- this.log.trace(`Enough time to transition to ${newState}`, { maxAllowedTime, secondsIntoSlot });
221
+ this.log?.trace(`Enough time to transition to ${newState}`, { maxAllowedTime, secondsIntoSlot });
210
222
  }
211
223
 
212
224
  /**
@@ -242,7 +254,7 @@ export class SequencerTimetable {
242
254
  const canStart = available >= this.minExecutionTime;
243
255
  const deadline = secondsIntoSlot + available;
244
256
 
245
- this.log.verbose(
257
+ this.log?.verbose(
246
258
  `${canStart ? 'Can' : 'Cannot'} start single-block checkpoint at ${secondsIntoSlot}s into slot`,
247
259
  { secondsIntoSlot, maxAllowed, available, deadline },
248
260
  );
@@ -262,7 +274,7 @@ export class SequencerTimetable {
262
274
  // Found an available sub-slot! Is this the last one?
263
275
  const isLastBlock = subSlot === this.maxNumberOfBlocks;
264
276
 
265
- this.log.verbose(
277
+ this.log?.verbose(
266
278
  `Can start ${isLastBlock ? 'last block' : 'block'} in sub-slot ${subSlot} with deadline ${deadline}s`,
267
279
  { secondsIntoSlot, deadline, timeUntilDeadline, subSlot, maxBlocks: this.maxNumberOfBlocks },
268
280
  );
@@ -272,7 +284,7 @@ export class SequencerTimetable {
272
284
  }
273
285
 
274
286
  // No sub-slots available with enough time
275
- this.log.verbose(`No time left to start any more blocks`, {
287
+ this.log?.verbose(`No time left to start any more blocks`, {
276
288
  secondsIntoSlot,
277
289
  maxBlocks: this.maxNumberOfBlocks,
278
290
  initializationOffset: this.initializationOffset,
@@ -2,5 +2,5 @@ import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
2
2
 
3
3
  export type SequencerRollupConstants = Pick<
4
4
  L1RollupConstants,
5
- 'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration'
5
+ 'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration' | 'rollupManaLimit'
6
6
  >;
package/src/test/index.ts CHANGED
@@ -1,18 +1,16 @@
1
- import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
1
+ import type { L1TxUtils } from '@aztec/ethereum/l1-tx-utils';
2
2
  import type { PublisherManager } from '@aztec/ethereum/publisher-manager';
3
3
  import type { PublicProcessorFactory } from '@aztec/simulator/server';
4
4
  import type { FullNodeCheckpointsBuilder, ValidatorClient } from '@aztec/validator-client';
5
5
 
6
6
  import { SequencerClient } from '../client/sequencer-client.js';
7
7
  import type { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
8
- import type { SequencerPublisher } from '../publisher/sequencer-publisher.js';
9
8
  import { Sequencer } from '../sequencer/sequencer.js';
10
9
  import type { SequencerTimetable } from '../sequencer/timetable.js';
11
10
 
12
11
  class TestSequencer_ extends Sequencer {
13
12
  declare public publicProcessorFactory: PublicProcessorFactory;
14
13
  declare public timetable: SequencerTimetable;
15
- declare public publisher: SequencerPublisher;
16
14
  declare public publisherFactory: SequencerPublisherFactory;
17
15
  declare public validatorClient: ValidatorClient;
18
16
  declare public checkpointsBuilder: FullNodeCheckpointsBuilder;
@@ -22,7 +20,7 @@ export type TestSequencer = TestSequencer_;
22
20
 
23
21
  class TestSequencerClient_ extends SequencerClient {
24
22
  declare public sequencer: TestSequencer;
25
- declare public publisherManager: PublisherManager<L1TxUtilsWithBlobs>;
23
+ declare public publisherManager: PublisherManager<L1TxUtils>;
26
24
  }
27
25
 
28
26
  export type TestSequencerClient = TestSequencerClient_;
@@ -1,14 +1,14 @@
1
- import { type BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
1
+ import { type BlockNumber, CheckpointNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types';
2
2
  import { Fr } from '@aztec/foundation/curves/bn254';
3
+ import { unfreeze } from '@aztec/foundation/types';
3
4
  import { L2Block } from '@aztec/stdlib/block';
4
5
  import { Checkpoint } from '@aztec/stdlib/checkpoint';
5
- import { Gas } from '@aztec/stdlib/gas';
6
6
  import type {
7
+ BlockBuilderOptions,
7
8
  FullNodeBlockBuilderConfig,
8
9
  ICheckpointBlockBuilder,
9
10
  ICheckpointsBuilder,
10
11
  MerkleTreeWriteOperations,
11
- PublicProcessorLimits,
12
12
  } from '@aztec/stdlib/interfaces/server';
13
13
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
14
14
  import { makeAppendOnlyTreeSnapshot } from '@aztec/stdlib/testing';
@@ -32,7 +32,7 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
32
32
  public buildBlockCalls: Array<{
33
33
  blockNumber: BlockNumber;
34
34
  timestamp: bigint;
35
- opts: PublicProcessorLimits;
35
+ opts: BlockBuilderOptions;
36
36
  }> = [];
37
37
  /** Track all consumed transaction hashes across buildBlock calls */
38
38
  public consumedTxHashes: Set<string> = new Set();
@@ -74,7 +74,7 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
74
74
  pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
75
75
  blockNumber: BlockNumber,
76
76
  timestamp: bigint,
77
- opts: PublicProcessorLimits,
77
+ opts: BlockBuilderOptions,
78
78
  ): Promise<BuildBlockInCheckpointResult> {
79
79
  this.buildBlockCalls.push({ blockNumber, timestamp, opts });
80
80
 
@@ -86,8 +86,10 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
86
86
  let usedTxs: Tx[];
87
87
 
88
88
  if (this.blockProvider) {
89
- // Dynamic mode: get block from provider
90
- block = this.blockProvider();
89
+ // Dynamic mode: get block from provider, cloning to avoid shared references across multiple buildBlock calls
90
+ block = L2Block.fromBuffer(this.blockProvider().toBuffer());
91
+ block.header.globalVariables.blockNumber = blockNumber;
92
+ await block.header.recomputeHash();
91
93
  usedTxs = [];
92
94
  this.builtBlocks.push(block);
93
95
  } else {
@@ -113,81 +115,79 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
113
115
 
114
116
  return {
115
117
  block,
116
- publicGas: Gas.empty(),
117
118
  publicProcessorDuration: 0,
118
119
  numTxs: block?.body?.txEffects?.length ?? usedTxs.length,
119
120
  usedTxs,
120
121
  failedTxs: [],
121
- usedTxBlobFields: block?.body?.txEffects?.reduce((sum, tx) => sum + tx.getNumBlobFields(), 0) ?? 0,
122
122
  };
123
123
  }
124
124
 
125
125
  completeCheckpoint(): Promise<Checkpoint> {
126
126
  this.completeCheckpointCalled = true;
127
127
  const allBlocks = this.blockProvider ? this.builtBlocks : this.blocks;
128
- const lastBlock = allBlocks[allBlocks.length - 1];
129
- // Create a CheckpointHeader from the last block's header for testing
130
- const checkpointHeader = this.createCheckpointHeader(lastBlock);
131
- return Promise.resolve(
132
- new Checkpoint(
133
- makeAppendOnlyTreeSnapshot(lastBlock.header.globalVariables.blockNumber + 1),
134
- checkpointHeader,
135
- allBlocks,
136
- this.checkpointNumber,
137
- ),
138
- );
128
+ return this.buildCheckpoint(allBlocks);
139
129
  }
140
130
 
141
131
  getCheckpoint(): Promise<Checkpoint> {
142
132
  this.getCheckpointCalled = true;
143
133
  const builtBlocks = this.blockProvider ? this.builtBlocks : this.blocks.slice(0, this.blockIndex);
144
- const lastBlock = builtBlocks[builtBlocks.length - 1];
145
- if (!lastBlock) {
134
+ if (builtBlocks.length === 0) {
146
135
  throw new Error('No blocks built yet');
147
136
  }
148
- // Create a CheckpointHeader from the last block's header for testing
149
- const checkpointHeader = this.createCheckpointHeader(lastBlock);
150
- return Promise.resolve(
151
- new Checkpoint(
152
- makeAppendOnlyTreeSnapshot(lastBlock.header.globalVariables.blockNumber + 1),
153
- checkpointHeader,
154
- builtBlocks,
155
- this.checkpointNumber,
156
- ),
157
- );
137
+ return this.buildCheckpoint(builtBlocks);
158
138
  }
159
139
 
160
- /**
161
- * Creates a CheckpointHeader from a block's header for testing.
162
- * This is a simplified version that creates a minimal CheckpointHeader.
163
- */
164
- private createCheckpointHeader(block: L2Block): CheckpointHeader {
165
- const header = block.header;
166
- const gv = header.globalVariables;
167
- return CheckpointHeader.empty({
168
- lastArchiveRoot: header.lastArchive.root,
169
- blockHeadersHash: Fr.random(), // Use random for testing
140
+ /** Builds a structurally valid Checkpoint from a list of blocks, fixing up indexes and archive chaining. */
141
+ private async buildCheckpoint(blocks: L2Block[]): Promise<Checkpoint> {
142
+ // Fix up indexWithinCheckpoint and archive chaining so the checkpoint passes structural validation.
143
+ for (let i = 0; i < blocks.length; i++) {
144
+ blocks[i].indexWithinCheckpoint = IndexWithinCheckpoint(i);
145
+ if (i > 0) {
146
+ unfreeze(blocks[i].header).lastArchive = blocks[i - 1].archive;
147
+ await blocks[i].header.recomputeHash();
148
+ }
149
+ }
150
+
151
+ const firstBlock = blocks[0];
152
+ const lastBlock = blocks[blocks.length - 1];
153
+ const gv = firstBlock.header.globalVariables;
154
+
155
+ const checkpointHeader = CheckpointHeader.empty({
156
+ lastArchiveRoot: firstBlock.header.lastArchive.root,
157
+ blockHeadersHash: Fr.random(),
170
158
  slotNumber: gv.slotNumber,
171
159
  timestamp: gv.timestamp,
172
160
  coinbase: gv.coinbase,
173
161
  feeRecipient: gv.feeRecipient,
174
162
  gasFees: gv.gasFees,
175
- totalManaUsed: header.totalManaUsed,
163
+ totalManaUsed: lastBlock.header.totalManaUsed,
176
164
  });
165
+
166
+ return new Checkpoint(
167
+ makeAppendOnlyTreeSnapshot(lastBlock.header.globalVariables.blockNumber + 1),
168
+ checkpointHeader,
169
+ blocks,
170
+ this.checkpointNumber,
171
+ );
177
172
  }
178
173
 
179
- /** Reset for reuse in another test */
180
- reset(): void {
181
- this.blocks = [];
174
+ /** Resets per-checkpoint state (built blocks, consumed txs) while preserving config (blockProvider, seeded blocks). */
175
+ resetCheckpointState(): void {
182
176
  this.builtBlocks = [];
183
- this.usedTxsPerBlock = [];
184
177
  this.blockIndex = 0;
185
- this.buildBlockCalls = [];
186
178
  this.consumedTxHashes.clear();
187
179
  this.completeCheckpointCalled = false;
188
180
  this.getCheckpointCalled = false;
181
+ }
182
+
183
+ /** Reset for reuse in another test */
184
+ reset(): void {
185
+ this.blocks = [];
186
+ this.usedTxsPerBlock = [];
187
+ this.buildBlockCalls = [];
189
188
  this.errorOnBuild = undefined;
190
189
  this.blockProvider = undefined;
190
+ this.resetCheckpointState();
191
191
  }
192
192
  }
193
193
 
@@ -249,6 +249,7 @@ export class MockCheckpointsBuilder implements ICheckpointsBuilder {
249
249
  slotDuration: 24,
250
250
  l1ChainId: 1,
251
251
  rollupVersion: 1,
252
+ rollupManaLimit: 200_000_000,
252
253
  };
253
254
  }
254
255
 
@@ -275,6 +276,8 @@ export class MockCheckpointsBuilder implements ICheckpointsBuilder {
275
276
  if (!this.checkpointBuilder) {
276
277
  // Auto-create a builder if none was set
277
278
  this.checkpointBuilder = new MockCheckpointBuilder(constants, checkpointNumber);
279
+ } else {
280
+ this.checkpointBuilder.resetCheckpointState();
278
281
  }
279
282
 
280
283
  return Promise.resolve(this.checkpointBuilder);