@aztec/sequencer-client 0.0.1-commit.cb6bed7c2 → 0.0.1-commit.cbf2c2d5d
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 +4 -12
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +27 -76
- package/dest/config.d.ts +4 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +9 -2
- package/dest/global_variable_builder/global_builder.d.ts +13 -7
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +22 -21
- package/dest/global_variable_builder/index.d.ts +2 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/publisher/config.d.ts +13 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +17 -2
- package/dest/publisher/sequencer-publisher-factory.d.ts +3 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +2 -2
- package/dest/publisher/sequencer-publisher.d.ts +9 -4
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +33 -9
- package/dest/sequencer/checkpoint_proposal_job.d.ts +12 -4
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +142 -97
- package/dest/sequencer/events.d.ts +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +5 -1
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +11 -0
- package/dest/sequencer/sequencer.d.ts +7 -5
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +71 -61
- package/dest/sequencer/types.d.ts +2 -5
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +4 -4
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +37 -101
- package/src/config.ts +12 -1
- package/src/global_variable_builder/global_builder.ts +22 -23
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +32 -0
- package/src/publisher/sequencer-publisher-factory.ts +3 -3
- package/src/publisher/sequencer-publisher.ts +39 -11
- package/src/sequencer/checkpoint_proposal_job.ts +190 -101
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +14 -0
- package/src/sequencer/sequencer.ts +97 -68
- package/src/sequencer/types.ts +2 -5
- package/src/test/mock_checkpoint_builder.ts +3 -3
|
@@ -446,9 +446,9 @@ import { Timer } from '@aztec/foundation/timer';
|
|
|
446
446
|
import { isErrorClass, unfreeze } from '@aztec/foundation/types';
|
|
447
447
|
import { CommitteeAttestationsAndSigners, MaliciousCommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
448
448
|
import { validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
449
|
-
import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
449
|
+
import { getSlotStartBuildTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
450
450
|
import { Gas } from '@aztec/stdlib/gas';
|
|
451
|
-
import {
|
|
451
|
+
import { InsufficientValidTxsError } from '@aztec/stdlib/interfaces/server';
|
|
452
452
|
import { computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
453
453
|
import { orderAttestations, trimAttestations } from '@aztec/stdlib/p2p';
|
|
454
454
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
@@ -462,7 +462,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
462
462
|
return {
|
|
463
463
|
// nullish operator needed for tests
|
|
464
464
|
[Attributes.COINBASE]: this.validatorClient.getCoinbaseForAttestor(this.attestorAddress)?.toString(),
|
|
465
|
-
[Attributes.SLOT_NUMBER]: this.
|
|
465
|
+
[Attributes.SLOT_NUMBER]: this.targetSlot
|
|
466
466
|
};
|
|
467
467
|
}), _dec2 = trackSpan('CheckpointProposalJob.buildBlocksForCheckpoint'), _dec3 = trackSpan('CheckpointProposalJob.waitUntilNextSubslot'), _dec4 = trackSpan('CheckpointProposalJob.buildSingleBlock'), _dec5 = trackSpan('CheckpointProposalJob.waitForMinTxs'), _dec6 = trackSpan('CheckpointProposalJob.waitForAttestations'), _dec7 = trackSpan('CheckpointProposalJob.waitUntilTimeInSlot');
|
|
468
468
|
/**
|
|
@@ -471,8 +471,10 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
471
471
|
* as well as enqueueing votes for slashing and governance proposals. This class is created from
|
|
472
472
|
* the Sequencer once the check for being the proposer for the slot has succeeded.
|
|
473
473
|
*/ export class CheckpointProposalJob {
|
|
474
|
-
|
|
475
|
-
|
|
474
|
+
slotNow;
|
|
475
|
+
targetSlot;
|
|
476
|
+
epochNow;
|
|
477
|
+
targetEpoch;
|
|
476
478
|
checkpointNumber;
|
|
477
479
|
syncedToBlockNumber;
|
|
478
480
|
proposer;
|
|
@@ -542,10 +544,12 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
542
544
|
], []));
|
|
543
545
|
}
|
|
544
546
|
log;
|
|
545
|
-
constructor(
|
|
547
|
+
constructor(slotNow, targetSlot, epochNow, targetEpoch, checkpointNumber, syncedToBlockNumber, // TODO(palla/mbps): Can we remove the proposer in favor of attestorAddress? Need to check fisherman-node flows.
|
|
546
548
|
proposer, publisher, attestorAddress, invalidateCheckpoint, validatorClient, globalsBuilder, p2pClient, worldState, l1ToL2MessageSource, l2BlockSource, checkpointsBuilder, blockSink, l1Constants, config, timetable, slasherClient, epochCache, dateProvider, metrics, eventEmitter, setStateFn, tracer, bindings){
|
|
547
|
-
this.
|
|
548
|
-
this.
|
|
549
|
+
this.slotNow = slotNow;
|
|
550
|
+
this.targetSlot = targetSlot;
|
|
551
|
+
this.epochNow = epochNow;
|
|
552
|
+
this.targetEpoch = targetEpoch;
|
|
549
553
|
this.checkpointNumber = checkpointNumber;
|
|
550
554
|
this.syncedToBlockNumber = syncedToBlockNumber;
|
|
551
555
|
this.proposer = proposer;
|
|
@@ -573,9 +577,15 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
573
577
|
_initProto(this);
|
|
574
578
|
this.log = createLogger('sequencer:checkpoint-proposal', {
|
|
575
579
|
...bindings,
|
|
576
|
-
instanceId: `slot-${
|
|
580
|
+
instanceId: `slot-${this.slotNow}`
|
|
577
581
|
});
|
|
578
582
|
}
|
|
583
|
+
/** The wall-clock slot during which the proposer builds. */ get slot() {
|
|
584
|
+
return this.slotNow;
|
|
585
|
+
}
|
|
586
|
+
/** The wall-clock epoch. */ get epoch() {
|
|
587
|
+
return this.epochNow;
|
|
588
|
+
}
|
|
579
589
|
/**
|
|
580
590
|
* Executes the checkpoint proposal job.
|
|
581
591
|
* Returns the published checkpoint if successful, undefined otherwise.
|
|
@@ -583,7 +593,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
583
593
|
// Enqueue governance and slashing votes (returns promises that will be awaited later)
|
|
584
594
|
// In fisherman mode, we simulate slashing but don't actually publish to L1
|
|
585
595
|
// These are constant for the whole slot, so we only enqueue them once
|
|
586
|
-
const votesPromises = new CheckpointVoter(this.
|
|
596
|
+
const votesPromises = new CheckpointVoter(this.targetSlot, this.publisher, this.attestorAddress, this.validatorClient, this.slasherClient, this.l1Constants, this.config, this.metrics, this.log).enqueueVotes();
|
|
587
597
|
// Build and propose the checkpoint. This will enqueue the request on the publisher if a checkpoint is built.
|
|
588
598
|
const checkpoint = await this.proposeCheckpoint();
|
|
589
599
|
// Wait until the voting promises have resolved, so all requests are enqueued (not sent)
|
|
@@ -596,6 +606,24 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
596
606
|
await this.handleCheckpointEndAsFisherman(checkpoint);
|
|
597
607
|
return;
|
|
598
608
|
}
|
|
609
|
+
// If pipelining, wait until the submission slot so L1 recognizes the pipelined proposer
|
|
610
|
+
if (this.epochCache.isProposerPipeliningEnabled()) {
|
|
611
|
+
const submissionSlotTimestamp = getTimestampForSlot(this.targetSlot, this.l1Constants) - BigInt(this.l1Constants.ethereumSlotDuration);
|
|
612
|
+
this.log.info(`Waiting until submission slot ${this.targetSlot} for L1 submission`, {
|
|
613
|
+
slot: this.slot,
|
|
614
|
+
submissionSlot: this.targetSlot,
|
|
615
|
+
submissionSlotTimestamp
|
|
616
|
+
});
|
|
617
|
+
await sleepUntil(new Date(Number(submissionSlotTimestamp) * 1000), this.dateProvider.nowAsDate());
|
|
618
|
+
// After waking, verify the parent checkpoint wasn't pruned during the sleep.
|
|
619
|
+
// We check L1's pending tip directly instead of canProposeAt, which also validates the proposer
|
|
620
|
+
// identity and would fail because the timestamp resolves to a different slot's proposer.
|
|
621
|
+
const l1Tips = await this.publisher.rollupContract.getTips();
|
|
622
|
+
if (l1Tips.pending < this.checkpointNumber - 1) {
|
|
623
|
+
this.log.warn(`Parent checkpoint was pruned during pipelining sleep (L1 pending=${l1Tips.pending}, expected>=${this.checkpointNumber - 1}), skipping L1 submission for checkpoint ${this.checkpointNumber}`);
|
|
624
|
+
return undefined;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
599
627
|
// Then send everything to L1
|
|
600
628
|
const l1Response = await this.publisher.sendRequests();
|
|
601
629
|
const proposedAction = l1Response?.successfulActions.find((a)=>a === 'propose');
|
|
@@ -627,19 +655,26 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
627
655
|
const coinbase = this.validatorClient.getCoinbaseForAttestor(this.attestorAddress);
|
|
628
656
|
const feeRecipient = this.validatorClient.getFeeRecipientForAttestor(this.attestorAddress);
|
|
629
657
|
// Start the checkpoint
|
|
630
|
-
this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.
|
|
631
|
-
this.
|
|
658
|
+
this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.targetSlot);
|
|
659
|
+
this.log.info(`Starting checkpoint proposal`, {
|
|
660
|
+
buildSlot: this.slot,
|
|
661
|
+
submissionSlot: this.targetSlot,
|
|
662
|
+
pipelining: this.epochCache.isProposerPipeliningEnabled(),
|
|
663
|
+
proposer: this.proposer?.toString(),
|
|
664
|
+
coinbase: coinbase.toString()
|
|
665
|
+
});
|
|
666
|
+
this.metrics.incOpenSlot(this.targetSlot, this.proposer?.toString() ?? 'unknown');
|
|
632
667
|
// Enqueues checkpoint invalidation (constant for the whole slot)
|
|
633
668
|
if (this.invalidateCheckpoint && !this.config.skipInvalidateBlockAsProposer) {
|
|
634
669
|
this.publisher.enqueueInvalidateCheckpoint(this.invalidateCheckpoint);
|
|
635
670
|
}
|
|
636
671
|
// Create checkpoint builder for the slot
|
|
637
|
-
const checkpointGlobalVariables = await this.globalsBuilder.buildCheckpointGlobalVariables(coinbase, feeRecipient, this.
|
|
672
|
+
const checkpointGlobalVariables = await this.globalsBuilder.buildCheckpointGlobalVariables(coinbase, feeRecipient, this.targetSlot);
|
|
638
673
|
// Collect L1 to L2 messages for the checkpoint and compute their hash
|
|
639
674
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(this.checkpointNumber);
|
|
640
675
|
const inHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
641
676
|
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
642
|
-
const previousCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(this.
|
|
677
|
+
const previousCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(this.targetEpoch)).filter((c)=>c.checkpointNumber < this.checkpointNumber).map((c)=>c.checkpointOutHash);
|
|
643
678
|
// Get the fee asset price modifier from the oracle
|
|
644
679
|
const feeAssetPriceModifier = await this.publisher.getFeeAssetPriceModifier();
|
|
645
680
|
const fork = _ts_add_disposable_resource(env, await this.worldState.fork(this.syncedToBlockNumber, {
|
|
@@ -674,18 +709,18 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
674
709
|
throw err;
|
|
675
710
|
}
|
|
676
711
|
if (blocksInCheckpoint.length === 0) {
|
|
677
|
-
this.log.warn(`No blocks were built for slot ${this.
|
|
678
|
-
slot: this.
|
|
712
|
+
this.log.warn(`No blocks were built for slot ${this.targetSlot}`, {
|
|
713
|
+
slot: this.targetSlot
|
|
679
714
|
});
|
|
680
715
|
this.eventEmitter.emit('checkpoint-empty', {
|
|
681
|
-
slot: this.
|
|
716
|
+
slot: this.targetSlot
|
|
682
717
|
});
|
|
683
718
|
return undefined;
|
|
684
719
|
}
|
|
685
720
|
const minBlocksForCheckpoint = this.config.minBlocksForCheckpoint;
|
|
686
721
|
if (minBlocksForCheckpoint !== undefined && blocksInCheckpoint.length < minBlocksForCheckpoint) {
|
|
687
722
|
this.log.warn(`Checkpoint has fewer blocks than minimum (${blocksInCheckpoint.length} < ${minBlocksForCheckpoint}), skipping proposal`, {
|
|
688
|
-
slot: this.
|
|
723
|
+
slot: this.targetSlot,
|
|
689
724
|
blocksBuilt: blocksInCheckpoint.length,
|
|
690
725
|
minBlocksForCheckpoint
|
|
691
726
|
});
|
|
@@ -693,9 +728,10 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
693
728
|
}
|
|
694
729
|
// Assemble and broadcast the checkpoint proposal, including the last block that was not
|
|
695
730
|
// broadcasted yet, and wait to collect the committee attestations.
|
|
696
|
-
this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.
|
|
731
|
+
this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.targetSlot);
|
|
697
732
|
const checkpoint = await checkpointBuilder.completeCheckpoint();
|
|
698
|
-
// Final validation
|
|
733
|
+
// Final validation: per-block limits are only checked if the operator set them explicitly.
|
|
734
|
+
// Otherwise, checkpoint-level budgets were already enforced by the redistribution logic.
|
|
699
735
|
try {
|
|
700
736
|
validateCheckpoint(checkpoint, {
|
|
701
737
|
rollupManaLimit: this.l1Constants.rollupManaLimit,
|
|
@@ -714,8 +750,8 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
714
750
|
this.metrics.recordCheckpointBuild(checkpointBuildTimer.ms(), blocksInCheckpoint.length, checkpoint.getStats().txCount, Number(checkpoint.header.totalManaUsed.toBigInt()));
|
|
715
751
|
// Do not collect attestations nor publish to L1 in fisherman mode
|
|
716
752
|
if (this.config.fishermanMode) {
|
|
717
|
-
this.log.info(`Built checkpoint for slot ${this.
|
|
718
|
-
slot: this.
|
|
753
|
+
this.log.info(`Built checkpoint for slot ${this.targetSlot} with ${blocksInCheckpoint.length} blocks. ` + `Skipping proposal in fisherman mode.`, {
|
|
754
|
+
slot: this.targetSlot,
|
|
719
755
|
checkpoint: checkpoint.header.toInspect(),
|
|
720
756
|
blocksBuilt: blocksInCheckpoint.length
|
|
721
757
|
});
|
|
@@ -732,7 +768,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
732
768
|
const proposal = await this.validatorClient.createCheckpointProposal(checkpoint.header, checkpoint.archive.root, feeAssetPriceModifier, lastBlock, this.proposer, checkpointProposalOptions);
|
|
733
769
|
const blockProposedAt = this.dateProvider.now();
|
|
734
770
|
await this.p2pClient.broadcastCheckpointProposal(proposal);
|
|
735
|
-
this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.
|
|
771
|
+
this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.targetSlot);
|
|
736
772
|
const attestations = await this.waitForAttestations(proposal);
|
|
737
773
|
const blockAttestedAt = this.dateProvider.now();
|
|
738
774
|
this.metrics.recordCheckpointAttestationDelay(blockAttestedAt - blockProposedAt);
|
|
@@ -740,7 +776,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
740
776
|
const signer = this.proposer ?? this.publisher.getSenderAddress();
|
|
741
777
|
let attestationsSignature;
|
|
742
778
|
try {
|
|
743
|
-
attestationsSignature = await this.validatorClient.signAttestationsAndSigners(attestations, signer, this.
|
|
779
|
+
attestationsSignature = await this.validatorClient.signAttestationsAndSigners(attestations, signer, this.targetSlot, this.checkpointNumber);
|
|
744
780
|
} catch (err) {
|
|
745
781
|
// We shouldn't really get here since we yield to another HA node
|
|
746
782
|
// as soon as we see these errors when creating block or checkpoint proposals.
|
|
@@ -750,10 +786,10 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
750
786
|
throw err;
|
|
751
787
|
}
|
|
752
788
|
// Enqueue publishing the checkpoint to L1
|
|
753
|
-
this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.
|
|
789
|
+
this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.targetSlot);
|
|
754
790
|
const aztecSlotDuration = this.l1Constants.slotDuration;
|
|
755
|
-
const
|
|
756
|
-
const txTimeoutAt = new Date((
|
|
791
|
+
const submissionSlotStart = Number(getTimestampForSlot(this.targetSlot, this.l1Constants));
|
|
792
|
+
const txTimeoutAt = new Date((submissionSlotStart + aztecSlotDuration) * 1000);
|
|
757
793
|
// If we have been configured to potentially skip publishing checkpoint then roll the dice here
|
|
758
794
|
if (this.config.skipPublishingCheckpointsPercent !== undefined && this.config.skipPublishingCheckpointsPercent > 0) {
|
|
759
795
|
const result = Math.max(0, randomInt(100));
|
|
@@ -799,7 +835,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
799
835
|
const timingInfo = this.timetable.canStartNextBlock(secondsIntoSlot);
|
|
800
836
|
if (!timingInfo.canStart) {
|
|
801
837
|
this.log.debug(`Not enough time left in slot to start another block`, {
|
|
802
|
-
slot: this.
|
|
838
|
+
slot: this.targetSlot,
|
|
803
839
|
blocksBuilt,
|
|
804
840
|
secondsIntoSlot
|
|
805
841
|
});
|
|
@@ -828,8 +864,8 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
828
864
|
} else if ('error' in buildResult) {
|
|
829
865
|
// If there was an error building the block, just exit the loop and give up the rest of the slot
|
|
830
866
|
if (!(buildResult.error instanceof SequencerInterruptedError)) {
|
|
831
|
-
this.log.warn(`Halting block building for slot ${this.
|
|
832
|
-
slot: this.
|
|
867
|
+
this.log.warn(`Halting block building for slot ${this.targetSlot}`, {
|
|
868
|
+
slot: this.targetSlot,
|
|
833
869
|
blocksBuilt,
|
|
834
870
|
error: buildResult.error
|
|
835
871
|
});
|
|
@@ -838,21 +874,13 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
838
874
|
}
|
|
839
875
|
const { block, usedTxs } = buildResult;
|
|
840
876
|
blocksInCheckpoint.push(block);
|
|
841
|
-
// Sync the proposed block to the archiver to make it available
|
|
842
|
-
// Note that the checkpoint builder uses its own fork so it should not need to wait for this syncing
|
|
843
|
-
// Eventually we should refactor the checkpoint builder to not need a separate long-lived fork
|
|
844
|
-
// Fire and forget - don't block the critical path, but log errors
|
|
845
|
-
this.syncProposedBlockToArchiver(block).catch((err)=>{
|
|
846
|
-
this.log.error(`Failed to sync proposed block ${block.number} to archiver`, {
|
|
847
|
-
blockNumber: block.number,
|
|
848
|
-
err
|
|
849
|
-
});
|
|
850
|
-
});
|
|
851
877
|
usedTxs.forEach((tx)=>txHashesAlreadyIncluded.add(tx.txHash.toString()));
|
|
852
|
-
// If this is the last block,
|
|
878
|
+
// If this is the last block, sync it to the archiver and exit the loop
|
|
879
|
+
// so we can build the checkpoint and start collecting attestations.
|
|
853
880
|
if (timingInfo.isLastBlock) {
|
|
854
|
-
this.
|
|
855
|
-
|
|
881
|
+
await this.syncProposedBlockToArchiver(block);
|
|
882
|
+
this.log.verbose(`Completed final block ${blockNumber} for slot ${this.targetSlot}`, {
|
|
883
|
+
slot: this.targetSlot,
|
|
856
884
|
blockNumber,
|
|
857
885
|
blocksBuilt
|
|
858
886
|
});
|
|
@@ -862,17 +890,22 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
862
890
|
};
|
|
863
891
|
break;
|
|
864
892
|
}
|
|
865
|
-
//
|
|
866
|
-
//
|
|
867
|
-
if
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
893
|
+
// Broadcast the block proposal (unless we're in fisherman mode) unless the block is the last one,
|
|
894
|
+
// in which case we'll broadcast it along with the checkpoint at the end of the loop.
|
|
895
|
+
// Note that we only send the block to the archiver if we manage to create the proposal, so if there's
|
|
896
|
+
// a HA error we don't pollute our archiver with a block that won't make it to the chain.
|
|
897
|
+
const proposal = await this.createBlockProposal(block, inHash, usedTxs, blockProposalOptions);
|
|
898
|
+
// Sync the proposed block to the archiver to make it available, only after we've managed to sign the proposal.
|
|
899
|
+
// We wait for the sync to succeed, as this helps catch consistency errors, even if it means we lose some time for block-building.
|
|
900
|
+
// If this throws, we abort the entire checkpoint.
|
|
901
|
+
await this.syncProposedBlockToArchiver(block);
|
|
902
|
+
// Once we have a signed proposal and the archiver agreed with our proposed block, then we broadcast it.
|
|
903
|
+
proposal && await this.p2pClient.broadcastProposal(proposal);
|
|
871
904
|
// Wait until the next block's start time
|
|
872
905
|
await this.waitUntilNextSubslot(timingInfo.deadline);
|
|
873
906
|
}
|
|
874
|
-
this.log.verbose(`Block building loop completed for slot ${this.
|
|
875
|
-
slot: this.
|
|
907
|
+
this.log.verbose(`Block building loop completed for slot ${this.targetSlot}`, {
|
|
908
|
+
slot: this.targetSlot,
|
|
876
909
|
blocksBuilt: blocksInCheckpoint.length
|
|
877
910
|
});
|
|
878
911
|
return {
|
|
@@ -880,33 +913,39 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
880
913
|
blockPendingBroadcast
|
|
881
914
|
};
|
|
882
915
|
}
|
|
916
|
+
/** Creates a block proposal for a given block via the validator client (unless in fisherman mode) */ createBlockProposal(block, inHash, usedTxs, blockProposalOptions) {
|
|
917
|
+
if (this.config.fishermanMode) {
|
|
918
|
+
this.log.info(`Skipping block proposal for block ${block.number} in fisherman mode`);
|
|
919
|
+
return Promise.resolve(undefined);
|
|
920
|
+
}
|
|
921
|
+
return this.validatorClient.createBlockProposal(block.header, block.indexWithinCheckpoint, inHash, block.archive.root, usedTxs, this.proposer, blockProposalOptions);
|
|
922
|
+
}
|
|
883
923
|
/** Sleeps until it is time to produce the next block in the slot */ async waitUntilNextSubslot(nextSubslotStart) {
|
|
884
|
-
this.setStateFn(SequencerState.WAITING_UNTIL_NEXT_BLOCK, this.
|
|
924
|
+
this.setStateFn(SequencerState.WAITING_UNTIL_NEXT_BLOCK, this.targetSlot);
|
|
885
925
|
this.log.verbose(`Waiting until time for the next block at ${nextSubslotStart}s into slot`, {
|
|
886
|
-
slot: this.
|
|
926
|
+
slot: this.targetSlot
|
|
887
927
|
});
|
|
888
928
|
await this.waitUntilTimeInSlot(nextSubslotStart);
|
|
889
929
|
}
|
|
890
930
|
/** Builds a single block. Called from the main block building loop. */ async buildSingleBlock(checkpointBuilder, opts) {
|
|
891
931
|
const { blockTimestamp, forceCreate, blockNumber, indexWithinCheckpoint, buildDeadline, txHashesAlreadyIncluded } = opts;
|
|
892
|
-
this.log.verbose(`Preparing block ${blockNumber} index ${indexWithinCheckpoint} at checkpoint ${this.checkpointNumber} for slot ${this.
|
|
932
|
+
this.log.verbose(`Preparing block ${blockNumber} index ${indexWithinCheckpoint} at checkpoint ${this.checkpointNumber} for slot ${this.targetSlot}`, {
|
|
893
933
|
...checkpointBuilder.getConstantData(),
|
|
894
934
|
...opts
|
|
895
935
|
});
|
|
896
936
|
try {
|
|
897
937
|
// Wait until we have enough txs to build the block
|
|
898
|
-
const minTxs = this.
|
|
899
|
-
const { availableTxs, canStartBuilding } = await this.waitForMinTxs(opts);
|
|
938
|
+
const { availableTxs, canStartBuilding, minTxs } = await this.waitForMinTxs(opts);
|
|
900
939
|
if (!canStartBuilding) {
|
|
901
|
-
this.log.warn(`Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.
|
|
940
|
+
this.log.warn(`Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot} (got ${availableTxs} txs but needs ${minTxs})`, {
|
|
902
941
|
blockNumber,
|
|
903
|
-
slot: this.
|
|
942
|
+
slot: this.targetSlot,
|
|
904
943
|
indexWithinCheckpoint
|
|
905
944
|
});
|
|
906
945
|
this.eventEmitter.emit('block-tx-count-check-failed', {
|
|
907
946
|
minTxs,
|
|
908
947
|
availableTxs,
|
|
909
|
-
slot: this.
|
|
948
|
+
slot: this.targetSlot
|
|
910
949
|
});
|
|
911
950
|
this.metrics.recordBlockProposalFailed('insufficient_txs');
|
|
912
951
|
return undefined;
|
|
@@ -914,46 +953,48 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
914
953
|
// Create iterator to pending txs. We filter out txs already included in previous blocks in the checkpoint
|
|
915
954
|
// just in case p2p failed to sync the provisional block and didn't get to remove those txs from the mempool yet.
|
|
916
955
|
const pendingTxs = filter(this.p2pClient.iterateEligiblePendingTxs(), (tx)=>!txHashesAlreadyIncluded.has(tx.txHash.toString()));
|
|
917
|
-
this.log.debug(`Building block ${blockNumber} at index ${indexWithinCheckpoint} for slot ${this.
|
|
918
|
-
slot: this.
|
|
956
|
+
this.log.debug(`Building block ${blockNumber} at index ${indexWithinCheckpoint} for slot ${this.targetSlot} with ${availableTxs} available txs`, {
|
|
957
|
+
slot: this.targetSlot,
|
|
919
958
|
blockNumber,
|
|
920
959
|
indexWithinCheckpoint
|
|
921
960
|
});
|
|
922
|
-
this.setStateFn(SequencerState.CREATING_BLOCK, this.
|
|
923
|
-
// Per-block limits
|
|
961
|
+
this.setStateFn(SequencerState.CREATING_BLOCK, this.targetSlot);
|
|
962
|
+
// Per-block limits are operator overrides (from SEQ_MAX_L2_BLOCK_GAS etc.) further capped
|
|
924
963
|
// by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
|
|
964
|
+
// minValidTxs is passed into the builder so it can reject the block *before* updating state.
|
|
965
|
+
const minValidTxs = forceCreate ? 0 : this.config.minValidTxsPerBlock ?? minTxs;
|
|
925
966
|
const blockBuilderOptions = {
|
|
926
967
|
maxTransactions: this.config.maxTxsPerBlock,
|
|
927
968
|
maxBlockGas: this.config.maxL2BlockGas !== undefined || this.config.maxDABlockGas !== undefined ? new Gas(this.config.maxDABlockGas ?? Infinity, this.config.maxL2BlockGas ?? Infinity) : undefined,
|
|
928
969
|
deadline: buildDeadline,
|
|
929
|
-
isBuildingProposal: true
|
|
970
|
+
isBuildingProposal: true,
|
|
971
|
+
minValidTxs,
|
|
972
|
+
maxBlocksPerCheckpoint: this.timetable.maxNumberOfBlocks,
|
|
973
|
+
perBlockAllocationMultiplier: this.config.perBlockAllocationMultiplier
|
|
930
974
|
};
|
|
931
|
-
// Actually build the block by executing txs
|
|
975
|
+
// Actually build the block by executing txs. The builder throws InsufficientValidTxsError
|
|
976
|
+
// if the number of successfully processed txs is below minValidTxs, ensuring state is not
|
|
977
|
+
// updated for blocks that will be discarded.
|
|
932
978
|
const buildResult = await this.buildSingleBlockWithCheckpointBuilder(checkpointBuilder, pendingTxs, blockNumber, blockTimestamp, blockBuilderOptions);
|
|
933
979
|
// If any txs failed during execution, drop them from the mempool so we don't pick them up again
|
|
934
980
|
await this.dropFailedTxsFromP2P(buildResult.failedTxs);
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
const numTxs = buildResult.status === 'no-valid-txs' ? 0 : buildResult.numTxs;
|
|
939
|
-
if (buildResult.status === 'no-valid-txs' || !forceCreate && numTxs < minValidTxs) {
|
|
940
|
-
this.log.warn(`Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.slot} has too few valid txs to be proposed`, {
|
|
941
|
-
slot: this.slot,
|
|
981
|
+
if (buildResult.status === 'insufficient-valid-txs') {
|
|
982
|
+
this.log.warn(`Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.targetSlot} has too few valid txs to be proposed`, {
|
|
983
|
+
slot: this.targetSlot,
|
|
942
984
|
blockNumber,
|
|
943
|
-
numTxs,
|
|
985
|
+
numTxs: buildResult.processedCount,
|
|
944
986
|
indexWithinCheckpoint,
|
|
945
|
-
minValidTxs
|
|
946
|
-
buildResult: buildResult.status
|
|
987
|
+
minValidTxs
|
|
947
988
|
});
|
|
948
989
|
this.eventEmitter.emit('block-build-failed', {
|
|
949
990
|
reason: `Insufficient valid txs`,
|
|
950
|
-
slot: this.
|
|
991
|
+
slot: this.targetSlot
|
|
951
992
|
});
|
|
952
993
|
this.metrics.recordBlockProposalFailed('insufficient_valid_txs');
|
|
953
994
|
return undefined;
|
|
954
995
|
}
|
|
955
996
|
// Block creation succeeded, emit stats and metrics
|
|
956
|
-
const { block, publicProcessorDuration, usedTxs, blockBuildDuration } = buildResult;
|
|
997
|
+
const { block, publicProcessorDuration, usedTxs, blockBuildDuration, numTxs } = buildResult;
|
|
957
998
|
const blockStats = {
|
|
958
999
|
eventName: 'l2-block-built',
|
|
959
1000
|
duration: blockBuildDuration,
|
|
@@ -963,7 +1004,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
963
1004
|
const blockHash = await block.hash();
|
|
964
1005
|
const txHashes = block.body.txEffects.map((tx)=>tx.txHash);
|
|
965
1006
|
const manaPerSec = block.header.totalManaUsed.toNumberUnsafe() / (blockBuildDuration / 1000);
|
|
966
|
-
this.log.info(`Built block ${block.number} at checkpoint ${this.checkpointNumber} for slot ${this.
|
|
1007
|
+
this.log.info(`Built block ${block.number} at checkpoint ${this.checkpointNumber} for slot ${this.targetSlot} with ${numTxs} txs`, {
|
|
967
1008
|
blockHash,
|
|
968
1009
|
txHashes,
|
|
969
1010
|
manaPerSec,
|
|
@@ -971,7 +1012,8 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
971
1012
|
});
|
|
972
1013
|
this.eventEmitter.emit('block-proposed', {
|
|
973
1014
|
blockNumber: block.number,
|
|
974
|
-
slot: this.
|
|
1015
|
+
slot: this.targetSlot,
|
|
1016
|
+
buildSlot: this.slotNow
|
|
975
1017
|
});
|
|
976
1018
|
this.metrics.recordBuiltBlock(blockBuildDuration, block.header.totalManaUsed.toNumberUnsafe());
|
|
977
1019
|
return {
|
|
@@ -981,11 +1023,11 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
981
1023
|
} catch (err) {
|
|
982
1024
|
this.eventEmitter.emit('block-build-failed', {
|
|
983
1025
|
reason: err.message,
|
|
984
|
-
slot: this.
|
|
1026
|
+
slot: this.targetSlot
|
|
985
1027
|
});
|
|
986
1028
|
this.log.error(`Error building block`, err, {
|
|
987
1029
|
blockNumber,
|
|
988
|
-
slot: this.
|
|
1030
|
+
slot: this.targetSlot
|
|
989
1031
|
});
|
|
990
1032
|
this.metrics.recordBlockProposalFailed(err.name || 'unknown_error');
|
|
991
1033
|
this.metrics.recordFailedBlock();
|
|
@@ -994,7 +1036,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
994
1036
|
};
|
|
995
1037
|
}
|
|
996
1038
|
}
|
|
997
|
-
/** Uses the checkpoint builder to build a block, catching
|
|
1039
|
+
/** Uses the checkpoint builder to build a block, catching InsufficientValidTxsError. */ async buildSingleBlockWithCheckpointBuilder(checkpointBuilder, pendingTxs, blockNumber, blockTimestamp, blockBuilderOptions) {
|
|
998
1040
|
try {
|
|
999
1041
|
const workTimer = new Timer();
|
|
1000
1042
|
const result = await checkpointBuilder.buildBlock(pendingTxs, blockNumber, blockTimestamp, blockBuilderOptions);
|
|
@@ -1005,10 +1047,11 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1005
1047
|
status: 'success'
|
|
1006
1048
|
};
|
|
1007
1049
|
} catch (err) {
|
|
1008
|
-
if (isErrorClass(err,
|
|
1050
|
+
if (isErrorClass(err, InsufficientValidTxsError)) {
|
|
1009
1051
|
return {
|
|
1010
1052
|
failedTxs: err.failedTxs,
|
|
1011
|
-
|
|
1053
|
+
processedCount: err.processedCount,
|
|
1054
|
+
status: 'insufficient-valid-txs'
|
|
1012
1055
|
};
|
|
1013
1056
|
}
|
|
1014
1057
|
throw err;
|
|
@@ -1027,14 +1070,15 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1027
1070
|
if (startBuildingDeadline === undefined || now >= startBuildingDeadline) {
|
|
1028
1071
|
return {
|
|
1029
1072
|
canStartBuilding: false,
|
|
1030
|
-
availableTxs
|
|
1073
|
+
availableTxs,
|
|
1074
|
+
minTxs
|
|
1031
1075
|
};
|
|
1032
1076
|
}
|
|
1033
1077
|
// Wait a bit before checking again
|
|
1034
|
-
this.setStateFn(SequencerState.WAITING_FOR_TXS, this.
|
|
1035
|
-
this.log.verbose(`Waiting for enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.
|
|
1078
|
+
this.setStateFn(SequencerState.WAITING_FOR_TXS, this.targetSlot);
|
|
1079
|
+
this.log.verbose(`Waiting for enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot} (have ${availableTxs} but need ${minTxs})`, {
|
|
1036
1080
|
blockNumber,
|
|
1037
|
-
slot: this.
|
|
1081
|
+
slot: this.targetSlot,
|
|
1038
1082
|
indexWithinCheckpoint
|
|
1039
1083
|
});
|
|
1040
1084
|
await this.waitForTxsPollingInterval();
|
|
@@ -1042,7 +1086,8 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1042
1086
|
}
|
|
1043
1087
|
return {
|
|
1044
1088
|
canStartBuilding: true,
|
|
1045
|
-
availableTxs
|
|
1089
|
+
availableTxs,
|
|
1090
|
+
minTxs
|
|
1046
1091
|
};
|
|
1047
1092
|
}
|
|
1048
1093
|
/**
|
|
@@ -1187,16 +1232,16 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1187
1232
|
/** Runs fee analysis and logs checkpoint outcome as fisherman */ async handleCheckpointEndAsFisherman(checkpoint) {
|
|
1188
1233
|
// Perform L1 fee analysis before clearing requests
|
|
1189
1234
|
// The callback is invoked asynchronously after the next block is mined
|
|
1190
|
-
const feeAnalysis = await this.publisher.analyzeL1Fees(this.
|
|
1235
|
+
const feeAnalysis = await this.publisher.analyzeL1Fees(this.targetSlot, (analysis)=>this.metrics.recordFishermanFeeAnalysis(analysis));
|
|
1191
1236
|
if (checkpoint) {
|
|
1192
|
-
this.log.info(`Validation checkpoint building SUCCEEDED for slot ${this.
|
|
1237
|
+
this.log.info(`Validation checkpoint building SUCCEEDED for slot ${this.targetSlot}`, {
|
|
1193
1238
|
...checkpoint.toCheckpointInfo(),
|
|
1194
1239
|
...checkpoint.getStats(),
|
|
1195
1240
|
feeAnalysisId: feeAnalysis?.id
|
|
1196
1241
|
});
|
|
1197
1242
|
} else {
|
|
1198
|
-
this.log.warn(`Validation block building FAILED for slot ${this.
|
|
1199
|
-
slot: this.
|
|
1243
|
+
this.log.warn(`Validation block building FAILED for slot ${this.targetSlot}`, {
|
|
1244
|
+
slot: this.targetSlot,
|
|
1200
1245
|
feeAnalysisId: feeAnalysis?.id
|
|
1201
1246
|
});
|
|
1202
1247
|
this.metrics.recordCheckpointProposalFailed('block_build_failed');
|
|
@@ -1207,15 +1252,15 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1207
1252
|
* Helper to handle HA double-signing errors. Returns true if the error was handled (caller should yield).
|
|
1208
1253
|
*/ handleHASigningError(err, errorContext) {
|
|
1209
1254
|
if (err instanceof DutyAlreadySignedError) {
|
|
1210
|
-
this.log.info(`${errorContext} for slot ${this.
|
|
1211
|
-
slot: this.
|
|
1255
|
+
this.log.info(`${errorContext} for slot ${this.targetSlot} already signed by another HA node, yielding`, {
|
|
1256
|
+
slot: this.targetSlot,
|
|
1212
1257
|
signedByNode: err.signedByNode
|
|
1213
1258
|
});
|
|
1214
1259
|
return true;
|
|
1215
1260
|
}
|
|
1216
1261
|
if (err instanceof SlashingProtectionError) {
|
|
1217
|
-
this.log.info(`${errorContext} for slot ${this.
|
|
1218
|
-
slot: this.
|
|
1262
|
+
this.log.info(`${errorContext} for slot ${this.targetSlot} blocked by slashing protection, yielding`, {
|
|
1263
|
+
slot: this.targetSlot,
|
|
1219
1264
|
existingMessageHash: err.existingMessageHash,
|
|
1220
1265
|
attemptedMessageHash: err.attemptedMessageHash
|
|
1221
1266
|
});
|
|
@@ -24,6 +24,7 @@ export type SequencerEvents = {
|
|
|
24
24
|
['block-proposed']: (args: {
|
|
25
25
|
blockNumber: BlockNumber;
|
|
26
26
|
slot: SlotNumber;
|
|
27
|
+
buildSlot: SlotNumber;
|
|
27
28
|
}) => void;
|
|
28
29
|
['checkpoint-empty']: (args: {
|
|
29
30
|
slot: SlotNumber;
|
|
@@ -43,4 +44,4 @@ export type SequencerEvents = {
|
|
|
43
44
|
error: Error;
|
|
44
45
|
}) => void;
|
|
45
46
|
};
|
|
46
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
47
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXZlbnRzLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VxdWVuY2VyL2V2ZW50cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxXQUFXLEVBQUUsZ0JBQWdCLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFFakcsT0FBTyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFDbEUsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRWpELE1BQU0sTUFBTSxlQUFlLEdBQUc7SUFDNUIsQ0FBQyxlQUFlLENBQUMsRUFBRSxDQUFDLElBQUksRUFBRTtRQUN4QixRQUFRLEVBQUUsY0FBYyxDQUFDO1FBQ3pCLFFBQVEsRUFBRSxjQUFjLENBQUM7UUFDekIsZUFBZSxDQUFDLEVBQUUsTUFBTSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxFQUFFLFVBQVUsQ0FBQztLQUNuQixLQUFLLElBQUksQ0FBQztJQUNYLENBQUMsOEJBQThCLENBQUMsRUFBRSxDQUFDLElBQUksRUFBRTtRQUFFLE1BQU0sRUFBRSxNQUFNLENBQUM7UUFBQyxJQUFJLEVBQUUsVUFBVSxDQUFBO0tBQUUsS0FBSyxJQUFJLENBQUM7SUFDdkYsQ0FBQyw2QkFBNkIsQ0FBQyxFQUFFLENBQUMsSUFBSSxFQUFFO1FBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQztRQUFDLFlBQVksRUFBRSxNQUFNLENBQUM7UUFBQyxJQUFJLEVBQUUsVUFBVSxDQUFBO0tBQUUsS0FBSyxJQUFJLENBQUM7SUFDNUcsQ0FBQyxvQkFBb0IsQ0FBQyxFQUFFLENBQUMsSUFBSSxFQUFFO1FBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQztRQUFDLElBQUksRUFBRSxVQUFVLENBQUE7S0FBRSxLQUFLLElBQUksQ0FBQztJQUM3RSxDQUFDLGdCQUFnQixDQUFDLEVBQUUsQ0FBQyxJQUFJLEVBQUU7UUFBRSxXQUFXLEVBQUUsV0FBVyxDQUFDO1FBQUMsSUFBSSxFQUFFLFVBQVUsQ0FBQztRQUFDLFNBQVMsRUFBRSxVQUFVLENBQUE7S0FBRSxLQUFLLElBQUksQ0FBQztJQUMxRyxDQUFDLGtCQUFrQixDQUFDLEVBQUUsQ0FBQyxJQUFJLEVBQUU7UUFBRSxJQUFJLEVBQUUsVUFBVSxDQUFBO0tBQUUsS0FBSyxJQUFJLENBQUM7SUFDM0QsQ0FBQywyQkFBMkIsQ0FBQyxFQUFFLENBQUMsSUFBSSxFQUFFO1FBQ3BDLElBQUksRUFBRSxVQUFVLENBQUM7UUFDakIsaUJBQWlCLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQztRQUM3QixhQUFhLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQztRQUN6QixXQUFXLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQztRQUN2QixjQUFjLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQztLQUMzQixLQUFLLElBQUksQ0FBQztJQUNYLENBQUMsc0JBQXNCLENBQUMsRUFBRSxDQUFDLElBQUksRUFBRTtRQUFFLFVBQVUsRUFBRSxnQkFBZ0IsQ0FBQztRQUFDLElBQUksRUFBRSxVQUFVLENBQUE7S0FBRSxLQUFLLElBQUksQ0FBQztJQUM3RixDQUFDLGtCQUFrQixDQUFDLEVBQUUsQ0FBQyxJQUFJLEVBQUU7UUFBRSxLQUFLLEVBQUUsS0FBSyxDQUFBO0tBQUUsS0FBSyxJQUFJLENBQUM7Q0FDeEQsQ0FBQyJ9
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/sequencer/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAEjG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,MAAM,eAAe,GAAG;IAC5B,CAAC,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE;QACxB,QAAQ,EAAE,cAAc,CAAC;QACzB,QAAQ,EAAE,cAAc,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,IAAI,CAAC,EAAE,UAAU,CAAC;KACnB,KAAK,IAAI,CAAC;IACX,CAAC,8BAA8B,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IACvF,CAAC,6BAA6B,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5G,CAAC,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7E,CAAC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,WAAW,EAAE,WAAW,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/sequencer/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAEjG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,MAAM,eAAe,GAAG;IAC5B,CAAC,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE;QACxB,QAAQ,EAAE,cAAc,CAAC;QACzB,QAAQ,EAAE,cAAc,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,IAAI,CAAC,EAAE,UAAU,CAAC;KACnB,KAAK,IAAI,CAAC;IACX,CAAC,8BAA8B,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IACvF,CAAC,6BAA6B,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5G,CAAC,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7E,CAAC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,WAAW,EAAE,WAAW,CAAC;QAAC,IAAI,EAAE,UAAU,CAAC;QAAC,SAAS,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IAC1G,CAAC,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3D,CAAC,2BAA2B,CAAC,EAAE,CAAC,IAAI,EAAE;QACpC,IAAI,EAAE,UAAU,CAAC;QACjB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;QACzB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;KAC3B,KAAK,IAAI,CAAC;IACX,CAAC,sBAAsB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,gBAAgB,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7F,CAAC,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAA;KAAE,KAAK,IAAI,CAAC;CACxD,CAAC"}
|
|
@@ -31,6 +31,8 @@ export declare class SequencerMetrics {
|
|
|
31
31
|
private checkpointBlockCount;
|
|
32
32
|
private checkpointTxCount;
|
|
33
33
|
private checkpointTotalMana;
|
|
34
|
+
private pipelineDepth;
|
|
35
|
+
private pipelineDiscards;
|
|
34
36
|
private fishermanWouldBeIncluded;
|
|
35
37
|
private fishermanTimeBeforeBlock;
|
|
36
38
|
private fishermanPendingBlobTxCount;
|
|
@@ -53,6 +55,8 @@ export declare class SequencerMetrics {
|
|
|
53
55
|
recordBuiltBlock(buildDurationMs: number, totalMana: number): void;
|
|
54
56
|
recordFailedBlock(): void;
|
|
55
57
|
recordStateTransitionBufferMs(durationMs: number, state: SequencerState): void;
|
|
58
|
+
recordPipelineDepth(depth: number): void;
|
|
59
|
+
recordPipelineDiscard(count?: number): void;
|
|
56
60
|
incOpenSlot(slot: SlotNumber, proposer: string): void;
|
|
57
61
|
incFilledSlot(proposer: string, coinbase: Hex | EthAddress | undefined): Promise<void>;
|
|
58
62
|
recordCheckpointSuccess(): void;
|
|
@@ -69,4 +73,4 @@ export declare class SequencerMetrics {
|
|
|
69
73
|
*/
|
|
70
74
|
recordFishermanFeeAnalysis(analysis: L1FeeAnalysisResult): void;
|
|
71
75
|
}
|
|
72
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
76
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcXVlbmNlci9tZXRyaWNzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUN2RCxPQUFPLEtBQUssRUFBRSxjQUFjLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUNoRSxPQUFPLEtBQUssRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQzNFLE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ2xFLE9BQU8sRUFNTCxLQUFLLGVBQWUsRUFDcEIsS0FBSyxNQUFNLEVBR1osTUFBTSx5QkFBeUIsQ0FBQztBQUVqQyxPQUFPLEVBQUUsS0FBSyxHQUFHLEVBQWUsTUFBTSxNQUFNLENBQUM7QUFFN0MsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRWpELHFCQUFhLGdCQUFnQjtJQXNEekIsT0FBTyxDQUFDLE1BQU07SUFyRGhCLFNBQWdCLE1BQU0sRUFBRSxNQUFNLENBQUM7SUFDL0IsT0FBTyxDQUFDLEtBQUssQ0FBUTtJQUVyQixPQUFPLENBQUMsWUFBWSxDQUFnQjtJQUNwQyxPQUFPLENBQUMsa0JBQWtCLENBQVk7SUFDdEMsT0FBTyxDQUFDLHVCQUF1QixDQUFRO0lBQ3ZDLE9BQU8sQ0FBQyw2QkFBNkIsQ0FBWTtJQUdqRCxPQUFPLENBQUMseUJBQXlCLENBQVE7SUFDekMsT0FBTyxDQUFDLDhCQUE4QixDQUFRO0lBQzlDLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBUTtJQUNsQyxPQUFPLENBQUMsbUJBQW1CLENBQVE7SUFFbkMsT0FBTyxDQUFDLE9BQU8sQ0FBUTtJQUV2QixPQUFPLENBQUMsS0FBSyxDQUFnQjtJQUM3QixPQUFPLENBQUMsV0FBVyxDQUFnQjtJQUVuQyxPQUFPLENBQUMsbUJBQW1CLENBQWdCO0lBQzND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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/sequencer/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAML,KAAK,eAAe,EACpB,KAAK,MAAM,EAGZ,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,KAAK,GAAG,EAAe,MAAM,MAAM,CAAC;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,qBAAa,gBAAgB;
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/sequencer/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAML,KAAK,eAAe,EACpB,KAAK,MAAM,EAGZ,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,KAAK,GAAG,EAAe,MAAM,MAAM,CAAC;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,qBAAa,gBAAgB;IAsDzB,OAAO,CAAC,MAAM;IArDhB,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,OAAO,CAAC,KAAK,CAAQ;IAErB,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,kBAAkB,CAAY;IACtC,OAAO,CAAC,uBAAuB,CAAQ;IACvC,OAAO,CAAC,6BAA6B,CAAY;IAGjD,OAAO,CAAC,yBAAyB,CAAQ;IACzC,OAAO,CAAC,8BAA8B,CAAQ;IAC9C,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,mBAAmB,CAAQ;IAEnC,OAAO,CAAC,OAAO,CAAQ;IAEvB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,WAAW,CAAgB;IAEnC,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,yBAAyB,CAAgB;IACjD,OAAO,CAAC,wBAAwB,CAAgB;IAChD,OAAO,CAAC,wBAAwB,CAAgB;IAChD,OAAO,CAAC,iBAAiB,CAAgB;IACzC,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,0BAA0B,CAAY;IAC9C,OAAO,CAAC,uBAAuB,CAAY;IAC3C,OAAO,CAAC,oBAAoB,CAAQ;IACpC,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,mBAAmB,CAAQ;IACnC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,gBAAgB,CAAgB;IAGxC,OAAO,CAAC,wBAAwB,CAAgB;IAChD,OAAO,CAAC,wBAAwB,CAAY;IAC5C,OAAO,CAAC,2BAA2B,CAAY;IAC/C,OAAO,CAAC,4BAA4B,CAAY;IAChD,OAAO,CAAC,yBAAyB,CAAY;IAC7C,OAAO,CAAC,0BAA0B,CAAY;IAC9C,OAAO,CAAC,uBAAuB,CAAgB;IAC/C,OAAO,CAAC,wBAAwB,CAAY;IAC5C,OAAO,CAAC,8BAA8B,CAAY;IAClD,OAAO,CAAC,yBAAyB,CAAY;IAC7C,OAAO,CAAC,sBAAsB,CAAY;IAC1C,OAAO,CAAC,6BAA6B,CAAY;IACjD,OAAO,CAAC,+BAA+B,CAAY;IACnD,OAAO,CAAC,6BAA6B,CAAY;IAEjD,OAAO,CAAC,YAAY,CAAC,CAAa;IAElC,YACE,MAAM,EAAE,eAAe,EACf,MAAM,EAAE,cAAc,EAC9B,IAAI,SAAc,EA+HnB;IAEM,0BAA0B,CAAC,yBAAyB,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,QAOvF;IAEM,gCAAgC,CAAC,QAAQ,EAAE,MAAM,QAEvD;IAEM,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAGnE;IAED,gBAAgB,CAAC,eAAe,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAM1D;IAED,iBAAiB,SAIhB;IAED,6BAA6B,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,QAItE;IAED,mBAAmB,CAAC,KAAK,EAAE,MAAM,QAEhC;IAED,qBAAqB,CAAC,KAAK,SAAI,QAE9B;IAED,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,QAW7C;IAEK,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB3F;IAED,uBAAuB,SAEtB;IAED,yBAAyB,CAAC,MAAM,CAAC,EAAE,MAAM,QAIxC;IAED,+BAA+B,SAE9B;IAED,8BAA8B,CAC5B,SAAS,EAAE,oBAAoB,GAAG,8BAA8B,GAAG,eAAe,GAAG,uBAAuB,QAG7G;IAED,8BAA8B,CAAC,MAAM,CAAC,EAAE,MAAM,QAI7C;IAED,kEAAkE;IAClE,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAK/F;IAED,qBAAqB,CAAC,WAAW,EAAE,MAAM,QAExC;IAED;;;OAGG;IACH,0BAA0B,CAAC,QAAQ,EAAE,mBAAmB,QAkHvD;CACF"}
|