@aztec/sequencer-client 0.0.1-commit.2d9cb6034 → 0.0.1-commit.2e20a94

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 (66) 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 -4
  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-factory.d.ts +1 -3
  25. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  26. package/dest/publisher/sequencer-publisher-factory.js +0 -1
  27. package/dest/publisher/sequencer-publisher.d.ts +16 -35
  28. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  29. package/dest/publisher/sequencer-publisher.js +60 -92
  30. package/dest/sequencer/chain_state_overrides.d.ts +25 -0
  31. package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
  32. package/dest/sequencer/chain_state_overrides.js +39 -0
  33. package/dest/sequencer/checkpoint_proposal_job.d.ts +13 -21
  34. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  35. package/dest/sequencer/checkpoint_proposal_job.js +143 -152
  36. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
  37. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
  38. package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
  39. package/dest/sequencer/metrics.d.ts +5 -10
  40. package/dest/sequencer/metrics.d.ts.map +1 -1
  41. package/dest/sequencer/metrics.js +12 -20
  42. package/dest/sequencer/sequencer.d.ts +5 -2
  43. package/dest/sequencer/sequencer.d.ts.map +1 -1
  44. package/dest/sequencer/sequencer.js +31 -23
  45. package/dest/sequencer/timetable.d.ts +14 -1
  46. package/dest/sequencer/timetable.d.ts.map +1 -1
  47. package/dest/sequencer/timetable.js +45 -36
  48. package/package.json +27 -27
  49. package/src/client/sequencer-client.ts +5 -7
  50. package/src/config.ts +8 -11
  51. package/src/global_variable_builder/README.md +44 -0
  52. package/src/global_variable_builder/fee_predictor.ts +172 -0
  53. package/src/global_variable_builder/fee_provider.ts +75 -0
  54. package/src/global_variable_builder/global_builder.ts +7 -54
  55. package/src/global_variable_builder/index.ts +2 -0
  56. package/src/publisher/config.ts +6 -4
  57. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +3 -1
  58. package/src/publisher/sequencer-publisher-factory.ts +0 -3
  59. package/src/publisher/sequencer-publisher.ts +106 -152
  60. package/src/sequencer/README.md +83 -13
  61. package/src/sequencer/chain_state_overrides.ts +87 -0
  62. package/src/sequencer/checkpoint_proposal_job.ts +185 -201
  63. package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
  64. package/src/sequencer/metrics.ts +15 -25
  65. package/src/sequencer/sequencer.ts +44 -26
  66. package/src/sequencer/timetable.ts +57 -45
@@ -436,11 +436,9 @@ function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
436
436
  return (_apply_decs_2203_r = applyDecs2203RFactory())(targetClass, memberDecs, classDecs, parentClass);
437
437
  }
438
438
  var _dec, _dec1, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _initProto;
439
- import { RollupContract } from '@aztec/ethereum/contracts';
440
- import { BlockNumber, CheckpointNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types';
439
+ import { BlockNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types';
441
440
  import { randomInt } from '@aztec/foundation/crypto/random';
442
441
  import { flipSignature, generateRecoverableSignature, generateUnrecoverableSignature } from '@aztec/foundation/crypto/secp256k1-signer';
443
- import { Signature } from '@aztec/foundation/eth-signature';
444
442
  import { filter } from '@aztec/foundation/iterator';
445
443
  import { createLogger } from '@aztec/foundation/log';
446
444
  import { sleep, sleepUntil } from '@aztec/foundation/sleep';
@@ -456,6 +454,7 @@ import { orderAttestations, trimAttestations } from '@aztec/stdlib/p2p';
456
454
  import { AttestationTimeoutError } from '@aztec/stdlib/validators';
457
455
  import { Attributes, trackSpan } from '@aztec/telemetry-client';
458
456
  import { DutyAlreadySignedError, SlashingProtectionError } from '@aztec/validator-ha-signer/errors';
457
+ import { buildPipelinedParentSimulationOverridesPlan, buildSubmissionSimulationOverridesPlan } from './chain_state_overrides.js';
459
458
  import { CheckpointVoter } from './checkpoint_voter.js';
460
459
  import { SequencerInterruptedError } from './errors.js';
461
460
  import { SequencerState } from './utils.js';
@@ -497,6 +496,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
497
496
  epochCache;
498
497
  dateProvider;
499
498
  metrics;
499
+ checkpointMetrics;
500
500
  eventEmitter;
501
501
  setStateFn;
502
502
  tracer;
@@ -547,9 +547,9 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
547
547
  }
548
548
  log;
549
549
  /** Tracks the fire-and-forget L1 submission promise so it can be awaited during shutdown. */ pendingL1Submission;
550
- /** Fee header override computed during proposeCheckpoint, reused in enqueueCheckpointForSubmission. */ computedForceProposedFeeHeader;
550
+ /** Pipelined parent chain state used while building and later submitting this checkpoint. */ pipelinedParentSimulationOverridesPlan;
551
551
  constructor(slotNow, targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, // TODO(palla/mbps): Can we remove the proposer in favor of attestorAddress? Need to check fisherman-node flows.
552
- proposer, publisher, attestorAddress, invalidateCheckpoint, validatorClient, globalsBuilder, p2pClient, worldState, l1ToL2MessageSource, l2BlockSource, checkpointsBuilder, blockSink, l1Constants, config, timetable, slasherClient, epochCache, dateProvider, metrics, eventEmitter, setStateFn, tracer, bindings, proposedCheckpointData){
552
+ proposer, publisher, attestorAddress, invalidateCheckpoint, validatorClient, globalsBuilder, p2pClient, worldState, l1ToL2MessageSource, l2BlockSource, checkpointsBuilder, blockSink, l1Constants, config, timetable, slasherClient, epochCache, dateProvider, metrics, checkpointMetrics, eventEmitter, setStateFn, tracer, bindings, proposedCheckpointData){
553
553
  this.slotNow = slotNow;
554
554
  this.targetSlot = targetSlot;
555
555
  this.targetEpoch = targetEpoch;
@@ -574,6 +574,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
574
574
  this.epochCache = epochCache;
575
575
  this.dateProvider = dateProvider;
576
576
  this.metrics = metrics;
577
+ this.checkpointMetrics = checkpointMetrics;
577
578
  this.eventEmitter = eventEmitter;
578
579
  this.setStateFn = setStateFn;
579
580
  this.tracer = tracer;
@@ -590,53 +591,77 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
590
591
  }
591
592
  /**
592
593
  * Executes the checkpoint proposal job.
593
- * Builds blocks, collects attestations, enqueues requests, and schedules L1 submission as a
594
- * background task so the work loop can return to IDLE immediately.
594
+ * Builds blocks, assembles checkpoint, and broadcasts the proposal (blocking).
595
+ * Attestation collection, signing, and L1 submission are backgrounded so the
596
+ * work loop can return to IDLE immediately for consecutive slot proposals.
595
597
  * Returns the built checkpoint if successful, undefined otherwise.
596
598
  */ async execute() {
597
599
  // Enqueue governance and slashing votes (returns promises that will be awaited later)
598
600
  // In fisherman mode, we simulate slashing but don't actually publish to L1
599
601
  // These are constant for the whole slot, so we only enqueue them once
600
602
  const votesPromises = new CheckpointVoter(this.targetSlot, this.publisher, this.attestorAddress, this.validatorClient, this.slasherClient, this.l1Constants, this.config, this.metrics, this.log).enqueueVotes();
601
- // Build and propose the checkpoint. Builds blocks, broadcasts, collects attestations, and signs.
602
- // Does NOT enqueue to L1 yet that happens after the pipeline sleep.
603
- const proposalResult = await this.proposeCheckpoint();
604
- const checkpoint = proposalResult?.checkpoint;
605
- // Wait until the voting promises have resolved, so all requests are enqueued (not sent)
606
- await Promise.all(votesPromises);
607
- if (checkpoint) {
608
- this.metrics.recordCheckpointProposalSuccess();
603
+ // Build blocks, assemble checkpoint, and broadcast proposal (BLOCKING).
604
+ // Returns after broadcastattestation collection is deferred.
605
+ const broadcast = await this.proposeCheckpoint();
606
+ if (!broadcast) {
607
+ await Promise.all(votesPromises);
608
+ // Still submit votes even without a checkpoint
609
+ if (!this.config.fishermanMode) {
610
+ this.pendingL1Submission = this.publisher.sendRequestsAt(this.dateProvider.nowAsDate()).then(()=>{});
611
+ }
612
+ return undefined;
609
613
  }
614
+ const { checkpoint } = broadcast;
615
+ this.metrics.recordCheckpointProposalSuccess();
610
616
  // Do not post anything to L1 if we are fishermen, but do perform L1 fee analysis
611
617
  if (this.config.fishermanMode) {
612
618
  await this.handleCheckpointEndAsFisherman(checkpoint);
613
- return;
619
+ return checkpoint;
614
620
  }
615
- // Enqueue the checkpoint for L1 submission
616
- if (proposalResult) {
621
+ // Background the attestation signing → L1 pipeline so the work loop is unblocked
622
+ this.pendingL1Submission = this.waitForAttestationsAndEnqueueSubmissionAsync(broadcast, votesPromises);
623
+ // Return the built checkpoint immediately — the work loop is now unblocked
624
+ return checkpoint;
625
+ }
626
+ /**
627
+ * Background pipeline: collects attestations, signs them, enqueues the checkpoint, and submits to L1.
628
+ * Runs as a fire-and-forget task stored in `pendingL1Submission` so the work loop is unblocked.
629
+ */ async waitForAttestationsAndEnqueueSubmissionAsync(broadcast, votesPromises) {
630
+ const { checkpoint, proposal, blockProposedAt } = broadcast;
631
+ try {
632
+ await Promise.all(votesPromises);
633
+ this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.targetSlot);
634
+ const attestations = await this.waitForAttestations(proposal);
635
+ this.checkpointMetrics.recordCheckpointAttestationDelay(this.dateProvider.now() - blockProposedAt);
636
+ // Proposer must sign over the attestations before pushing them to L1
637
+ const signer = this.proposer ?? this.publisher.getSenderAddress();
638
+ let attestationsSignature;
617
639
  try {
618
- await this.enqueueCheckpointForSubmission(proposalResult);
640
+ attestationsSignature = await this.validatorClient.signAttestationsAndSigners(attestations, signer, this.targetSlot, this.checkpointNumber);
619
641
  } catch (err) {
620
- this.log.error(`Failed to enqueue checkpoint for L1 submission at slot ${this.targetSlot}`, err);
621
- // Continue to sendRequestsAt so votes are still sent
642
+ if (this.handleHASigningError(err, 'Attestations signature')) {
643
+ return;
644
+ }
645
+ throw err;
622
646
  }
623
- }
624
- // Compute the earliest time to submit: pipeline slot start when pipelining, now otherwise.
625
- const submitAfter = this.epochCache.isProposerPipeliningEnabled() ? new Date(Number(getTimestampForSlot(this.targetSlot, this.l1Constants)) * 1000) : new Date(this.dateProvider.now());
626
- // TODO(https://github.com/AztecProtocol/aztec-packages/pull/21250): should discard the pending submission if a reorg occurs underneath
627
- // Schedule L1 submission in the background so the work loop returns immediately.
628
- // The publisher will sleep until submitAfter, then send the bundled requests.
629
- // The promise is stored so it can be awaited during shutdown.
630
- this.pendingL1Submission = this.publisher.sendRequestsAt(submitAfter).then(async (l1Response)=>{
647
+ // Enqueue the checkpoint for L1 submission
648
+ await this.enqueueCheckpointForSubmission({
649
+ checkpoint,
650
+ attestations,
651
+ attestationsSignature
652
+ });
653
+ // Compute the earliest time to submit: pipeline slot start when pipelining, now otherwise.
654
+ const submitAfter = this.epochCache.isProposerPipeliningEnabled() ? new Date(Number(getTimestampForSlot(this.targetSlot, this.l1Constants)) * 1000) : new Date(this.dateProvider.now());
655
+ const l1Response = await this.publisher.sendRequestsAt(submitAfter);
631
656
  const proposedAction = l1Response?.successfulActions.find((a)=>a === 'propose');
632
657
  if (proposedAction) {
633
658
  this.eventEmitter.emit('checkpoint-published', {
634
659
  checkpoint: this.checkpointNumber,
635
660
  slot: this.targetSlot
636
661
  });
637
- const coinbase = checkpoint?.header.coinbase;
662
+ const coinbase = checkpoint.header.coinbase;
638
663
  await this.metrics.incFilledSlot(this.publisher.getSenderAddress().toString(), coinbase);
639
- } else if (checkpoint) {
664
+ } else {
640
665
  this.eventEmitter.emit('checkpoint-publish-failed', {
641
666
  ...l1Response,
642
667
  slot: this.targetSlot
@@ -645,19 +670,18 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
645
670
  this.metrics.recordPipelineDiscard();
646
671
  }
647
672
  }
648
- }).catch((err)=>{
649
- this.log.error(`Background L1 submission failed for slot ${this.targetSlot}`, err);
650
- if (checkpoint) {
651
- this.eventEmitter.emit('checkpoint-publish-failed', {
652
- slot: this.targetSlot
653
- });
654
- if (this.epochCache.isProposerPipeliningEnabled()) {
655
- this.metrics.recordPipelineDiscard();
656
- }
673
+ } catch (err) {
674
+ if (err instanceof SequencerInterruptedError) {
675
+ return;
657
676
  }
658
- });
659
- // Return the built checkpoint immediately — the work loop is now unblocked
660
- return checkpoint;
677
+ this.log.error(`Background attestation/L1 pipeline failed for slot ${this.targetSlot}`, err);
678
+ this.eventEmitter.emit('checkpoint-publish-failed', {
679
+ slot: this.targetSlot
680
+ });
681
+ if (this.epochCache.isProposerPipeliningEnabled()) {
682
+ this.metrics.recordPipelineDiscard();
683
+ }
684
+ }
661
685
  }
662
686
  /** Enqueues the checkpoint for L1 submission. Called after pipeline sleep in execute(). */ async enqueueCheckpointForSubmission(result) {
663
687
  const { checkpoint, attestations, attestationsSignature } = result;
@@ -673,10 +697,18 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
673
697
  return;
674
698
  }
675
699
  }
700
+ const isPipelining = this.epochCache.isProposerPipeliningEnabled();
701
+ const submissionSimulationOverridesPlan = buildSubmissionSimulationOverridesPlan({
702
+ pipelinedParentPlan: this.pipelinedParentSimulationOverridesPlan,
703
+ invalidateToPendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
704
+ lastArchiveRoot: checkpoint.header.lastArchiveRoot,
705
+ pipeliningEnabled: isPipelining
706
+ });
676
707
  await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
677
708
  txTimeoutAt,
678
- forcePendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
679
- forceProposedFeeHeader: this.computedForceProposedFeeHeader
709
+ ...submissionSimulationOverridesPlan ? {
710
+ simulationOverridesPlan: submissionSimulationOverridesPlan
711
+ } : {}
680
712
  });
681
713
  }
682
714
  async proposeCheckpoint() {
@@ -687,6 +719,15 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
687
719
  hasError: false
688
720
  };
689
721
  try {
722
+ const now = this.dateProvider.now();
723
+ if (this.epochCache.isProposerPipeliningEnabled() && this.proposedCheckpointData) {
724
+ // Measure against the wall-clock slot whose build window we are currently using.
725
+ // In pipelining mode `targetSlot` is intentionally one slot ahead, which makes the
726
+ // target-slot boundary a full slot away from the actual build start time.
727
+ const slotBoundaryMs = Number(getTimestampForSlot(this.slotNow, this.l1Constants)) * 1000;
728
+ this.checkpointMetrics.recordPipelinedCheckpointBuildStartOffsetFromSlotBoundary(now - slotBoundaryMs);
729
+ }
730
+ this.checkpointMetrics.startCheckpointTiming(now);
690
731
  // Get operator configured coinbase and fee recipient for this attestor
691
732
  const coinbase = this.validatorClient.getCoinbaseForAttestor(this.attestorAddress);
692
733
  const feeRecipient = this.validatorClient.getFeeRecipientForAttestor(this.attestorAddress);
@@ -708,15 +749,13 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
708
749
  // When pipelining, force the proposed checkpoint number and fee header to our parent so the
709
750
  // fee computation sees the same chain tip that L1 will see once the previous pipelined checkpoint lands.
710
751
  const isPipelining = this.epochCache.isProposerPipeliningEnabled();
711
- const parentCheckpointNumber = isPipelining ? CheckpointNumber(this.checkpointNumber - 1) : undefined;
712
- // Compute the parent's fee header override when pipelining
713
- if (isPipelining && this.proposedCheckpointData) {
714
- this.computedForceProposedFeeHeader = await this.computeForceProposedFeeHeader(parentCheckpointNumber);
715
- }
716
- const checkpointGlobalVariables = await this.globalsBuilder.buildCheckpointGlobalVariables(coinbase, feeRecipient, this.targetSlot, {
717
- forcePendingCheckpointNumber: parentCheckpointNumber,
718
- forceProposedFeeHeader: this.computedForceProposedFeeHeader
719
- });
752
+ this.pipelinedParentSimulationOverridesPlan = isPipelining ? await buildPipelinedParentSimulationOverridesPlan({
753
+ checkpointNumber: this.checkpointNumber,
754
+ proposedCheckpointData: this.proposedCheckpointData,
755
+ rollup: this.publisher.rollupContract,
756
+ log: this.log
757
+ }) : undefined;
758
+ const checkpointGlobalVariables = await this.globalsBuilder.buildCheckpointGlobalVariables(coinbase, feeRecipient, this.targetSlot, this.pipelinedParentSimulationOverridesPlan);
720
759
  // Collect L1 to L2 messages for the checkpoint and compute their hash
721
760
  const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(this.checkpointNumber);
722
761
  const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
@@ -794,8 +833,8 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
794
833
  return undefined;
795
834
  }
796
835
  // Record checkpoint-level build metrics
797
- this.metrics.recordCheckpointBuild(checkpointBuildTimer.ms(), blocksInCheckpoint.length, checkpoint.getStats().txCount, Number(checkpoint.header.totalManaUsed.toBigInt()));
798
- // Do not collect attestations nor publish to L1 in fisherman mode
836
+ this.checkpointMetrics.recordCheckpointBuild(checkpointBuildTimer.ms(), blocksInCheckpoint.length, checkpoint.getStats().txCount, Number(checkpoint.header.totalManaUsed.toBigInt()));
837
+ // In fisherman mode, return the checkpoint without broadcasting or collecting attestations
799
838
  if (this.config.fishermanMode) {
800
839
  this.log.info(`Built checkpoint for slot ${this.targetSlot} with ${blocksInCheckpoint.length} blocks. ` + `Skipping proposal in fisherman mode.`, {
801
840
  slot: this.targetSlot,
@@ -803,44 +842,23 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
803
842
  blocksBuilt: blocksInCheckpoint.length
804
843
  });
805
844
  this.metrics.recordCheckpointSuccess();
845
+ // Return a broadcast result with a dummy proposal — fisherman mode skips attestation collection
806
846
  return {
807
847
  checkpoint,
808
- attestations: CommitteeAttestationsAndSigners.empty(),
809
- attestationsSignature: Signature.empty()
848
+ proposal: undefined,
849
+ blockProposedAt: this.dateProvider.now()
810
850
  };
811
851
  }
812
- // Include the block pending broadcast in the checkpoint proposal if any
813
- const lastBlock = blockPendingBroadcast && {
814
- blockHeader: blockPendingBroadcast.block.header,
815
- indexWithinCheckpoint: blockPendingBroadcast.block.indexWithinCheckpoint,
816
- txs: blockPendingBroadcast.txs
817
- };
818
852
  // Create the checkpoint proposal and broadcast it
819
- const proposal = await this.validatorClient.createCheckpointProposal(checkpoint.header, checkpoint.archive.root, feeAssetPriceModifier, lastBlock, this.proposer, checkpointProposalOptions);
853
+ const proposal = await this.validatorClient.createCheckpointProposal(checkpoint.header, checkpoint.archive.root, this.checkpointNumber, feeAssetPriceModifier, blockPendingBroadcast, this.proposer, checkpointProposalOptions);
820
854
  const blockProposedAt = this.dateProvider.now();
821
855
  await this.p2pClient.broadcastCheckpointProposal(proposal);
822
- this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.targetSlot);
823
- const attestations = await this.waitForAttestations(proposal);
824
- const blockAttestedAt = this.dateProvider.now();
825
- this.metrics.recordCheckpointAttestationDelay(blockAttestedAt - blockProposedAt);
826
- // Proposer must sign over the attestations before pushing them to L1
827
- const signer = this.proposer ?? this.publisher.getSenderAddress();
828
- let attestationsSignature;
829
- try {
830
- attestationsSignature = await this.validatorClient.signAttestationsAndSigners(attestations, signer, this.targetSlot, this.checkpointNumber);
831
- } catch (err) {
832
- // We shouldn't really get here since we yield to another HA node
833
- // as soon as we see these errors when creating block or checkpoint proposals.
834
- if (this.handleHASigningError(err, 'Attestations signature')) {
835
- return undefined;
836
- }
837
- throw err;
838
- }
839
- // Return the result for the caller to enqueue after the pipeline sleep
856
+ this.checkpointMetrics.noteCheckpointBroadcast(this.dateProvider.now());
857
+ // Return immediately after broadcast — attestation collection happens in the background
840
858
  return {
841
859
  checkpoint,
842
- attestations,
843
- attestationsSignature
860
+ proposal,
861
+ blockProposedAt
844
862
  };
845
863
  } catch (e) {
846
864
  env.error = e;
@@ -891,17 +909,19 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
891
909
  indexWithinCheckpoint,
892
910
  txHashesAlreadyIncluded
893
911
  });
894
- // TODO(palla/mbps): Review these conditions. We may want to keep trying in some scenarios.
895
- if (!buildResult && timingInfo.isLastBlock) {
896
- break;
897
- } else if (!buildResult && timingInfo.deadline !== undefined) {
898
- // But if there is still time for more blocks, wait until the next subslot and try again
912
+ // If we failed to build the block due to insufficient txs, we try again if there is still time left in the slot
913
+ if ('failure' in buildResult) {
914
+ // If this was the last subslot, or we're running with a single block per slot, we're done
915
+ if (timingInfo.isLastBlock || timingInfo.deadline === undefined) {
916
+ break;
917
+ }
918
+ // Otherwise, if there is still time for more blocks, we wait until the next subslot and try again
899
919
  await this.waitUntilNextSubslot(timingInfo.deadline);
900
920
  continue;
901
- } else if (!buildResult) {
902
- break;
903
- } else if ('error' in buildResult) {
904
- // If there was an error building the block, just exit the loop and give up the rest of the slot
921
+ }
922
+ // If there was an error building the block, we just exit the loop and give up the rest of the slot.
923
+ // We don't want to risk building more blocks if something went wrong.
924
+ if ('error' in buildResult) {
905
925
  if (!(buildResult.error instanceof SequencerInterruptedError)) {
906
926
  this.log.warn(`Halting block building for slot ${this.targetSlot}`, {
907
927
  slot: this.targetSlot,
@@ -912,32 +932,29 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
912
932
  break;
913
933
  }
914
934
  const { block, usedTxs } = buildResult;
935
+ this.checkpointMetrics.noteCheckpointBlockBuilt(this.dateProvider.now(), {
936
+ isFirstBlock: blocksBuilt === 0,
937
+ isLastBlock: timingInfo.isLastBlock
938
+ });
915
939
  blocksInCheckpoint.push(block);
916
940
  usedTxs.forEach((tx)=>txHashesAlreadyIncluded.add(tx.txHash.toString()));
917
- // If this is the last block, sync it to the archiver and exit the loop
918
- // so we can build the checkpoint and start collecting attestations.
941
+ // Sign the block proposal. This will throw if HA signing fails.
942
+ const proposal = await this.createBlockProposal(block, inHash, usedTxs, blockProposalOptions);
943
+ // Sync the proposed block to the archiver to make it available, only after we've managed to sign the proposal,
944
+ // so we avoid polluting our archive with a block that would fail.
945
+ // We wait for the sync to succeed, as this helps catch consistency errors, even if it means we lose some time for block-building.
946
+ // If this throws, we abort the entire checkpoint.
947
+ await this.syncProposedBlockToArchiver(block);
948
+ // If this is the last block, do not broadcast it, since it will be included in the checkpoint proposal.
919
949
  if (timingInfo.isLastBlock) {
920
- await this.syncProposedBlockToArchiver(block);
921
950
  this.log.verbose(`Completed final block ${blockNumber} for slot ${this.targetSlot}`, {
922
951
  slot: this.targetSlot,
923
952
  blockNumber,
924
953
  blocksBuilt
925
954
  });
926
- blockPendingBroadcast = {
927
- block,
928
- txs: usedTxs
929
- };
955
+ blockPendingBroadcast = proposal;
930
956
  break;
931
957
  }
932
- // Broadcast the block proposal (unless we're in fisherman mode) unless the block is the last one,
933
- // in which case we'll broadcast it along with the checkpoint at the end of the loop.
934
- // Note that we only send the block to the archiver if we manage to create the proposal, so if there's
935
- // a HA error we don't pollute our archiver with a block that won't make it to the chain.
936
- const proposal = await this.createBlockProposal(block, inHash, usedTxs, blockProposalOptions);
937
- // Sync the proposed block to the archiver to make it available, only after we've managed to sign the proposal.
938
- // We wait for the sync to succeed, as this helps catch consistency errors, even if it means we lose some time for block-building.
939
- // If this throws, we abort the entire checkpoint.
940
- await this.syncProposedBlockToArchiver(block);
941
958
  // Once we have a signed proposal and the archiver agreed with our proposed block, then we broadcast it.
942
959
  proposal && await this.p2pClient.broadcastProposal(proposal);
943
960
  // Wait until the next block's start time
@@ -957,7 +974,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
957
974
  this.log.info(`Skipping block proposal for block ${block.number} in fisherman mode`);
958
975
  return Promise.resolve(undefined);
959
976
  }
960
- return this.validatorClient.createBlockProposal(block.header, block.indexWithinCheckpoint, inHash, block.archive.root, usedTxs, this.proposer, blockProposalOptions);
977
+ return this.validatorClient.createBlockProposal(block.header, this.checkpointNumber, block.indexWithinCheckpoint, inHash, block.archive.root, usedTxs, this.proposer, blockProposalOptions);
961
978
  }
962
979
  /** Sleeps until it is time to produce the next block in the slot */ async waitUntilNextSubslot(nextSubslotStart) {
963
980
  this.setStateFn(SequencerState.WAITING_UNTIL_NEXT_BLOCK, this.targetSlot);
@@ -987,7 +1004,9 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
987
1004
  slot: this.targetSlot
988
1005
  });
989
1006
  this.metrics.recordBlockProposalFailed('insufficient_txs');
990
- return undefined;
1007
+ return {
1008
+ failure: 'insufficient-txs'
1009
+ };
991
1010
  }
992
1011
  // Create iterator to pending txs. We filter out txs already included in previous blocks in the checkpoint
993
1012
  // just in case p2p failed to sync the provisional block and didn't get to remove those txs from the mempool yet.
@@ -1030,7 +1049,9 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
1030
1049
  slot: this.targetSlot
1031
1050
  });
1032
1051
  this.metrics.recordBlockProposalFailed('insufficient_valid_txs');
1033
- return undefined;
1052
+ return {
1053
+ failure: 'insufficient-valid-txs'
1054
+ };
1034
1055
  }
1035
1056
  // Block creation succeeded, emit stats and metrics
1036
1057
  const { block, publicProcessorDuration, usedTxs, blockBuildDuration, numTxs } = buildResult;
@@ -1056,7 +1077,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
1056
1077
  slot: this.targetSlot,
1057
1078
  buildSlot: this.slotNow
1058
1079
  });
1059
- this.metrics.recordBuiltBlock(blockBuildDuration, block.header.totalManaUsed.toNumberUnsafe());
1080
+ this.metrics.recordBuiltBlock(blockBuildDuration, block.header.totalManaUsed.toNumberUnsafe(), this.targetSlot);
1060
1081
  return {
1061
1082
  block,
1062
1083
  usedTxs
@@ -1154,16 +1175,16 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
1154
1175
  const numberOfRequiredAttestations = computeQuorum(committee.length);
1155
1176
  if (this.config.skipCollectingAttestations) {
1156
1177
  this.log.warn('Skipping attestation collection as per config (attesting with own keys only)');
1157
- const attestations = await this.validatorClient?.collectOwnAttestations(proposal);
1178
+ const attestations = await this.validatorClient?.collectOwnAttestations(proposal, this.checkpointNumber);
1158
1179
  return new CommitteeAttestationsAndSigners(orderAttestations(attestations ?? [], committee));
1159
1180
  }
1160
- const attestationTimeAllowed = this.config.enforceTimeTable ? this.timetable.getMaxAllowedTime(SequencerState.PUBLISHING_CHECKPOINT) : this.l1Constants.slotDuration;
1181
+ const attestationTimeAllowed = this.config.enforceTimeTable ? this.timetable.getCheckpointAttestationDeadline() : this.l1Constants.slotDuration;
1161
1182
  const attestationDeadline = new Date((this.getSlotStartBuildTimestamp() + attestationTimeAllowed) * 1000);
1162
1183
  this.metrics.recordRequiredAttestations(numberOfRequiredAttestations, attestationTimeAllowed);
1163
1184
  const collectAttestationsTimer = new Timer();
1164
1185
  let collectedAttestationsCount = 0;
1165
1186
  try {
1166
- const attestations = await this.validatorClient.collectAttestations(proposal, numberOfRequiredAttestations, attestationDeadline);
1187
+ const attestations = await this.validatorClient.collectAttestations(proposal, numberOfRequiredAttestations, attestationDeadline, this.checkpointNumber);
1167
1188
  collectedAttestationsCount = attestations.length;
1168
1189
  // Trim attestations to minimum required to save L1 calldata gas
1169
1190
  const localAddresses = this.validatorClient.getValidatorAddresses();
@@ -1256,8 +1277,12 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
1256
1277
  * Adds the proposed block to the archiver so it's available via P2P.
1257
1278
  * Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
1258
1279
  * would never receive its own block without this explicit sync.
1280
+ *
1281
+ * In fisherman mode we skip this push: the fisherman builds blocks locally for validation
1282
+ * and fee analysis only, and pushing them to the archiver causes spurious reorg cascades
1283
+ * whenever the real proposer's block arrives from L1.
1259
1284
  */ async syncProposedBlockToArchiver(block) {
1260
- if (this.config.skipPushProposedBlocksToArchiver) {
1285
+ if (this.config.skipPushProposedBlocksToArchiver || this.config.fishermanMode) {
1261
1286
  this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
1262
1287
  blockNumber: block.number,
1263
1288
  slot: block.header.globalVariables.slotNumber
@@ -1309,40 +1334,6 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
1309
1334
  }
1310
1335
  return false;
1311
1336
  }
1312
- /**
1313
- * In times of congestion we need to simulate using the correct fee header override for the previous block
1314
- * We calculate the correct fee header values.
1315
- *
1316
- * If we are in block 1, or the checkpoint we are querying does not exist, we return undefined. However
1317
- * If we are pipelining - where this function is called, the grandparentCheckpointNumber should always exist
1318
- * @param parentCheckpointNumber
1319
- * @returns
1320
- */ async computeForceProposedFeeHeader(parentCheckpointNumber) {
1321
- if (!this.proposedCheckpointData) {
1322
- return undefined;
1323
- }
1324
- const rollup = this.publisher.rollupContract;
1325
- const grandparentCheckpointNumber = CheckpointNumber(this.checkpointNumber - 2);
1326
- try {
1327
- const [grandparentCheckpoint, manaTarget] = await Promise.all([
1328
- rollup.getCheckpoint(grandparentCheckpointNumber),
1329
- rollup.getManaTarget()
1330
- ]);
1331
- if (!grandparentCheckpoint || !grandparentCheckpoint.feeHeader) {
1332
- this.log.error(`Grandparent checkpoint or its feeHeader is undefined for checkpointNumber=${grandparentCheckpointNumber.toString()}`);
1333
- return undefined;
1334
- } else {
1335
- const parentFeeHeader = RollupContract.computeChildFeeHeader(grandparentCheckpoint.feeHeader, this.proposedCheckpointData.totalManaUsed, this.proposedCheckpointData.feeAssetPriceModifier, manaTarget);
1336
- return {
1337
- checkpointNumber: parentCheckpointNumber,
1338
- feeHeader: parentFeeHeader
1339
- };
1340
- }
1341
- } catch (err) {
1342
- this.log.error(`Failed to fetch grandparent checkpoint or mana target for checkpointNumber=${grandparentCheckpointNumber.toString()}: ${err}`);
1343
- return undefined;
1344
- }
1345
- }
1346
1337
  /** Waits until a specific time within the current slot */ async waitUntilTimeInSlot(targetSecondsIntoSlot) {
1347
1338
  const slotStartTimestamp = this.getSlotStartBuildTimestamp();
1348
1339
  const targetTimestamp = slotStartTimestamp + targetSecondsIntoSlot;
@@ -0,0 +1,34 @@
1
+ import { type TelemetryClient } from '@aztec/telemetry-client';
2
+ /**
3
+ * Per-job recording surface used by {@link CheckpointProposalJob}.
4
+ *
5
+ * Ownership split:
6
+ * - {@link CheckpointProposalJobMetrics} owns the OpenTelemetry meter and instruments and should live for the
7
+ * lifetime of the sequencer process.
8
+ * - A recorder owns only mutable timing state for one checkpoint proposal job so overlapping jobs cannot
9
+ * overwrite each other's timing markers.
10
+ */
11
+ export interface CheckpointProposalJobMetricsRecorder {
12
+ recordCheckpointAttestationDelay(durationMs: number): void;
13
+ recordCheckpointBuild(durationMs: number, blockCount: number, txCount: number, totalMana: number): void;
14
+ recordPipelinedCheckpointBuildStartOffsetFromSlotBoundary(offsetMs: number): void;
15
+ startCheckpointTiming(nowMs: number): void;
16
+ noteCheckpointBlockBuilt(nowMs: number, opts: {
17
+ isFirstBlock: boolean;
18
+ isLastBlock: boolean;
19
+ }): void;
20
+ noteCheckpointBroadcast(nowMs: number): void;
21
+ }
22
+ /**
23
+ * Long-lived owner of checkpoint proposal job telemetry instruments.
24
+ *
25
+ * The sequencer should construct this once and reuse it across jobs. Each call to {@link createRecorder} returns a
26
+ * lightweight recorder with isolated mutable state for a single checkpoint proposal job.
27
+ */
28
+ export declare class CheckpointProposalJobMetrics {
29
+ private readonly meter;
30
+ private readonly instruments;
31
+ constructor(client: TelemetryClient, name?: string);
32
+ createRecorder(): CheckpointProposalJobMetricsRecorder;
33
+ }
34
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludF9wcm9wb3NhbF9qb2JfbWV0cmljcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcXVlbmNlci9jaGVja3BvaW50X3Byb3Bvc2FsX2pvYl9tZXRyaWNzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFNTCxLQUFLLGVBQWUsRUFDckIsTUFBTSx5QkFBeUIsQ0FBQztBQWFqQzs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sV0FBVyxvQ0FBb0M7SUFDbkQsZ0NBQWdDLENBQUMsVUFBVSxFQUFFLE1BQU0sR0FBRyxJQUFJLENBQUM7SUFDM0QscUJBQXFCLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sR0FBRyxJQUFJLENBQUM7SUFDeEcseURBQXlELENBQUMsUUFBUSxFQUFFLE1BQU0sR0FBRyxJQUFJLENBQUM7SUFDbEYscUJBQXFCLENBQUMsS0FBSyxFQUFFLE1BQU0sR0FBRyxJQUFJLENBQUM7SUFDM0Msd0JBQXdCLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUU7UUFBRSxZQUFZLEVBQUUsT0FBTyxDQUFDO1FBQUMsV0FBVyxFQUFFLE9BQU8sQ0FBQTtLQUFFLEdBQUcsSUFBSSxDQUFDO0lBQ3JHLHVCQUF1QixDQUFDLEtBQUssRUFBRSxNQUFNLEdBQUcsSUFBSSxDQUFDO0NBQzlDO0FBMEREOzs7OztHQUtHO0FBQ0gscUJBQWEsNEJBQTRCO0lBQ3ZDLE9BQU8sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFRO0lBQzlCLE9BQU8sQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFtQztJQUUvRCxZQUFZLE1BQU0sRUFBRSxlQUFlLEVBQUUsSUFBSSxTQUEwQixFQWtCbEU7SUFFTSxjQUFjLElBQUksb0NBQW9DLENBRTVEO0NBQ0YifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint_proposal_job_metrics.d.ts","sourceRoot":"","sources":["../../src/sequencer/checkpoint_proposal_job_metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AAajC;;;;;;;;GAQG;AACH,MAAM,WAAW,oCAAoC;IACnD,gCAAgC,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3D,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACxG,yDAAyD,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAClF,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,YAAY,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACrG,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9C;AA0DD;;;;;GAKG;AACH,qBAAa,4BAA4B;IACvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmC;IAE/D,YAAY,MAAM,EAAE,eAAe,EAAE,IAAI,SAA0B,EAkBlE;IAEM,cAAc,IAAI,oCAAoC,CAE5D;CACF"}
@@ -0,0 +1,72 @@
1
+ import { Attributes, Metrics } from '@aztec/telemetry-client';
2
+ /**
3
+ * Concrete per-job recorder.
4
+ *
5
+ * This class should be short-lived: create one recorder per checkpoint proposal job and discard it when the job
6
+ * completes. It intentionally does not create instruments; it only holds the job-local timestamps needed to derive
7
+ * timing metrics safely.
8
+ */ class CheckpointProposalJobMetricsRecorderImpl {
9
+ instruments;
10
+ checkpointStartedAt;
11
+ checkpointLastBlockBuiltAt;
12
+ constructor(instruments){
13
+ this.instruments = instruments;
14
+ }
15
+ recordCheckpointAttestationDelay(durationMs) {
16
+ this.instruments.checkpointAttestationDelay.record(Math.ceil(durationMs));
17
+ }
18
+ recordCheckpointBuild(durationMs, blockCount, txCount, totalMana) {
19
+ this.instruments.checkpointBuildDuration.record(Math.ceil(durationMs));
20
+ this.instruments.checkpointBlockCount.record(blockCount);
21
+ this.instruments.checkpointTxCount.record(txCount);
22
+ this.instruments.checkpointTotalMana.record(totalMana);
23
+ }
24
+ recordPipelinedCheckpointBuildStartOffsetFromSlotBoundary(offsetMs) {
25
+ this.instruments.pipelinedCheckpointBuildStartOffsetFromSlotBoundary.record(Math.ceil(Math.abs(offsetMs)), {
26
+ [Attributes.SLOT_BOUNDARY_SIDE]: offsetMs < 0 ? 'before' : 'after'
27
+ });
28
+ }
29
+ startCheckpointTiming(nowMs) {
30
+ this.checkpointStartedAt = nowMs;
31
+ this.checkpointLastBlockBuiltAt = undefined;
32
+ }
33
+ noteCheckpointBlockBuilt(nowMs, opts) {
34
+ if (opts.isFirstBlock && this.checkpointStartedAt !== undefined) {
35
+ this.instruments.checkpointStartToFirstBlockDuration.record(Math.ceil(nowMs - this.checkpointStartedAt));
36
+ }
37
+ if (opts.isLastBlock) {
38
+ this.checkpointLastBlockBuiltAt = nowMs;
39
+ }
40
+ }
41
+ noteCheckpointBroadcast(nowMs) {
42
+ if (this.checkpointLastBlockBuiltAt !== undefined) {
43
+ this.instruments.checkpointLastBlockToBroadcastDuration.record(Math.ceil(nowMs - this.checkpointLastBlockBuiltAt));
44
+ }
45
+ this.checkpointLastBlockBuiltAt = undefined;
46
+ }
47
+ }
48
+ /**
49
+ * Long-lived owner of checkpoint proposal job telemetry instruments.
50
+ *
51
+ * The sequencer should construct this once and reuse it across jobs. Each call to {@link createRecorder} returns a
52
+ * lightweight recorder with isolated mutable state for a single checkpoint proposal job.
53
+ */ export class CheckpointProposalJobMetrics {
54
+ meter;
55
+ instruments;
56
+ constructor(client, name = 'CheckpointProposalJob'){
57
+ this.meter = client.getMeter(name);
58
+ this.instruments = {
59
+ checkpointAttestationDelay: this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY),
60
+ checkpointBuildDuration: this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_BUILD_DURATION),
61
+ checkpointStartToFirstBlockDuration: this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_START_TO_FIRST_BLOCK_DURATION),
62
+ checkpointLastBlockToBroadcastDuration: this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_LAST_BLOCK_TO_BROADCAST_DURATION),
63
+ pipelinedCheckpointBuildStartOffsetFromSlotBoundary: this.meter.createHistogram(Metrics.SEQUENCER_PIPELINED_CHECKPOINT_BUILD_START_OFFSET_FROM_SLOT_BOUNDARY),
64
+ checkpointBlockCount: this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_BLOCK_COUNT),
65
+ checkpointTxCount: this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_TX_COUNT),
66
+ checkpointTotalMana: this.meter.createGauge(Metrics.SEQUENCER_CHECKPOINT_TOTAL_MANA)
67
+ };
68
+ }
69
+ createRecorder() {
70
+ return new CheckpointProposalJobMetricsRecorderImpl(this.instruments);
71
+ }
72
+ }