@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.
- package/dest/client/sequencer-client.d.ts +3 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +3 -4
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +8 -11
- package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
- package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_predictor.js +128 -0
- package/dest/global_variable_builder/fee_provider.d.ts +21 -0
- package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_provider.js +58 -0
- package/dest/global_variable_builder/global_builder.d.ts +4 -9
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +3 -41
- package/dest/global_variable_builder/index.d.ts +3 -1
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/global_variable_builder/index.js +2 -0
- package/dest/publisher/config.d.ts +1 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +2 -2
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +3 -4
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts +1 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +0 -1
- package/dest/publisher/sequencer-publisher.d.ts +16 -35
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +60 -92
- package/dest/sequencer/chain_state_overrides.d.ts +25 -0
- package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
- package/dest/sequencer/chain_state_overrides.js +39 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +13 -21
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +143 -152
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
- package/dest/sequencer/metrics.d.ts +5 -10
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +12 -20
- package/dest/sequencer/sequencer.d.ts +5 -2
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +31 -23
- package/dest/sequencer/timetable.d.ts +14 -1
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +45 -36
- package/package.json +27 -27
- package/src/client/sequencer-client.ts +5 -7
- package/src/config.ts +8 -11
- package/src/global_variable_builder/README.md +44 -0
- package/src/global_variable_builder/fee_predictor.ts +172 -0
- package/src/global_variable_builder/fee_provider.ts +75 -0
- package/src/global_variable_builder/global_builder.ts +7 -54
- package/src/global_variable_builder/index.ts +2 -0
- package/src/publisher/config.ts +6 -4
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +3 -1
- package/src/publisher/sequencer-publisher-factory.ts +0 -3
- package/src/publisher/sequencer-publisher.ts +106 -152
- package/src/sequencer/README.md +83 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +185 -201
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/metrics.ts +15 -25
- package/src/sequencer/sequencer.ts +44 -26
- 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 {
|
|
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
|
-
/**
|
|
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,
|
|
594
|
-
*
|
|
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
|
|
602
|
-
//
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
603
|
+
// Build blocks, assemble checkpoint, and broadcast proposal (BLOCKING).
|
|
604
|
+
// Returns after broadcast — attestation 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
|
-
//
|
|
616
|
-
|
|
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.
|
|
640
|
+
attestationsSignature = await this.validatorClient.signAttestationsAndSigners(attestations, signer, this.targetSlot, this.checkpointNumber);
|
|
619
641
|
} catch (err) {
|
|
620
|
-
this.
|
|
621
|
-
|
|
642
|
+
if (this.handleHASigningError(err, 'Attestations signature')) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
throw err;
|
|
622
646
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
|
662
|
+
const coinbase = checkpoint.header.coinbase;
|
|
638
663
|
await this.metrics.incFilledSlot(this.publisher.getSenderAddress().toString(), coinbase);
|
|
639
|
-
} else
|
|
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
|
-
}
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
660
|
-
|
|
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
|
-
|
|
679
|
-
|
|
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
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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.
|
|
798
|
-
//
|
|
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
|
-
|
|
809
|
-
|
|
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,
|
|
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.
|
|
823
|
-
|
|
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
|
-
|
|
843
|
-
|
|
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
|
-
//
|
|
895
|
-
if (
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
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
|
-
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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
|
-
//
|
|
918
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
+
}
|