@aztec/sequencer-client 0.0.1-commit.b64cb54f6 → 0.0.1-commit.b6e433891

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 (35) hide show
  1. package/dest/client/sequencer-client.d.ts +1 -12
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +19 -63
  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 +1 -1
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +5 -4
  10. package/dest/publisher/sequencer-publisher.d.ts +6 -4
  11. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  12. package/dest/publisher/sequencer-publisher.js +24 -8
  13. package/dest/sequencer/checkpoint_proposal_job.d.ts +12 -4
  14. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  15. package/dest/sequencer/checkpoint_proposal_job.js +137 -93
  16. package/dest/sequencer/events.d.ts +2 -1
  17. package/dest/sequencer/events.d.ts.map +1 -1
  18. package/dest/sequencer/metrics.d.ts +5 -1
  19. package/dest/sequencer/metrics.d.ts.map +1 -1
  20. package/dest/sequencer/metrics.js +11 -0
  21. package/dest/sequencer/sequencer.d.ts +6 -4
  22. package/dest/sequencer/sequencer.d.ts.map +1 -1
  23. package/dest/sequencer/sequencer.js +57 -45
  24. package/dest/test/mock_checkpoint_builder.d.ts +4 -4
  25. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  26. package/package.json +27 -28
  27. package/src/client/sequencer-client.ts +26 -84
  28. package/src/config.ts +12 -1
  29. package/src/global_variable_builder/global_builder.ts +3 -2
  30. package/src/publisher/sequencer-publisher.ts +28 -10
  31. package/src/sequencer/checkpoint_proposal_job.ts +186 -96
  32. package/src/sequencer/events.ts +1 -1
  33. package/src/sequencer/metrics.ts +14 -0
  34. package/src/sequencer/sequencer.ts +82 -51
  35. package/src/test/mock_checkpoint_builder.ts +3 -3
@@ -192,10 +192,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
192
192
  @trackSpan('Sequencer.work')
193
193
  protected async work() {
194
194
  this.setState(SequencerState.SYNCHRONIZING, undefined);
195
- const { slot, ts, now, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
195
+ const { slot, ts, nowSeconds, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
196
+ const { slot: targetSlot, epoch: targetEpoch } = this.epochCache.getTargetEpochAndSlotInNextL1Slot();
196
197
 
197
198
  // Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
198
- const checkpointProposalJob = await this.prepareCheckpointProposal(epoch, slot, ts, now);
199
+ const checkpointProposalJob = await this.prepareCheckpointProposal(
200
+ slot,
201
+ targetSlot,
202
+ epoch,
203
+ targetEpoch,
204
+ ts,
205
+ nowSeconds,
206
+ );
199
207
  if (!checkpointProposalJob) {
200
208
  return;
201
209
  }
@@ -208,13 +216,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
208
216
  this.lastCheckpointProposed = checkpoint;
209
217
  }
210
218
 
211
- // Log fee strategy comparison if on fisherman
219
+ // Log fee strategy comparison if on fisherman (uses target epoch since we mirror the proposer's perspective)
212
220
  if (
213
221
  this.config.fishermanMode &&
214
- (this.lastEpochForStrategyComparison === undefined || epoch > this.lastEpochForStrategyComparison)
222
+ (this.lastEpochForStrategyComparison === undefined || targetEpoch > this.lastEpochForStrategyComparison)
215
223
  ) {
216
- this.logStrategyComparison(epoch, checkpointProposalJob.getPublisher());
217
- this.lastEpochForStrategyComparison = epoch;
224
+ this.logStrategyComparison(targetEpoch, checkpointProposalJob.getPublisher());
225
+ this.lastEpochForStrategyComparison = targetEpoch;
218
226
  }
219
227
 
220
228
  return checkpoint;
@@ -227,43 +235,48 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
227
235
  */
228
236
  @trackSpan('Sequencer.prepareCheckpointProposal')
229
237
  private async prepareCheckpointProposal(
230
- epoch: EpochNumber,
231
238
  slot: SlotNumber,
239
+ targetSlot: SlotNumber,
240
+ epoch: EpochNumber,
241
+ targetEpoch: EpochNumber,
232
242
  ts: bigint,
233
- now: bigint,
243
+ nowSeconds: bigint,
234
244
  ): Promise<CheckpointProposalJob | undefined> {
235
- // Check we have not already processed this slot (cheapest check)
245
+ // Check we have not already processed this target slot (cheapest check)
236
246
  // We only check this if enforce timetable is set, since we want to keep processing the same slot if we are not
237
247
  // running against actual time (eg when we use sandbox-style automining)
238
248
  if (
239
249
  this.lastSlotForCheckpointProposalJob &&
240
- this.lastSlotForCheckpointProposalJob >= slot &&
250
+ this.lastSlotForCheckpointProposalJob >= targetSlot &&
241
251
  this.config.enforceTimeTable
242
252
  ) {
243
- this.log.trace(`Slot ${slot} has already been processed`);
253
+ this.log.trace(`Target slot ${targetSlot} has already been processed`);
244
254
  return undefined;
245
255
  }
246
256
 
247
- // But if we have already proposed for this slot, the we definitely have to skip it, automining or not
248
- if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >= slot) {
249
- this.log.trace(`Slot ${slot} has already been published as checkpoint ${this.lastCheckpointProposed.number}`);
257
+ // But if we have already proposed for this slot, then we definitely have to skip it, automining or not
258
+ if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >= targetSlot) {
259
+ this.log.trace(
260
+ `Slot ${targetSlot} has already been published as checkpoint ${this.lastCheckpointProposed.number}`,
261
+ );
250
262
  return undefined;
251
263
  }
252
264
 
253
265
  // Check all components are synced to latest as seen by the archiver (queries all subsystems)
254
266
  const syncedTo = await this.checkSync({ ts, slot });
255
267
  if (!syncedTo) {
256
- await this.tryVoteWhenSyncFails({ slot, ts });
268
+ await this.tryVoteWhenSyncFails({ slot, targetSlot, ts });
257
269
  return undefined;
258
270
  }
259
271
 
260
- // If escape hatch is open for this epoch, do not start checkpoint proposal work and do not attempt invalidations.
272
+ // If escape hatch is open for the target epoch, do not start checkpoint proposal work and do not attempt invalidations.
261
273
  // Still perform governance/slashing voting (as proposer) once per slot.
262
- const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(epoch);
274
+ // When pipelining, we check the target epoch (slot+1's epoch) since that's the epoch we're building for.
275
+ const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(targetEpoch);
263
276
 
264
277
  if (isEscapeHatchOpen) {
265
278
  this.setState(SequencerState.PROPOSER_CHECK, slot);
266
- const [canPropose, proposer] = await this.checkCanPropose(slot);
279
+ const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
267
280
  if (canPropose) {
268
281
  await this.tryVoteWhenEscapeHatchOpen({ slot, proposer });
269
282
  } else {
@@ -280,17 +293,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
280
293
  const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
281
294
 
282
295
  const logCtx = {
283
- now,
296
+ nowSeconds,
284
297
  syncedToL2Slot: syncedTo.syncedL2Slot,
285
298
  slot,
299
+ targetSlot,
286
300
  slotTs: ts,
287
301
  checkpointNumber,
288
302
  isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex'),
289
303
  };
290
304
 
291
- // Check that we are a proposer for the next slot
305
+ // Check that we are a proposer for the target slot.
292
306
  this.setState(SequencerState.PROPOSER_CHECK, slot);
293
- const [canPropose, proposer] = await this.checkCanPropose(slot);
307
+ const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
294
308
 
295
309
  // If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
296
310
  if (!canPropose) {
@@ -298,10 +312,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
298
312
  return undefined;
299
313
  }
300
314
 
301
- // Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
302
- if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= slot) {
315
+ // Check that the target slot is not taken by a block already (should never happen, since only us can propose for this slot)
316
+ if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= targetSlot) {
303
317
  this.log.warn(
304
- `Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`,
318
+ `Cannot propose block at target slot ${targetSlot} since that slot was taken by block ${syncedTo.blockNumber}`,
305
319
  { ...logCtx, block: syncedTo.blockData.header.toInspect() },
306
320
  );
307
321
  this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
@@ -325,13 +339,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
325
339
  // Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
326
340
  const invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
327
341
 
328
- // Check with the rollup contract if we can indeed propose at the next L2 slot. This check should not fail
342
+ // Check with the rollup contract if we can indeed propose at the target slot. This check should not fail
329
343
  // if all the previous checks are good, but we do it just in case.
330
- const canProposeCheck = await publisher.canProposeAtNextEthBlock(
331
- syncedTo.archive,
332
- proposer ?? EthAddress.ZERO,
333
- invalidateCheckpoint,
334
- );
344
+ const canProposeCheck = await publisher.canProposeAt(syncedTo.archive, proposer ?? EthAddress.ZERO, {
345
+ ...invalidateCheckpoint,
346
+ });
335
347
 
336
348
  if (canProposeCheck === undefined) {
337
349
  this.log.warn(
@@ -343,10 +355,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
343
355
  return undefined;
344
356
  }
345
357
 
346
- if (canProposeCheck.slot !== slot) {
358
+ if (canProposeCheck.slot !== targetSlot) {
347
359
  this.log.warn(
348
- `Cannot propose block due to slot mismatch with rollup contract (this can be caused by a clock out of sync). Expected slot ${slot} but got ${canProposeCheck.slot}.`,
349
- { ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
360
+ `Cannot propose block due to slot mismatch with rollup contract (this can be caused by a clock out of sync). Expected slot ${targetSlot} but got ${canProposeCheck.slot}.`,
361
+ { ...logCtx, rollup: canProposeCheck, expectedSlot: targetSlot },
350
362
  );
351
363
  this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch', slot });
352
364
  this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
@@ -363,14 +375,24 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
363
375
  return undefined;
364
376
  }
365
377
 
366
- this.lastSlotForCheckpointProposalJob = slot;
367
- await this.p2pClient.prepareForSlot(slot);
368
- this.log.info(`Preparing checkpoint proposal ${checkpointNumber} at slot ${slot}`, { ...logCtx, proposer });
378
+ this.lastSlotForCheckpointProposalJob = targetSlot;
379
+
380
+ await this.p2pClient.prepareForSlot(targetSlot);
381
+ this.log.info(
382
+ `Preparing checkpoint proposal ${checkpointNumber} for target slot ${targetSlot} during wall-clock slot ${slot}`,
383
+ {
384
+ ...logCtx,
385
+ proposer,
386
+ pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled(),
387
+ },
388
+ );
369
389
 
370
390
  // Create and return the checkpoint proposal job
371
391
  return this.createCheckpointProposalJob(
372
- epoch,
373
392
  slot,
393
+ targetSlot,
394
+ epoch,
395
+ targetEpoch,
374
396
  checkpointNumber,
375
397
  syncedTo.blockNumber,
376
398
  proposer,
@@ -381,8 +403,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
381
403
  }
382
404
 
383
405
  protected createCheckpointProposalJob(
384
- epoch: EpochNumber,
385
406
  slot: SlotNumber,
407
+ targetSlot: SlotNumber,
408
+ epoch: EpochNumber,
409
+ targetEpoch: EpochNumber,
386
410
  checkpointNumber: CheckpointNumber,
387
411
  syncedToBlockNumber: BlockNumber,
388
412
  proposer: EthAddress | undefined,
@@ -391,8 +415,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
391
415
  invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
392
416
  ): CheckpointProposalJob {
393
417
  return new CheckpointProposalJob(
394
- epoch,
395
418
  slot,
419
+ targetSlot,
420
+ epoch,
421
+ targetEpoch,
396
422
  checkpointNumber,
397
423
  syncedToBlockNumber,
398
424
  proposer,
@@ -548,20 +574,20 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
548
574
  * Checks if we are the proposer for the next slot.
549
575
  * @returns True if we can propose, and the proposer address (undefined if anyone can propose)
550
576
  */
551
- protected async checkCanPropose(slot: SlotNumber): Promise<[boolean, EthAddress | undefined]> {
577
+ protected async checkCanPropose(targetSlot: SlotNumber): Promise<[boolean, EthAddress | undefined]> {
552
578
  let proposer: EthAddress | undefined;
553
579
 
554
580
  try {
555
- proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
581
+ proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
556
582
  } catch (e) {
557
583
  if (e instanceof NoCommitteeError) {
558
- if (this.lastSlotForNoCommitteeWarning !== slot) {
559
- this.lastSlotForNoCommitteeWarning = slot;
560
- this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
584
+ if (this.lastSlotForNoCommitteeWarning !== targetSlot) {
585
+ this.lastSlotForNoCommitteeWarning = targetSlot;
586
+ this.log.warn(`Cannot propose at target slot ${targetSlot} since the committee does not exist on L1`);
561
587
  }
562
588
  return [false, undefined];
563
589
  }
564
- this.log.error(`Error getting proposer for slot ${slot}`, e);
590
+ this.log.error(`Error getting proposer for target slot ${targetSlot}`, e);
565
591
  return [false, undefined];
566
592
  }
567
593
 
@@ -578,10 +604,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
578
604
  const weAreProposer = validatorAddresses.some(addr => addr.equals(proposer));
579
605
 
580
606
  if (!weAreProposer) {
581
- this.log.debug(`Cannot propose at slot ${slot} since we are not a proposer`, { validatorAddresses, proposer });
607
+ this.log.debug(`Cannot propose at target slot ${targetSlot} since we are not a proposer`, {
608
+ targetSlot,
609
+ validatorAddresses,
610
+ proposer,
611
+ });
582
612
  return [false, proposer];
583
613
  }
584
614
 
615
+ this.log.debug(`We are the proposer for target slot ${targetSlot}`, { targetSlot, proposer });
585
616
  return [true, proposer];
586
617
  }
587
618
 
@@ -590,8 +621,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
590
621
  * This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
591
622
  */
592
623
  @trackSpan('Seqeuencer.tryVoteWhenSyncFails', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
593
- protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; ts: bigint }): Promise<void> {
594
- const { slot } = args;
624
+ protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; targetSlot: SlotNumber; ts: bigint }): Promise<void> {
625
+ const { slot, targetSlot } = args;
595
626
 
596
627
  // Prevent duplicate attempts in the same slot
597
628
  if (this.lastSlotForFallbackVote === slot) {
@@ -619,7 +650,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
619
650
  });
620
651
 
621
652
  // Check if we're a proposer or proposal is open
622
- const [canPropose, proposer] = await this.checkCanPropose(slot);
653
+ const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
623
654
  if (!canPropose) {
624
655
  this.log.trace(`Cannot vote in slot ${slot} since we are not a proposer`, { slot, proposer });
625
656
  return;
@@ -636,9 +667,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
636
667
  slot,
637
668
  });
638
669
 
639
- // Enqueue governance and slashing votes
670
+ // Enqueue governance and slashing votes (voter uses the target slot for L1 submission)
640
671
  const voter = new CheckpointVoter(
641
- slot,
672
+ targetSlot,
642
673
  publisher,
643
674
  attestorAddress,
644
675
  this.validatorClient,
@@ -4,11 +4,11 @@ import { unfreeze } from '@aztec/foundation/types';
4
4
  import { L2Block } from '@aztec/stdlib/block';
5
5
  import { Checkpoint } from '@aztec/stdlib/checkpoint';
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