@aztec/sequencer-client 0.0.1-commit.9ef841308 → 0.0.1-commit.a89ec08

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 (59) hide show
  1. package/dest/client/sequencer-client.d.ts +3 -2
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +7 -8
  4. package/dest/config.d.ts +3 -3
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +1 -2
  7. package/dest/publisher/config.d.ts +1 -17
  8. package/dest/publisher/config.d.ts.map +1 -1
  9. package/dest/publisher/config.js +3 -23
  10. package/dest/publisher/index.d.ts +1 -2
  11. package/dest/publisher/index.d.ts.map +1 -1
  12. package/dest/publisher/sequencer-publisher-factory.d.ts +3 -3
  13. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  14. package/dest/publisher/sequencer-publisher-factory.js +2 -16
  15. package/dest/publisher/sequencer-publisher.d.ts +2 -12
  16. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  17. package/dest/publisher/sequencer-publisher.js +11 -260
  18. package/dest/sequencer/checkpoint_proposal_job.d.ts +4 -10
  19. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  20. package/dest/sequencer/checkpoint_proposal_job.js +75 -117
  21. package/dest/sequencer/events.d.ts +1 -2
  22. package/dest/sequencer/events.d.ts.map +1 -1
  23. package/dest/sequencer/metrics.d.ts +1 -5
  24. package/dest/sequencer/metrics.d.ts.map +1 -1
  25. package/dest/sequencer/metrics.js +0 -11
  26. package/dest/sequencer/sequencer.d.ts +5 -4
  27. package/dest/sequencer/sequencer.d.ts.map +1 -1
  28. package/dest/sequencer/sequencer.js +49 -58
  29. package/dest/test/mock_checkpoint_builder.d.ts +1 -1
  30. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  31. package/dest/test/mock_checkpoint_builder.js +2 -0
  32. package/package.json +28 -27
  33. package/src/client/sequencer-client.ts +12 -8
  34. package/src/config.ts +0 -4
  35. package/src/publisher/config.ts +0 -41
  36. package/src/publisher/index.ts +0 -3
  37. package/src/publisher/sequencer-publisher-factory.ts +3 -18
  38. package/src/publisher/sequencer-publisher.ts +16 -237
  39. package/src/sequencer/checkpoint_proposal_job.ts +66 -139
  40. package/src/sequencer/events.ts +1 -1
  41. package/src/sequencer/metrics.ts +0 -14
  42. package/src/sequencer/sequencer.ts +57 -83
  43. package/src/test/mock_checkpoint_builder.ts +2 -0
  44. package/dest/publisher/l1_tx_failed_store/factory.d.ts +0 -11
  45. package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +0 -1
  46. package/dest/publisher/l1_tx_failed_store/factory.js +0 -22
  47. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +0 -59
  48. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +0 -1
  49. package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +0 -1
  50. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +0 -15
  51. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +0 -1
  52. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +0 -34
  53. package/dest/publisher/l1_tx_failed_store/index.d.ts +0 -4
  54. package/dest/publisher/l1_tx_failed_store/index.d.ts.map +0 -1
  55. package/dest/publisher/l1_tx_failed_store/index.js +0 -2
  56. package/src/publisher/l1_tx_failed_store/factory.ts +0 -32
  57. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +0 -55
  58. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +0 -46
  59. package/src/publisher/l1_tx_failed_store/index.ts +0 -3
@@ -31,7 +31,7 @@ import {
31
31
  MaliciousCommitteeAttestationsAndSigners,
32
32
  } from '@aztec/stdlib/block';
33
33
  import { type Checkpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
34
- import { getSlotStartBuildTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
34
+ import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
35
35
  import { Gas } from '@aztec/stdlib/gas';
36
36
  import {
37
37
  type BlockBuilderOptions,
@@ -77,10 +77,8 @@ export class CheckpointProposalJob implements Traceable {
77
77
  protected readonly log: Logger;
78
78
 
79
79
  constructor(
80
- private readonly slotNow: SlotNumber,
81
- private readonly targetSlot: SlotNumber,
82
- private readonly epochNow: EpochNumber,
83
- private readonly targetEpoch: EpochNumber,
80
+ private readonly epoch: EpochNumber,
81
+ private readonly slot: SlotNumber,
84
82
  private readonly checkpointNumber: CheckpointNumber,
85
83
  private readonly syncedToBlockNumber: BlockNumber,
86
84
  // TODO(palla/mbps): Can we remove the proposer in favor of attestorAddress? Need to check fisherman-node flows.
@@ -108,20 +106,7 @@ export class CheckpointProposalJob implements Traceable {
108
106
  public readonly tracer: Tracer,
109
107
  bindings?: LoggerBindings,
110
108
  ) {
111
- this.log = createLogger('sequencer:checkpoint-proposal', {
112
- ...bindings,
113
- instanceId: `slot-${this.slotNow}`,
114
- });
115
- }
116
-
117
- /** The wall-clock slot during which the proposer builds. */
118
- private get slot(): SlotNumber {
119
- return this.slotNow;
120
- }
121
-
122
- /** The wall-clock epoch. */
123
- private get epoch(): EpochNumber {
124
- return this.epochNow;
109
+ this.log = createLogger('sequencer:checkpoint-proposal', { ...bindings, instanceId: `slot-${slot}` });
125
110
  }
126
111
 
127
112
  /**
@@ -134,7 +119,7 @@ export class CheckpointProposalJob implements Traceable {
134
119
  // In fisherman mode, we simulate slashing but don't actually publish to L1
135
120
  // These are constant for the whole slot, so we only enqueue them once
136
121
  const votesPromises = new CheckpointVoter(
137
- this.targetSlot,
122
+ this.slot,
138
123
  this.publisher,
139
124
  this.attestorAddress,
140
125
  this.validatorClient,
@@ -161,29 +146,6 @@ export class CheckpointProposalJob implements Traceable {
161
146
  return;
162
147
  }
163
148
 
164
- // If pipelining, wait until the submission slot so L1 recognizes the pipelined proposer
165
- if (this.epochCache.isProposerPipeliningEnabled()) {
166
- const submissionSlotTimestamp =
167
- getTimestampForSlot(this.targetSlot, this.l1Constants) - BigInt(this.l1Constants.ethereumSlotDuration);
168
- this.log.info(`Waiting until submission slot ${this.targetSlot} for L1 submission`, {
169
- slot: this.slot,
170
- submissionSlot: this.targetSlot,
171
- submissionSlotTimestamp,
172
- });
173
- await sleepUntil(new Date(Number(submissionSlotTimestamp) * 1000), this.dateProvider.nowAsDate());
174
-
175
- // After waking, verify the parent checkpoint wasn't pruned during the sleep.
176
- // We check L1's pending tip directly instead of canProposeAt, which also validates the proposer
177
- // identity and would fail because the timestamp resolves to a different slot's proposer.
178
- const l1Tips = await this.publisher.rollupContract.getTips();
179
- if (l1Tips.pending < this.checkpointNumber - 1) {
180
- this.log.warn(
181
- `Parent checkpoint was pruned during pipelining sleep (L1 pending=${l1Tips.pending}, expected>=${this.checkpointNumber - 1}), skipping L1 submission for checkpoint ${this.checkpointNumber}`,
182
- );
183
- return undefined;
184
- }
185
- }
186
-
187
149
  // Then send everything to L1
188
150
  const l1Response = await this.publisher.sendRequests();
189
151
  const proposedAction = l1Response?.successfulActions.find(a => a === 'propose');
@@ -202,7 +164,7 @@ export class CheckpointProposalJob implements Traceable {
202
164
  return {
203
165
  // nullish operator needed for tests
204
166
  [Attributes.COINBASE]: this.validatorClient.getCoinbaseForAttestor(this.attestorAddress)?.toString(),
205
- [Attributes.SLOT_NUMBER]: this.targetSlot,
167
+ [Attributes.SLOT_NUMBER]: this.slot,
206
168
  };
207
169
  })
208
170
  private async proposeCheckpoint(): Promise<Checkpoint | undefined> {
@@ -212,15 +174,8 @@ export class CheckpointProposalJob implements Traceable {
212
174
  const feeRecipient = this.validatorClient.getFeeRecipientForAttestor(this.attestorAddress);
213
175
 
214
176
  // Start the checkpoint
215
- this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.targetSlot);
216
- this.log.info(`Starting checkpoint proposal`, {
217
- buildSlot: this.slot,
218
- submissionSlot: this.targetSlot,
219
- pipelining: this.epochCache.isProposerPipeliningEnabled(),
220
- proposer: this.proposer?.toString(),
221
- coinbase: coinbase.toString(),
222
- });
223
- this.metrics.incOpenSlot(this.targetSlot, this.proposer?.toString() ?? 'unknown');
177
+ this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.slot);
178
+ this.metrics.incOpenSlot(this.slot, this.proposer?.toString() ?? 'unknown');
224
179
 
225
180
  // Enqueues checkpoint invalidation (constant for the whole slot)
226
181
  if (this.invalidateCheckpoint && !this.config.skipInvalidateBlockAsProposer) {
@@ -231,7 +186,7 @@ export class CheckpointProposalJob implements Traceable {
231
186
  const checkpointGlobalVariables = await this.globalsBuilder.buildCheckpointGlobalVariables(
232
187
  coinbase,
233
188
  feeRecipient,
234
- this.targetSlot,
189
+ this.slot,
235
190
  );
236
191
 
237
192
  // Collect L1 to L2 messages for the checkpoint and compute their hash
@@ -239,7 +194,7 @@ export class CheckpointProposalJob implements Traceable {
239
194
  const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
240
195
 
241
196
  // Collect the out hashes of all the checkpoints before this one in the same epoch
242
- const previousCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(this.targetEpoch))
197
+ const previousCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(this.epoch))
243
198
  .filter(c => c.checkpointNumber < this.checkpointNumber)
244
199
  .map(c => c.checkpointOutHash);
245
200
 
@@ -296,8 +251,8 @@ export class CheckpointProposalJob implements Traceable {
296
251
  }
297
252
 
298
253
  if (blocksInCheckpoint.length === 0) {
299
- this.log.warn(`No blocks were built for slot ${this.targetSlot}`, { slot: this.targetSlot });
300
- this.eventEmitter.emit('checkpoint-empty', { slot: this.targetSlot });
254
+ this.log.warn(`No blocks were built for slot ${this.slot}`, { slot: this.slot });
255
+ this.eventEmitter.emit('checkpoint-empty', { slot: this.slot });
301
256
  return undefined;
302
257
  }
303
258
 
@@ -305,14 +260,14 @@ export class CheckpointProposalJob implements Traceable {
305
260
  if (minBlocksForCheckpoint !== undefined && blocksInCheckpoint.length < minBlocksForCheckpoint) {
306
261
  this.log.warn(
307
262
  `Checkpoint has fewer blocks than minimum (${blocksInCheckpoint.length} < ${minBlocksForCheckpoint}), skipping proposal`,
308
- { slot: this.targetSlot, blocksBuilt: blocksInCheckpoint.length, minBlocksForCheckpoint },
263
+ { slot: this.slot, blocksBuilt: blocksInCheckpoint.length, minBlocksForCheckpoint },
309
264
  );
310
265
  return undefined;
311
266
  }
312
267
 
313
268
  // Assemble and broadcast the checkpoint proposal, including the last block that was not
314
269
  // broadcasted yet, and wait to collect the committee attestations.
315
- this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.targetSlot);
270
+ this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.slot);
316
271
  const checkpoint = await checkpointBuilder.completeCheckpoint();
317
272
 
318
273
  // Final validation: per-block limits are only checked if the operator set them explicitly.
@@ -343,10 +298,10 @@ export class CheckpointProposalJob implements Traceable {
343
298
  // Do not collect attestations nor publish to L1 in fisherman mode
344
299
  if (this.config.fishermanMode) {
345
300
  this.log.info(
346
- `Built checkpoint for slot ${this.targetSlot} with ${blocksInCheckpoint.length} blocks. ` +
301
+ `Built checkpoint for slot ${this.slot} with ${blocksInCheckpoint.length} blocks. ` +
347
302
  `Skipping proposal in fisherman mode.`,
348
303
  {
349
- slot: this.targetSlot,
304
+ slot: this.slot,
350
305
  checkpoint: checkpoint.header.toInspect(),
351
306
  blocksBuilt: blocksInCheckpoint.length,
352
307
  },
@@ -375,7 +330,7 @@ export class CheckpointProposalJob implements Traceable {
375
330
  const blockProposedAt = this.dateProvider.now();
376
331
  await this.p2pClient.broadcastCheckpointProposal(proposal);
377
332
 
378
- this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.targetSlot);
333
+ this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.slot);
379
334
  const attestations = await this.waitForAttestations(proposal);
380
335
  const blockAttestedAt = this.dateProvider.now();
381
336
 
@@ -388,7 +343,7 @@ export class CheckpointProposalJob implements Traceable {
388
343
  attestationsSignature = await this.validatorClient.signAttestationsAndSigners(
389
344
  attestations,
390
345
  signer,
391
- this.targetSlot,
346
+ this.slot,
392
347
  this.checkpointNumber,
393
348
  );
394
349
  } catch (err) {
@@ -401,10 +356,10 @@ export class CheckpointProposalJob implements Traceable {
401
356
  }
402
357
 
403
358
  // Enqueue publishing the checkpoint to L1
404
- this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.targetSlot);
359
+ this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.slot);
405
360
  const aztecSlotDuration = this.l1Constants.slotDuration;
406
- const submissionSlotStart = Number(getTimestampForSlot(this.targetSlot, this.l1Constants));
407
- const txTimeoutAt = new Date((submissionSlotStart + aztecSlotDuration) * 1000);
361
+ const slotStartBuildTimestamp = this.getSlotStartBuildTimestamp();
362
+ const txTimeoutAt = new Date((slotStartBuildTimestamp + aztecSlotDuration) * 1000);
408
363
 
409
364
  // If we have been configured to potentially skip publishing checkpoint then roll the dice here
410
365
  if (
@@ -453,6 +408,7 @@ export class CheckpointProposalJob implements Traceable {
453
408
  const blocksInCheckpoint: L2Block[] = [];
454
409
  const txHashesAlreadyIncluded = new Set<string>();
455
410
  const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
411
+ const slot = this.slot;
456
412
 
457
413
  // Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
458
414
  let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
@@ -466,11 +422,7 @@ export class CheckpointProposalJob implements Traceable {
466
422
  const timingInfo = this.timetable.canStartNextBlock(secondsIntoSlot);
467
423
 
468
424
  if (!timingInfo.canStart) {
469
- this.log.debug(`Not enough time left in slot to start another block`, {
470
- slot: this.targetSlot,
471
- blocksBuilt,
472
- secondsIntoSlot,
473
- });
425
+ this.log.debug(`Not enough time left in slot to start another block`, { slot, blocksBuilt, secondsIntoSlot });
474
426
  break;
475
427
  }
476
428
 
@@ -502,11 +454,7 @@ export class CheckpointProposalJob implements Traceable {
502
454
  } else if ('error' in buildResult) {
503
455
  // If there was an error building the block, just exit the loop and give up the rest of the slot
504
456
  if (!(buildResult.error instanceof SequencerInterruptedError)) {
505
- this.log.warn(`Halting block building for slot ${this.targetSlot}`, {
506
- slot: this.targetSlot,
507
- blocksBuilt,
508
- error: buildResult.error,
509
- });
457
+ this.log.warn(`Halting block building for slot ${slot}`, { slot, blocksBuilt, error: buildResult.error });
510
458
  }
511
459
  break;
512
460
  }
@@ -515,15 +463,11 @@ export class CheckpointProposalJob implements Traceable {
515
463
  blocksInCheckpoint.push(block);
516
464
  usedTxs.forEach(tx => txHashesAlreadyIncluded.add(tx.txHash.toString()));
517
465
 
518
- // If this is the last block, sync it to the archiver and exit the loop
519
- // so we can build the checkpoint and start collecting attestations.
466
+ // If this is the last block, send the proposed block to the archiver,
467
+ // and exit the loop now so we can build the checkpoint and start collecting attestations.
520
468
  if (timingInfo.isLastBlock) {
521
469
  await this.syncProposedBlockToArchiver(block);
522
- this.log.verbose(`Completed final block ${blockNumber} for slot ${this.targetSlot}`, {
523
- slot: this.targetSlot,
524
- blockNumber,
525
- blocksBuilt,
526
- });
470
+ this.log.verbose(`Completed final block ${blockNumber} for slot ${slot}`, { slot, blockNumber, blocksBuilt });
527
471
  blockPendingBroadcast = { block, txs: usedTxs };
528
472
  break;
529
473
  }
@@ -546,8 +490,8 @@ export class CheckpointProposalJob implements Traceable {
546
490
  await this.waitUntilNextSubslot(timingInfo.deadline);
547
491
  }
548
492
 
549
- this.log.verbose(`Block building loop completed for slot ${this.targetSlot}`, {
550
- slot: this.targetSlot,
493
+ this.log.verbose(`Block building loop completed for slot ${this.slot}`, {
494
+ slot: this.slot,
551
495
  blocksBuilt: blocksInCheckpoint.length,
552
496
  });
553
497
 
@@ -579,10 +523,8 @@ export class CheckpointProposalJob implements Traceable {
579
523
  /** Sleeps until it is time to produce the next block in the slot */
580
524
  @trackSpan('CheckpointProposalJob.waitUntilNextSubslot')
581
525
  private async waitUntilNextSubslot(nextSubslotStart: number) {
582
- this.setStateFn(SequencerState.WAITING_UNTIL_NEXT_BLOCK, this.targetSlot);
583
- this.log.verbose(`Waiting until time for the next block at ${nextSubslotStart}s into slot`, {
584
- slot: this.targetSlot,
585
- });
526
+ this.setStateFn(SequencerState.WAITING_UNTIL_NEXT_BLOCK, this.slot);
527
+ this.log.verbose(`Waiting until time for the next block at ${nextSubslotStart}s into slot`, { slot: this.slot });
586
528
  await this.waitUntilTimeInSlot(nextSubslotStart);
587
529
  }
588
530
 
@@ -603,7 +545,7 @@ export class CheckpointProposalJob implements Traceable {
603
545
  opts;
604
546
 
605
547
  this.log.verbose(
606
- `Preparing block ${blockNumber} index ${indexWithinCheckpoint} at checkpoint ${this.checkpointNumber} for slot ${this.targetSlot}`,
548
+ `Preparing block ${blockNumber} index ${indexWithinCheckpoint} at checkpoint ${this.checkpointNumber} for slot ${this.slot}`,
607
549
  { ...checkpointBuilder.getConstantData(), ...opts },
608
550
  );
609
551
 
@@ -612,10 +554,10 @@ export class CheckpointProposalJob implements Traceable {
612
554
  const { availableTxs, canStartBuilding, minTxs } = await this.waitForMinTxs(opts);
613
555
  if (!canStartBuilding) {
614
556
  this.log.warn(
615
- `Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot} (got ${availableTxs} txs but needs ${minTxs})`,
616
- { blockNumber, slot: this.targetSlot, indexWithinCheckpoint },
557
+ `Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.slot} (got ${availableTxs} txs but needs ${minTxs})`,
558
+ { blockNumber, slot: this.slot, indexWithinCheckpoint },
617
559
  );
618
- this.eventEmitter.emit('block-tx-count-check-failed', { minTxs, availableTxs, slot: this.targetSlot });
560
+ this.eventEmitter.emit('block-tx-count-check-failed', { minTxs, availableTxs, slot: this.slot });
619
561
  this.metrics.recordBlockProposalFailed('insufficient_txs');
620
562
  return undefined;
621
563
  }
@@ -628,10 +570,10 @@ export class CheckpointProposalJob implements Traceable {
628
570
  );
629
571
 
630
572
  this.log.debug(
631
- `Building block ${blockNumber} at index ${indexWithinCheckpoint} for slot ${this.targetSlot} with ${availableTxs} available txs`,
632
- { slot: this.targetSlot, blockNumber, indexWithinCheckpoint },
573
+ `Building block ${blockNumber} at index ${indexWithinCheckpoint} for slot ${this.slot} with ${availableTxs} available txs`,
574
+ { slot: this.slot, blockNumber, indexWithinCheckpoint },
633
575
  );
634
- this.setStateFn(SequencerState.CREATING_BLOCK, this.targetSlot);
576
+ this.setStateFn(SequencerState.CREATING_BLOCK, this.slot);
635
577
 
636
578
  // Per-block limits are operator overrides (from SEQ_MAX_L2_BLOCK_GAS etc.) further capped
637
579
  // by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
@@ -666,19 +608,16 @@ export class CheckpointProposalJob implements Traceable {
666
608
 
667
609
  if (buildResult.status === 'insufficient-valid-txs') {
668
610
  this.log.warn(
669
- `Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.targetSlot} has too few valid txs to be proposed`,
611
+ `Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.slot} has too few valid txs to be proposed`,
670
612
  {
671
- slot: this.targetSlot,
613
+ slot: this.slot,
672
614
  blockNumber,
673
615
  numTxs: buildResult.processedCount,
674
616
  indexWithinCheckpoint,
675
617
  minValidTxs,
676
618
  },
677
619
  );
678
- this.eventEmitter.emit('block-build-failed', {
679
- reason: `Insufficient valid txs`,
680
- slot: this.targetSlot,
681
- });
620
+ this.eventEmitter.emit('block-build-failed', { reason: `Insufficient valid txs`, slot: this.slot });
682
621
  this.metrics.recordBlockProposalFailed('insufficient_valid_txs');
683
622
  return undefined;
684
623
  }
@@ -698,24 +637,17 @@ export class CheckpointProposalJob implements Traceable {
698
637
  const manaPerSec = block.header.totalManaUsed.toNumberUnsafe() / (blockBuildDuration / 1000);
699
638
 
700
639
  this.log.info(
701
- `Built block ${block.number} at checkpoint ${this.checkpointNumber} for slot ${this.targetSlot} with ${numTxs} txs`,
640
+ `Built block ${block.number} at checkpoint ${this.checkpointNumber} for slot ${this.slot} with ${numTxs} txs`,
702
641
  { blockHash, txHashes, manaPerSec, ...blockStats },
703
642
  );
704
643
 
705
- this.eventEmitter.emit('block-proposed', {
706
- blockNumber: block.number,
707
- slot: this.targetSlot,
708
- buildSlot: this.slotNow,
709
- });
644
+ this.eventEmitter.emit('block-proposed', { blockNumber: block.number, slot: this.slot });
710
645
  this.metrics.recordBuiltBlock(blockBuildDuration, block.header.totalManaUsed.toNumberUnsafe());
711
646
 
712
647
  return { block, usedTxs };
713
648
  } catch (err: any) {
714
- this.eventEmitter.emit('block-build-failed', {
715
- reason: err.message,
716
- slot: this.targetSlot,
717
- });
718
- this.log.error(`Error building block`, err, { blockNumber, slot: this.targetSlot });
649
+ this.eventEmitter.emit('block-build-failed', { reason: err.message, slot: this.slot });
650
+ this.log.error(`Error building block`, err, { blockNumber, slot: this.slot });
719
651
  this.metrics.recordBlockProposalFailed(err.name || 'unknown_error');
720
652
  this.metrics.recordFailedBlock();
721
653
  return { error: err };
@@ -775,10 +707,10 @@ export class CheckpointProposalJob implements Traceable {
775
707
  }
776
708
 
777
709
  // Wait a bit before checking again
778
- this.setStateFn(SequencerState.WAITING_FOR_TXS, this.targetSlot);
710
+ this.setStateFn(SequencerState.WAITING_FOR_TXS, this.slot);
779
711
  this.log.verbose(
780
- `Waiting for enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot} (have ${availableTxs} but need ${minTxs})`,
781
- { blockNumber, slot: this.targetSlot, indexWithinCheckpoint },
712
+ `Waiting for enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.slot} (have ${availableTxs} but need ${minTxs})`,
713
+ { blockNumber, slot: this.slot, indexWithinCheckpoint },
782
714
  );
783
715
  await this.waitForTxsPollingInterval();
784
716
  availableTxs = await this.p2pClient.getPendingTxCount();
@@ -922,20 +854,11 @@ export class CheckpointProposalJob implements Traceable {
922
854
  this.log.warn(`Shuffling attestation ordering in checkpoint for slot ${slotNumber} (proposer #${proposerIndex})`);
923
855
 
924
856
  const shuffled = [...attestations];
925
-
926
- // Find two non-proposer positions that both have non-empty signatures to swap.
927
- // This ensures the bitmap doesn't change, so the MaliciousCommitteeAttestationsAndSigners
928
- // signers array stays correctly aligned with L1's committee reconstruction.
929
- const swappable: number[] = [];
930
- for (let k = 0; k < shuffled.length; k++) {
931
- if (!shuffled[k].signature.isEmpty() && k !== proposerIndex) {
932
- swappable.push(k);
933
- }
934
- }
935
- if (swappable.length >= 2) {
936
- const [i, j] = [swappable[0], swappable[1]];
937
- [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
938
- }
857
+ const [i, j] = [(proposerIndex + 1) % shuffled.length, (proposerIndex + 2) % shuffled.length];
858
+ const valueI = shuffled[i];
859
+ const valueJ = shuffled[j];
860
+ shuffled[i] = valueJ;
861
+ shuffled[j] = valueI;
939
862
 
940
863
  const signers = new CommitteeAttestationsAndSigners(attestations).getSigners();
941
864
  return new MaliciousCommitteeAttestationsAndSigners(shuffled, signers);
@@ -958,9 +881,13 @@ export class CheckpointProposalJob implements Traceable {
958
881
  * Adds the proposed block to the archiver so it's available via P2P.
959
882
  * Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
960
883
  * would never receive its own block without this explicit sync.
884
+ *
885
+ * In fisherman mode we skip this push: the fisherman builds blocks locally for validation
886
+ * and fee analysis only, and pushing them to the archiver causes spurious reorg cascades
887
+ * whenever the real proposer's block arrives from L1.
961
888
  */
962
889
  private async syncProposedBlockToArchiver(block: L2Block): Promise<void> {
963
- if (this.config.skipPushProposedBlocksToArchiver) {
890
+ if (this.config.skipPushProposedBlocksToArchiver !== false || this.config.fishermanMode) {
964
891
  this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
965
892
  blockNumber: block.number,
966
893
  slot: block.header.globalVariables.slotNumber,
@@ -978,19 +905,19 @@ export class CheckpointProposalJob implements Traceable {
978
905
  private async handleCheckpointEndAsFisherman(checkpoint: Checkpoint | undefined) {
979
906
  // Perform L1 fee analysis before clearing requests
980
907
  // The callback is invoked asynchronously after the next block is mined
981
- const feeAnalysis = await this.publisher.analyzeL1Fees(this.targetSlot, analysis =>
908
+ const feeAnalysis = await this.publisher.analyzeL1Fees(this.slot, analysis =>
982
909
  this.metrics.recordFishermanFeeAnalysis(analysis),
983
910
  );
984
911
 
985
912
  if (checkpoint) {
986
- this.log.info(`Validation checkpoint building SUCCEEDED for slot ${this.targetSlot}`, {
913
+ this.log.info(`Validation checkpoint building SUCCEEDED for slot ${this.slot}`, {
987
914
  ...checkpoint.toCheckpointInfo(),
988
915
  ...checkpoint.getStats(),
989
916
  feeAnalysisId: feeAnalysis?.id,
990
917
  });
991
918
  } else {
992
- this.log.warn(`Validation block building FAILED for slot ${this.targetSlot}`, {
993
- slot: this.targetSlot,
919
+ this.log.warn(`Validation block building FAILED for slot ${this.slot}`, {
920
+ slot: this.slot,
994
921
  feeAnalysisId: feeAnalysis?.id,
995
922
  });
996
923
  this.metrics.recordCheckpointProposalFailed('block_build_failed');
@@ -1004,15 +931,15 @@ export class CheckpointProposalJob implements Traceable {
1004
931
  */
1005
932
  private handleHASigningError(err: any, errorContext: string): boolean {
1006
933
  if (err instanceof DutyAlreadySignedError) {
1007
- this.log.info(`${errorContext} for slot ${this.targetSlot} already signed by another HA node, yielding`, {
1008
- slot: this.targetSlot,
934
+ this.log.info(`${errorContext} for slot ${this.slot} already signed by another HA node, yielding`, {
935
+ slot: this.slot,
1009
936
  signedByNode: err.signedByNode,
1010
937
  });
1011
938
  return true;
1012
939
  }
1013
940
  if (err instanceof SlashingProtectionError) {
1014
- this.log.info(`${errorContext} for slot ${this.targetSlot} blocked by slashing protection, yielding`, {
1015
- slot: this.targetSlot,
941
+ this.log.info(`${errorContext} for slot ${this.slot} blocked by slashing protection, yielding`, {
942
+ slot: this.slot,
1016
943
  existingMessageHash: err.existingMessageHash,
1017
944
  attemptedMessageHash: err.attemptedMessageHash,
1018
945
  });
@@ -13,7 +13,7 @@ export type SequencerEvents = {
13
13
  ['proposer-rollup-check-failed']: (args: { reason: string; slot: SlotNumber }) => void;
14
14
  ['block-tx-count-check-failed']: (args: { minTxs: number; availableTxs: number; slot: SlotNumber }) => void;
15
15
  ['block-build-failed']: (args: { reason: string; slot: SlotNumber }) => void;
16
- ['block-proposed']: (args: { blockNumber: BlockNumber; slot: SlotNumber; buildSlot: SlotNumber }) => void;
16
+ ['block-proposed']: (args: { blockNumber: BlockNumber; slot: SlotNumber }) => void;
17
17
  ['checkpoint-empty']: (args: { slot: SlotNumber }) => void;
18
18
  ['checkpoint-publish-failed']: (args: {
19
19
  slot: SlotNumber;
@@ -49,8 +49,6 @@ export class SequencerMetrics {
49
49
  private checkpointBlockCount: Gauge;
50
50
  private checkpointTxCount: Gauge;
51
51
  private checkpointTotalMana: Gauge;
52
- private pipelineDepth: Gauge;
53
- private pipelineDiscards: UpDownCounter;
54
52
 
55
53
  // Fisherman fee analysis metrics
56
54
  private fishermanWouldBeIncluded: UpDownCounter;
@@ -145,10 +143,6 @@ export class SequencerMetrics {
145
143
 
146
144
  this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
147
145
 
148
- this.pipelineDepth = this.meter.createGauge(Metrics.SEQUENCER_PIPELINE_DEPTH);
149
- this.pipelineDiscards = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_PIPELINE_DISCARDS_COUNT);
150
- this.pipelineDepth.record(0);
151
-
152
146
  // Fisherman fee analysis metrics
153
147
  this.fishermanWouldBeIncluded = createUpDownCounterWithDefault(
154
148
  this.meter,
@@ -240,14 +234,6 @@ export class SequencerMetrics {
240
234
  });
241
235
  }
242
236
 
243
- recordPipelineDepth(depth: number) {
244
- this.pipelineDepth.record(depth);
245
- }
246
-
247
- recordPipelineDiscard(count = 1) {
248
- this.pipelineDiscards.add(count);
249
- }
250
-
251
237
  incOpenSlot(slot: SlotNumber, proposer: string) {
252
238
  // sequencer went through the loop a second time. Noop
253
239
  if (slot === this.lastSeenSlot) {