@lodestar/fork-choice 1.41.0-dev.aeb5a213ee → 1.41.0-dev.bb273175f2
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 +9 -1
- package/lib/forkChoice/errors.d.ts.map +1 -1
- package/lib/forkChoice/errors.js +4 -0
- package/lib/forkChoice/errors.js.map +1 -1
- package/lib/forkChoice/forkChoice.d.ts +74 -18
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +302 -117
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +53 -20
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/forkChoice/interface.js.map +1 -1
- package/lib/forkChoice/store.d.ts +40 -16
- package/lib/forkChoice/store.d.ts.map +1 -1
- package/lib/forkChoice/store.js +22 -4
- package/lib/forkChoice/store.js.map +1 -1
- package/lib/index.d.ts +4 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/protoArray/computeDeltas.d.ts.map +1 -1
- package/lib/protoArray/computeDeltas.js +3 -0
- package/lib/protoArray/computeDeltas.js.map +1 -1
- package/lib/protoArray/errors.d.ts +15 -2
- package/lib/protoArray/errors.d.ts.map +1 -1
- package/lib/protoArray/errors.js +3 -0
- package/lib/protoArray/errors.js.map +1 -1
- package/lib/protoArray/interface.d.ts +33 -3
- package/lib/protoArray/interface.d.ts.map +1 -1
- package/lib/protoArray/interface.js +28 -0
- package/lib/protoArray/interface.js.map +1 -1
- package/lib/protoArray/protoArray.d.ts +217 -22
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +754 -133
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +7 -7
- package/src/forkChoice/errors.ts +7 -2
- package/src/forkChoice/forkChoice.ts +387 -127
- package/src/forkChoice/interface.ts +72 -20
- package/src/forkChoice/store.ts +52 -20
- package/src/index.ts +10 -2
- package/src/protoArray/computeDeltas.ts +6 -0
- package/src/protoArray/errors.ts +7 -1
- package/src/protoArray/interface.ts +48 -3
- package/src/protoArray/protoArray.ts +886 -134
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
-
import {SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
|
|
2
|
+
import {ForkSeq, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
|
|
3
3
|
import {
|
|
4
4
|
CachedBeaconStateAllForks,
|
|
5
|
+
CachedBeaconStateGloas,
|
|
5
6
|
DataAvailabilityStatus,
|
|
6
7
|
EffectiveBalanceIncrements,
|
|
7
8
|
ZERO_HASH,
|
|
@@ -37,9 +38,11 @@ import {
|
|
|
37
38
|
LVHExecResponse,
|
|
38
39
|
MaybeValidExecutionStatus,
|
|
39
40
|
NULL_VOTE_INDEX,
|
|
41
|
+
PayloadStatus,
|
|
40
42
|
ProtoBlock,
|
|
41
43
|
ProtoNode,
|
|
42
44
|
VoteIndex,
|
|
45
|
+
isGloasBlock,
|
|
43
46
|
} from "../protoArray/interface.js";
|
|
44
47
|
import {ProtoArray} from "../protoArray/protoArray.js";
|
|
45
48
|
import {ForkChoiceError, ForkChoiceErrorCode, InvalidAttestationCode, InvalidBlockCode} from "./errors.js";
|
|
@@ -51,7 +54,7 @@ import {
|
|
|
51
54
|
NotReorgedReason,
|
|
52
55
|
ShouldOverrideForkChoiceUpdateResult,
|
|
53
56
|
} from "./interface.js";
|
|
54
|
-
import {
|
|
57
|
+
import {CheckpointWithPayloadStatus, IForkChoiceStore, JustifiedBalances, toCheckpointWithPayload} from "./store.js";
|
|
55
58
|
|
|
56
59
|
export type ForkChoiceOpts = {
|
|
57
60
|
proposerBoost?: boolean;
|
|
@@ -71,7 +74,7 @@ export type UpdateAndGetHeadOpt =
|
|
|
71
74
|
| {mode: UpdateHeadOpt.GetPredictedProposerHead; secFromSlot: number; slot: Slot};
|
|
72
75
|
|
|
73
76
|
// the initial vote epoch for all validators
|
|
74
|
-
const
|
|
77
|
+
const INIT_VOTE_SLOT: Slot = 0;
|
|
75
78
|
|
|
76
79
|
/**
|
|
77
80
|
* Provides an implementation of "Ethereum Consensus -- Beacon Chain Fork Choice":
|
|
@@ -94,18 +97,28 @@ export class ForkChoice implements IForkChoice {
|
|
|
94
97
|
irrecoverableError?: Error;
|
|
95
98
|
/**
|
|
96
99
|
* Votes currently tracked in the protoArray. Instead of tracking a VoteTracker of currentIndex, nextIndex and epoch,
|
|
97
|
-
* we decompose the struct and track them in
|
|
100
|
+
* we decompose the struct and track them in separate arrays for performance reason.
|
|
101
|
+
*
|
|
102
|
+
* For Gloas (ePBS), LatestMessage tracks slot instead of epoch and includes payload_present flag.
|
|
103
|
+
* Spec: gloas/fork-choice.md#modified-latestmessage
|
|
104
|
+
*
|
|
105
|
+
* IMPORTANT: voteCurrentIndices and voteNextIndices point to the EXACT variant node index.
|
|
106
|
+
* The payload status is encoded in the node index itself (different variants have different indices).
|
|
107
|
+
* For example, if a validator votes for the EMPTY variant, voteNextIndices[i] points to that specific EMPTY node.
|
|
98
108
|
*/
|
|
99
109
|
private readonly voteCurrentIndices: VoteIndex[];
|
|
100
110
|
private readonly voteNextIndices: VoteIndex[];
|
|
101
|
-
private readonly
|
|
111
|
+
private readonly voteNextSlots: Slot[];
|
|
102
112
|
|
|
103
113
|
/**
|
|
104
114
|
* Attestations that arrived at the current slot and must be queued for later processing.
|
|
105
115
|
* NOT currently tracked in the protoArray
|
|
116
|
+
*
|
|
117
|
+
* Modified for Gloas to track PayloadStatus per validator.
|
|
118
|
+
* Maps: Slot -> BlockRoot -> ValidatorIndex -> PayloadStatus
|
|
106
119
|
*/
|
|
107
|
-
private readonly queuedAttestations: MapDef<Slot, MapDef<RootHex,
|
|
108
|
-
() => new MapDef(() => new
|
|
120
|
+
private readonly queuedAttestations: MapDef<Slot, MapDef<RootHex, Map<ValidatorIndex, PayloadStatus>>> = new MapDef(
|
|
121
|
+
() => new MapDef(() => new Map())
|
|
109
122
|
);
|
|
110
123
|
|
|
111
124
|
/**
|
|
@@ -150,13 +163,14 @@ export class ForkChoice implements IForkChoice {
|
|
|
150
163
|
this.voteCurrentIndices = new Array(validatorCount).fill(NULL_VOTE_INDEX);
|
|
151
164
|
this.voteNextIndices = new Array(validatorCount).fill(NULL_VOTE_INDEX);
|
|
152
165
|
// when compute deltas, we ignore epoch if voteNextIndex is NULL_VOTE_INDEX anyway
|
|
153
|
-
|
|
166
|
+
|
|
167
|
+
this.voteNextSlots = new Array(validatorCount).fill(0);
|
|
154
168
|
|
|
155
169
|
this.head = this.updateHead();
|
|
156
170
|
this.balances = this.fcStore.justified.balances;
|
|
157
171
|
|
|
158
172
|
metrics?.forkChoice.votes.addCollect(() => {
|
|
159
|
-
metrics.forkChoice.votes.set(this.
|
|
173
|
+
metrics.forkChoice.votes.set(this.voteNextSlots.length);
|
|
160
174
|
metrics.forkChoice.queuedAttestations.set(this.queuedAttestationsPreviousSlot);
|
|
161
175
|
metrics.forkChoice.validatedAttestationDatas.set(this.validatedAttestationDatas.size);
|
|
162
176
|
metrics.forkChoice.balancesLength.set(this.balances.length);
|
|
@@ -177,7 +191,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
177
191
|
*
|
|
178
192
|
* https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#get_ancestor
|
|
179
193
|
*/
|
|
180
|
-
getAncestor(blockRoot: RootHex, ancestorSlot: Slot):
|
|
194
|
+
getAncestor(blockRoot: RootHex, ancestorSlot: Slot): ProtoNode {
|
|
181
195
|
return this.protoArray.getAncestor(blockRoot, ancestorSlot);
|
|
182
196
|
}
|
|
183
197
|
|
|
@@ -237,11 +251,10 @@ export class ForkChoice implements IForkChoice {
|
|
|
237
251
|
// Return false otherwise.
|
|
238
252
|
// Note when proposer boost reorg is disabled, it always returns false
|
|
239
253
|
shouldOverrideForkChoiceUpdate(
|
|
240
|
-
|
|
254
|
+
headBlock: ProtoBlock,
|
|
241
255
|
secFromSlot: number,
|
|
242
256
|
currentSlot: Slot
|
|
243
257
|
): ShouldOverrideForkChoiceUpdateResult {
|
|
244
|
-
const headBlock = this.getBlockHex(blockRoot);
|
|
245
258
|
if (headBlock === null) {
|
|
246
259
|
// should not happen because this block just got imported. Fall back to no-reorg.
|
|
247
260
|
return {shouldOverrideFcu: false, reason: NotReorgedReason.HeadBlockNotAvailable};
|
|
@@ -257,7 +270,10 @@ export class ForkChoice implements IForkChoice {
|
|
|
257
270
|
return {shouldOverrideFcu: false, reason: NotReorgedReason.ProposerBoostReorgDisabled};
|
|
258
271
|
}
|
|
259
272
|
|
|
260
|
-
const parentBlock = this.protoArray.getBlock(
|
|
273
|
+
const parentBlock = this.protoArray.getBlock(
|
|
274
|
+
headBlock.parentRoot,
|
|
275
|
+
this.protoArray.getParentPayloadStatus(headBlock)
|
|
276
|
+
);
|
|
261
277
|
const proposalSlot = headBlock.slot + 1;
|
|
262
278
|
|
|
263
279
|
// No reorg if parentBlock isn't available
|
|
@@ -282,7 +298,10 @@ export class ForkChoice implements IForkChoice {
|
|
|
282
298
|
return {shouldOverrideFcu: false, reason: NotReorgedReason.ReorgMoreThanOneSlot};
|
|
283
299
|
}
|
|
284
300
|
|
|
285
|
-
this.logger?.verbose("Block is weak. Should override forkchoice update", {
|
|
301
|
+
this.logger?.verbose("Block is weak. Should override forkchoice update", {
|
|
302
|
+
blockRoot: headBlock.blockRoot,
|
|
303
|
+
slot: currentSlot,
|
|
304
|
+
});
|
|
286
305
|
return {shouldOverrideFcu: true, parentBlock};
|
|
287
306
|
}
|
|
288
307
|
|
|
@@ -316,7 +335,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
316
335
|
}
|
|
317
336
|
|
|
318
337
|
const blockRoot = headBlock.blockRoot;
|
|
319
|
-
const result = this.shouldOverrideForkChoiceUpdate(
|
|
338
|
+
const result = this.shouldOverrideForkChoiceUpdate(headBlock, secFromSlot, currentSlot);
|
|
320
339
|
|
|
321
340
|
if (result.shouldOverrideFcu) {
|
|
322
341
|
this.logger?.verbose("Current head is weak. Predicting next block to be built on parent of head.", {
|
|
@@ -363,7 +382,10 @@ export class ForkChoice implements IForkChoice {
|
|
|
363
382
|
return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ProposerBoostReorgDisabled};
|
|
364
383
|
}
|
|
365
384
|
|
|
366
|
-
const parentBlock = this.protoArray.getBlock(
|
|
385
|
+
const parentBlock = this.protoArray.getBlock(
|
|
386
|
+
headBlock.parentRoot,
|
|
387
|
+
this.protoArray.getParentPayloadStatus(headBlock)
|
|
388
|
+
);
|
|
367
389
|
|
|
368
390
|
// No reorg if parentBlock isn't available
|
|
369
391
|
if (parentBlock === undefined) {
|
|
@@ -400,7 +422,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
400
422
|
slotsPerEpoch: SLOTS_PER_EPOCH,
|
|
401
423
|
committeePercent: this.config.REORG_HEAD_WEIGHT_THRESHOLD,
|
|
402
424
|
});
|
|
403
|
-
const headNode = this.protoArray.getNode(headBlock.blockRoot);
|
|
425
|
+
const headNode = this.protoArray.getNode(headBlock.blockRoot, headBlock.payloadStatus);
|
|
404
426
|
// If headNode is unavailable, give up reorg
|
|
405
427
|
if (headNode === undefined || headNode.weight >= reorgThreshold) {
|
|
406
428
|
return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.HeadBlockNotWeak};
|
|
@@ -412,7 +434,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
412
434
|
slotsPerEpoch: SLOTS_PER_EPOCH,
|
|
413
435
|
committeePercent: this.config.REORG_PARENT_WEIGHT_THRESHOLD,
|
|
414
436
|
});
|
|
415
|
-
const parentNode = this.protoArray.getNode(parentBlock.blockRoot);
|
|
437
|
+
const parentNode = this.protoArray.getNode(parentBlock.blockRoot, parentBlock.payloadStatus);
|
|
416
438
|
// If parentNode is unavailable, give up reorg
|
|
417
439
|
if (parentNode === undefined || parentNode.weight <= parentThreshold) {
|
|
418
440
|
return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ParentBlockNotStrong};
|
|
@@ -509,23 +531,10 @@ export class ForkChoice implements IForkChoice {
|
|
|
509
531
|
currentSlot,
|
|
510
532
|
});
|
|
511
533
|
|
|
512
|
-
|
|
513
|
-
const
|
|
514
|
-
if (headIndex === undefined) {
|
|
515
|
-
throw new ForkChoiceError({
|
|
516
|
-
code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK,
|
|
517
|
-
root: headRoot,
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
const headNode = this.protoArray.nodes[headIndex];
|
|
521
|
-
if (headNode === undefined) {
|
|
522
|
-
throw new ForkChoiceError({
|
|
523
|
-
code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK,
|
|
524
|
-
root: headRoot,
|
|
525
|
-
});
|
|
526
|
-
}
|
|
534
|
+
// findHead returns the ProtoNode representing the head
|
|
535
|
+
const head = this.protoArray.findHead(this.fcStore.justified.checkpoint.rootHex, currentSlot);
|
|
527
536
|
|
|
528
|
-
this.head =
|
|
537
|
+
this.head = head;
|
|
529
538
|
return this.head;
|
|
530
539
|
}
|
|
531
540
|
|
|
@@ -548,11 +557,11 @@ export class ForkChoice implements IForkChoice {
|
|
|
548
557
|
return this.protoArray.nodes;
|
|
549
558
|
}
|
|
550
559
|
|
|
551
|
-
getFinalizedCheckpoint():
|
|
560
|
+
getFinalizedCheckpoint(): CheckpointWithPayloadStatus {
|
|
552
561
|
return this.fcStore.finalizedCheckpoint;
|
|
553
562
|
}
|
|
554
563
|
|
|
555
|
-
getJustifiedCheckpoint():
|
|
564
|
+
getJustifiedCheckpoint(): CheckpointWithPayloadStatus {
|
|
556
565
|
return this.fcStore.justified.checkpoint;
|
|
557
566
|
}
|
|
558
567
|
|
|
@@ -585,14 +594,18 @@ export class ForkChoice implements IForkChoice {
|
|
|
585
594
|
): ProtoBlock {
|
|
586
595
|
const {parentRoot, slot} = block;
|
|
587
596
|
const parentRootHex = toRootHex(parentRoot);
|
|
588
|
-
// Parent block must be known
|
|
589
|
-
const
|
|
597
|
+
// Parent block must be known because state_transition would have failed otherwise.
|
|
598
|
+
const parentHashHex = isGloasBeaconBlock(block)
|
|
599
|
+
? toRootHex(block.body.signedExecutionPayloadBid.message.parentBlockHash)
|
|
600
|
+
: null;
|
|
601
|
+
const parentBlock = this.protoArray.getParent(parentRootHex, parentHashHex);
|
|
590
602
|
if (!parentBlock) {
|
|
591
603
|
throw new ForkChoiceError({
|
|
592
604
|
code: ForkChoiceErrorCode.INVALID_BLOCK,
|
|
593
605
|
err: {
|
|
594
606
|
code: InvalidBlockCode.UNKNOWN_PARENT,
|
|
595
607
|
root: parentRootHex,
|
|
608
|
+
hash: parentHashHex,
|
|
596
609
|
},
|
|
597
610
|
});
|
|
598
611
|
}
|
|
@@ -627,15 +640,18 @@ export class ForkChoice implements IForkChoice {
|
|
|
627
640
|
}
|
|
628
641
|
|
|
629
642
|
// Check block is a descendant of the finalized block at the checkpoint finalized slot.
|
|
630
|
-
const
|
|
631
|
-
const
|
|
632
|
-
if (
|
|
643
|
+
const blockAncestorNode = this.getAncestor(parentRootHex, finalizedSlot);
|
|
644
|
+
const fcStoreFinalized = this.fcStore.finalizedCheckpoint;
|
|
645
|
+
if (
|
|
646
|
+
blockAncestorNode.blockRoot !== fcStoreFinalized.rootHex ||
|
|
647
|
+
blockAncestorNode.payloadStatus !== fcStoreFinalized.payloadStatus
|
|
648
|
+
) {
|
|
633
649
|
throw new ForkChoiceError({
|
|
634
650
|
code: ForkChoiceErrorCode.INVALID_BLOCK,
|
|
635
651
|
err: {
|
|
636
652
|
code: InvalidBlockCode.NOT_FINALIZED_DESCENDANT,
|
|
637
|
-
finalizedRoot,
|
|
638
|
-
blockAncestor:
|
|
653
|
+
finalizedRoot: fcStoreFinalized.rootHex,
|
|
654
|
+
blockAncestor: blockAncestorNode.blockRoot,
|
|
639
655
|
},
|
|
640
656
|
});
|
|
641
657
|
}
|
|
@@ -655,10 +671,15 @@ export class ForkChoice implements IForkChoice {
|
|
|
655
671
|
this.proposerBoostRoot = blockRootHex;
|
|
656
672
|
}
|
|
657
673
|
|
|
658
|
-
|
|
659
|
-
const
|
|
674
|
+
// Get justified checkpoint with payload status for Gloas
|
|
675
|
+
const justifiedPayloadStatus = getCheckpointPayloadStatus(state, state.currentJustifiedCheckpoint.epoch);
|
|
676
|
+
const justifiedCheckpoint = toCheckpointWithPayload(state.currentJustifiedCheckpoint, justifiedPayloadStatus);
|
|
660
677
|
const stateJustifiedEpoch = justifiedCheckpoint.epoch;
|
|
661
678
|
|
|
679
|
+
// Get finalized checkpoint with payload status for Gloas
|
|
680
|
+
const finalizedPayloadStatus = getCheckpointPayloadStatus(state, state.finalizedCheckpoint.epoch);
|
|
681
|
+
const finalizedCheckpoint = toCheckpointWithPayload(state.finalizedCheckpoint, finalizedPayloadStatus);
|
|
682
|
+
|
|
662
683
|
// Justified balances for `justifiedCheckpoint` are new to the fork-choice. Compute them on demand only if
|
|
663
684
|
// the justified checkpoint changes
|
|
664
685
|
this.updateCheckpoints(justifiedCheckpoint, finalizedCheckpoint, () =>
|
|
@@ -679,29 +700,57 @@ export class ForkChoice implements IForkChoice {
|
|
|
679
700
|
// This is an optimization. It should reduce the amount of times we run
|
|
680
701
|
// `process_justification_and_finalization` by approximately 1/3rd when the chain is
|
|
681
702
|
// performing optimally.
|
|
682
|
-
let unrealizedJustifiedCheckpoint:
|
|
683
|
-
let unrealizedFinalizedCheckpoint:
|
|
703
|
+
let unrealizedJustifiedCheckpoint: CheckpointWithPayloadStatus;
|
|
704
|
+
let unrealizedFinalizedCheckpoint: CheckpointWithPayloadStatus;
|
|
684
705
|
if (this.opts?.computeUnrealized) {
|
|
685
706
|
if (
|
|
686
707
|
parentBlock.unrealizedJustifiedEpoch === blockEpoch &&
|
|
687
708
|
parentBlock.unrealizedFinalizedEpoch + 1 >= blockEpoch
|
|
688
709
|
) {
|
|
689
710
|
// reuse from parent, happens at 1/3 last blocks of epoch as monitored in mainnet
|
|
711
|
+
// Get payload status for unrealized justified checkpoint
|
|
712
|
+
const unrealizedJustifiedPayloadStatus = getCheckpointPayloadStatus(
|
|
713
|
+
state,
|
|
714
|
+
parentBlock.unrealizedJustifiedEpoch
|
|
715
|
+
);
|
|
690
716
|
unrealizedJustifiedCheckpoint = {
|
|
691
717
|
epoch: parentBlock.unrealizedJustifiedEpoch,
|
|
692
718
|
root: fromHex(parentBlock.unrealizedJustifiedRoot),
|
|
693
719
|
rootHex: parentBlock.unrealizedJustifiedRoot,
|
|
720
|
+
payloadStatus: unrealizedJustifiedPayloadStatus,
|
|
694
721
|
};
|
|
722
|
+
// Get payload status for unrealized finalized checkpoint
|
|
723
|
+
const unrealizedFinalizedPayloadStatus = getCheckpointPayloadStatus(
|
|
724
|
+
state,
|
|
725
|
+
parentBlock.unrealizedFinalizedEpoch
|
|
726
|
+
);
|
|
695
727
|
unrealizedFinalizedCheckpoint = {
|
|
696
728
|
epoch: parentBlock.unrealizedFinalizedEpoch,
|
|
697
729
|
root: fromHex(parentBlock.unrealizedFinalizedRoot),
|
|
698
730
|
rootHex: parentBlock.unrealizedFinalizedRoot,
|
|
731
|
+
payloadStatus: unrealizedFinalizedPayloadStatus,
|
|
699
732
|
};
|
|
700
733
|
} else {
|
|
701
734
|
// compute new, happens 2/3 first blocks of epoch as monitored in mainnet
|
|
702
735
|
const unrealized = computeUnrealizedCheckpoints(state);
|
|
703
|
-
|
|
704
|
-
|
|
736
|
+
// Get payload status for unrealized justified checkpoint
|
|
737
|
+
const unrealizedJustifiedPayloadStatus = getCheckpointPayloadStatus(
|
|
738
|
+
state,
|
|
739
|
+
unrealized.justifiedCheckpoint.epoch
|
|
740
|
+
);
|
|
741
|
+
unrealizedJustifiedCheckpoint = toCheckpointWithPayload(
|
|
742
|
+
unrealized.justifiedCheckpoint,
|
|
743
|
+
unrealizedJustifiedPayloadStatus
|
|
744
|
+
);
|
|
745
|
+
// Get payload status for unrealized finalized checkpoint
|
|
746
|
+
const unrealizedFinalizedPayloadStatus = getCheckpointPayloadStatus(
|
|
747
|
+
state,
|
|
748
|
+
unrealized.finalizedCheckpoint.epoch
|
|
749
|
+
);
|
|
750
|
+
unrealizedFinalizedCheckpoint = toCheckpointWithPayload(
|
|
751
|
+
unrealized.finalizedCheckpoint,
|
|
752
|
+
unrealizedFinalizedPayloadStatus
|
|
753
|
+
);
|
|
705
754
|
}
|
|
706
755
|
} else {
|
|
707
756
|
unrealizedJustifiedCheckpoint = justifiedCheckpoint;
|
|
@@ -743,30 +792,56 @@ export class ForkChoice implements IForkChoice {
|
|
|
743
792
|
unrealizedFinalizedEpoch: unrealizedFinalizedCheckpoint.epoch,
|
|
744
793
|
unrealizedFinalizedRoot: unrealizedFinalizedCheckpoint.rootHex,
|
|
745
794
|
|
|
746
|
-
...(isExecutionBlockBodyType(block.body) && isExecutionStateType(state) && isExecutionEnabled(state, block)
|
|
747
|
-
? {
|
|
748
|
-
executionPayloadBlockHash: toRootHex(block.body.executionPayload.blockHash),
|
|
749
|
-
executionPayloadNumber: block.body.executionPayload.blockNumber,
|
|
750
|
-
executionStatus: this.getPostMergeExecStatus(executionStatus),
|
|
751
|
-
dataAvailabilityStatus,
|
|
752
|
-
}
|
|
753
|
-
: {
|
|
754
|
-
executionPayloadBlockHash: null,
|
|
755
|
-
executionStatus: this.getPreMergeExecStatus(executionStatus),
|
|
756
|
-
dataAvailabilityStatus: this.getPreMergeDataStatus(dataAvailabilityStatus),
|
|
757
|
-
}),
|
|
758
795
|
...(isGloasBeaconBlock(block)
|
|
759
796
|
? {
|
|
760
|
-
|
|
761
|
-
|
|
797
|
+
executionPayloadBlockHash: toRootHex(block.body.signedExecutionPayloadBid.message.parentBlockHash), // post-gloas, we don't know payload hash until we import execution payload. Set to parent payload hash for now
|
|
798
|
+
executionPayloadNumber: (() => {
|
|
799
|
+
// Determine parent's execution payload number based on which variant the block extends
|
|
800
|
+
const parentBlockHashFromBid = toRootHex(block.body.signedExecutionPayloadBid.message.parentBlockHash);
|
|
801
|
+
|
|
802
|
+
// If parent is pre-merge, return 0
|
|
803
|
+
if (parentBlock.executionPayloadBlockHash === null) {
|
|
804
|
+
return 0;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// If parent is pre-Gloas, it only has FULL variant
|
|
808
|
+
if (parentBlock.parentBlockHash === null) {
|
|
809
|
+
return parentBlock.executionPayloadNumber;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Parent is Gloas: get the variant that matches the parentBlockHash from bid
|
|
813
|
+
const parentVariant = this.getBlockHexAndBlockHash(parentRootHex, parentBlockHashFromBid);
|
|
814
|
+
if (parentVariant && parentVariant.executionPayloadBlockHash !== null) {
|
|
815
|
+
return parentVariant.executionPayloadNumber;
|
|
816
|
+
}
|
|
817
|
+
// Fallback to parent block's number (we know it's post-merge from check above)
|
|
818
|
+
return parentBlock.executionPayloadNumber;
|
|
819
|
+
})(),
|
|
820
|
+
executionStatus: this.getPostGloasExecStatus(executionStatus),
|
|
821
|
+
dataAvailabilityStatus,
|
|
762
822
|
}
|
|
763
|
-
:
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
823
|
+
: isExecutionBlockBodyType(block.body) && isExecutionStateType(state) && isExecutionEnabled(state, block)
|
|
824
|
+
? {
|
|
825
|
+
executionPayloadBlockHash: toRootHex(block.body.executionPayload.blockHash),
|
|
826
|
+
executionPayloadNumber: block.body.executionPayload.blockNumber,
|
|
827
|
+
executionStatus: this.getPreGloasExecStatus(executionStatus),
|
|
828
|
+
dataAvailabilityStatus,
|
|
829
|
+
}
|
|
830
|
+
: {
|
|
831
|
+
executionPayloadBlockHash: null,
|
|
832
|
+
executionStatus: this.getPreMergeExecStatus(executionStatus),
|
|
833
|
+
dataAvailabilityStatus: this.getPreMergeDataStatus(dataAvailabilityStatus),
|
|
834
|
+
}),
|
|
835
|
+
|
|
836
|
+
payloadStatus: isGloasBeaconBlock(block) ? PayloadStatus.PENDING : PayloadStatus.FULL,
|
|
837
|
+
builderIndex: isGloasBeaconBlock(block) ? block.body.signedExecutionPayloadBid.message.builderIndex : null,
|
|
838
|
+
blockHashFromBid: isGloasBeaconBlock(block)
|
|
839
|
+
? toRootHex(block.body.signedExecutionPayloadBid.message.blockHash)
|
|
840
|
+
: null,
|
|
841
|
+
parentBlockHash: parentHashHex,
|
|
767
842
|
};
|
|
768
843
|
|
|
769
|
-
this.protoArray.onBlock(protoBlock, currentSlot);
|
|
844
|
+
this.protoArray.onBlock(protoBlock, currentSlot, this.proposerBoostRoot);
|
|
770
845
|
|
|
771
846
|
return protoBlock;
|
|
772
847
|
}
|
|
@@ -813,10 +888,45 @@ export class ForkChoice implements IForkChoice {
|
|
|
813
888
|
|
|
814
889
|
this.validateOnAttestation(attestation, slot, blockRootHex, targetEpoch, attDataRoot, forceImport);
|
|
815
890
|
|
|
891
|
+
// Pre-gloas: payload is always present
|
|
892
|
+
// Post-gloas:
|
|
893
|
+
// - always add weight to PENDING
|
|
894
|
+
// - if message.slot > block.slot, it also add weights to FULL or EMPTY
|
|
895
|
+
let payloadStatus: PayloadStatus;
|
|
896
|
+
|
|
897
|
+
// We need to retrieve block to check if it's Gloas and to compare slot
|
|
898
|
+
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/fork-choice.md#new-is_supporting_vote
|
|
899
|
+
const block = this.getBlockHexDefaultStatus(blockRootHex);
|
|
900
|
+
|
|
901
|
+
if (block && isGloasBlock(block)) {
|
|
902
|
+
// Post-Gloas block: determine FULL/EMPTY/PENDING based on slot and committee index
|
|
903
|
+
// If slot > block.slot, we can determine FULL or EMPTY. Else always PENDING
|
|
904
|
+
if (slot > block.slot) {
|
|
905
|
+
if (attestationData.index === 1) {
|
|
906
|
+
payloadStatus = PayloadStatus.FULL;
|
|
907
|
+
} else if (attestationData.index === 0) {
|
|
908
|
+
payloadStatus = PayloadStatus.EMPTY;
|
|
909
|
+
} else {
|
|
910
|
+
throw new ForkChoiceError({
|
|
911
|
+
code: ForkChoiceErrorCode.INVALID_ATTESTATION,
|
|
912
|
+
err: {
|
|
913
|
+
code: InvalidAttestationCode.INVALID_DATA_INDEX,
|
|
914
|
+
index: attestationData.index,
|
|
915
|
+
},
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
} else {
|
|
919
|
+
payloadStatus = PayloadStatus.PENDING;
|
|
920
|
+
}
|
|
921
|
+
} else {
|
|
922
|
+
// Pre-Gloas block or block not found: always FULL
|
|
923
|
+
payloadStatus = PayloadStatus.FULL;
|
|
924
|
+
}
|
|
925
|
+
|
|
816
926
|
if (slot < this.fcStore.currentSlot) {
|
|
817
927
|
for (const validatorIndex of attestation.attestingIndices) {
|
|
818
928
|
if (!this.fcStore.equivocatingIndices.has(validatorIndex)) {
|
|
819
|
-
this.addLatestMessage(validatorIndex,
|
|
929
|
+
this.addLatestMessage(validatorIndex, slot, blockRootHex, payloadStatus);
|
|
820
930
|
}
|
|
821
931
|
}
|
|
822
932
|
} else {
|
|
@@ -827,10 +937,10 @@ export class ForkChoice implements IForkChoice {
|
|
|
827
937
|
// Delay consideration in the fork choice until their slot is in the past.
|
|
828
938
|
// ```
|
|
829
939
|
const byRoot = this.queuedAttestations.getOrDefault(slot);
|
|
830
|
-
const
|
|
940
|
+
const validatorVotes = byRoot.getOrDefault(blockRootHex);
|
|
831
941
|
for (const validatorIndex of attestation.attestingIndices) {
|
|
832
942
|
if (!this.fcStore.equivocatingIndices.has(validatorIndex)) {
|
|
833
|
-
|
|
943
|
+
validatorVotes.set(validatorIndex, payloadStatus);
|
|
834
944
|
}
|
|
835
945
|
}
|
|
836
946
|
}
|
|
@@ -849,6 +959,36 @@ export class ForkChoice implements IForkChoice {
|
|
|
849
959
|
}
|
|
850
960
|
}
|
|
851
961
|
|
|
962
|
+
/**
|
|
963
|
+
* Process a PTC (Payload Timeliness Committee) message
|
|
964
|
+
* Updates the PTC votes for multiple validators attesting to a block
|
|
965
|
+
* Spec: gloas/fork-choice.md#new-on_payload_attestation_message
|
|
966
|
+
*/
|
|
967
|
+
notifyPtcMessages(blockRoot: RootHex, ptcIndices: number[], payloadPresent: boolean): void {
|
|
968
|
+
this.protoArray.notifyPtcMessages(blockRoot, ptcIndices, payloadPresent);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Notify fork choice that an execution payload has arrived (Gloas fork)
|
|
973
|
+
* Creates the FULL variant of a Gloas block when the payload becomes available
|
|
974
|
+
* Spec: gloas/fork-choice.md#new-on_execution_payload
|
|
975
|
+
*/
|
|
976
|
+
onExecutionPayload(
|
|
977
|
+
blockRoot: RootHex,
|
|
978
|
+
executionPayloadBlockHash: RootHex,
|
|
979
|
+
executionPayloadNumber: number,
|
|
980
|
+
executionPayloadStateRoot: RootHex
|
|
981
|
+
): void {
|
|
982
|
+
this.protoArray.onExecutionPayload(
|
|
983
|
+
blockRoot,
|
|
984
|
+
this.fcStore.currentSlot,
|
|
985
|
+
executionPayloadBlockHash,
|
|
986
|
+
executionPayloadNumber,
|
|
987
|
+
executionPayloadStateRoot,
|
|
988
|
+
this.proposerBoostRoot
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
|
|
852
992
|
/**
|
|
853
993
|
* Call `onTick` for all slots between `fcStore.getCurrentSlot()` and the provided `currentSlot`.
|
|
854
994
|
* This should only be called once per slot because:
|
|
@@ -880,15 +1020,21 @@ export class ForkChoice implements IForkChoice {
|
|
|
880
1020
|
return this.hasBlockHex(toRootHex(blockRoot));
|
|
881
1021
|
}
|
|
882
1022
|
/** Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root. */
|
|
883
|
-
getBlock(blockRoot: Root): ProtoBlock | null {
|
|
884
|
-
return this.getBlockHex(toRootHex(blockRoot));
|
|
1023
|
+
getBlock(blockRoot: Root, payloadStatus: PayloadStatus): ProtoBlock | null {
|
|
1024
|
+
return this.getBlockHex(toRootHex(blockRoot), payloadStatus);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
getBlockDefaultStatus(blockRoot: Root): ProtoBlock | null {
|
|
1028
|
+
return this.getBlockHexDefaultStatus(toRootHex(blockRoot));
|
|
885
1029
|
}
|
|
886
1030
|
|
|
887
1031
|
/**
|
|
888
1032
|
* Returns `true` if the block is known **and** a descendant of the finalized root.
|
|
1033
|
+
* Uses default variant (PENDING for Gloas, FULL for pre-Gloas).
|
|
889
1034
|
*/
|
|
890
1035
|
hasBlockHex(blockRoot: RootHex): boolean {
|
|
891
|
-
const
|
|
1036
|
+
const defaultStatus = this.protoArray.getDefaultVariant(blockRoot);
|
|
1037
|
+
const node = defaultStatus !== undefined ? this.protoArray.getNode(blockRoot, defaultStatus) : undefined;
|
|
892
1038
|
if (node === undefined) {
|
|
893
1039
|
return false;
|
|
894
1040
|
}
|
|
@@ -913,8 +1059,8 @@ export class ForkChoice implements IForkChoice {
|
|
|
913
1059
|
/**
|
|
914
1060
|
* Returns a MUTABLE `ProtoBlock` if the block is known **and** a descendant of the finalized root.
|
|
915
1061
|
*/
|
|
916
|
-
getBlockHex(blockRoot: RootHex): ProtoBlock | null {
|
|
917
|
-
const node = this.protoArray.getNode(blockRoot);
|
|
1062
|
+
getBlockHex(blockRoot: RootHex, payloadStatus: PayloadStatus): ProtoBlock | null {
|
|
1063
|
+
const node = this.protoArray.getNode(blockRoot, payloadStatus);
|
|
918
1064
|
if (!node) {
|
|
919
1065
|
return null;
|
|
920
1066
|
}
|
|
@@ -928,23 +1074,49 @@ export class ForkChoice implements IForkChoice {
|
|
|
928
1074
|
};
|
|
929
1075
|
}
|
|
930
1076
|
|
|
1077
|
+
/**
|
|
1078
|
+
* Returns a `ProtoBlock` with the default variant for the given block root
|
|
1079
|
+
* - Pre-Gloas blocks: returns FULL variant (only variant)
|
|
1080
|
+
* - Gloas blocks: returns PENDING variant
|
|
1081
|
+
*
|
|
1082
|
+
* Use this when you need the canonical block reference regardless of payload status.
|
|
1083
|
+
* For searching by execution payload hash and variant-specific info, use `getBlockHexAndBlockHash` instead.
|
|
1084
|
+
*/
|
|
1085
|
+
getBlockHexDefaultStatus(blockRoot: RootHex): ProtoBlock | null {
|
|
1086
|
+
const defaultStatus = this.protoArray.getDefaultVariant(blockRoot);
|
|
1087
|
+
if (defaultStatus === undefined) {
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
return this.getBlockHex(blockRoot, defaultStatus);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Returns EMPTY or FULL `ProtoBlock` that has matching block root and block hash
|
|
1096
|
+
*/
|
|
1097
|
+
getBlockHexAndBlockHash(blockRoot: RootHex, blockHash: RootHex): ProtoBlock | null {
|
|
1098
|
+
return this.protoArray.getBlockHexAndBlockHash(blockRoot, blockHash);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
931
1101
|
getJustifiedBlock(): ProtoBlock {
|
|
932
|
-
const
|
|
1102
|
+
const {rootHex, payloadStatus} = this.fcStore.justified.checkpoint;
|
|
1103
|
+
const block = this.getBlockHex(rootHex, payloadStatus);
|
|
933
1104
|
if (!block) {
|
|
934
1105
|
throw new ForkChoiceError({
|
|
935
1106
|
code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK,
|
|
936
|
-
root:
|
|
1107
|
+
root: rootHex,
|
|
937
1108
|
});
|
|
938
1109
|
}
|
|
939
1110
|
return block;
|
|
940
1111
|
}
|
|
941
1112
|
|
|
942
1113
|
getFinalizedBlock(): ProtoBlock {
|
|
943
|
-
const
|
|
1114
|
+
const {rootHex, payloadStatus} = this.fcStore.finalizedCheckpoint;
|
|
1115
|
+
const block = this.getBlockHex(rootHex, payloadStatus);
|
|
944
1116
|
if (!block) {
|
|
945
1117
|
throw new ForkChoiceError({
|
|
946
1118
|
code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK,
|
|
947
|
-
root:
|
|
1119
|
+
root: rootHex,
|
|
948
1120
|
});
|
|
949
1121
|
}
|
|
950
1122
|
return block;
|
|
@@ -961,8 +1133,13 @@ export class ForkChoice implements IForkChoice {
|
|
|
961
1133
|
* Always returns `false` if either input roots are unknown.
|
|
962
1134
|
* Still returns `true` if `ancestorRoot===descendantRoot` (and the roots are known)
|
|
963
1135
|
*/
|
|
964
|
-
isDescendant(
|
|
965
|
-
|
|
1136
|
+
isDescendant(
|
|
1137
|
+
ancestorRoot: RootHex,
|
|
1138
|
+
ancestorPayloadStatus: PayloadStatus,
|
|
1139
|
+
descendantRoot: RootHex,
|
|
1140
|
+
descendantPayloadStatus: PayloadStatus
|
|
1141
|
+
): boolean {
|
|
1142
|
+
return this.protoArray.isDescendant(ancestorRoot, ancestorPayloadStatus, descendantRoot, descendantPayloadStatus);
|
|
966
1143
|
}
|
|
967
1144
|
|
|
968
1145
|
/**
|
|
@@ -971,7 +1148,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
971
1148
|
prune(finalizedRoot: RootHex): ProtoBlock[] {
|
|
972
1149
|
const prunedNodes = this.protoArray.maybePrune(finalizedRoot);
|
|
973
1150
|
const prunedCount = prunedNodes.length;
|
|
974
|
-
for (let i = 0; i < this.
|
|
1151
|
+
for (let i = 0; i < this.voteNextSlots.length; i++) {
|
|
975
1152
|
const currentIndex = this.voteCurrentIndices[i];
|
|
976
1153
|
|
|
977
1154
|
if (currentIndex !== NULL_VOTE_INDEX) {
|
|
@@ -1005,16 +1182,16 @@ export class ForkChoice implements IForkChoice {
|
|
|
1005
1182
|
* Iterates backwards through block summaries, starting from a block root.
|
|
1006
1183
|
* Return only the non-finalized blocks.
|
|
1007
1184
|
*/
|
|
1008
|
-
iterateAncestorBlocks(blockRoot: RootHex): IterableIterator<ProtoBlock> {
|
|
1009
|
-
return this.protoArray.iterateAncestorNodes(blockRoot);
|
|
1185
|
+
iterateAncestorBlocks(blockRoot: RootHex, payloadStatus: PayloadStatus): IterableIterator<ProtoBlock> {
|
|
1186
|
+
return this.protoArray.iterateAncestorNodes(blockRoot, payloadStatus);
|
|
1010
1187
|
}
|
|
1011
1188
|
|
|
1012
1189
|
/**
|
|
1013
1190
|
* Returns all blocks backwards starting from a block root.
|
|
1014
1191
|
* Return only the non-finalized blocks.
|
|
1015
1192
|
*/
|
|
1016
|
-
getAllAncestorBlocks(blockRoot: RootHex): ProtoBlock[] {
|
|
1017
|
-
const blocks = this.protoArray.getAllAncestorNodes(blockRoot);
|
|
1193
|
+
getAllAncestorBlocks(blockRoot: RootHex, payloadStatus: PayloadStatus): ProtoBlock[] {
|
|
1194
|
+
const blocks = this.protoArray.getAllAncestorNodes(blockRoot, payloadStatus);
|
|
1018
1195
|
// the last node is the previous finalized one, it's there to check onBlock finalized checkpoint only.
|
|
1019
1196
|
return blocks.slice(0, blocks.length - 1);
|
|
1020
1197
|
}
|
|
@@ -1022,15 +1199,18 @@ export class ForkChoice implements IForkChoice {
|
|
|
1022
1199
|
/**
|
|
1023
1200
|
* The same to iterateAncestorBlocks but this gets non-ancestor nodes instead of ancestor nodes.
|
|
1024
1201
|
*/
|
|
1025
|
-
getAllNonAncestorBlocks(blockRoot: RootHex): ProtoBlock[] {
|
|
1026
|
-
return this.protoArray.getAllNonAncestorNodes(blockRoot);
|
|
1202
|
+
getAllNonAncestorBlocks(blockRoot: RootHex, payloadStatus: PayloadStatus): ProtoBlock[] {
|
|
1203
|
+
return this.protoArray.getAllNonAncestorNodes(blockRoot, payloadStatus);
|
|
1027
1204
|
}
|
|
1028
1205
|
|
|
1029
1206
|
/**
|
|
1030
1207
|
* Returns both ancestor and non-ancestor blocks in a single traversal.
|
|
1031
1208
|
*/
|
|
1032
|
-
getAllAncestorAndNonAncestorBlocks(
|
|
1033
|
-
|
|
1209
|
+
getAllAncestorAndNonAncestorBlocks(
|
|
1210
|
+
blockRoot: RootHex,
|
|
1211
|
+
payloadStatus: PayloadStatus
|
|
1212
|
+
): {ancestors: ProtoBlock[]; nonAncestors: ProtoBlock[]} {
|
|
1213
|
+
const {ancestors, nonAncestors} = this.protoArray.getAllAncestorAndNonAncestorNodes(blockRoot, payloadStatus);
|
|
1034
1214
|
|
|
1035
1215
|
return {
|
|
1036
1216
|
// the last node is the previous finalized one, it's there to check onBlock finalized checkpoint only.
|
|
@@ -1039,6 +1219,21 @@ export class ForkChoice implements IForkChoice {
|
|
|
1039
1219
|
};
|
|
1040
1220
|
}
|
|
1041
1221
|
|
|
1222
|
+
getCanonicalBlockByRoot(blockRoot: Root): ProtoBlock | null {
|
|
1223
|
+
const blockRootHex = toRootHex(blockRoot);
|
|
1224
|
+
if (blockRootHex === this.head.blockRoot) {
|
|
1225
|
+
return this.head;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
for (const block of this.protoArray.iterateAncestorNodes(this.head.blockRoot, this.head.payloadStatus)) {
|
|
1229
|
+
if (block.blockRoot === blockRootHex) {
|
|
1230
|
+
return block;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1042
1237
|
getCanonicalBlockAtSlot(slot: Slot): ProtoBlock | null {
|
|
1043
1238
|
if (slot > this.head.slot) {
|
|
1044
1239
|
return null;
|
|
@@ -1048,7 +1243,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
1048
1243
|
return this.head;
|
|
1049
1244
|
}
|
|
1050
1245
|
|
|
1051
|
-
for (const block of this.protoArray.iterateAncestorNodes(this.head.blockRoot)) {
|
|
1246
|
+
for (const block of this.protoArray.iterateAncestorNodes(this.head.blockRoot, this.head.payloadStatus)) {
|
|
1052
1247
|
if (block.slot === slot) {
|
|
1053
1248
|
return block;
|
|
1054
1249
|
}
|
|
@@ -1061,7 +1256,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
1061
1256
|
return this.head;
|
|
1062
1257
|
}
|
|
1063
1258
|
|
|
1064
|
-
for (const block of this.protoArray.iterateAncestorNodes(this.head.blockRoot)) {
|
|
1259
|
+
for (const block of this.protoArray.iterateAncestorNodes(this.head.blockRoot, this.head.payloadStatus)) {
|
|
1065
1260
|
if (slot >= block.slot) {
|
|
1066
1261
|
return block;
|
|
1067
1262
|
}
|
|
@@ -1074,10 +1269,9 @@ export class ForkChoice implements IForkChoice {
|
|
|
1074
1269
|
return this.protoArray.nodes;
|
|
1075
1270
|
}
|
|
1076
1271
|
|
|
1077
|
-
*forwardIterateDescendants(blockRoot: RootHex): IterableIterator<ProtoBlock> {
|
|
1272
|
+
*forwardIterateDescendants(blockRoot: RootHex, payloadStatus: PayloadStatus): IterableIterator<ProtoBlock> {
|
|
1078
1273
|
const rootsInChain = new Set([blockRoot]);
|
|
1079
|
-
|
|
1080
|
-
const blockIndex = this.protoArray.indices.get(blockRoot);
|
|
1274
|
+
const blockIndex = this.protoArray.getNodeIndexByRootAndStatus(blockRoot, payloadStatus);
|
|
1081
1275
|
if (blockIndex === undefined) {
|
|
1082
1276
|
throw new ForkChoiceError({
|
|
1083
1277
|
code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK,
|
|
@@ -1114,8 +1308,8 @@ export class ForkChoice implements IForkChoice {
|
|
|
1114
1308
|
|
|
1115
1309
|
/** Returns the distance of common ancestor of nodes to the max of the newNode and the prevNode. */
|
|
1116
1310
|
getCommonAncestorDepth(prevBlock: ProtoBlock, newBlock: ProtoBlock): AncestorResult {
|
|
1117
|
-
const prevNode = this.protoArray.getNode(prevBlock.blockRoot);
|
|
1118
|
-
const newNode = this.protoArray.getNode(newBlock.blockRoot);
|
|
1311
|
+
const prevNode = this.protoArray.getNode(prevBlock.blockRoot, prevBlock.payloadStatus);
|
|
1312
|
+
const newNode = this.protoArray.getNode(newBlock.blockRoot, newBlock.payloadStatus);
|
|
1119
1313
|
if (!prevNode || !newNode) {
|
|
1120
1314
|
return {code: AncestorStatus.BlockUnknown};
|
|
1121
1315
|
}
|
|
@@ -1196,12 +1390,17 @@ export class ForkChoice implements IForkChoice {
|
|
|
1196
1390
|
return block.parentRoot;
|
|
1197
1391
|
}
|
|
1198
1392
|
|
|
1199
|
-
block
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1393
|
+
// For the first slot of the epoch, a block is it's own target
|
|
1394
|
+
const nextRoot = block.blockRoot === block.targetRoot ? block.parentRoot : block.targetRoot;
|
|
1395
|
+
// Use default variant (PENDING for Gloas, FULL for pre-Gloas)
|
|
1396
|
+
// For Gloas: we search for PENDING blocks because dependent root is determined by the block itself,
|
|
1397
|
+
// not the payload. In state-transition, block parentage is independent of payload status,
|
|
1398
|
+
// so linking by PENDING block in fork-choice is correct.
|
|
1399
|
+
const defaultStatus = this.protoArray.getDefaultVariant(nextRoot);
|
|
1400
|
+
if (defaultStatus === undefined) {
|
|
1401
|
+
throw Error(`No block for root ${nextRoot}`);
|
|
1402
|
+
}
|
|
1403
|
+
block = this.protoArray.getBlockReadonly(nextRoot, defaultStatus);
|
|
1205
1404
|
}
|
|
1206
1405
|
|
|
1207
1406
|
throw Error(`Not found dependent root for block slot ${block.slot}, epoch difference ${epochDifference}`);
|
|
@@ -1240,12 +1439,20 @@ export class ForkChoice implements IForkChoice {
|
|
|
1240
1439
|
return dataAvailabilityStatus;
|
|
1241
1440
|
}
|
|
1242
1441
|
|
|
1243
|
-
private
|
|
1442
|
+
private getPreGloasExecStatus(
|
|
1244
1443
|
executionStatus: MaybeValidExecutionStatus
|
|
1245
1444
|
): ExecutionStatus.Valid | ExecutionStatus.Syncing {
|
|
1246
|
-
if (executionStatus === ExecutionStatus.PreMerge)
|
|
1445
|
+
if (executionStatus === ExecutionStatus.PreMerge || executionStatus === ExecutionStatus.PayloadSeparated)
|
|
1247
1446
|
throw Error(
|
|
1248
|
-
`Invalid post-merge execution status: expected: ${ExecutionStatus.Syncing} or ${ExecutionStatus.Valid}
|
|
1447
|
+
`Invalid post-merge execution status: expected: ${ExecutionStatus.Syncing} or ${ExecutionStatus.Valid}, got ${executionStatus}`
|
|
1448
|
+
);
|
|
1449
|
+
return executionStatus;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
private getPostGloasExecStatus(executionStatus: MaybeValidExecutionStatus): ExecutionStatus.PayloadSeparated {
|
|
1453
|
+
if (executionStatus !== ExecutionStatus.PayloadSeparated)
|
|
1454
|
+
throw Error(
|
|
1455
|
+
`Invalid post-gloas execution status: expected: ${ExecutionStatus.PayloadSeparated}, got ${executionStatus}`
|
|
1249
1456
|
);
|
|
1250
1457
|
return executionStatus;
|
|
1251
1458
|
}
|
|
@@ -1267,12 +1474,12 @@ export class ForkChoice implements IForkChoice {
|
|
|
1267
1474
|
*
|
|
1268
1475
|
* **`on_tick`**
|
|
1269
1476
|
* May need the justified balances of:
|
|
1270
|
-
* - unrealizedJustified: Already available in `
|
|
1477
|
+
* - unrealizedJustified: Already available in `CheckpointWithPayloadAndBalance`
|
|
1271
1478
|
* Since this balances are already available the getter is just `() => balances`, without cache interaction
|
|
1272
1479
|
*/
|
|
1273
1480
|
private updateCheckpoints(
|
|
1274
|
-
justifiedCheckpoint:
|
|
1275
|
-
finalizedCheckpoint:
|
|
1481
|
+
justifiedCheckpoint: CheckpointWithPayloadStatus,
|
|
1482
|
+
finalizedCheckpoint: CheckpointWithPayloadStatus,
|
|
1276
1483
|
getJustifiedBalances: () => JustifiedBalances
|
|
1277
1484
|
): void {
|
|
1278
1485
|
// Update justified checkpoint.
|
|
@@ -1292,8 +1499,8 @@ export class ForkChoice implements IForkChoice {
|
|
|
1292
1499
|
* Update unrealized checkpoints in store if necessary
|
|
1293
1500
|
*/
|
|
1294
1501
|
private updateUnrealizedCheckpoints(
|
|
1295
|
-
unrealizedJustifiedCheckpoint:
|
|
1296
|
-
unrealizedFinalizedCheckpoint:
|
|
1502
|
+
unrealizedJustifiedCheckpoint: CheckpointWithPayloadStatus,
|
|
1503
|
+
unrealizedFinalizedCheckpoint: CheckpointWithPayloadStatus,
|
|
1297
1504
|
getJustifiedBalances: () => JustifiedBalances
|
|
1298
1505
|
): void {
|
|
1299
1506
|
if (unrealizedJustifiedCheckpoint.epoch > this.fcStore.unrealizedJustified.checkpoint.epoch) {
|
|
@@ -1412,7 +1619,9 @@ export class ForkChoice implements IForkChoice {
|
|
|
1412
1619
|
//
|
|
1413
1620
|
// Attestations must be for a known block. If the block is unknown, we simply drop the
|
|
1414
1621
|
// attestation and do not delay consideration for later.
|
|
1415
|
-
|
|
1622
|
+
// We don't care which variant it is, just need to find the block
|
|
1623
|
+
const defaultStatus = this.protoArray.getDefaultVariant(beaconBlockRootHex);
|
|
1624
|
+
const block = defaultStatus !== undefined ? this.protoArray.getBlock(beaconBlockRootHex, defaultStatus) : undefined;
|
|
1416
1625
|
if (!block) {
|
|
1417
1626
|
throw new ForkChoiceError({
|
|
1418
1627
|
code: ForkChoiceErrorCode.INVALID_ATTESTATION,
|
|
@@ -1453,33 +1662,57 @@ export class ForkChoice implements IForkChoice {
|
|
|
1453
1662
|
});
|
|
1454
1663
|
}
|
|
1455
1664
|
|
|
1665
|
+
// For Gloas blocks, attestation index must be 0 or 1
|
|
1666
|
+
if (isGloasBlock(block) && attestationData.index !== 0 && attestationData.index !== 1) {
|
|
1667
|
+
throw new ForkChoiceError({
|
|
1668
|
+
code: ForkChoiceErrorCode.INVALID_ATTESTATION,
|
|
1669
|
+
err: {
|
|
1670
|
+
code: InvalidAttestationCode.INVALID_DATA_INDEX,
|
|
1671
|
+
index: attestationData.index,
|
|
1672
|
+
},
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1456
1676
|
this.validatedAttestationDatas.add(attDataRoot);
|
|
1457
1677
|
}
|
|
1458
1678
|
|
|
1459
1679
|
/**
|
|
1460
1680
|
* Add a validator's latest message to the tracked votes.
|
|
1461
1681
|
* Always sync voteCurrentIndices and voteNextIndices so that it'll not throw in computeDeltas()
|
|
1682
|
+
*
|
|
1683
|
+
* Modified for Gloas to accept slot and payloadPresent.
|
|
1684
|
+
* Spec: gloas/fork-choice.md#modified-update_latest_messages
|
|
1685
|
+
*
|
|
1686
|
+
* For backward compatibility with Fulu (pre-Gloas):
|
|
1687
|
+
* - Accepts both epoch-derived and slot parameters
|
|
1688
|
+
* - payloadPresent defaults to true for Fulu (payloads embedded in blocks)
|
|
1462
1689
|
*/
|
|
1463
|
-
private addLatestMessage(
|
|
1690
|
+
private addLatestMessage(
|
|
1691
|
+
validatorIndex: ValidatorIndex,
|
|
1692
|
+
nextSlot: Slot,
|
|
1693
|
+
nextRoot: RootHex,
|
|
1694
|
+
nextPayloadStatus: PayloadStatus
|
|
1695
|
+
): void {
|
|
1464
1696
|
// should not happen, attestation is validated before this step
|
|
1465
|
-
|
|
1697
|
+
// Get the node index for the voted block
|
|
1698
|
+
const nextIndex = this.protoArray.getNodeIndexByRootAndStatus(nextRoot, nextPayloadStatus);
|
|
1466
1699
|
if (nextIndex === undefined) {
|
|
1467
|
-
throw new Error(`Could not find proto index for nextRoot ${nextRoot}`);
|
|
1700
|
+
throw new Error(`Could not find proto index for nextRoot ${nextRoot} with payloadStatus ${nextPayloadStatus}`);
|
|
1468
1701
|
}
|
|
1469
1702
|
|
|
1470
1703
|
// ensure there is no undefined entries in Votes arrays
|
|
1471
|
-
if (this.
|
|
1472
|
-
for (let i = this.
|
|
1473
|
-
this.
|
|
1704
|
+
if (this.voteNextSlots.length < validatorIndex + 1) {
|
|
1705
|
+
for (let i = this.voteNextSlots.length; i < validatorIndex + 1; i++) {
|
|
1706
|
+
this.voteNextSlots[i] = INIT_VOTE_SLOT;
|
|
1474
1707
|
this.voteCurrentIndices[i] = this.voteNextIndices[i] = NULL_VOTE_INDEX;
|
|
1475
1708
|
}
|
|
1476
1709
|
}
|
|
1477
1710
|
|
|
1478
|
-
const
|
|
1479
|
-
if (
|
|
1711
|
+
const existingNextSlot = this.voteNextSlots[validatorIndex];
|
|
1712
|
+
if (existingNextSlot === INIT_VOTE_SLOT || computeEpochAtSlot(nextSlot) > computeEpochAtSlot(existingNextSlot)) {
|
|
1480
1713
|
// nextIndex is transfered to currentIndex in computeDeltas()
|
|
1481
1714
|
this.voteNextIndices[validatorIndex] = nextIndex;
|
|
1482
|
-
this.
|
|
1715
|
+
this.voteNextSlots[validatorIndex] = nextSlot;
|
|
1483
1716
|
}
|
|
1484
1717
|
// else its an old vote, don't count it
|
|
1485
1718
|
}
|
|
@@ -1491,18 +1724,17 @@ export class ForkChoice implements IForkChoice {
|
|
|
1491
1724
|
private processAttestationQueue(): void {
|
|
1492
1725
|
const currentSlot = this.fcStore.currentSlot;
|
|
1493
1726
|
for (const [slot, byRoot] of this.queuedAttestations.entries()) {
|
|
1494
|
-
const targetEpoch = computeEpochAtSlot(slot);
|
|
1495
1727
|
if (slot < currentSlot) {
|
|
1496
1728
|
this.queuedAttestations.delete(slot);
|
|
1497
|
-
for (const [blockRoot,
|
|
1729
|
+
for (const [blockRoot, validatorVotes] of byRoot.entries()) {
|
|
1498
1730
|
const blockRootHex = blockRoot;
|
|
1499
|
-
for (const validatorIndex of
|
|
1731
|
+
for (const [validatorIndex, payloadStatus] of validatorVotes.entries()) {
|
|
1500
1732
|
// equivocatingIndices was checked in onAttestation
|
|
1501
|
-
this.addLatestMessage(validatorIndex,
|
|
1733
|
+
this.addLatestMessage(validatorIndex, slot, blockRootHex, payloadStatus);
|
|
1502
1734
|
}
|
|
1503
1735
|
|
|
1504
1736
|
if (slot === currentSlot - 1) {
|
|
1505
|
-
this.queuedAttestationsPreviousSlot +=
|
|
1737
|
+
this.queuedAttestationsPreviousSlot += validatorVotes.size;
|
|
1506
1738
|
}
|
|
1507
1739
|
}
|
|
1508
1740
|
} else {
|
|
@@ -1616,3 +1848,31 @@ export function getCommitteeFraction(
|
|
|
1616
1848
|
const committeeWeight = Math.floor(justifiedTotalActiveBalanceByIncrement / config.slotsPerEpoch);
|
|
1617
1849
|
return Math.floor((committeeWeight * config.committeePercent) / 100);
|
|
1618
1850
|
}
|
|
1851
|
+
|
|
1852
|
+
/**
|
|
1853
|
+
* Get the payload status for a checkpoint.
|
|
1854
|
+
*
|
|
1855
|
+
* Pre-Gloas: always FULL (payload embedded in block)
|
|
1856
|
+
* Gloas: determined by state.execution_payload_availability
|
|
1857
|
+
*
|
|
1858
|
+
* @param state - The state to check execution_payload_availability
|
|
1859
|
+
* @param checkpointEpoch - The epoch of the checkpoint
|
|
1860
|
+
*/
|
|
1861
|
+
export function getCheckpointPayloadStatus(state: CachedBeaconStateAllForks, checkpointEpoch: number): PayloadStatus {
|
|
1862
|
+
// Compute checkpoint slot first to determine the correct fork
|
|
1863
|
+
const checkpointSlot = computeStartSlotAtEpoch(checkpointEpoch);
|
|
1864
|
+
const fork = state.config.getForkSeq(checkpointSlot);
|
|
1865
|
+
|
|
1866
|
+
// Pre-Gloas: always FULL
|
|
1867
|
+
if (fork < ForkSeq.gloas) {
|
|
1868
|
+
return PayloadStatus.FULL;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
// For Gloas, check state.execution_payload_availability
|
|
1872
|
+
// - For non-skipped slots at checkpoint: returns false (EMPTY) since payload hasn't arrived yet
|
|
1873
|
+
// - For skipped slots at checkpoint: returns the actual availability status from state
|
|
1874
|
+
const gloasState = state as CachedBeaconStateGloas;
|
|
1875
|
+
const payloadAvailable = gloasState.executionPayloadAvailability.get(checkpointSlot % SLOTS_PER_HISTORICAL_ROOT);
|
|
1876
|
+
|
|
1877
|
+
return payloadAvailable ? PayloadStatus.FULL : PayloadStatus.EMPTY;
|
|
1878
|
+
}
|