@aztec/sequencer-client 0.0.1-commit.993d240 → 0.0.1-commit.9a89641
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/README.md +40 -41
- package/dest/client/sequencer-client.d.ts +16 -3
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +18 -14
- package/dest/config.d.ts +9 -4
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +29 -15
- package/dest/global_variable_builder/fee_predictor.d.ts +1 -1
- package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -1
- package/dest/global_variable_builder/fee_predictor.js +11 -1
- package/dest/global_variable_builder/fee_provider.d.ts +1 -1
- package/dest/global_variable_builder/fee_provider.d.ts.map +1 -1
- package/dest/global_variable_builder/fee_provider.js +27 -5
- package/dest/global_variable_builder/global_builder.d.ts +3 -16
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +2 -25
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/publisher/config.d.ts +5 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +11 -1
- package/dest/publisher/l1_to_l2_messaging.d.ts +21 -0
- package/dest/publisher/l1_to_l2_messaging.d.ts.map +1 -0
- package/dest/publisher/l1_to_l2_messaging.js +70 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +48 -8
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +68 -1
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +2 -2
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +4 -2
- package/dest/publisher/l1_tx_failed_store/index.d.ts +2 -2
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/index.js +1 -0
- package/dest/publisher/sequencer-publisher.d.ts +39 -10
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +403 -75
- package/dest/publisher/write_json.d.ts +11 -0
- package/dest/publisher/write_json.d.ts.map +1 -0
- package/dest/publisher/write_json.js +57 -0
- package/dest/sequencer/automine/automine_factory.d.ts +5 -3
- package/dest/sequencer/automine/automine_factory.d.ts.map +1 -1
- package/dest/sequencer/automine/automine_factory.js +2 -1
- package/dest/sequencer/automine/automine_sequencer.d.ts +43 -5
- package/dest/sequencer/automine/automine_sequencer.d.ts.map +1 -1
- package/dest/sequencer/automine/automine_sequencer.js +209 -19
- package/dest/sequencer/automine/index.d.ts +3 -0
- package/dest/sequencer/automine/index.d.ts.map +1 -0
- package/dest/sequencer/automine/index.js +2 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +28 -15
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +158 -117
- package/dest/sequencer/events.d.ts +16 -4
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/index.d.ts +1 -3
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +0 -2
- package/dest/sequencer/missing_committee.d.ts +72 -0
- package/dest/sequencer/missing_committee.d.ts.map +1 -0
- package/dest/sequencer/missing_committee.js +139 -0
- package/dest/sequencer/requests_tracker.d.ts +22 -0
- package/dest/sequencer/requests_tracker.d.ts.map +1 -0
- package/dest/sequencer/requests_tracker.js +33 -0
- package/dest/sequencer/sequencer.d.ts +113 -28
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +352 -162
- package/dest/test/index.d.ts +3 -3
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/utils.d.ts +15 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +18 -1
- package/package.json +29 -28
- package/src/client/sequencer-client.ts +20 -14
- package/src/config.ts +32 -16
- package/src/global_variable_builder/fee_predictor.ts +11 -1
- package/src/global_variable_builder/fee_provider.ts +27 -5
- package/src/global_variable_builder/global_builder.ts +2 -34
- package/src/index.ts +1 -10
- package/src/publisher/config.ts +22 -1
- package/src/publisher/l1_to_l2_messaging.ts +85 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +114 -7
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +4 -3
- package/src/publisher/l1_tx_failed_store/index.ts +1 -1
- package/src/publisher/sequencer-publisher.ts +430 -86
- package/src/publisher/write_json.ts +78 -0
- package/src/sequencer/README.md +16 -6
- package/src/sequencer/automine/README.md +18 -4
- package/src/sequencer/automine/automine_factory.ts +8 -1
- package/src/sequencer/automine/automine_sequencer.ts +229 -21
- package/src/sequencer/automine/index.ts +6 -0
- package/src/sequencer/checkpoint_proposal_job.ts +177 -132
- package/src/sequencer/events.ts +16 -3
- package/src/sequencer/index.ts +0 -2
- package/src/sequencer/missing_committee.ts +192 -0
- package/src/sequencer/requests_tracker.ts +43 -0
- package/src/sequencer/sequencer.ts +382 -176
- package/src/test/index.ts +2 -2
- package/src/test/utils.ts +33 -0
- package/dest/sequencer/chain_state_overrides.d.ts +0 -61
- package/dest/sequencer/chain_state_overrides.d.ts.map +0 -1
- package/dest/sequencer/chain_state_overrides.js +0 -98
- package/dest/sequencer/timetable.d.ts +0 -101
- package/dest/sequencer/timetable.d.ts.map +0 -1
- package/dest/sequencer/timetable.js +0 -231
- package/src/sequencer/chain_state_overrides.ts +0 -162
- package/src/sequencer/timetable.ts +0 -295
|
@@ -435,19 +435,20 @@ function applyDecs2203RFactory() {
|
|
|
435
435
|
function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
|
|
436
436
|
return (_apply_decs_2203_r = applyDecs2203RFactory())(targetClass, memberDecs, classDecs, parentClass);
|
|
437
437
|
}
|
|
438
|
-
var _dec, _dec1, _dec2, _dec3, _dec4, _dec5, _dec6,
|
|
439
|
-
import {
|
|
438
|
+
var _dec, _dec1, _dec2, _dec3, _dec4, _dec5, _dec6, _initProto;
|
|
439
|
+
import { PROPOSER_PIPELINING_SLOT_OFFSET } from '@aztec/epoch-cache';
|
|
440
|
+
import { BlockNumber, CheckpointNumber, IndexWithinCheckpoint, SlotNumber } from '@aztec/foundation/branded-types';
|
|
440
441
|
import { randomInt } from '@aztec/foundation/crypto/random';
|
|
441
442
|
import { flipSignature, generateRecoverableSignature, generateUnrecoverableSignature } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
443
|
+
import { InterruptError, TimeoutError } from '@aztec/foundation/error';
|
|
442
444
|
import { filter } from '@aztec/foundation/iterator';
|
|
443
445
|
import { createLogger } from '@aztec/foundation/log';
|
|
444
|
-
import {
|
|
445
|
-
import { sleep, sleepUntil } from '@aztec/foundation/sleep';
|
|
446
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
446
447
|
import { Timer } from '@aztec/foundation/timer';
|
|
447
448
|
import { isErrorClass, unfreeze } from '@aztec/foundation/types';
|
|
448
|
-
import { CommitteeAttestationsAndSigners, MaliciousCommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
449
|
-
import { getPreviousCheckpointOutHashes, validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
450
|
-
import { computeQuorum,
|
|
449
|
+
import { CommitteeAttestationsAndSigners, MaliciousCommitteeAttestationsAndSigners, MaliciousYParityCommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
450
|
+
import { buildCheckpointSimulationOverridesPlan, getPreviousCheckpointOutHashes, validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
451
|
+
import { computeQuorum, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
451
452
|
import { Gas } from '@aztec/stdlib/gas';
|
|
452
453
|
import { InsufficientValidTxsError } from '@aztec/stdlib/interfaces/server';
|
|
453
454
|
import { computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
@@ -455,25 +456,24 @@ import { orderAttestations, trimAttestations } from '@aztec/stdlib/p2p';
|
|
|
455
456
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
456
457
|
import { Attributes, trackSpan } from '@aztec/telemetry-client';
|
|
457
458
|
import { DutyAlreadySignedError, SlashingProtectionError } from '@aztec/validator-ha-signer/errors';
|
|
458
|
-
import { buildCheckpointSimulationOverridesPlan } from './chain_state_overrides.js';
|
|
459
459
|
import { CheckpointVoter } from './checkpoint_voter.js';
|
|
460
460
|
import { SequencerInterruptedError } from './errors.js';
|
|
461
461
|
import { SequencerState } from './utils.js';
|
|
462
462
|
/** How much time to sleep while waiting for min transactions to accumulate for a block */ const TXS_POLLING_MS = 500;
|
|
463
|
+
const ARCHIVER_SYNC_POLLING_MS = 200;
|
|
463
464
|
_dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('CheckpointProposalJob.proposeCheckpoint', function() {
|
|
464
465
|
return {
|
|
465
466
|
// nullish operator needed for tests
|
|
466
467
|
[Attributes.COINBASE]: this.validatorClient.getCoinbaseForAttestor(this.attestorAddress)?.toString(),
|
|
467
468
|
[Attributes.SLOT_NUMBER]: this.targetSlot
|
|
468
469
|
};
|
|
469
|
-
}), _dec2 = trackSpan('CheckpointProposalJob.buildBlocksForCheckpoint'), _dec3 = trackSpan('CheckpointProposalJob.waitUntilNextSubslot'), _dec4 = trackSpan('CheckpointProposalJob.buildSingleBlock'), _dec5 = trackSpan('CheckpointProposalJob.waitForMinTxs'), _dec6 = trackSpan('CheckpointProposalJob.waitForAttestations')
|
|
470
|
+
}), _dec2 = trackSpan('CheckpointProposalJob.buildBlocksForCheckpoint'), _dec3 = trackSpan('CheckpointProposalJob.waitUntilNextSubslot'), _dec4 = trackSpan('CheckpointProposalJob.buildSingleBlock'), _dec5 = trackSpan('CheckpointProposalJob.waitForMinTxs'), _dec6 = trackSpan('CheckpointProposalJob.waitForAttestations');
|
|
470
471
|
/**
|
|
471
472
|
* Handles the execution of a checkpoint proposal after the initial preparation phase.
|
|
472
473
|
* This includes building blocks, collecting attestations, and publishing the checkpoint to L1,
|
|
473
474
|
* as well as enqueueing votes for slashing and governance proposals. This class is created from
|
|
474
475
|
* the Sequencer once the check for being the proposer for the slot has succeeded.
|
|
475
476
|
*/ export class CheckpointProposalJob {
|
|
476
|
-
slotNow;
|
|
477
477
|
targetSlot;
|
|
478
478
|
targetEpoch;
|
|
479
479
|
checkpointNumber;
|
|
@@ -501,6 +501,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
501
501
|
metrics;
|
|
502
502
|
checkpointMetrics;
|
|
503
503
|
eventEmitter;
|
|
504
|
+
pendingRequests;
|
|
504
505
|
setStateFn;
|
|
505
506
|
tracer;
|
|
506
507
|
proposedCheckpointData;
|
|
@@ -540,17 +541,13 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
540
541
|
_dec6,
|
|
541
542
|
2,
|
|
542
543
|
"waitForAttestations"
|
|
543
|
-
],
|
|
544
|
-
[
|
|
545
|
-
_dec7,
|
|
546
|
-
2,
|
|
547
|
-
"waitUntilTimeInSlot"
|
|
548
544
|
]
|
|
549
545
|
], []));
|
|
550
546
|
}
|
|
551
547
|
log;
|
|
552
548
|
checkpointEventLog;
|
|
553
|
-
|
|
549
|
+
interruptibleSleep;
|
|
550
|
+
interrupted;
|
|
554
551
|
/**
|
|
555
552
|
* Chain state overrides built once per slot in proposeCheckpoint after the checkpoint is
|
|
556
553
|
* complete. Carries the pending parent override (archive + slot + fee header) for pipelining,
|
|
@@ -560,9 +557,10 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
560
557
|
getSignatureContext() {
|
|
561
558
|
return this.signatureContext;
|
|
562
559
|
}
|
|
563
|
-
constructor(
|
|
564
|
-
proposer, publisher, attestorAddress, invalidateCheckpoint, validatorClient, globalsBuilder, p2pClient, worldState, l1ToL2MessageSource, l2BlockSource, checkpointsBuilder, blockSink, l1Constants, signatureContext, config, timetable, slasherClient, epochCache, dateProvider, metrics, checkpointMetrics, eventEmitter,
|
|
565
|
-
|
|
560
|
+
constructor(targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, checkpointedCheckpointNumber, // TODO(palla/mbps): Can we remove the proposer in favor of attestorAddress? Need to check fisherman-node flows.
|
|
561
|
+
proposer, publisher, attestorAddress, invalidateCheckpoint, validatorClient, globalsBuilder, p2pClient, worldState, l1ToL2MessageSource, l2BlockSource, checkpointsBuilder, blockSink, l1Constants, signatureContext, config, timetable, slasherClient, epochCache, dateProvider, metrics, checkpointMetrics, eventEmitter, // Shared with the owning sequencer, which drains it during shutdown; the fire-and-forget L1
|
|
562
|
+
// submission this job backgrounds is tracked here rather than in a job-local tracker.
|
|
563
|
+
pendingRequests, setStateFn, tracer, bindings, proposedCheckpointData){
|
|
566
564
|
this.targetSlot = targetSlot;
|
|
567
565
|
this.targetEpoch = targetEpoch;
|
|
568
566
|
this.checkpointNumber = checkpointNumber;
|
|
@@ -590,22 +588,54 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
590
588
|
this.metrics = metrics;
|
|
591
589
|
this.checkpointMetrics = checkpointMetrics;
|
|
592
590
|
this.eventEmitter = eventEmitter;
|
|
591
|
+
this.pendingRequests = pendingRequests;
|
|
593
592
|
this.setStateFn = setStateFn;
|
|
594
593
|
this.tracer = tracer;
|
|
595
594
|
this.proposedCheckpointData = proposedCheckpointData;
|
|
596
|
-
_initProto(this);
|
|
595
|
+
this.interruptibleSleep = (_initProto(this), new InterruptibleSleep());
|
|
596
|
+
this.interrupted = false;
|
|
597
597
|
this.log = createLogger('sequencer:checkpoint-proposal', {
|
|
598
598
|
...bindings,
|
|
599
|
-
instanceId: `slot-${this.
|
|
599
|
+
instanceId: `slot-${this.getBuildSlot()}`
|
|
600
600
|
});
|
|
601
601
|
this.checkpointEventLog = createLogger('sequencer:checkpoint-events', {
|
|
602
602
|
...bindings,
|
|
603
|
-
instanceId: `slot-${this.
|
|
603
|
+
instanceId: `slot-${this.getBuildSlot()}`
|
|
604
604
|
});
|
|
605
605
|
}
|
|
606
|
-
/**
|
|
607
|
-
|
|
608
|
-
|
|
606
|
+
/**
|
|
607
|
+
* The wall-clock slot during which this job builds, i.e. the slot one before {@link targetSlot} under
|
|
608
|
+
* proposer pipelining. Also the slot of the parent checkpoint this job builds on top of.
|
|
609
|
+
*/ getBuildSlot() {
|
|
610
|
+
return SlotNumber(this.targetSlot - PROPOSER_PIPELINING_SLOT_OFFSET);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Sets the sequencer state for this job, reporting the target slot the checkpoint is being proposed for
|
|
614
|
+
* (not the wall-clock build slot). The slot is informational on the event payload/metrics; the job knows
|
|
615
|
+
* its own target slot, so callers only pass the state.
|
|
616
|
+
*/ setState(state) {
|
|
617
|
+
this.setStateFn(state, this.targetSlot);
|
|
618
|
+
}
|
|
619
|
+
/** Interrupts job-owned waits, including the publisher's send-at-slot sleep, so shutdown can finish. */ interrupt() {
|
|
620
|
+
this.interrupted = true;
|
|
621
|
+
this.interruptibleSleep.interrupt(true);
|
|
622
|
+
this.publisher.interrupt();
|
|
623
|
+
}
|
|
624
|
+
async awaitInterruptibleSleep(ms) {
|
|
625
|
+
if (this.interrupted) {
|
|
626
|
+
throw new SequencerInterruptedError();
|
|
627
|
+
}
|
|
628
|
+
if (ms <= 0) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
await this.interruptibleSleep.sleep(ms);
|
|
633
|
+
} catch (err) {
|
|
634
|
+
if (err instanceof InterruptError) {
|
|
635
|
+
throw new SequencerInterruptedError();
|
|
636
|
+
}
|
|
637
|
+
throw err;
|
|
638
|
+
}
|
|
609
639
|
}
|
|
610
640
|
logCheckpointEvent(eventName, message, fields) {
|
|
611
641
|
this.checkpointEventLog.debug(message, {
|
|
@@ -636,7 +666,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
636
666
|
// signature verification to fail silently inside Multicall3. Delay submission to the
|
|
637
667
|
// start of `targetSlot` so the tx mines in the slot the vote was signed for.
|
|
638
668
|
if (!this.config.fishermanMode) {
|
|
639
|
-
this.
|
|
669
|
+
this.pendingRequests.trackRequest(this.publisher.sendRequestsAt(this.targetSlot), ()=>this.interrupt());
|
|
640
670
|
}
|
|
641
671
|
return undefined;
|
|
642
672
|
}
|
|
@@ -648,32 +678,30 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
648
678
|
return checkpoint;
|
|
649
679
|
}
|
|
650
680
|
// Background the attestation → signing → L1 pipeline so the work loop is unblocked
|
|
651
|
-
this.
|
|
681
|
+
this.pendingRequests.trackRequest(this.waitForAttestationsAndEnqueueSubmissionAsync(broadcast, votesPromises), ()=>this.interrupt());
|
|
652
682
|
// Return the built checkpoint immediately — the work loop is now unblocked
|
|
653
683
|
return checkpoint;
|
|
654
684
|
}
|
|
655
685
|
/**
|
|
656
686
|
* Background pipeline: collects attestations, signs them, enqueues the checkpoint, and submits to L1.
|
|
657
|
-
* Runs as a fire-and-forget task
|
|
687
|
+
* Runs as a fire-and-forget task tracked in the sequencer's shared tracker so the work loop is unblocked.
|
|
658
688
|
*/ async waitForAttestationsAndEnqueueSubmissionAsync(broadcast, votesPromises) {
|
|
659
689
|
const { checkpoint } = broadcast;
|
|
660
|
-
const isPipelining = this.epochCache.isProposerPipeliningEnabled();
|
|
661
690
|
try {
|
|
662
691
|
// Wait for all votes actions, enqueued at the beginning, to resolve
|
|
663
692
|
await Promise.all(votesPromises);
|
|
664
693
|
// Try to collect attestations from the committee
|
|
665
694
|
const signedAttestations = await this.getSignedCommitteeAttestations(broadcast);
|
|
666
|
-
//
|
|
667
|
-
//
|
|
668
|
-
if (signedAttestations &&
|
|
695
|
+
// Wait for the previous checkpoint to land on L1 before submitting, so we can check it
|
|
696
|
+
// matches the proposed checkpoint we used as parent, and has valid attestations.
|
|
697
|
+
if (signedAttestations && await this.waitForValidParentCheckpointOnL1()) {
|
|
669
698
|
await this.enqueueCheckpointForSubmission({
|
|
670
699
|
checkpoint,
|
|
671
700
|
...signedAttestations
|
|
672
701
|
});
|
|
673
702
|
}
|
|
674
703
|
// If we failed to collect attestations, at least check if we need to issue an invalidation
|
|
675
|
-
|
|
676
|
-
if (!signedAttestations && isPipelining && await this.waitForSyncedL2SlotNumber(this.slotNow)) {
|
|
704
|
+
if (!signedAttestations && await this.waitForSyncedL2SlotNumber(this.getBuildSlot())) {
|
|
677
705
|
const validationStatus = await this.l2BlockSource.getPendingChainValidationStatus();
|
|
678
706
|
if (!validationStatus.valid) {
|
|
679
707
|
this.log.warn(`Checkpoint ${validationStatus.checkpoint.checkpointNumber} has invalid attestations, enqueuing invalidation in spite of attestation collection failure`, {
|
|
@@ -722,9 +750,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
722
750
|
...l1Response,
|
|
723
751
|
slot: this.targetSlot
|
|
724
752
|
});
|
|
725
|
-
|
|
726
|
-
this.metrics.recordPipelineDiscard();
|
|
727
|
-
}
|
|
753
|
+
this.metrics.recordPipelineDiscard();
|
|
728
754
|
}
|
|
729
755
|
} catch (err) {
|
|
730
756
|
if (err instanceof SequencerInterruptedError) {
|
|
@@ -743,17 +769,20 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
743
769
|
this.eventEmitter.emit('checkpoint-publish-failed', {
|
|
744
770
|
slot: this.targetSlot
|
|
745
771
|
});
|
|
746
|
-
|
|
747
|
-
this.metrics.recordPipelineDiscard();
|
|
748
|
-
}
|
|
772
|
+
this.metrics.recordPipelineDiscard();
|
|
749
773
|
}
|
|
750
774
|
}
|
|
751
775
|
/** Enqueues the checkpoint for L1 submission. Called after pipeline sleep in execute(). */ async enqueueCheckpointForSubmission(result) {
|
|
752
776
|
const { checkpoint, attestations, attestationsSignature } = result;
|
|
753
|
-
this.
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
777
|
+
this.setState(SequencerState.PUBLISHING_CHECKPOINT);
|
|
778
|
+
// Latest L1 block the propose can still land in for the target slot: the last Ethereum block inside
|
|
779
|
+
// the target slot (`target_slot_start + S - E`). This is one ethereum slot later than
|
|
780
|
+
// `attestation_deadline` (= last_ethereum_block_in_target_slot - E), which bounds when validators must
|
|
781
|
+
// have signed, not when the proposer must have sent. Using the attestation deadline here is too tight:
|
|
782
|
+
// attestations are collected up to (and, when not enforcing, past) it, so the propose tx would be
|
|
783
|
+
// enqueued already expired and time out before it can mine.
|
|
784
|
+
const lastL1BlockInTargetSlot = Number(getTimestampForSlot(this.targetSlot, this.l1Constants)) + this.l1Constants.slotDuration - this.l1Constants.ethereumSlotDuration;
|
|
785
|
+
const txTimeoutAt = new Date(lastL1BlockInTargetSlot * 1000);
|
|
757
786
|
// If we have been configured to potentially skip publishing checkpoint then roll the dice here
|
|
758
787
|
if (this.config.skipPublishingCheckpointsPercent !== undefined && this.config.skipPublishingCheckpointsPercent > 0) {
|
|
759
788
|
const roll = Math.max(0, randomInt(100));
|
|
@@ -776,11 +805,21 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
776
805
|
const syncDelayTolerance = this.l1Constants.ethereumSlotDuration * 2 * 1000;
|
|
777
806
|
const timeoutSeconds = Math.max(0.1, (targetSlotEndMs + syncDelayTolerance - this.dateProvider.now()) / 1000);
|
|
778
807
|
try {
|
|
779
|
-
|
|
808
|
+
const timer = new Timer();
|
|
809
|
+
while(true){
|
|
780
810
|
const syncedSlot = await this.l2BlockSource.getSyncedL2SlotNumber();
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
811
|
+
if (syncedSlot !== undefined && syncedSlot >= waitForSlot) {
|
|
812
|
+
return true;
|
|
813
|
+
}
|
|
814
|
+
if (timeoutSeconds && timer.s() > timeoutSeconds) {
|
|
815
|
+
throw new TimeoutError(`Timeout awaiting archiver sync past slot ${waitForSlot}`);
|
|
816
|
+
}
|
|
817
|
+
await this.awaitInterruptibleSleep(ARCHIVER_SYNC_POLLING_MS);
|
|
818
|
+
}
|
|
819
|
+
} catch (err) {
|
|
820
|
+
if (err instanceof SequencerInterruptedError) {
|
|
821
|
+
throw err;
|
|
822
|
+
}
|
|
784
823
|
this.log.warn(`Archiver did not sync L1 past slot ${waitForSlot} before slot ${this.targetSlot} expired, discarding pipelined work`, {
|
|
785
824
|
checkpointNumber: this.checkpointNumber
|
|
786
825
|
});
|
|
@@ -802,8 +841,8 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
802
841
|
return true;
|
|
803
842
|
}
|
|
804
843
|
const parentCheckpointNumber = CheckpointNumber(this.checkpointNumber - 1);
|
|
805
|
-
// Wait until archiver has synced L1 past the parent's slot (
|
|
806
|
-
if (!await this.waitForSyncedL2SlotNumber(this.
|
|
844
|
+
// Wait until archiver has synced L1 past the parent's slot (the build slot, one before targetSlot)
|
|
845
|
+
if (!await this.waitForSyncedL2SlotNumber(this.getBuildSlot())) {
|
|
807
846
|
return false;
|
|
808
847
|
}
|
|
809
848
|
const tips = await this.l2BlockSource.getL2Tips();
|
|
@@ -896,11 +935,11 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
896
935
|
};
|
|
897
936
|
try {
|
|
898
937
|
const now = this.dateProvider.now();
|
|
899
|
-
if (this.
|
|
938
|
+
if (this.proposedCheckpointData) {
|
|
900
939
|
// Measure against the wall-clock slot whose build window we are currently using.
|
|
901
940
|
// In pipelining mode `targetSlot` is intentionally one slot ahead, which makes the
|
|
902
941
|
// target-slot boundary a full slot away from the actual build start time.
|
|
903
|
-
const slotBoundaryMs = Number(getTimestampForSlot(this.
|
|
942
|
+
const slotBoundaryMs = Number(getTimestampForSlot(this.getBuildSlot(), this.l1Constants)) * 1000;
|
|
904
943
|
this.checkpointMetrics.recordPipelinedCheckpointBuildStartOffsetFromSlotBoundary(now - slotBoundaryMs);
|
|
905
944
|
}
|
|
906
945
|
this.checkpointMetrics.startCheckpointTiming(now);
|
|
@@ -908,13 +947,12 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
908
947
|
const coinbase = this.validatorClient.getCoinbaseForAttestor(this.attestorAddress);
|
|
909
948
|
const feeRecipient = this.validatorClient.getFeeRecipientForAttestor(this.attestorAddress);
|
|
910
949
|
// Start the checkpoint
|
|
911
|
-
this.
|
|
950
|
+
this.setState(SequencerState.INITIALIZING_CHECKPOINT);
|
|
912
951
|
this.logCheckpointEvent('slot-started', `Starting checkpoint proposal for slot ${this.targetSlot}`, {
|
|
913
|
-
buildSlot: this.
|
|
952
|
+
buildSlot: this.getBuildSlot(),
|
|
914
953
|
submissionSlot: this.targetSlot,
|
|
915
954
|
slot: this.targetSlot,
|
|
916
955
|
checkpointNumber: this.checkpointNumber,
|
|
917
|
-
pipelining: this.epochCache.isProposerPipeliningEnabled(),
|
|
918
956
|
proposer: this.proposer?.toString(),
|
|
919
957
|
attestorAddress: this.attestorAddress.toString(),
|
|
920
958
|
publisherAddress: this.publisher.getSenderAddress().toString(),
|
|
@@ -929,10 +967,9 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
929
967
|
// pending/archive/fee-header to "as if the proposed parent had landed", so both the
|
|
930
968
|
// mana-min-fee simulation (in the globals builder) and the pre-broadcast
|
|
931
969
|
// validateBlockHeader see the chain tip the eventual L1 send will see.
|
|
932
|
-
const isPipelining = this.epochCache.isProposerPipeliningEnabled();
|
|
933
970
|
this.checkpointSimulationOverridesPlan = await buildCheckpointSimulationOverridesPlan({
|
|
934
971
|
checkpointNumber: this.checkpointNumber,
|
|
935
|
-
proposedCheckpointData:
|
|
972
|
+
proposedCheckpointData: this.proposedCheckpointData,
|
|
936
973
|
invalidateToPendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
|
|
937
974
|
checkpointedCheckpointNumber: this.checkpointedCheckpointNumber,
|
|
938
975
|
rollup: this.publisher.rollupContract,
|
|
@@ -953,7 +990,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
953
990
|
epoch: this.targetEpoch,
|
|
954
991
|
checkpointNumber: this.checkpointNumber,
|
|
955
992
|
l1Constants: this.epochCache.getL1Constants(),
|
|
956
|
-
pipeliningEnabled:
|
|
993
|
+
pipeliningEnabled: true,
|
|
957
994
|
proposedCheckpointData: this.proposedCheckpointData,
|
|
958
995
|
log: this.log
|
|
959
996
|
});
|
|
@@ -1028,7 +1065,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1028
1065
|
}
|
|
1029
1066
|
// Assemble and broadcast the checkpoint proposal, including the last block that was not
|
|
1030
1067
|
// broadcasted yet, and wait to collect the committee attestations.
|
|
1031
|
-
this.
|
|
1068
|
+
this.setState(SequencerState.ASSEMBLING_CHECKPOINT);
|
|
1032
1069
|
const checkpoint = await checkpointBuilder.completeCheckpoint();
|
|
1033
1070
|
// Final validation: per-block limits are only checked if the operator set them explicitly.
|
|
1034
1071
|
// Otherwise, checkpoint-level budgets were already enforced by the redistribution logic.
|
|
@@ -1048,7 +1085,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1048
1085
|
reason: 'invalid_checkpoint',
|
|
1049
1086
|
checkpoint: checkpoint.header.toInspect()
|
|
1050
1087
|
});
|
|
1051
|
-
this.log.error(`Built an invalid checkpoint at slot ${this.
|
|
1088
|
+
this.log.error(`Built an invalid checkpoint at slot ${this.targetSlot} (skipping proposal)`, err, {
|
|
1052
1089
|
slot: this.targetSlot,
|
|
1053
1090
|
checkpointNumber: this.checkpointNumber,
|
|
1054
1091
|
blocksBuilt: blocksInCheckpoint.length,
|
|
@@ -1061,7 +1098,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1061
1098
|
this.checkpointMetrics.recordCheckpointBuild(checkpointBuildTimer.ms(), blocksInCheckpoint.length, checkpoint.getStats().txCount, Number(checkpoint.header.totalManaUsed.toBigInt()));
|
|
1062
1099
|
this.logCheckpointEvent('built', `Checkpoint built for slot ${this.targetSlot}`, {
|
|
1063
1100
|
slot: this.targetSlot,
|
|
1064
|
-
buildSlot: this.
|
|
1101
|
+
buildSlot: this.getBuildSlot(),
|
|
1065
1102
|
checkpointNumber: this.checkpointNumber,
|
|
1066
1103
|
proposer: this.proposer?.toString(),
|
|
1067
1104
|
attestorAddress: this.attestorAddress.toString(),
|
|
@@ -1166,13 +1203,13 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1166
1203
|
});
|
|
1167
1204
|
break;
|
|
1168
1205
|
}
|
|
1169
|
-
const
|
|
1170
|
-
const timingInfo = this.timetable.
|
|
1206
|
+
const nowSeconds = this.dateProvider.now() / 1000;
|
|
1207
|
+
const timingInfo = this.timetable.selectNextSubslot(this.targetSlot, nowSeconds);
|
|
1171
1208
|
if (!timingInfo.canStart) {
|
|
1172
1209
|
this.log.debug(`Not enough time left in slot to start another block`, {
|
|
1173
1210
|
slot: this.targetSlot,
|
|
1174
1211
|
blocksBuilt,
|
|
1175
|
-
|
|
1212
|
+
nowSeconds
|
|
1176
1213
|
});
|
|
1177
1214
|
break;
|
|
1178
1215
|
}
|
|
@@ -1181,16 +1218,15 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1181
1218
|
blockTimestamp: timestamp,
|
|
1182
1219
|
// Create an empty block if we haven't already and this is the last one
|
|
1183
1220
|
forceCreate: timingInfo.isLastBlock && blocksBuilt === 0 && this.config.buildCheckpointIfEmpty,
|
|
1184
|
-
|
|
1185
|
-
buildDeadline: timingInfo.deadline ? new Date((this.getSlotStartBuildTimestamp() + timingInfo.deadline) * 1000) : undefined,
|
|
1221
|
+
buildDeadline: new Date(timingInfo.deadline * 1000),
|
|
1186
1222
|
blockNumber,
|
|
1187
1223
|
indexWithinCheckpoint,
|
|
1188
1224
|
txHashesAlreadyIncluded
|
|
1189
1225
|
});
|
|
1190
1226
|
// If we failed to build the block due to insufficient txs, we try again if there is still time left in the slot
|
|
1191
1227
|
if ('failure' in buildResult) {
|
|
1192
|
-
// If this was the last subslot,
|
|
1193
|
-
if (timingInfo.isLastBlock
|
|
1228
|
+
// If this was the last subslot, we're done.
|
|
1229
|
+
if (timingInfo.isLastBlock) {
|
|
1194
1230
|
break;
|
|
1195
1231
|
}
|
|
1196
1232
|
// Otherwise, if there is still time for more blocks, we wait until the next subslot and try again
|
|
@@ -1259,12 +1295,15 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1259
1295
|
}
|
|
1260
1296
|
return this.validatorClient.createBlockProposal(block.header, this.checkpointNumber, block.indexWithinCheckpoint, inHash, block.archive.root, usedTxs, this.proposer, blockProposalOptions);
|
|
1261
1297
|
}
|
|
1262
|
-
/**
|
|
1263
|
-
|
|
1264
|
-
|
|
1298
|
+
/**
|
|
1299
|
+
* Sleeps until it is time to produce the next block in the slot.
|
|
1300
|
+
* @param nextSubslotStart - Absolute wall-clock timestamp in seconds of the previous sub-slot deadline.
|
|
1301
|
+
*/ async waitUntilNextSubslot(nextSubslotStart) {
|
|
1302
|
+
this.setState(SequencerState.WAITING_UNTIL_NEXT_BLOCK);
|
|
1303
|
+
this.log.verbose(`Waiting until time for the next block at ${nextSubslotStart}s`, {
|
|
1265
1304
|
slot: this.targetSlot
|
|
1266
1305
|
});
|
|
1267
|
-
await this.
|
|
1306
|
+
await this.awaitInterruptibleSleep(Math.max(0, nextSubslotStart * 1000 - this.dateProvider.now()));
|
|
1268
1307
|
}
|
|
1269
1308
|
/** Builds a single block. Called from the main block building loop. */ async buildSingleBlock(checkpointBuilder, opts) {
|
|
1270
1309
|
const { blockTimestamp, forceCreate, blockNumber, indexWithinCheckpoint, buildDeadline, txHashesAlreadyIncluded } = opts;
|
|
@@ -1274,7 +1313,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1274
1313
|
});
|
|
1275
1314
|
try {
|
|
1276
1315
|
// Wait until we have enough txs to build the block
|
|
1277
|
-
const {
|
|
1316
|
+
const { canStartBuilding, minTxs } = await this.waitForMinTxs(opts);
|
|
1278
1317
|
if (!canStartBuilding) {
|
|
1279
1318
|
this.logCheckpointEvent('block-build-failed', `Block build failed for slot ${this.targetSlot}`, {
|
|
1280
1319
|
reason: 'insufficient_txs',
|
|
@@ -1282,21 +1321,18 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1282
1321
|
slot: this.targetSlot,
|
|
1283
1322
|
checkpointNumber: this.checkpointNumber,
|
|
1284
1323
|
indexWithinCheckpoint,
|
|
1285
|
-
availableTxs,
|
|
1286
1324
|
minTxs
|
|
1287
1325
|
});
|
|
1288
|
-
this.log.
|
|
1326
|
+
this.log.verbose(`Not enough age-eligible txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot} (needs ${minTxs} eligible)`, {
|
|
1289
1327
|
reason: 'insufficient_txs',
|
|
1290
1328
|
blockNumber,
|
|
1291
1329
|
slot: this.targetSlot,
|
|
1292
1330
|
checkpointNumber: this.checkpointNumber,
|
|
1293
1331
|
indexWithinCheckpoint,
|
|
1294
|
-
availableTxs,
|
|
1295
1332
|
minTxs
|
|
1296
1333
|
});
|
|
1297
1334
|
this.eventEmitter.emit('block-tx-count-check-failed', {
|
|
1298
1335
|
minTxs,
|
|
1299
|
-
availableTxs,
|
|
1300
1336
|
slot: this.targetSlot
|
|
1301
1337
|
});
|
|
1302
1338
|
this.metrics.recordBlockProposalFailed('insufficient_txs');
|
|
@@ -1306,13 +1342,17 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1306
1342
|
}
|
|
1307
1343
|
// Create iterator to pending txs. We filter out txs already included in previous blocks in the checkpoint
|
|
1308
1344
|
// just in case p2p failed to sync the provisional block and didn't get to remove those txs from the mempool yet.
|
|
1309
|
-
|
|
1310
|
-
|
|
1345
|
+
// Block building only executes txs, so we skip loading their proofs unless these same tx objects get attached
|
|
1346
|
+
// to the broadcasted proposals via publishTxsWithProposals.
|
|
1347
|
+
const pendingTxs = filter(this.p2pClient.iterateEligiblePendingTxs({
|
|
1348
|
+
includeProof: !!this.config.publishTxsWithProposals
|
|
1349
|
+
}), (tx)=>!txHashesAlreadyIncluded.has(tx.txHash.toString()));
|
|
1350
|
+
this.log.debug(`Building block ${blockNumber} at index ${indexWithinCheckpoint} for slot ${this.targetSlot}`, {
|
|
1311
1351
|
slot: this.targetSlot,
|
|
1312
1352
|
blockNumber,
|
|
1313
1353
|
indexWithinCheckpoint
|
|
1314
1354
|
});
|
|
1315
|
-
this.
|
|
1355
|
+
this.setState(SequencerState.CREATING_BLOCK);
|
|
1316
1356
|
// Per-block limits are operator overrides (from SEQ_MAX_L2_BLOCK_GAS etc.) further capped
|
|
1317
1357
|
// by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
|
|
1318
1358
|
// minValidTxs is passed into the builder so it can reject the block *before* updating state.
|
|
@@ -1323,8 +1363,9 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1323
1363
|
deadline: buildDeadline,
|
|
1324
1364
|
isBuildingProposal: true,
|
|
1325
1365
|
minValidTxs,
|
|
1326
|
-
maxBlocksPerCheckpoint: this.timetable.
|
|
1327
|
-
perBlockAllocationMultiplier: this.config.perBlockAllocationMultiplier
|
|
1366
|
+
maxBlocksPerCheckpoint: this.timetable.getMaxBlocksPerCheckpoint(),
|
|
1367
|
+
perBlockAllocationMultiplier: this.config.perBlockAllocationMultiplier,
|
|
1368
|
+
perBlockDAAllocationMultiplier: this.config.perBlockDAAllocationMultiplier
|
|
1328
1369
|
};
|
|
1329
1370
|
// Actually build the block by executing txs. The builder throws InsufficientValidTxsError
|
|
1330
1371
|
// if the number of successfully processed txs is below minValidTxs, ensuring state is not
|
|
@@ -1385,7 +1426,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1385
1426
|
checkpointNumber: this.checkpointNumber,
|
|
1386
1427
|
indexWithinCheckpoint: block.indexWithinCheckpoint,
|
|
1387
1428
|
slot: this.targetSlot,
|
|
1388
|
-
buildSlot: this.
|
|
1429
|
+
buildSlot: this.getBuildSlot()
|
|
1389
1430
|
});
|
|
1390
1431
|
this.metrics.recordBuiltBlock(blockBuildDuration, block.header.totalManaUsed.toNumberUnsafe(), this.targetSlot);
|
|
1391
1432
|
return {
|
|
@@ -1441,38 +1482,37 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1441
1482
|
const { indexWithinCheckpoint, blockNumber, buildDeadline, forceCreate } = opts;
|
|
1442
1483
|
// We only allow a block with 0 txs in the first block of the checkpoint
|
|
1443
1484
|
const minTxs = indexWithinCheckpoint > 0 && this.config.minTxsPerBlock === 0 ? 1 : this.config.minTxsPerBlock;
|
|
1444
|
-
//
|
|
1445
|
-
const startBuildingDeadline = buildDeadline ? new Date(buildDeadline.getTime() - this.timetable.
|
|
1446
|
-
|
|
1447
|
-
|
|
1485
|
+
// Latest time to keep waiting for txs: wait_for_txs_deadline = block_build_deadline(k) - min_block_duration.
|
|
1486
|
+
const startBuildingDeadline = buildDeadline ? new Date(buildDeadline.getTime() - this.timetable.minBlockDuration * 1000) : undefined;
|
|
1487
|
+
// Gate on age-eligible txs so we don't start building on txs the builder would then filter out for being
|
|
1488
|
+
// too fresh. hasEligiblePendingTxs early-exits once minTxs are eligible instead of counting the whole pool.
|
|
1489
|
+
while(!forceCreate && !await this.p2pClient.hasEligiblePendingTxs(minTxs)){
|
|
1448
1490
|
// If we're past deadline, or we have no deadline, give up
|
|
1449
1491
|
const now = this.dateProvider.nowAsDate();
|
|
1450
1492
|
if (startBuildingDeadline === undefined || now >= startBuildingDeadline) {
|
|
1451
1493
|
return {
|
|
1452
1494
|
canStartBuilding: false,
|
|
1453
|
-
availableTxs,
|
|
1454
1495
|
minTxs
|
|
1455
1496
|
};
|
|
1456
1497
|
}
|
|
1457
1498
|
// Wait a bit before checking again
|
|
1458
|
-
this.
|
|
1459
|
-
this.log.verbose(`Waiting for
|
|
1499
|
+
this.setState(SequencerState.WAITING_FOR_TXS);
|
|
1500
|
+
this.log.verbose(`Waiting for ${minTxs} age-eligible txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot}`, {
|
|
1460
1501
|
blockNumber,
|
|
1461
1502
|
slot: this.targetSlot,
|
|
1462
|
-
indexWithinCheckpoint
|
|
1503
|
+
indexWithinCheckpoint,
|
|
1504
|
+
minTxs
|
|
1463
1505
|
});
|
|
1464
1506
|
await this.waitForTxsPollingInterval();
|
|
1465
|
-
availableTxs = await this.p2pClient.getPendingTxCount();
|
|
1466
1507
|
}
|
|
1467
1508
|
return {
|
|
1468
1509
|
canStartBuilding: true,
|
|
1469
|
-
availableTxs,
|
|
1470
1510
|
minTxs
|
|
1471
1511
|
};
|
|
1472
1512
|
}
|
|
1473
1513
|
async getSignedCommitteeAttestations(broadcast) {
|
|
1474
1514
|
const { proposal, blockProposedAt } = broadcast;
|
|
1475
|
-
this.
|
|
1515
|
+
this.setState(SequencerState.COLLECTING_ATTESTATIONS);
|
|
1476
1516
|
const attestations = await this.waitForAttestations(proposal);
|
|
1477
1517
|
if (!attestations) {
|
|
1478
1518
|
return undefined;
|
|
@@ -1523,9 +1563,10 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1523
1563
|
});
|
|
1524
1564
|
return new CommitteeAttestationsAndSigners(orderAttestations(attestations ?? [], committee), this.getSignatureContext());
|
|
1525
1565
|
}
|
|
1526
|
-
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1566
|
+
// Hard attestation-collection cutoff = the single consensus attestation_deadline (target_slot_start + S - 2E).
|
|
1567
|
+
const attestationDeadlineSeconds = this.timetable.getAttestationDeadline(this.targetSlot);
|
|
1568
|
+
const attestationDeadline = new Date(attestationDeadlineSeconds * 1000);
|
|
1569
|
+
this.metrics.recordRequiredAttestations(numberOfRequiredAttestations, Math.max(0, attestationDeadline.getTime() - this.dateProvider.now()));
|
|
1529
1570
|
const collectAttestationsTimer = new Timer();
|
|
1530
1571
|
let collectedAttestationsCount = 0;
|
|
1531
1572
|
try {
|
|
@@ -1543,7 +1584,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1543
1584
|
submittedCount: trimmed.length
|
|
1544
1585
|
});
|
|
1545
1586
|
// Manipulate the attestations if we've been configured to do so
|
|
1546
|
-
if (this.config.injectFakeAttestation || this.config.injectHighSValueAttestation || this.config.injectUnrecoverableSignatureAttestation || this.config.shuffleAttestationOrdering) {
|
|
1587
|
+
if (this.config.injectFakeAttestation || this.config.injectHighSValueAttestation || this.config.injectUnrecoverableSignatureAttestation || this.config.injectYParityAttestation || this.config.shuffleAttestationOrdering) {
|
|
1547
1588
|
return this.manipulateAttestations(proposal.slotNumber, epoch, seed, committee, sorted);
|
|
1548
1589
|
}
|
|
1549
1590
|
return new CommitteeAttestationsAndSigners(sorted, this.getSignatureContext());
|
|
@@ -1615,6 +1656,14 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1615
1656
|
}
|
|
1616
1657
|
return new CommitteeAttestationsAndSigners(attestations, this.getSignatureContext());
|
|
1617
1658
|
}
|
|
1659
|
+
if (this.config.injectYParityAttestation) {
|
|
1660
|
+
// Force every non-proposer signed slot's recovery byte to yParity (v ∈ {0, 1}) form in the packed L1
|
|
1661
|
+
// tuple, after packAttestations has canonicalized it. The proposer's own slot is left canonical so
|
|
1662
|
+
// propose() still passes verifyProposer. Models a malicious proposer landing a checkpoint L1 accepts
|
|
1663
|
+
// but that can never be proven (ECDSA.recover rejects v ∉ {27, 28}).
|
|
1664
|
+
this.log.warn(`Injecting yParity attestations in checkpoint for slot ${slotNumber} (proposer #${proposerIndex})`);
|
|
1665
|
+
return new MaliciousYParityCommitteeAttestationsAndSigners(attestations, proposerIndex, this.getSignatureContext());
|
|
1666
|
+
}
|
|
1618
1667
|
if (this.config.shuffleAttestationOrdering) {
|
|
1619
1668
|
this.log.warn(`Shuffling attestation ordering in checkpoint for slot ${slotNumber} (proposer #${proposerIndex})`);
|
|
1620
1669
|
const shuffled = [
|
|
@@ -1650,7 +1699,15 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1650
1699
|
}
|
|
1651
1700
|
const failedTxData = failedTxs.map((fail)=>fail.tx);
|
|
1652
1701
|
const failedTxHashes = failedTxData.map((tx)=>tx.getTxHash());
|
|
1653
|
-
|
|
1702
|
+
const failures = failedTxs.map((fail)=>({
|
|
1703
|
+
txHash: fail.tx.getTxHash().toString(),
|
|
1704
|
+
reason: fail.error.message
|
|
1705
|
+
}));
|
|
1706
|
+
this.log.warn(`Dropping ${failedTxs.length} txs from mempool due to failures during block building for slot ${this.targetSlot}`, {
|
|
1707
|
+
slot: this.targetSlot,
|
|
1708
|
+
checkpointNumber: this.checkpointNumber,
|
|
1709
|
+
failures
|
|
1710
|
+
});
|
|
1654
1711
|
await this.p2pClient.handleFailedExecution(failedTxHashes);
|
|
1655
1712
|
}
|
|
1656
1713
|
/**
|
|
@@ -1680,8 +1737,7 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1680
1737
|
* tip advances locally. Gossip doesn't echo our own messages back, so without this the proposer
|
|
1681
1738
|
* would never see its own proposed checkpoint and couldn't pipeline the next slot.
|
|
1682
1739
|
*
|
|
1683
|
-
*
|
|
1684
|
-
* skipped whenever proposed blocks aren't pushed (`skipPushProposedBlocksToArchiver`, fisherman
|
|
1740
|
+
* Skipped whenever proposed blocks aren't pushed (`skipPushProposedBlocksToArchiver`, fisherman
|
|
1685
1741
|
* mode): the archiver derives the checkpoint archive from its stored blocks, so without them the
|
|
1686
1742
|
* push would fail. All blocks were already added (and awaited) during block building, so this
|
|
1687
1743
|
* needs no retry — they are guaranteed present by the time we get here.
|
|
@@ -1689,9 +1745,6 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1689
1745
|
if (this.config.skipPushProposedBlocksToArchiver || this.config.fishermanMode) {
|
|
1690
1746
|
return;
|
|
1691
1747
|
}
|
|
1692
|
-
if (!this.epochCache.isProposerPipeliningEnabled()) {
|
|
1693
|
-
return;
|
|
1694
|
-
}
|
|
1695
1748
|
const startBlock = BlockNumber(this.syncedToBlockNumber + 1);
|
|
1696
1749
|
this.log.debug(`Syncing proposed checkpoint ${this.checkpointNumber} to archiver`, {
|
|
1697
1750
|
checkpointNumber: this.checkpointNumber,
|
|
@@ -1747,20 +1800,8 @@ _dec = trackSpan('CheckpointProposalJob.execute'), _dec1 = trackSpan('Checkpoint
|
|
|
1747
1800
|
}
|
|
1748
1801
|
return false;
|
|
1749
1802
|
}
|
|
1750
|
-
/** Waits until a specific time within the current slot */ async waitUntilTimeInSlot(targetSecondsIntoSlot) {
|
|
1751
|
-
const slotStartTimestamp = this.getSlotStartBuildTimestamp();
|
|
1752
|
-
const targetTimestamp = slotStartTimestamp + targetSecondsIntoSlot;
|
|
1753
|
-
await sleepUntil(new Date(targetTimestamp * 1000), this.dateProvider.nowAsDate());
|
|
1754
|
-
}
|
|
1755
1803
|
/** Waits the polling interval for transactions. Extracted for test overriding. */ async waitForTxsPollingInterval() {
|
|
1756
|
-
await
|
|
1757
|
-
}
|
|
1758
|
-
getSlotStartBuildTimestamp() {
|
|
1759
|
-
return getSlotStartBuildTimestamp(this.slotNow, this.l1Constants);
|
|
1760
|
-
}
|
|
1761
|
-
getSecondsIntoSlot() {
|
|
1762
|
-
const slotStartTimestamp = this.getSlotStartBuildTimestamp();
|
|
1763
|
-
return Number((this.dateProvider.now() / 1000 - slotStartTimestamp).toFixed(3));
|
|
1804
|
+
await this.awaitInterruptibleSleep(TXS_POLLING_MS);
|
|
1764
1805
|
}
|
|
1765
1806
|
getPublisher() {
|
|
1766
1807
|
return this.publisher;
|