@aztec/sequencer-client 0.0.1-commit.2b2662070 → 0.0.1-commit.2c0ee1788
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/config.d.ts +2 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +12 -7
- package/dest/publisher/sequencer-publisher.d.ts +1 -1
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +2 -2
- package/dest/sequencer/checkpoint_proposal_job.d.ts +22 -3
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +361 -43
- package/dest/sequencer/events.d.ts +6 -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 +22 -0
- package/dest/sequencer/sequencer.d.ts +12 -4
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +33 -8
- package/dest/test/utils.d.ts +1 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +7 -6
- package/package.json +27 -27
- package/src/config.ts +11 -4
- package/src/publisher/sequencer-publisher.ts +2 -4
- package/src/sequencer/README.md +20 -21
- package/src/sequencer/checkpoint_proposal_job.ts +414 -44
- package/src/sequencer/events.ts +5 -0
- package/src/sequencer/metrics.ts +29 -0
- package/src/sequencer/sequencer.ts +37 -9
- package/src/test/utils.ts +28 -10
|
@@ -436,11 +436,12 @@ 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 { BlockNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types';
|
|
439
|
+
import { BlockNumber, CheckpointNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types';
|
|
440
440
|
import { randomInt } from '@aztec/foundation/crypto/random';
|
|
441
441
|
import { flipSignature, generateRecoverableSignature, generateUnrecoverableSignature } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
442
442
|
import { filter } from '@aztec/foundation/iterator';
|
|
443
443
|
import { createLogger } from '@aztec/foundation/log';
|
|
444
|
+
import { retryUntil } from '@aztec/foundation/retry';
|
|
444
445
|
import { sleep, sleepUntil } from '@aztec/foundation/sleep';
|
|
445
446
|
import { Timer } from '@aztec/foundation/timer';
|
|
446
447
|
import { isErrorClass, unfreeze } from '@aztec/foundation/types';
|
|
@@ -490,6 +491,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
490
491
|
checkpointsBuilder;
|
|
491
492
|
blockSink;
|
|
492
493
|
l1Constants;
|
|
494
|
+
signatureContext;
|
|
493
495
|
config;
|
|
494
496
|
timetable;
|
|
495
497
|
slasherClient;
|
|
@@ -546,10 +548,14 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
546
548
|
], []));
|
|
547
549
|
}
|
|
548
550
|
log;
|
|
551
|
+
checkpointEventLog;
|
|
549
552
|
/** Tracks the fire-and-forget L1 submission promise so it can be awaited during shutdown. */ pendingL1Submission;
|
|
550
553
|
/** Pipelined parent chain state used while building and later submitting this checkpoint. */ pipelinedParentSimulationOverridesPlan;
|
|
554
|
+
getSignatureContext() {
|
|
555
|
+
return this.signatureContext;
|
|
556
|
+
}
|
|
551
557
|
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, checkpointMetrics, eventEmitter, setStateFn, tracer, bindings, proposedCheckpointData){
|
|
558
|
+
proposer, publisher, attestorAddress, invalidateCheckpoint, validatorClient, globalsBuilder, p2pClient, worldState, l1ToL2MessageSource, l2BlockSource, checkpointsBuilder, blockSink, l1Constants, signatureContext, config, timetable, slasherClient, epochCache, dateProvider, metrics, checkpointMetrics, eventEmitter, setStateFn, tracer, bindings, proposedCheckpointData){
|
|
553
559
|
this.slotNow = slotNow;
|
|
554
560
|
this.targetSlot = targetSlot;
|
|
555
561
|
this.targetEpoch = targetEpoch;
|
|
@@ -568,6 +574,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
568
574
|
this.checkpointsBuilder = checkpointsBuilder;
|
|
569
575
|
this.blockSink = blockSink;
|
|
570
576
|
this.l1Constants = l1Constants;
|
|
577
|
+
this.signatureContext = signatureContext;
|
|
571
578
|
this.config = config;
|
|
572
579
|
this.timetable = timetable;
|
|
573
580
|
this.slasherClient = slasherClient;
|
|
@@ -584,11 +591,21 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
584
591
|
...bindings,
|
|
585
592
|
instanceId: `slot-${this.slotNow}`
|
|
586
593
|
});
|
|
594
|
+
this.checkpointEventLog = createLogger('sequencer:checkpoint-events', {
|
|
595
|
+
...bindings,
|
|
596
|
+
instanceId: `slot-${this.slotNow}`
|
|
597
|
+
});
|
|
587
598
|
}
|
|
588
599
|
/** Awaits the pending L1 submission if one is in progress. Call during shutdown. */ async awaitPendingSubmission() {
|
|
589
600
|
this.log.info('Awaiting pending L1 payload submission');
|
|
590
601
|
await this.pendingL1Submission;
|
|
591
602
|
}
|
|
603
|
+
logCheckpointEvent(eventName, message, fields) {
|
|
604
|
+
this.checkpointEventLog.debug(message, {
|
|
605
|
+
eventName: `sequencer-checkpoint-${eventName}`,
|
|
606
|
+
...fields
|
|
607
|
+
});
|
|
608
|
+
}
|
|
592
609
|
/**
|
|
593
610
|
* Executes the checkpoint proposal job.
|
|
594
611
|
* Builds blocks, assembles checkpoint, and broadcasts the proposal (blocking).
|
|
@@ -627,34 +644,45 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
627
644
|
* Background pipeline: collects attestations, signs them, enqueues the checkpoint, and submits to L1.
|
|
628
645
|
* Runs as a fire-and-forget task stored in `pendingL1Submission` so the work loop is unblocked.
|
|
629
646
|
*/ async waitForAttestationsAndEnqueueSubmissionAsync(broadcast, votesPromises) {
|
|
630
|
-
const { checkpoint
|
|
647
|
+
const { checkpoint } = broadcast;
|
|
648
|
+
const isPipelining = this.epochCache.isProposerPipeliningEnabled();
|
|
631
649
|
try {
|
|
650
|
+
// Wait for all votes actions, enqueued at the beginning, to resolve
|
|
632
651
|
await Promise.all(votesPromises);
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
652
|
+
// Try to collect attestations from the committee
|
|
653
|
+
const signedAttestations = await this.getSignedCommitteeAttestations(broadcast);
|
|
654
|
+
// If pipelining, wait for the previous checkpoint to land on L1 before submitting,
|
|
655
|
+
// so we can check it matches the proposed checkpoint we used as parent, and has valid attestations.
|
|
656
|
+
if (signedAttestations && (!isPipelining || await this.waitForValidParentCheckpointOnL1())) {
|
|
657
|
+
await this.enqueueCheckpointForSubmission({
|
|
658
|
+
checkpoint,
|
|
659
|
+
...signedAttestations
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
// If we failed to collect attestations, at least check if we need to issue an invalidation
|
|
663
|
+
// Note that if we are not pipelining, we enqueued the invalidation at the beginning
|
|
664
|
+
if (!signedAttestations && isPipelining && await this.waitForSyncedL2SlotNumber(this.slotNow)) {
|
|
665
|
+
const validationStatus = await this.l2BlockSource.getPendingChainValidationStatus();
|
|
666
|
+
if (!validationStatus.valid) {
|
|
667
|
+
this.log.warn(`Checkpoint ${validationStatus.checkpoint.checkpointNumber} has invalid attestations, enqueuing invalidation in spite of attestation collection failure`, {
|
|
668
|
+
checkpoint: validationStatus.checkpoint,
|
|
669
|
+
reason: validationStatus.reason
|
|
670
|
+
});
|
|
671
|
+
await this.enqueueInvalidation(validationStatus);
|
|
644
672
|
}
|
|
645
|
-
throw err;
|
|
646
673
|
}
|
|
647
|
-
//
|
|
648
|
-
await this.enqueueCheckpointForSubmission({
|
|
649
|
-
checkpoint,
|
|
650
|
-
attestations,
|
|
651
|
-
attestationsSignature
|
|
652
|
-
});
|
|
674
|
+
// Send whatever was enqueued: votes + (propose | invalidation | nothing).
|
|
653
675
|
// Compute the earliest time to submit: pipeline slot start when pipelining, now otherwise.
|
|
654
|
-
const submitAfter =
|
|
676
|
+
const submitAfter = isPipelining ? new Date(Number(getTimestampForSlot(this.targetSlot, this.l1Constants)) * 1000) : new Date(this.dateProvider.now());
|
|
655
677
|
const l1Response = await this.publisher.sendRequestsAt(submitAfter);
|
|
656
678
|
const proposedAction = l1Response?.successfulActions.find((a)=>a === 'propose');
|
|
657
679
|
if (proposedAction) {
|
|
680
|
+
this.logCheckpointEvent('published', `Checkpoint published for slot ${this.targetSlot}`, {
|
|
681
|
+
slot: this.targetSlot,
|
|
682
|
+
checkpointNumber: this.checkpointNumber,
|
|
683
|
+
successfulActions: l1Response?.successfulActions,
|
|
684
|
+
sentActions: l1Response?.sentActions
|
|
685
|
+
});
|
|
658
686
|
this.eventEmitter.emit('checkpoint-published', {
|
|
659
687
|
checkpoint: this.checkpointNumber,
|
|
660
688
|
slot: this.targetSlot
|
|
@@ -662,11 +690,29 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
662
690
|
const coinbase = checkpoint.header.coinbase;
|
|
663
691
|
await this.metrics.incFilledSlot(this.publisher.getSenderAddress().toString(), coinbase);
|
|
664
692
|
} else {
|
|
693
|
+
this.logCheckpointEvent('publish-failed', `Checkpoint publish failed for slot ${this.targetSlot}`, {
|
|
694
|
+
slot: this.targetSlot,
|
|
695
|
+
checkpointNumber: this.checkpointNumber,
|
|
696
|
+
successfulActions: l1Response?.successfulActions,
|
|
697
|
+
failedActions: l1Response?.failedActions,
|
|
698
|
+
sentActions: l1Response?.sentActions,
|
|
699
|
+
expiredActions: l1Response?.expiredActions,
|
|
700
|
+
reason: 'propose_action_not_successful'
|
|
701
|
+
});
|
|
702
|
+
this.log.warn(`Checkpoint publish failed for slot ${this.targetSlot}`, {
|
|
703
|
+
slot: this.targetSlot,
|
|
704
|
+
checkpointNumber: this.checkpointNumber,
|
|
705
|
+
successfulActions: l1Response?.successfulActions,
|
|
706
|
+
failedActions: l1Response?.failedActions,
|
|
707
|
+
sentActions: l1Response?.sentActions,
|
|
708
|
+
expiredActions: l1Response?.expiredActions,
|
|
709
|
+
reason: 'propose_action_not_successful'
|
|
710
|
+
});
|
|
665
711
|
this.eventEmitter.emit('checkpoint-publish-failed', {
|
|
666
712
|
...l1Response,
|
|
667
713
|
slot: this.targetSlot
|
|
668
714
|
});
|
|
669
|
-
if (
|
|
715
|
+
if (isPipelining) {
|
|
670
716
|
this.metrics.recordPipelineDiscard();
|
|
671
717
|
}
|
|
672
718
|
}
|
|
@@ -674,11 +720,20 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
674
720
|
if (err instanceof SequencerInterruptedError) {
|
|
675
721
|
return;
|
|
676
722
|
}
|
|
677
|
-
this.
|
|
723
|
+
this.logCheckpointEvent('publish-failed', `Checkpoint publish failed for slot ${this.targetSlot}`, {
|
|
724
|
+
slot: this.targetSlot,
|
|
725
|
+
checkpointNumber: this.checkpointNumber,
|
|
726
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
727
|
+
});
|
|
728
|
+
this.log.error(`Background attestation/L1 pipeline failed for slot ${this.targetSlot}`, err, {
|
|
729
|
+
slot: this.targetSlot,
|
|
730
|
+
checkpointNumber: this.checkpointNumber,
|
|
731
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
732
|
+
});
|
|
678
733
|
this.eventEmitter.emit('checkpoint-publish-failed', {
|
|
679
734
|
slot: this.targetSlot
|
|
680
735
|
});
|
|
681
|
-
if (
|
|
736
|
+
if (isPipelining) {
|
|
682
737
|
this.metrics.recordPipelineDiscard();
|
|
683
738
|
}
|
|
684
739
|
}
|
|
@@ -711,6 +766,121 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
711
766
|
} : {}
|
|
712
767
|
});
|
|
713
768
|
}
|
|
769
|
+
/**
|
|
770
|
+
* Wait until the archiver syncs past the given L2 slot number.
|
|
771
|
+
* The deadline is the end of `this.targetSlot`, beyond which any pipelined work would miss its
|
|
772
|
+
* L1 submission window and is no longer useful.
|
|
773
|
+
*/ async waitForSyncedL2SlotNumber(waitForSlot) {
|
|
774
|
+
const targetSlotStart = Number(getTimestampForSlot(this.targetSlot, this.l1Constants));
|
|
775
|
+
const targetSlotEndMs = (targetSlotStart + this.l1Constants.slotDuration) * 1000;
|
|
776
|
+
const syncDelayTolerance = this.l1Constants.ethereumSlotDuration * 2 * 1000;
|
|
777
|
+
const timeoutSeconds = Math.max(0.1, (targetSlotEndMs + syncDelayTolerance - this.dateProvider.now()) / 1000);
|
|
778
|
+
try {
|
|
779
|
+
return await retryUntil(async ()=>{
|
|
780
|
+
const syncedSlot = await this.l2BlockSource.getSyncedL2SlotNumber();
|
|
781
|
+
return syncedSlot !== undefined && syncedSlot >= waitForSlot;
|
|
782
|
+
}, `archiver sync past slot ${waitForSlot}`, timeoutSeconds, 0.2);
|
|
783
|
+
} catch {
|
|
784
|
+
this.log.warn(`Archiver did not sync L1 past slot ${waitForSlot} before slot ${this.targetSlot} expired, discarding pipelined work`, {
|
|
785
|
+
checkpointNumber: this.checkpointNumber
|
|
786
|
+
});
|
|
787
|
+
this.emitPipelinedCheckpointDiscarded('archiver-sync-timeout');
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Waits for the parent checkpoint to land on L1 before submitting a pipelined checkpoint.
|
|
793
|
+
* Polls until the archiver has synced L1 past the parent's slot, then verifies:
|
|
794
|
+
* - If we built on a proposed parent: it must have landed on L1 with matching hash and valid attestations.
|
|
795
|
+
* - If we built without a proposed parent: no new checkpoint must have appeared for that slot.
|
|
796
|
+
* If the parent has invalid attestations, enqueues an invalidation. Returns whether to proceed with the proposal.
|
|
797
|
+
*/ async waitForValidParentCheckpointOnL1() {
|
|
798
|
+
const parentCheckpointNumber = CheckpointNumber(this.checkpointNumber - 1);
|
|
799
|
+
// Wait until archiver has synced L1 past the parent's slot (slotNow)
|
|
800
|
+
if (!await this.waitForSyncedL2SlotNumber(this.slotNow)) {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
const tips = await this.l2BlockSource.getL2Tips();
|
|
804
|
+
const checkpointedNumber = tips.checkpointed.checkpoint.number;
|
|
805
|
+
// We built on top of a proposed checkpoint. Verify it landed on L1 as expected.
|
|
806
|
+
if (this.proposedCheckpointData) {
|
|
807
|
+
// After syncing from L1 we see the chain tip has invalid attestations. This means the parent checkpoint was posted
|
|
808
|
+
// with invalid attestations, or it built on top of something with invalid attestations and didnt invalidate them.
|
|
809
|
+
// Either way, we thought our parent would be valid, so we have to throw away our work. But at least we'll try and
|
|
810
|
+
// invalidate on L1 so we clean up the chain for the next proposer. And we'll slash them, but that's handled elsewhere.
|
|
811
|
+
const validationStatus = await this.l2BlockSource.getPendingChainValidationStatus();
|
|
812
|
+
if (!validationStatus.valid) {
|
|
813
|
+
this.log.warn(`Parent checkpoint ${parentCheckpointNumber} has invalid attestations, discarding pipelined work`, {
|
|
814
|
+
checkpointNumber: this.checkpointNumber,
|
|
815
|
+
reason: validationStatus.reason
|
|
816
|
+
});
|
|
817
|
+
this.emitPipelinedCheckpointDiscarded('parent-invalid-attestations');
|
|
818
|
+
await this.enqueueInvalidation(validationStatus);
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
// The pending chain is valid. But did the parent checkpoint land on L1 at all?
|
|
822
|
+
if (checkpointedNumber < parentCheckpointNumber) {
|
|
823
|
+
this.log.warn(`Parent checkpoint ${parentCheckpointNumber} did not land on L1, discarding pipelined work`, {
|
|
824
|
+
checkpointNumber: this.checkpointNumber,
|
|
825
|
+
checkpointedNumber
|
|
826
|
+
});
|
|
827
|
+
this.emitPipelinedCheckpointDiscarded('parent-not-on-l1');
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
// It landed. But is it the one we were expecting?
|
|
831
|
+
const expectedHash = this.proposedCheckpointData.header.hash().toString();
|
|
832
|
+
if (tips.checkpointed.checkpoint.hash !== expectedHash) {
|
|
833
|
+
this.log.warn(`Parent checkpoint ${parentCheckpointNumber} hash mismatch on L1, discarding pipelined work`, {
|
|
834
|
+
checkpointNumber: this.checkpointNumber,
|
|
835
|
+
expectedHash,
|
|
836
|
+
actualHash: tips.checkpointed.checkpoint.hash
|
|
837
|
+
});
|
|
838
|
+
this.emitPipelinedCheckpointDiscarded('parent-hash-mismatch');
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
return true;
|
|
842
|
+
} else {
|
|
843
|
+
// We didn't see a proposed checkpoint at build time, so we built on checkpointed parent from two slots ago.
|
|
844
|
+
// But if a new checkpoint for the previous slot appeared on L1 in the meantime, our checkpoint assumed the wrong parent,
|
|
845
|
+
// so we have to discard our work. This can happen if we're somehow cut off from p2p and fail to see the checkpoint
|
|
846
|
+
// proposal for the previous slot.
|
|
847
|
+
if (checkpointedNumber > parentCheckpointNumber) {
|
|
848
|
+
this.log.warn(`Unexpected checkpoint ${checkpointedNumber} landed on L1 after we built on top of parent ${parentCheckpointNumber}, discarding pipelined work`, {
|
|
849
|
+
checkpointNumber: this.checkpointNumber,
|
|
850
|
+
checkpointedNumber
|
|
851
|
+
});
|
|
852
|
+
this.emitPipelinedCheckpointDiscarded('unexpected-parent-appeared');
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
return true;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
/** Emits the pipelined-checkpoint-discarded event and records the metric. */ emitPipelinedCheckpointDiscarded(reason) {
|
|
859
|
+
this.metrics.recordPipelineParentCheckpointMismatch(reason);
|
|
860
|
+
this.eventEmitter.emit('pipelined-checkpoint-discarded', {
|
|
861
|
+
slot: this.targetSlot,
|
|
862
|
+
checkpointNumber: this.checkpointNumber,
|
|
863
|
+
reason
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
/** Simulates and enqueues an invalidation request for the invalid parent checkpoint. */ async enqueueInvalidation(validationStatus) {
|
|
867
|
+
if (this.config.skipInvalidateBlockAsProposer) {
|
|
868
|
+
this.log.warn(`Skipping checkpoint invalidation as proposer due to test configuration`);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
const invalidateRequest = await this.publisher.simulateInvalidateCheckpoint(validationStatus);
|
|
872
|
+
if (invalidateRequest) {
|
|
873
|
+
const submissionSlotStart = Number(getTimestampForSlot(this.targetSlot, this.l1Constants));
|
|
874
|
+
const txTimeoutAt = new Date((submissionSlotStart + this.l1Constants.slotDuration) * 1000);
|
|
875
|
+
this.publisher.enqueueInvalidateCheckpoint(invalidateRequest, {
|
|
876
|
+
txTimeoutAt
|
|
877
|
+
});
|
|
878
|
+
} else {
|
|
879
|
+
this.log.info(`Invalidation simulation returned undefined, checkpoint may have been removed already`, {
|
|
880
|
+
checkpointNumber: this.checkpointNumber
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
}
|
|
714
884
|
async proposeCheckpoint() {
|
|
715
885
|
try {
|
|
716
886
|
const env = {
|
|
@@ -733,11 +903,15 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
733
903
|
const feeRecipient = this.validatorClient.getFeeRecipientForAttestor(this.attestorAddress);
|
|
734
904
|
// Start the checkpoint
|
|
735
905
|
this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.targetSlot);
|
|
736
|
-
this.
|
|
906
|
+
this.logCheckpointEvent('slot-started', `Starting checkpoint proposal for slot ${this.targetSlot}`, {
|
|
737
907
|
buildSlot: this.slotNow,
|
|
738
908
|
submissionSlot: this.targetSlot,
|
|
909
|
+
slot: this.targetSlot,
|
|
910
|
+
checkpointNumber: this.checkpointNumber,
|
|
739
911
|
pipelining: this.epochCache.isProposerPipeliningEnabled(),
|
|
740
912
|
proposer: this.proposer?.toString(),
|
|
913
|
+
attestorAddress: this.attestorAddress.toString(),
|
|
914
|
+
publisherAddress: this.publisher.getSenderAddress().toString(),
|
|
741
915
|
coinbase: coinbase.toString()
|
|
742
916
|
});
|
|
743
917
|
this.metrics.incOpenSlot(this.targetSlot, this.proposer?.toString() ?? 'unknown');
|
|
@@ -795,8 +969,15 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
795
969
|
throw err;
|
|
796
970
|
}
|
|
797
971
|
if (blocksInCheckpoint.length === 0) {
|
|
972
|
+
this.logCheckpointEvent('build-failed', `Checkpoint build failed for slot ${this.targetSlot}`, {
|
|
973
|
+
slot: this.targetSlot,
|
|
974
|
+
checkpointNumber: this.checkpointNumber,
|
|
975
|
+
reason: 'no_blocks_built'
|
|
976
|
+
});
|
|
798
977
|
this.log.warn(`No blocks were built for slot ${this.targetSlot}`, {
|
|
799
|
-
slot: this.targetSlot
|
|
978
|
+
slot: this.targetSlot,
|
|
979
|
+
checkpointNumber: this.checkpointNumber,
|
|
980
|
+
reason: 'no_blocks_built'
|
|
800
981
|
});
|
|
801
982
|
this.eventEmitter.emit('checkpoint-empty', {
|
|
802
983
|
slot: this.targetSlot
|
|
@@ -805,10 +986,19 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
805
986
|
}
|
|
806
987
|
const minBlocksForCheckpoint = this.config.minBlocksForCheckpoint;
|
|
807
988
|
if (minBlocksForCheckpoint !== undefined && blocksInCheckpoint.length < minBlocksForCheckpoint) {
|
|
989
|
+
this.logCheckpointEvent('build-failed', `Checkpoint build failed for slot ${this.targetSlot}`, {
|
|
990
|
+
slot: this.targetSlot,
|
|
991
|
+
checkpointNumber: this.checkpointNumber,
|
|
992
|
+
blocksBuilt: blocksInCheckpoint.length,
|
|
993
|
+
minBlocksForCheckpoint,
|
|
994
|
+
reason: 'min_blocks_not_met'
|
|
995
|
+
});
|
|
808
996
|
this.log.warn(`Checkpoint has fewer blocks than minimum (${blocksInCheckpoint.length} < ${minBlocksForCheckpoint}), skipping proposal`, {
|
|
809
997
|
slot: this.targetSlot,
|
|
998
|
+
checkpointNumber: this.checkpointNumber,
|
|
810
999
|
blocksBuilt: blocksInCheckpoint.length,
|
|
811
|
-
minBlocksForCheckpoint
|
|
1000
|
+
minBlocksForCheckpoint,
|
|
1001
|
+
reason: 'min_blocks_not_met'
|
|
812
1002
|
});
|
|
813
1003
|
return undefined;
|
|
814
1004
|
}
|
|
@@ -827,13 +1017,35 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
827
1017
|
maxTxsPerCheckpoint: this.config.maxTxsPerCheckpoint
|
|
828
1018
|
});
|
|
829
1019
|
} catch (err) {
|
|
1020
|
+
this.logCheckpointEvent('build-failed', `Checkpoint build failed for slot ${this.targetSlot}`, {
|
|
1021
|
+
slot: this.targetSlot,
|
|
1022
|
+
checkpointNumber: this.checkpointNumber,
|
|
1023
|
+
blocksBuilt: blocksInCheckpoint.length,
|
|
1024
|
+
reason: 'invalid_checkpoint',
|
|
1025
|
+
checkpoint: checkpoint.header.toInspect()
|
|
1026
|
+
});
|
|
830
1027
|
this.log.error(`Built an invalid checkpoint at slot ${this.slotNow} (skipping proposal)`, err, {
|
|
1028
|
+
slot: this.targetSlot,
|
|
1029
|
+
checkpointNumber: this.checkpointNumber,
|
|
1030
|
+
blocksBuilt: blocksInCheckpoint.length,
|
|
1031
|
+
reason: 'invalid_checkpoint',
|
|
831
1032
|
checkpoint: checkpoint.header.toInspect()
|
|
832
1033
|
});
|
|
833
1034
|
return undefined;
|
|
834
1035
|
}
|
|
835
1036
|
// Record checkpoint-level build metrics
|
|
836
1037
|
this.checkpointMetrics.recordCheckpointBuild(checkpointBuildTimer.ms(), blocksInCheckpoint.length, checkpoint.getStats().txCount, Number(checkpoint.header.totalManaUsed.toBigInt()));
|
|
1038
|
+
this.logCheckpointEvent('built', `Checkpoint built for slot ${this.targetSlot}`, {
|
|
1039
|
+
slot: this.targetSlot,
|
|
1040
|
+
buildSlot: this.slotNow,
|
|
1041
|
+
checkpointNumber: this.checkpointNumber,
|
|
1042
|
+
proposer: this.proposer?.toString(),
|
|
1043
|
+
attestorAddress: this.attestorAddress.toString(),
|
|
1044
|
+
publisherAddress: this.publisher.getSenderAddress().toString(),
|
|
1045
|
+
blocksBuilt: blocksInCheckpoint.length,
|
|
1046
|
+
txCount: checkpoint.getStats().txCount,
|
|
1047
|
+
totalMana: Number(checkpoint.header.totalManaUsed.toBigInt())
|
|
1048
|
+
});
|
|
837
1049
|
// In fisherman mode, return the checkpoint without broadcasting or collecting attestations
|
|
838
1050
|
if (this.config.fishermanMode) {
|
|
839
1051
|
this.log.info(`Built checkpoint for slot ${this.targetSlot} with ${blocksInCheckpoint.length} blocks. ` + `Skipping proposal in fisherman mode.`, {
|
|
@@ -852,8 +1064,10 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
852
1064
|
// Create the checkpoint proposal and broadcast it
|
|
853
1065
|
const proposal = await this.validatorClient.createCheckpointProposal(checkpoint.header, checkpoint.archive.root, this.checkpointNumber, feeAssetPriceModifier, blockPendingBroadcast, this.proposer, checkpointProposalOptions);
|
|
854
1066
|
const blockProposedAt = this.dateProvider.now();
|
|
855
|
-
|
|
856
|
-
|
|
1067
|
+
if (!this.config.skipBroadcastProposals) {
|
|
1068
|
+
await this.p2pClient.broadcastCheckpointProposal(proposal);
|
|
1069
|
+
this.checkpointMetrics.noteCheckpointBroadcast(this.dateProvider.now());
|
|
1070
|
+
}
|
|
857
1071
|
// Return immediately after broadcast — attestation collection happens in the background
|
|
858
1072
|
return {
|
|
859
1073
|
checkpoint,
|
|
@@ -888,6 +1102,14 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
888
1102
|
const blocksBuilt = blocksInCheckpoint.length;
|
|
889
1103
|
const indexWithinCheckpoint = IndexWithinCheckpoint(blocksBuilt);
|
|
890
1104
|
const blockNumber = BlockNumber(initialBlockNumber + blocksBuilt);
|
|
1105
|
+
if (blocksBuilt >= this.config.maxBlocksPerCheckpoint) {
|
|
1106
|
+
this.log.debug(`Reached max blocks per checkpoint`, {
|
|
1107
|
+
slot: this.targetSlot,
|
|
1108
|
+
blocksBuilt,
|
|
1109
|
+
maxBlocksPerCheckpoint: this.config.maxBlocksPerCheckpoint
|
|
1110
|
+
});
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
891
1113
|
const secondsIntoSlot = this.getSecondsIntoSlot();
|
|
892
1114
|
const timingInfo = this.timetable.canStartNextBlock(secondsIntoSlot);
|
|
893
1115
|
if (!timingInfo.canStart) {
|
|
@@ -956,7 +1178,9 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
956
1178
|
break;
|
|
957
1179
|
}
|
|
958
1180
|
// Once we have a signed proposal and the archiver agreed with our proposed block, then we broadcast it.
|
|
959
|
-
proposal &&
|
|
1181
|
+
if (proposal && !this.config.skipBroadcastProposals) {
|
|
1182
|
+
await this.p2pClient.broadcastProposal(proposal);
|
|
1183
|
+
}
|
|
960
1184
|
// Wait until the next block's start time
|
|
961
1185
|
await this.waitUntilNextSubslot(timingInfo.deadline);
|
|
962
1186
|
}
|
|
@@ -993,10 +1217,23 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
993
1217
|
// Wait until we have enough txs to build the block
|
|
994
1218
|
const { availableTxs, canStartBuilding, minTxs } = await this.waitForMinTxs(opts);
|
|
995
1219
|
if (!canStartBuilding) {
|
|
1220
|
+
this.logCheckpointEvent('block-build-failed', `Block build failed for slot ${this.targetSlot}`, {
|
|
1221
|
+
reason: 'insufficient_txs',
|
|
1222
|
+
blockNumber,
|
|
1223
|
+
slot: this.targetSlot,
|
|
1224
|
+
checkpointNumber: this.checkpointNumber,
|
|
1225
|
+
indexWithinCheckpoint,
|
|
1226
|
+
availableTxs,
|
|
1227
|
+
minTxs
|
|
1228
|
+
});
|
|
996
1229
|
this.log.warn(`Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot} (got ${availableTxs} txs but needs ${minTxs})`, {
|
|
1230
|
+
reason: 'insufficient_txs',
|
|
997
1231
|
blockNumber,
|
|
998
1232
|
slot: this.targetSlot,
|
|
999
|
-
|
|
1233
|
+
checkpointNumber: this.checkpointNumber,
|
|
1234
|
+
indexWithinCheckpoint,
|
|
1235
|
+
availableTxs,
|
|
1236
|
+
minTxs
|
|
1000
1237
|
});
|
|
1001
1238
|
this.eventEmitter.emit('block-tx-count-check-failed', {
|
|
1002
1239
|
minTxs,
|
|
@@ -1037,8 +1274,19 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1037
1274
|
// If any txs failed during execution, drop them from the mempool so we don't pick them up again
|
|
1038
1275
|
await this.dropFailedTxsFromP2P(buildResult.failedTxs);
|
|
1039
1276
|
if (buildResult.status === 'insufficient-valid-txs') {
|
|
1277
|
+
this.logCheckpointEvent('block-build-failed', `Block build failed for slot ${this.targetSlot}`, {
|
|
1278
|
+
reason: 'insufficient_valid_txs',
|
|
1279
|
+
slot: this.targetSlot,
|
|
1280
|
+
checkpointNumber: this.checkpointNumber,
|
|
1281
|
+
blockNumber,
|
|
1282
|
+
numTxs: buildResult.processedCount,
|
|
1283
|
+
indexWithinCheckpoint,
|
|
1284
|
+
minValidTxs
|
|
1285
|
+
});
|
|
1040
1286
|
this.log.warn(`Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.targetSlot} has too few valid txs to be proposed`, {
|
|
1287
|
+
reason: 'insufficient_valid_txs',
|
|
1041
1288
|
slot: this.targetSlot,
|
|
1289
|
+
checkpointNumber: this.checkpointNumber,
|
|
1042
1290
|
blockNumber,
|
|
1043
1291
|
numTxs: buildResult.processedCount,
|
|
1044
1292
|
indexWithinCheckpoint,
|
|
@@ -1087,9 +1335,17 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1087
1335
|
reason: err.message,
|
|
1088
1336
|
slot: this.targetSlot
|
|
1089
1337
|
});
|
|
1338
|
+
this.logCheckpointEvent('block-build-failed', `Block build failed for slot ${this.targetSlot}`, {
|
|
1339
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
1340
|
+
slot: this.targetSlot,
|
|
1341
|
+
checkpointNumber: this.checkpointNumber,
|
|
1342
|
+
blockNumber
|
|
1343
|
+
});
|
|
1090
1344
|
this.log.error(`Error building block`, err, {
|
|
1091
|
-
|
|
1092
|
-
slot: this.targetSlot
|
|
1345
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
1346
|
+
slot: this.targetSlot,
|
|
1347
|
+
checkpointNumber: this.checkpointNumber,
|
|
1348
|
+
blockNumber
|
|
1093
1349
|
});
|
|
1094
1350
|
this.metrics.recordBlockProposalFailed(err.name || 'unknown_error');
|
|
1095
1351
|
this.metrics.recordFailedBlock();
|
|
@@ -1152,13 +1408,37 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1152
1408
|
minTxs
|
|
1153
1409
|
};
|
|
1154
1410
|
}
|
|
1411
|
+
async getSignedCommitteeAttestations(broadcast) {
|
|
1412
|
+
const { proposal, blockProposedAt } = broadcast;
|
|
1413
|
+
this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.targetSlot);
|
|
1414
|
+
const attestations = await this.waitForAttestations(proposal);
|
|
1415
|
+
if (!attestations) {
|
|
1416
|
+
return undefined;
|
|
1417
|
+
}
|
|
1418
|
+
this.checkpointMetrics.recordCheckpointAttestationDelay(this.dateProvider.now() - blockProposedAt);
|
|
1419
|
+
// Proposer must sign over the attestations before pushing them to L1
|
|
1420
|
+
const signer = this.proposer ?? this.publisher.getSenderAddress();
|
|
1421
|
+
try {
|
|
1422
|
+
const attestationsSignature = await this.validatorClient.signAttestationsAndSigners(attestations, signer, this.targetSlot, this.checkpointNumber);
|
|
1423
|
+
return {
|
|
1424
|
+
attestations,
|
|
1425
|
+
attestationsSignature
|
|
1426
|
+
};
|
|
1427
|
+
} catch (err) {
|
|
1428
|
+
if (this.handleHASigningError(err, 'Attestations signature')) {
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
this.log.error(`Error signing attestations for checkpoint proposal at slot ${proposal.slotNumber}`, err);
|
|
1432
|
+
return undefined;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1155
1435
|
/**
|
|
1156
1436
|
* Waits for enough attestations to be collected via p2p.
|
|
1157
1437
|
* This is run after all blocks for the checkpoint have been built.
|
|
1158
1438
|
*/ async waitForAttestations(proposal) {
|
|
1159
1439
|
if (this.config.fishermanMode) {
|
|
1160
1440
|
this.log.debug('Skipping attestation collection in fisherman mode');
|
|
1161
|
-
return CommitteeAttestationsAndSigners.empty();
|
|
1441
|
+
return CommitteeAttestationsAndSigners.empty(this.getSignatureContext());
|
|
1162
1442
|
}
|
|
1163
1443
|
const slotNumber = proposal.slotNumber;
|
|
1164
1444
|
const { committee, seed, epoch } = await this.epochCache.getCommittee(slotNumber);
|
|
@@ -1166,7 +1446,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1166
1446
|
throw new Error('No committee when collecting attestations');
|
|
1167
1447
|
} else if (committee.length === 0) {
|
|
1168
1448
|
this.log.verbose(`Attesting committee is empty`);
|
|
1169
|
-
return CommitteeAttestationsAndSigners.empty();
|
|
1449
|
+
return CommitteeAttestationsAndSigners.empty(this.getSignatureContext());
|
|
1170
1450
|
} else {
|
|
1171
1451
|
this.log.debug(`Attesting committee length is ${committee.length}`, {
|
|
1172
1452
|
committee
|
|
@@ -1176,7 +1456,10 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1176
1456
|
if (this.config.skipCollectingAttestations) {
|
|
1177
1457
|
this.log.warn('Skipping attestation collection as per config (attesting with own keys only)');
|
|
1178
1458
|
const attestations = await this.validatorClient?.collectOwnAttestations(proposal, this.checkpointNumber);
|
|
1179
|
-
|
|
1459
|
+
this.logCheckpointAttestations('collected', committee, attestations ?? [], numberOfRequiredAttestations, {
|
|
1460
|
+
reason: 'collect_own_only'
|
|
1461
|
+
});
|
|
1462
|
+
return new CommitteeAttestationsAndSigners(orderAttestations(attestations ?? [], committee), this.getSignatureContext());
|
|
1180
1463
|
}
|
|
1181
1464
|
const attestationTimeAllowed = this.config.enforceTimeTable ? this.timetable.getCheckpointAttestationDeadline() : this.l1Constants.slotDuration;
|
|
1182
1465
|
const attestationDeadline = new Date((this.getSlotStartBuildTimestamp() + attestationTimeAllowed) * 1000);
|
|
@@ -1194,20 +1477,55 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1194
1477
|
}
|
|
1195
1478
|
// Rollup contract requires that the signatures are provided in the order of the committee
|
|
1196
1479
|
const sorted = orderAttestations(trimmed, committee);
|
|
1480
|
+
this.logCheckpointAttestations('collected', committee, attestations, numberOfRequiredAttestations, {
|
|
1481
|
+
submittedCount: trimmed.length
|
|
1482
|
+
});
|
|
1197
1483
|
// Manipulate the attestations if we've been configured to do so
|
|
1198
1484
|
if (this.config.injectFakeAttestation || this.config.injectHighSValueAttestation || this.config.injectUnrecoverableSignatureAttestation || this.config.shuffleAttestationOrdering) {
|
|
1199
1485
|
return this.manipulateAttestations(proposal.slotNumber, epoch, seed, committee, sorted);
|
|
1200
1486
|
}
|
|
1201
|
-
return new CommitteeAttestationsAndSigners(sorted);
|
|
1487
|
+
return new CommitteeAttestationsAndSigners(sorted, this.getSignatureContext());
|
|
1202
1488
|
} catch (err) {
|
|
1203
1489
|
if (err && err instanceof AttestationTimeoutError) {
|
|
1204
1490
|
collectedAttestationsCount = err.collectedCount;
|
|
1491
|
+
this.logCheckpointAttestations('failed', committee, undefined, numberOfRequiredAttestations, {
|
|
1492
|
+
collectedCount: collectedAttestationsCount,
|
|
1493
|
+
reason: 'timeout'
|
|
1494
|
+
});
|
|
1495
|
+
this.log.error(`Timeout while waiting for attestations for checkpoint proposal at slot ${proposal.slotNumber} (collected ${collectedAttestationsCount}/${numberOfRequiredAttestations})`, err);
|
|
1496
|
+
} else {
|
|
1497
|
+
this.logCheckpointAttestations('failed', committee, undefined, numberOfRequiredAttestations, {
|
|
1498
|
+
collectedCount: collectedAttestationsCount,
|
|
1499
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
1500
|
+
});
|
|
1501
|
+
this.log.error(`Error collecting attestations for checkpoint proposal at slot ${proposal.slotNumber}`, err);
|
|
1205
1502
|
}
|
|
1206
|
-
|
|
1503
|
+
return undefined;
|
|
1207
1504
|
} finally{
|
|
1208
1505
|
this.metrics.recordCollectedAttestations(collectedAttestationsCount, collectAttestationsTimer.ms());
|
|
1209
1506
|
}
|
|
1210
1507
|
}
|
|
1508
|
+
logCheckpointAttestations(status, committee, attestations, requiredAttestations, opts = {}) {
|
|
1509
|
+
const signedValidators = attestations?.map((attestation)=>attestation.getSender()?.toString()).filter((address)=>address !== undefined) ?? [];
|
|
1510
|
+
const collectedCount = opts.collectedCount ?? new Set(signedValidators).size;
|
|
1511
|
+
const missingValidatorCount = status === 'failed' ? Math.max(0, requiredAttestations - collectedCount) : undefined;
|
|
1512
|
+
this.logCheckpointEvent(`attestations-${status}`, `Checkpoint attestations ${status} for slot ${this.targetSlot}`, {
|
|
1513
|
+
slot: this.targetSlot,
|
|
1514
|
+
checkpointNumber: this.checkpointNumber,
|
|
1515
|
+
committeeSize: committee.length,
|
|
1516
|
+
requiredAttestations,
|
|
1517
|
+
collectedAttestations: collectedCount,
|
|
1518
|
+
...opts.submittedCount !== undefined && {
|
|
1519
|
+
submittedAttestations: opts.submittedCount
|
|
1520
|
+
},
|
|
1521
|
+
...missingValidatorCount !== undefined && {
|
|
1522
|
+
missingValidatorCount
|
|
1523
|
+
},
|
|
1524
|
+
...opts.reason !== undefined && {
|
|
1525
|
+
reason: opts.reason
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1211
1529
|
/** Breaks the attestations before publishing based on attack configs */ manipulateAttestations(slotNumber, epoch, seed, committee, attestations) {
|
|
1212
1530
|
// Compute the proposer index in the committee, since we dont want to tweak it.
|
|
1213
1531
|
// Otherwise, the L1 rollup contract will reject the block outright.
|
|
@@ -1233,7 +1551,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1233
1551
|
unfreeze(attestations[targetIndex]).signature = generateRecoverableSignature();
|
|
1234
1552
|
}
|
|
1235
1553
|
}
|
|
1236
|
-
return new CommitteeAttestationsAndSigners(attestations);
|
|
1554
|
+
return new CommitteeAttestationsAndSigners(attestations, this.getSignatureContext());
|
|
1237
1555
|
}
|
|
1238
1556
|
if (this.config.shuffleAttestationOrdering) {
|
|
1239
1557
|
this.log.warn(`Shuffling attestation ordering in checkpoint for slot ${slotNumber} (proposer #${proposerIndex})`);
|
|
@@ -1259,10 +1577,10 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1259
1577
|
shuffled[i]
|
|
1260
1578
|
];
|
|
1261
1579
|
}
|
|
1262
|
-
const signers = new CommitteeAttestationsAndSigners(attestations).getSigners();
|
|
1263
|
-
return new MaliciousCommitteeAttestationsAndSigners(shuffled, signers);
|
|
1580
|
+
const signers = new CommitteeAttestationsAndSigners(attestations, this.getSignatureContext()).getSigners();
|
|
1581
|
+
return new MaliciousCommitteeAttestationsAndSigners(shuffled, signers, this.getSignatureContext());
|
|
1264
1582
|
}
|
|
1265
|
-
return new CommitteeAttestationsAndSigners(attestations);
|
|
1583
|
+
return new CommitteeAttestationsAndSigners(attestations, this.getSignatureContext());
|
|
1266
1584
|
}
|
|
1267
1585
|
async dropFailedTxsFromP2P(failedTxs) {
|
|
1268
1586
|
if (failedTxs.length === 0) {
|