@lodestar/fork-choice 1.51.0-dev.83da04fef3 → 1.51.0-dev.8d5cdd33be
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/lib/forkChoice/errors.d.ts +6 -1
- package/lib/forkChoice/errors.d.ts.map +1 -1
- package/lib/forkChoice/errors.js +1 -0
- package/lib/forkChoice/errors.js.map +1 -1
- package/lib/forkChoice/fastConfirmation/utils.d.ts.map +1 -1
- package/lib/forkChoice/fastConfirmation/utils.js +19 -24
- package/lib/forkChoice/fastConfirmation/utils.js.map +1 -1
- package/lib/forkChoice/forkChoice.d.ts +24 -19
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +97 -39
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +18 -4
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/metrics.d.ts +4 -0
- package/lib/metrics.d.ts.map +1 -1
- package/lib/metrics.js +9 -0
- package/lib/metrics.js.map +1 -1
- package/lib/protoArray/interface.d.ts +2 -0
- package/lib/protoArray/interface.d.ts.map +1 -1
- package/lib/protoArray/interface.js.map +1 -1
- package/lib/protoArray/protoArray.d.ts +35 -4
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +142 -51
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +8 -8
- package/src/forkChoice/errors.ts +3 -1
- package/src/forkChoice/fastConfirmation/utils.ts +21 -20
- package/src/forkChoice/forkChoice.ts +107 -41
- package/src/forkChoice/interface.ts +17 -5
- package/src/metrics.ts +9 -0
- package/src/protoArray/interface.ts +2 -0
- package/src/protoArray/protoArray.ts +164 -56
|
@@ -86,8 +86,8 @@ export enum UpdateHeadOpt {
|
|
|
86
86
|
|
|
87
87
|
export type UpdateAndGetHeadOpt =
|
|
88
88
|
| {mode: UpdateHeadOpt.GetCanonicalHead}
|
|
89
|
-
| {mode: UpdateHeadOpt.GetProposerHead;
|
|
90
|
-
| {mode: UpdateHeadOpt.GetPredictedProposerHead;
|
|
89
|
+
| {mode: UpdateHeadOpt.GetProposerHead; msFromSlot: number; slot: Slot}
|
|
90
|
+
| {mode: UpdateHeadOpt.GetPredictedProposerHead; msFromSlot: number; slot: Slot};
|
|
91
91
|
|
|
92
92
|
// the initial vote epoch for all validators
|
|
93
93
|
const INIT_VOTE_SLOT: Slot = 0;
|
|
@@ -315,13 +315,13 @@ export class ForkChoice implements IForkChoice {
|
|
|
315
315
|
const canonicalHeadBlock = mode === UpdateHeadOpt.GetPredictedProposerHead ? this.getHead() : this.updateHead();
|
|
316
316
|
switch (mode) {
|
|
317
317
|
case UpdateHeadOpt.GetPredictedProposerHead:
|
|
318
|
-
return {head: this.predictProposerHead(canonicalHeadBlock, opt.
|
|
318
|
+
return {head: this.predictProposerHead(canonicalHeadBlock, opt.msFromSlot, opt.slot)};
|
|
319
319
|
case UpdateHeadOpt.GetProposerHead: {
|
|
320
320
|
const {
|
|
321
321
|
proposerHead: head,
|
|
322
322
|
isHeadTimely,
|
|
323
323
|
notReorgedReason,
|
|
324
|
-
} = this.getProposerHead(canonicalHeadBlock, opt.
|
|
324
|
+
} = this.getProposerHead(canonicalHeadBlock, opt.msFromSlot, opt.slot);
|
|
325
325
|
return {head, isHeadTimely, notReorgedReason};
|
|
326
326
|
}
|
|
327
327
|
case UpdateHeadOpt.GetCanonicalHead:
|
|
@@ -338,7 +338,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
338
338
|
// Note when proposer boost reorg is disabled, it always returns false
|
|
339
339
|
shouldOverrideForkChoiceUpdate(
|
|
340
340
|
headBlock: ProtoBlock,
|
|
341
|
-
|
|
341
|
+
msFromSlot: number,
|
|
342
342
|
currentSlot: Slot
|
|
343
343
|
): ShouldOverrideForkChoiceUpdateResult {
|
|
344
344
|
if (headBlock === null) {
|
|
@@ -369,7 +369,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
369
369
|
|
|
370
370
|
const currentTimeOk =
|
|
371
371
|
headBlock.slot === currentSlot ||
|
|
372
|
-
(proposalSlot === currentSlot && this.isProposingOnTime(
|
|
372
|
+
(proposalSlot === currentSlot && this.isProposingOnTime(msFromSlot, currentSlot));
|
|
373
373
|
|
|
374
374
|
// Mirror the proposer equivocation branch of getProposerHead(). The head slot's attestations are
|
|
375
375
|
// still queued at this point so the head is assumed weak, same as for the regular branch below.
|
|
@@ -422,6 +422,24 @@ export class ForkChoice implements IForkChoice {
|
|
|
422
422
|
return this.protoArray.shouldExtendPayload(blockRoot, this.proposerBoostRoot);
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
+
/** Inclusion list verdict for the payload of `blockRoot`, as reported by the execution engine */
|
|
426
|
+
recordPayloadInclusionListSatisfaction(blockRoot: RootHex, satisfied: boolean): boolean {
|
|
427
|
+
const changed = this.protoArray.recordPayloadInclusionListSatisfaction(blockRoot, satisfied);
|
|
428
|
+
if (changed && !satisfied) {
|
|
429
|
+
this.metrics?.forkChoice.unsatisfiedInclusionListBlocks.inc();
|
|
430
|
+
this.logger?.verbose("Payload does not satisfy the inclusion list constraints, not extending it", {blockRoot});
|
|
431
|
+
}
|
|
432
|
+
return changed;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
isPayloadInclusionListSatisfied(blockRoot: RootHex): boolean {
|
|
436
|
+
return this.protoArray.isPayloadInclusionListSatisfied(blockRoot);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
getPayloadInclusionListSatisfaction(blockRoot: RootHex): boolean | null {
|
|
440
|
+
return this.protoArray.getPayloadInclusionListSatisfaction(blockRoot);
|
|
441
|
+
}
|
|
442
|
+
|
|
425
443
|
/** Spec: should_build_on_full(store, head, slot) */
|
|
426
444
|
shouldBuildOnFull(head: ProtoBlock, slot: Slot): boolean {
|
|
427
445
|
return this.protoArray.shouldBuildOnFull(head, slot);
|
|
@@ -437,7 +455,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
437
455
|
* By calling this function, we assume we are the proposer of next slot
|
|
438
456
|
*
|
|
439
457
|
*/
|
|
440
|
-
predictProposerHead(headBlock: ProtoBlock,
|
|
458
|
+
predictProposerHead(headBlock: ProtoBlock, msFromSlot: number, currentSlot: Slot): ProtoBlock {
|
|
441
459
|
const {proposerBoost, proposerBoostReorg} = this.opts ?? {};
|
|
442
460
|
// Skip re-org attempt if proposer boost (reorg) are disabled
|
|
443
461
|
if (!proposerBoost || !proposerBoostReorg) {
|
|
@@ -450,7 +468,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
450
468
|
}
|
|
451
469
|
|
|
452
470
|
const blockRoot = headBlock.blockRoot;
|
|
453
|
-
const result = this.shouldOverrideForkChoiceUpdate(headBlock,
|
|
471
|
+
const result = this.shouldOverrideForkChoiceUpdate(headBlock, msFromSlot, currentSlot);
|
|
454
472
|
|
|
455
473
|
if (result.shouldOverrideFcu) {
|
|
456
474
|
this.logger?.verbose("Current head is weak. Predicting next block to be built on parent of head.", {
|
|
@@ -481,7 +499,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
481
499
|
*/
|
|
482
500
|
getProposerHead(
|
|
483
501
|
headBlock: ProtoBlock,
|
|
484
|
-
|
|
502
|
+
msFromSlot: number,
|
|
485
503
|
slot: Slot
|
|
486
504
|
): {proposerHead: ProtoBlock; isHeadTimely: boolean; notReorgedReason?: NotReorgedReason} {
|
|
487
505
|
const isHeadTimely = headBlock.timeliness;
|
|
@@ -538,7 +556,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
538
556
|
}
|
|
539
557
|
|
|
540
558
|
// Only re-org if we are proposing on-time
|
|
541
|
-
if (!this.isProposingOnTime(
|
|
559
|
+
if (!this.isProposingOnTime(msFromSlot, slot)) {
|
|
542
560
|
return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.NotProposingOnTime};
|
|
543
561
|
}
|
|
544
562
|
|
|
@@ -675,7 +693,20 @@ export class ForkChoice implements IForkChoice {
|
|
|
675
693
|
}
|
|
676
694
|
|
|
677
695
|
// findHead returns the ProtoNode representing the head
|
|
678
|
-
|
|
696
|
+
let head: ProtoNode;
|
|
697
|
+
try {
|
|
698
|
+
head = this.protoArray.findHead(this.fcStore.justified.checkpoint.rootHex, currentSlot);
|
|
699
|
+
} catch (e) {
|
|
700
|
+
// A poisoned tree never recovers, shut the node down instead of failing every head computation
|
|
701
|
+
if (
|
|
702
|
+
e instanceof ProtoArrayError &&
|
|
703
|
+
(e.type.code === ProtoArrayErrorCode.INVALID_LVH_EXECUTION_RESPONSE ||
|
|
704
|
+
e.type.code === ProtoArrayErrorCode.INVALID_JUSTIFIED_EXECUTION_STATUS)
|
|
705
|
+
) {
|
|
706
|
+
this.irrecoverableError = e;
|
|
707
|
+
}
|
|
708
|
+
throw e;
|
|
709
|
+
}
|
|
679
710
|
|
|
680
711
|
this.head = head;
|
|
681
712
|
return this.head;
|
|
@@ -735,12 +766,15 @@ export class ForkChoice implements IForkChoice {
|
|
|
735
766
|
*
|
|
736
767
|
* `justifiedBalances` balances of justified state which is updated synchronously.
|
|
737
768
|
* This ensures that the forkchoice is never out of sync.
|
|
769
|
+
*
|
|
770
|
+
* A block whose parent payload chain was invalidated is rejected. `executionStatus` is ignored for a gloas
|
|
771
|
+
* block: it has no payload of its own and inherits the status of the parent variant it builds on.
|
|
738
772
|
*/
|
|
739
773
|
onBlock(
|
|
740
774
|
block: BeaconBlock,
|
|
741
775
|
state: IBeaconStateView,
|
|
742
|
-
|
|
743
|
-
|
|
776
|
+
receiveDelayMs: number,
|
|
777
|
+
importDelayMs: number,
|
|
744
778
|
currentSlot: Slot,
|
|
745
779
|
executionStatus: BlockExecutionStatus,
|
|
746
780
|
dataAvailabilityStatus: DataAvailabilityStatus
|
|
@@ -763,6 +797,19 @@ export class ForkChoice implements IForkChoice {
|
|
|
763
797
|
});
|
|
764
798
|
}
|
|
765
799
|
|
|
800
|
+
// An execution engine verdict can invalidate the parent while this block is being verified. Inserting the
|
|
801
|
+
// block would put a viable node below an invalidated one, or poison the tree if this block is VALID
|
|
802
|
+
if (parentBlock.executionStatus === ExecutionStatus.Invalid) {
|
|
803
|
+
throw new ForkChoiceError({
|
|
804
|
+
code: ForkChoiceErrorCode.INVALID_BLOCK,
|
|
805
|
+
err: {
|
|
806
|
+
code: InvalidBlockCode.PARENT_EXECUTION_INVALID,
|
|
807
|
+
root: parentRootHex,
|
|
808
|
+
hash: parentHashHex,
|
|
809
|
+
},
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
766
813
|
// Blocks cannot be in the future. If they are, their consideration must be delayed until
|
|
767
814
|
// the are in the past.
|
|
768
815
|
//
|
|
@@ -812,7 +859,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
812
859
|
// Decide whether this block should receive proposer boost, and whether the block is timely.
|
|
813
860
|
// The store field `this.proposerBoostRoot` and `updateCheckpoints()` are mutated only after
|
|
814
861
|
// `protoArray.onBlock()` succeeds
|
|
815
|
-
const isTimely = this.isBlockReceivedTimely(block,
|
|
862
|
+
const isTimely = this.isBlockReceivedTimely(block, receiveDelayMs);
|
|
816
863
|
const isProposerBoostBlock =
|
|
817
864
|
this.opts?.proposerBoost === true &&
|
|
818
865
|
isTimely &&
|
|
@@ -831,14 +878,17 @@ export class ForkChoice implements IForkChoice {
|
|
|
831
878
|
const blockEpoch = computeEpochAtSlot(slot);
|
|
832
879
|
|
|
833
880
|
// same logic to compute_pulled_up_tip in the spec, making it inline because of reusing variables
|
|
834
|
-
// If the parent checkpoints are already at the same epoch as the
|
|
835
|
-
// it's impossible for the unrealized checkpoints to differ from the parent's.
|
|
836
|
-
// holds true because:
|
|
881
|
+
// If the block has no slashings and the parent checkpoints are already at the same epoch as the
|
|
882
|
+
// block being imported, it's impossible for the unrealized checkpoints to differ from the parent's.
|
|
883
|
+
// This holds true because:
|
|
837
884
|
//
|
|
838
885
|
// 1. A child block cannot have lower FFG checkpoints than its parent.
|
|
839
886
|
// 2. A block in epoch `N` cannot contain attestations which would justify an epoch higher than `N`.
|
|
840
887
|
// 3. A block in epoch `N` cannot contain attestations which would finalize an epoch higher than `N - 1`.
|
|
841
888
|
//
|
|
889
|
+
// Slashings are excluded because they can reduce the unslashed target balance of the child state
|
|
890
|
+
// and therefore lower its unrealized checkpoints.
|
|
891
|
+
//
|
|
842
892
|
// This is an optimization. It should reduce the amount of times we run
|
|
843
893
|
// `process_justification_and_finalization` by approximately 1/3rd when the chain is
|
|
844
894
|
// performing optimally.
|
|
@@ -846,6 +896,8 @@ export class ForkChoice implements IForkChoice {
|
|
|
846
896
|
let unrealizedFinalizedCheckpoint: CheckpointWithHex;
|
|
847
897
|
if (this.opts?.computeUnrealized) {
|
|
848
898
|
if (
|
|
899
|
+
block.body.proposerSlashings.length === 0 &&
|
|
900
|
+
block.body.attesterSlashings.length === 0 &&
|
|
849
901
|
parentBlock.unrealizedJustifiedEpoch === blockEpoch &&
|
|
850
902
|
parentBlock.unrealizedFinalizedEpoch + 1 >= blockEpoch
|
|
851
903
|
) {
|
|
@@ -883,8 +935,8 @@ export class ForkChoice implements IForkChoice {
|
|
|
883
935
|
targetRoot: toRootHex(targetRoot),
|
|
884
936
|
stateRoot: toRootHex(block.stateRoot),
|
|
885
937
|
timeliness: isTimely,
|
|
886
|
-
ptcTimeliness: this.isBlockPtcTimely(block,
|
|
887
|
-
importedTimely: this.isBlockImportedTimely(block,
|
|
938
|
+
ptcTimeliness: this.isBlockPtcTimely(block, receiveDelayMs),
|
|
939
|
+
importedTimely: this.isBlockImportedTimely(block, importDelayMs),
|
|
888
940
|
proposerIndex: block.proposerIndex,
|
|
889
941
|
|
|
890
942
|
justifiedEpoch: stateJustifiedEpoch,
|
|
@@ -919,7 +971,9 @@ export class ForkChoice implements IForkChoice {
|
|
|
919
971
|
executionPayloadBlockHash: parentBlockHashFromBid,
|
|
920
972
|
executionPayloadNumber: parentMeta.number,
|
|
921
973
|
executionPayloadGasLimit: parentMeta.gasLimit,
|
|
922
|
-
|
|
974
|
+
// The parent's default (PENDING) variant does not reflect a SYNCING parent payload, inheriting
|
|
975
|
+
// from the variant matching the bid's parent block hash keeps that payload from being marked VALID
|
|
976
|
+
executionStatus: this.getPostMergeExecStatus(parentBlock.executionStatus),
|
|
923
977
|
dataAvailabilityStatus,
|
|
924
978
|
};
|
|
925
979
|
})()
|
|
@@ -980,14 +1034,8 @@ export class ForkChoice implements IForkChoice {
|
|
|
980
1034
|
*
|
|
981
1035
|
* https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#on_attestation
|
|
982
1036
|
*
|
|
983
|
-
*
|
|
984
|
-
*
|
|
985
|
-
* too expensive to call again.
|
|
986
|
-
*
|
|
987
|
-
* ## Notes:
|
|
988
|
-
*
|
|
989
|
-
* The supplied `attestation` **must** pass the `in_valid_indexed_attestation` function as it
|
|
990
|
-
* will not be run here.
|
|
1037
|
+
* Does not run `is_valid_indexed_attestation`. The caller must ensure all of its
|
|
1038
|
+
* validity requirements except index ordering: `attestingIndices` may be unsorted.
|
|
991
1039
|
*/
|
|
992
1040
|
onAttestation(attestation: IndexedAttestation, attDataRoot: string, forceImport?: boolean): void {
|
|
993
1041
|
// Ignore any attestations to the zero hash.
|
|
@@ -1126,6 +1174,19 @@ export class ForkChoice implements IForkChoice {
|
|
|
1126
1174
|
executionStatus: PayloadExecutionStatus,
|
|
1127
1175
|
dataAvailabilityStatus: DataAvailabilityStatus
|
|
1128
1176
|
): void {
|
|
1177
|
+
// Same guard as onBlock: the PENDING variant carries the status of the payload chain this block builds on
|
|
1178
|
+
const pendingBlock = this.getBlockHexDefaultStatus(blockRoot);
|
|
1179
|
+
if (pendingBlock?.executionStatus === ExecutionStatus.Invalid) {
|
|
1180
|
+
throw new ForkChoiceError({
|
|
1181
|
+
code: ForkChoiceErrorCode.INVALID_BLOCK,
|
|
1182
|
+
err: {
|
|
1183
|
+
code: InvalidBlockCode.PARENT_EXECUTION_INVALID,
|
|
1184
|
+
root: pendingBlock.parentRoot,
|
|
1185
|
+
hash: pendingBlock.executionPayloadBlockHash,
|
|
1186
|
+
},
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1129
1190
|
this.protoArray.onExecutionPayload(
|
|
1130
1191
|
blockRoot,
|
|
1131
1192
|
this.fcStore.currentSlot,
|
|
@@ -1414,7 +1475,9 @@ export class ForkChoice implements IForkChoice {
|
|
|
1414
1475
|
*
|
|
1415
1476
|
* `ancestors` is the raw walk and includes the previous finalized block as its last element —
|
|
1416
1477
|
* callers that don't want the boundary should slice it off themselves.
|
|
1417
|
-
*
|
|
1478
|
+
* For Gloas parent blocks, `ancestors` contains the selected EMPTY or FULL variant.
|
|
1479
|
+
* The other variant, if present in the traversed range, goes into `nonAncestors` with the same root.
|
|
1480
|
+
* Exclude roots present in `ancestors` before pruning by block root.
|
|
1418
1481
|
*/
|
|
1419
1482
|
getAllAncestorAndNonAncestorBlocks(
|
|
1420
1483
|
blockRoot: RootHex,
|
|
@@ -1522,7 +1585,9 @@ export class ForkChoice implements IForkChoice {
|
|
|
1522
1585
|
|
|
1523
1586
|
/** Very expensive function, iterates the entire ProtoArray. TODO: Is this function even necessary? */
|
|
1524
1587
|
getBlockSummariesByParentRoot(parentRoot: RootHex): ProtoBlock[] {
|
|
1525
|
-
return this.protoArray.nodes.filter(
|
|
1588
|
+
return this.protoArray.nodes.filter(
|
|
1589
|
+
(node) => node.parentRoot === parentRoot && (!isGloasBlock(node) || node.payloadStatus === PayloadStatus.PENDING)
|
|
1590
|
+
);
|
|
1526
1591
|
}
|
|
1527
1592
|
|
|
1528
1593
|
/** Very expensive function, iterates the entire ProtoArray. TODO: Is this function even necessary? */
|
|
@@ -1531,7 +1596,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
1531
1596
|
const blocksAtSlot: ProtoBlock[] = [];
|
|
1532
1597
|
for (let i = 0, len = nodes.length; i < len; i++) {
|
|
1533
1598
|
const node = nodes[i];
|
|
1534
|
-
if (node.slot === slot) {
|
|
1599
|
+
if (node.slot === slot && (!isGloasBlock(node) || node.payloadStatus === PayloadStatus.PENDING)) {
|
|
1535
1600
|
blocksAtSlot.push(node);
|
|
1536
1601
|
}
|
|
1537
1602
|
}
|
|
@@ -1568,13 +1633,14 @@ export class ForkChoice implements IForkChoice {
|
|
|
1568
1633
|
* justified's status as well as validate the terminal conditions if terminal block
|
|
1569
1634
|
* becomes valid
|
|
1570
1635
|
*/
|
|
1571
|
-
validateLatestHash(execResponse: LVHExecResponse):
|
|
1636
|
+
validateLatestHash(execResponse: LVHExecResponse, opts?: {strict?: boolean}): boolean {
|
|
1572
1637
|
try {
|
|
1573
|
-
this.protoArray.validateLatestHash(execResponse, this.fcStore.currentSlot);
|
|
1638
|
+
return this.protoArray.validateLatestHash(execResponse, this.fcStore.currentSlot, opts);
|
|
1574
1639
|
} catch (e) {
|
|
1575
1640
|
if (e instanceof ProtoArrayError && e.type.code === ProtoArrayErrorCode.INVALID_LVH_EXECUTION_RESPONSE) {
|
|
1576
1641
|
this.irrecoverableError = e;
|
|
1577
1642
|
}
|
|
1643
|
+
return false;
|
|
1578
1644
|
}
|
|
1579
1645
|
}
|
|
1580
1646
|
|
|
@@ -1757,9 +1823,9 @@ export class ForkChoice implements IForkChoice {
|
|
|
1757
1823
|
* Return true if the block is timely for the current slot.
|
|
1758
1824
|
* Child class can overwrite this for testing purpose.
|
|
1759
1825
|
*/
|
|
1760
|
-
protected isBlockReceivedTimely(block: BeaconBlock,
|
|
1826
|
+
protected isBlockReceivedTimely(block: BeaconBlock, blockDelayMs: number): boolean {
|
|
1761
1827
|
const fork = this.config.getForkName(block.slot);
|
|
1762
|
-
const isBeforeLateBlockCutoff =
|
|
1828
|
+
const isBeforeLateBlockCutoff = blockDelayMs < this.config.getAttestationDueMs(fork);
|
|
1763
1829
|
return this.fcStore.currentSlot === block.slot && isBeforeLateBlockCutoff;
|
|
1764
1830
|
}
|
|
1765
1831
|
|
|
@@ -1773,12 +1839,12 @@ export class ForkChoice implements IForkChoice {
|
|
|
1773
1839
|
*
|
|
1774
1840
|
* Child class can overwrite this for testing purpose.
|
|
1775
1841
|
*/
|
|
1776
|
-
protected isBlockPtcTimely(block: BeaconBlock,
|
|
1842
|
+
protected isBlockPtcTimely(block: BeaconBlock, blockDelayMs: number): boolean {
|
|
1777
1843
|
const ptcThresholdMs = this.config.getSlotComponentDurationMs(
|
|
1778
1844
|
this.config.getForkName(block.slot),
|
|
1779
1845
|
this.config.PAYLOAD_ATTESTATION_DUE_BPS
|
|
1780
1846
|
);
|
|
1781
|
-
return this.fcStore.currentSlot === block.slot &&
|
|
1847
|
+
return this.fcStore.currentSlot === block.slot && blockDelayMs < ptcThresholdMs;
|
|
1782
1848
|
}
|
|
1783
1849
|
|
|
1784
1850
|
/**
|
|
@@ -1786,9 +1852,9 @@ export class ForkChoice implements IForkChoice {
|
|
|
1786
1852
|
* block was imported in a timely manner for its own slot.
|
|
1787
1853
|
* This is not part of the spec, we use this to determine late canonical blocks.
|
|
1788
1854
|
*/
|
|
1789
|
-
protected isBlockImportedTimely(block: BeaconBlock,
|
|
1855
|
+
protected isBlockImportedTimely(block: BeaconBlock, importDelayMs: number): boolean {
|
|
1790
1856
|
const fork = this.config.getForkName(block.slot);
|
|
1791
|
-
const isBeforeLateBlockCutoff =
|
|
1857
|
+
const isBeforeLateBlockCutoff = importDelayMs < this.config.getAttestationDueMs(fork);
|
|
1792
1858
|
return this.fcStore.currentSlot === block.slot && isBeforeLateBlockCutoff;
|
|
1793
1859
|
}
|
|
1794
1860
|
|
|
@@ -1873,10 +1939,10 @@ export class ForkChoice implements IForkChoice {
|
|
|
1873
1939
|
/**
|
|
1874
1940
|
* https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/fork-choice.md#is_proposing_on_time
|
|
1875
1941
|
*/
|
|
1876
|
-
private isProposingOnTime(
|
|
1942
|
+
private isProposingOnTime(msFromSlot: number, slot: Slot): boolean {
|
|
1877
1943
|
const fork = this.config.getForkName(slot);
|
|
1878
1944
|
const proposerReorgCutoff = this.config.getProposerReorgCutoffMs(fork);
|
|
1879
|
-
return
|
|
1945
|
+
return msFromSlot <= proposerReorgCutoff;
|
|
1880
1946
|
}
|
|
1881
1947
|
|
|
1882
1948
|
private getPreMergeExecStatus(executionStatus: BlockExecutionStatus): ExecutionStatus.PreMerge {
|
|
@@ -123,7 +123,7 @@ export interface IForkChoice {
|
|
|
123
123
|
*/
|
|
124
124
|
shouldOverrideForkChoiceUpdate(
|
|
125
125
|
headBlock: ProtoBlock,
|
|
126
|
-
|
|
126
|
+
msFromSlot: number,
|
|
127
127
|
currentSlot: Slot
|
|
128
128
|
): ShouldOverrideForkChoiceUpdateResult;
|
|
129
129
|
/**
|
|
@@ -155,12 +155,15 @@ export interface IForkChoice {
|
|
|
155
155
|
* ## Notes:
|
|
156
156
|
*
|
|
157
157
|
* The supplied block **must** pass the `state_transition` function as it will not be run here.
|
|
158
|
+
*
|
|
159
|
+
* Throws `InvalidBlockCode.PARENT_EXECUTION_INVALID` if the parent payload chain was invalidated.
|
|
160
|
+
* `executionStatus` is ignored for a gloas block, it inherits the status of the parent variant it builds on.
|
|
158
161
|
*/
|
|
159
162
|
onBlock(
|
|
160
163
|
block: BeaconBlock,
|
|
161
164
|
state: IBeaconStateView,
|
|
162
|
-
|
|
163
|
-
|
|
165
|
+
receiveDelayMs: number,
|
|
166
|
+
importDelayMs: number,
|
|
164
167
|
currentSlot: Slot,
|
|
165
168
|
executionStatus: BlockExecutionStatus,
|
|
166
169
|
dataAvailabilityStatus: DataAvailabilityStatus
|
|
@@ -279,6 +282,12 @@ export interface IForkChoice {
|
|
|
279
282
|
shouldExtendPayload(blockRoot: RootHex): boolean;
|
|
280
283
|
/** Spec: should_build_on_full(store, head, slot) */
|
|
281
284
|
shouldBuildOnFull(head: ProtoBlock, slot: Slot): boolean;
|
|
285
|
+
/** Inclusion list verdict for the payload of `blockRoot`, as reported by the execution engine. True if it changed */
|
|
286
|
+
recordPayloadInclusionListSatisfaction(blockRoot: RootHex, satisfied: boolean): boolean;
|
|
287
|
+
/** True once the payload is delivered and not recorded unsatisfied */
|
|
288
|
+
isPayloadInclusionListSatisfied(blockRoot: RootHex): boolean;
|
|
289
|
+
/** Engine verdict recorded for the payload of `blockRoot`, null when none was recorded */
|
|
290
|
+
getPayloadInclusionListSatisfaction(blockRoot: RootHex): boolean | null;
|
|
282
291
|
getFinalizedBlock(): ProtoBlock;
|
|
283
292
|
getJustifiedBlock(): ProtoBlock;
|
|
284
293
|
getFinalizedCheckpointSlot(): Slot;
|
|
@@ -344,14 +353,17 @@ export interface IForkChoice {
|
|
|
344
353
|
* `CheckpointWithHex` / finalized root without a specific payload-status variant in mind.
|
|
345
354
|
*/
|
|
346
355
|
forwardIterateDescendantsDefaultStatus(blockRoot: RootHex): IterableIterator<ProtoBlock>;
|
|
356
|
+
/** One summary per block, its default variant (FULL pre-Gloas, PENDING for Gloas) */
|
|
347
357
|
getBlockSummariesByParentRoot(parentRoot: RootHex): ProtoBlock[];
|
|
358
|
+
/** One summary per block, its default variant (FULL pre-Gloas, PENDING for Gloas) */
|
|
348
359
|
getBlockSummariesAtSlot(slot: Slot): ProtoBlock[];
|
|
349
360
|
/** Returns the distance of common ancestor of nodes to the max of the newNode and the prevNode. */
|
|
350
361
|
getCommonAncestorDepth(prevBlock: ProtoBlock, newBlock: ProtoBlock): AncestorResult;
|
|
351
362
|
/**
|
|
352
|
-
*
|
|
363
|
+
* Apply an execution engine verdict with a latest valid hash, see `ProtoArray.validateLatestHash`.
|
|
364
|
+
* Returns whether any execution status changed. A consensus failure is recorded in `irrecoverableError`.
|
|
353
365
|
*/
|
|
354
|
-
validateLatestHash(execResponse: LVHExecResponse):
|
|
366
|
+
validateLatestHash(execResponse: LVHExecResponse, opts?: {strict?: boolean}): boolean;
|
|
355
367
|
|
|
356
368
|
/**
|
|
357
369
|
* A dependent root is the block root of the last block before the state transition that decided a specific shuffling
|
package/src/metrics.ts
CHANGED
|
@@ -19,6 +19,10 @@ export function getForkChoiceMetrics(register: MetricsRegisterExtra) {
|
|
|
19
19
|
name: "beacon_fork_choice_requests_total",
|
|
20
20
|
help: "Count of occasions where fork choice has tried to find a head",
|
|
21
21
|
}),
|
|
22
|
+
unsatisfiedInclusionListBlocks: register.gauge({
|
|
23
|
+
name: "beacon_fork_choice_unsatisfied_inclusion_list_blocks_total",
|
|
24
|
+
help: "Count of payloads not extended because they did not satisfy the inclusion list constraints",
|
|
25
|
+
}),
|
|
22
26
|
errors: register.gauge<{entrypoint: UpdateHeadOpt}>({
|
|
23
27
|
name: "beacon_fork_choice_errors_total",
|
|
24
28
|
help: "Count of occasions where fork choice has returned an error when trying to find a head",
|
|
@@ -28,6 +32,11 @@ export function getForkChoiceMetrics(register: MetricsRegisterExtra) {
|
|
|
28
32
|
name: "beacon_fork_choice_changed_head_total",
|
|
29
33
|
help: "Count of occasions fork choice has found a new head",
|
|
30
34
|
}),
|
|
35
|
+
executionInvalidations: register.gauge<{source: string}>({
|
|
36
|
+
name: "beacon_fork_choice_execution_invalidations_total",
|
|
37
|
+
help: "Count of execution engine INVALID verdicts applied to fork choice, by verdict source",
|
|
38
|
+
labelNames: ["source"],
|
|
39
|
+
}),
|
|
31
40
|
reorg: register.gauge({
|
|
32
41
|
name: "beacon_fork_choice_reorg_total",
|
|
33
42
|
help: "Count of occasions fork choice has switched to a different chain",
|
|
@@ -55,6 +55,8 @@ export function isGloasBlock(block: ProtoBlock): block is ProtoBlock & {parentBl
|
|
|
55
55
|
export type LVHValidResponse = {
|
|
56
56
|
executionStatus: ExecutionStatus.Valid;
|
|
57
57
|
latestValidExecHash: RootHex;
|
|
58
|
+
/** Resolves the exact node to validate when several nodes carry the same payload hash (gloas variants) */
|
|
59
|
+
blockRoot?: RootHex;
|
|
58
60
|
};
|
|
59
61
|
export type LVHInvalidResponse = {
|
|
60
62
|
executionStatus: ExecutionStatus.Invalid;
|