@lodestar/fork-choice 1.43.0-dev.2870b59b6a → 1.43.0-dev.2fba242f5d
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/forkChoice.d.ts +27 -20
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +64 -77
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +27 -8
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/forkChoice/store.d.ts +16 -40
- package/lib/forkChoice/store.d.ts.map +1 -1
- package/lib/forkChoice/store.js +4 -22
- package/lib/forkChoice/store.js.map +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/protoArray/interface.d.ts +7 -8
- package/lib/protoArray/interface.d.ts.map +1 -1
- package/lib/protoArray/interface.js +3 -4
- package/lib/protoArray/interface.js.map +1 -1
- package/lib/protoArray/protoArray.d.ts +10 -2
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +59 -40
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +8 -8
- package/src/forkChoice/forkChoice.ts +78 -126
- package/src/forkChoice/interface.ts +28 -9
- package/src/forkChoice/store.ts +20 -52
- package/src/index.ts +3 -9
- package/src/protoArray/interface.ts +8 -7
- package/src/protoArray/protoArray.ts +71 -43
|
@@ -24,16 +24,15 @@ export type VoteIndex = number;
|
|
|
24
24
|
* - Syncing: EL is syncing, payload validity unknown (optimistic sync)
|
|
25
25
|
* - PreMerge: Block is from before The Merge, no execution payload exists
|
|
26
26
|
* - Invalid: Execution payload was invalidated by the EL (post-import status)
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
*
|
|
28
|
+
* For gloas blocks the PENDING/EMPTY variants inherit `executionStatus` from the parent's chain
|
|
29
|
+
* (Valid/Syncing/PreMerge); the FULL variant carries the EL response for this block's own payload.
|
|
30
30
|
*/
|
|
31
31
|
export enum ExecutionStatus {
|
|
32
32
|
Valid = "Valid",
|
|
33
33
|
Syncing = "Syncing",
|
|
34
34
|
PreMerge = "PreMerge",
|
|
35
35
|
Invalid = "Invalid",
|
|
36
|
-
PayloadSeparated = "PayloadSeparated",
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
/**
|
|
@@ -61,19 +60,21 @@ export type LVHInvalidResponse = {
|
|
|
61
60
|
executionStatus: ExecutionStatus.Invalid;
|
|
62
61
|
latestValidExecHash: RootHex | null;
|
|
63
62
|
invalidateFromParentBlockRoot: RootHex;
|
|
63
|
+
// EL block hash from invalid block's bid (gloas) or payload's parentHash (pre-gloas).
|
|
64
|
+
// Disambiguates which variant of the parent (FULL vs EMPTY) to invalidate from.
|
|
65
|
+
invalidateFromParentBlockHash: RootHex;
|
|
64
66
|
};
|
|
65
67
|
export type LVHExecResponse = LVHValidResponse | LVHInvalidResponse;
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
70
|
* Any execution status that is not definitively invalid.
|
|
69
|
-
*
|
|
70
|
-
* Post-Gloas: execution status must be PayloadSeparated (beacon block imported before its payload arrives via SignedExecutionPayloadEnvelope)
|
|
71
|
+
* Valid | Syncing | PreMerge
|
|
71
72
|
*/
|
|
72
73
|
export type BlockExecutionStatus = Exclude<ExecutionStatus, ExecutionStatus.Invalid>;
|
|
73
74
|
|
|
74
75
|
/**
|
|
75
76
|
* Execution status for a block whose execution payload is present and has been submitted to the EL.
|
|
76
|
-
* Used post-Gloas when transitioning a
|
|
77
|
+
* Used post-Gloas when transitioning a PENDING block to FULL via onExecutionPayload().
|
|
77
78
|
*/
|
|
78
79
|
export type PayloadExecutionStatus = ExecutionStatus.Valid | ExecutionStatus.Syncing;
|
|
79
80
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {BitArray} from "@chainsafe/ssz";
|
|
2
2
|
import {GENESIS_EPOCH, PTC_SIZE} from "@lodestar/params";
|
|
3
|
-
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
3
|
+
import {DataAvailabilityStatus, computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
4
4
|
import {Epoch, RootHex, Slot} from "@lodestar/types";
|
|
5
5
|
import {bitCount, toRootHex} from "@lodestar/utils";
|
|
6
6
|
import {ForkChoiceError, ForkChoiceErrorCode} from "../forkChoice/errors.js";
|
|
@@ -109,13 +109,6 @@ export class ProtoArray {
|
|
|
109
109
|
null
|
|
110
110
|
);
|
|
111
111
|
|
|
112
|
-
// Anchor block PTC votes must be all-true per spec get_forkchoice_store:
|
|
113
|
-
// payload_timeliness_vote={anchor_root: Vector[boolean, PTC_SIZE](True for _ in range(PTC_SIZE))}
|
|
114
|
-
// Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.4/specs/gloas/fork-choice.md#modified-get_forkchoice_store
|
|
115
|
-
if (protoArray.ptcVotes.has(block.blockRoot)) {
|
|
116
|
-
protoArray.ptcVotes.set(block.blockRoot, BitArray.fromBoolArray(Array.from({length: PTC_SIZE}, () => true)));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
112
|
return protoArray;
|
|
120
113
|
}
|
|
121
114
|
|
|
@@ -259,38 +252,43 @@ export class ProtoArray {
|
|
|
259
252
|
}
|
|
260
253
|
|
|
261
254
|
/**
|
|
262
|
-
* Returns an EMPTY or FULL
|
|
255
|
+
* Returns the node index of an EMPTY or FULL variant matching block root and block hash.
|
|
256
|
+
* Pre-gloas: checks the single variant. Post-gloas: prefers FULL, falls back to EMPTY.
|
|
263
257
|
*/
|
|
264
|
-
|
|
258
|
+
getNodeIndexByRootAndBlockHash(blockRoot: RootHex, blockHash: RootHex): number | undefined {
|
|
265
259
|
const variantIndices = this.indices.get(blockRoot);
|
|
266
260
|
if (variantIndices === undefined) {
|
|
267
|
-
return
|
|
261
|
+
return undefined;
|
|
268
262
|
}
|
|
269
263
|
|
|
270
264
|
// Pre-Gloas
|
|
271
265
|
if (!Array.isArray(variantIndices)) {
|
|
272
|
-
|
|
273
|
-
return node.executionPayloadBlockHash === blockHash ? node : null;
|
|
266
|
+
return this.nodes[variantIndices].executionPayloadBlockHash === blockHash ? variantIndices : undefined;
|
|
274
267
|
}
|
|
275
268
|
|
|
276
|
-
// Post-Gloas,
|
|
269
|
+
// Post-Gloas, prefer FULL then EMPTY
|
|
277
270
|
const fullNodeIndex = variantIndices[PayloadStatus.FULL];
|
|
278
|
-
if (fullNodeIndex !== undefined) {
|
|
279
|
-
|
|
280
|
-
if (fullNode && fullNode.executionPayloadBlockHash === blockHash) {
|
|
281
|
-
return fullNode;
|
|
282
|
-
}
|
|
271
|
+
if (fullNodeIndex !== undefined && this.nodes[fullNodeIndex].executionPayloadBlockHash === blockHash) {
|
|
272
|
+
return fullNodeIndex;
|
|
283
273
|
}
|
|
284
274
|
|
|
285
|
-
const
|
|
286
|
-
if (
|
|
287
|
-
return
|
|
275
|
+
const emptyNodeIndex = variantIndices[PayloadStatus.EMPTY];
|
|
276
|
+
if (this.nodes[emptyNodeIndex].executionPayloadBlockHash === blockHash) {
|
|
277
|
+
return emptyNodeIndex;
|
|
288
278
|
}
|
|
289
279
|
|
|
290
280
|
// PENDING is the same to EMPTY so not likely we can return it
|
|
291
281
|
// also it's only specific for fork-choice
|
|
292
282
|
|
|
293
|
-
return
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Returns an EMPTY or FULL `ProtoBlock` that has matching block root and block hash
|
|
288
|
+
*/
|
|
289
|
+
getBlockHexAndBlockHash(blockRoot: RootHex, blockHash: RootHex): ProtoBlock | null {
|
|
290
|
+
const idx = this.getNodeIndexByRootAndBlockHash(blockRoot, blockHash);
|
|
291
|
+
return idx !== undefined ? this.nodes[idx] : null;
|
|
294
292
|
}
|
|
295
293
|
|
|
296
294
|
/**
|
|
@@ -555,9 +553,9 @@ export class ProtoArray {
|
|
|
555
553
|
currentSlot: Slot,
|
|
556
554
|
executionPayloadBlockHash: RootHex,
|
|
557
555
|
executionPayloadNumber: number,
|
|
558
|
-
executionPayloadStateRoot: RootHex,
|
|
559
556
|
proposerBoostRoot: RootHex | null,
|
|
560
|
-
executionStatus: PayloadExecutionStatus
|
|
557
|
+
executionStatus: PayloadExecutionStatus,
|
|
558
|
+
dataAvailabilityStatus: DataAvailabilityStatus
|
|
561
559
|
): void {
|
|
562
560
|
// First check if block exists
|
|
563
561
|
const variants = this.indices.get(blockRoot);
|
|
@@ -599,7 +597,7 @@ export class ProtoArray {
|
|
|
599
597
|
});
|
|
600
598
|
}
|
|
601
599
|
|
|
602
|
-
// Create FULL variant as a child of PENDING (sibling to EMPTY)
|
|
600
|
+
// Create FULL variant as a child of PENDING (sibling to EMPTY).
|
|
603
601
|
const fullNode: ProtoNode = {
|
|
604
602
|
...pendingNode,
|
|
605
603
|
parent: pendingIndex, // Points to own PENDING (same as EMPTY)
|
|
@@ -607,11 +605,10 @@ export class ProtoArray {
|
|
|
607
605
|
weight: 0,
|
|
608
606
|
bestChild: undefined,
|
|
609
607
|
bestDescendant: undefined,
|
|
610
|
-
// TODO GLOAS: handle optimistic sync
|
|
611
608
|
executionStatus,
|
|
612
609
|
executionPayloadBlockHash,
|
|
613
610
|
executionPayloadNumber,
|
|
614
|
-
|
|
611
|
+
dataAvailabilityStatus,
|
|
615
612
|
};
|
|
616
613
|
|
|
617
614
|
const fullIndex = this.nodes.length;
|
|
@@ -620,6 +617,12 @@ export class ProtoArray {
|
|
|
620
617
|
// Add FULL variant to the indices array
|
|
621
618
|
variants[PayloadStatus.FULL] = fullIndex;
|
|
622
619
|
|
|
620
|
+
if (executionStatus === ExecutionStatus.Valid) {
|
|
621
|
+
// Walk up from FULL's parent (its own PENDING). FULL is already Valid; the loop breaks
|
|
622
|
+
// immediately if we start at FULL. Same pattern as pre-gloas onBlock at line ~533.
|
|
623
|
+
this.propagateValidExecutionStatusByIndex(pendingIndex);
|
|
624
|
+
}
|
|
625
|
+
|
|
623
626
|
// Update bestChild for PENDING node (may now prefer FULL over EMPTY)
|
|
624
627
|
this.maybeUpdateBestChildAndDescendant(pendingIndex, fullIndex, currentSlot, proposerBoostRoot);
|
|
625
628
|
}
|
|
@@ -648,6 +651,16 @@ export class ProtoArray {
|
|
|
648
651
|
}
|
|
649
652
|
}
|
|
650
653
|
|
|
654
|
+
getPTCVotes(blockRootHex: RootHex): BitArray | null {
|
|
655
|
+
const votes = this.ptcVotes.get(blockRootHex);
|
|
656
|
+
if (votes === undefined) {
|
|
657
|
+
// Block not found or not a Gloas block
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return votes;
|
|
662
|
+
}
|
|
663
|
+
|
|
651
664
|
/**
|
|
652
665
|
* Check if execution payload for a block is timely
|
|
653
666
|
* Spec: gloas/fork-choice.md#new-is_payload_timely
|
|
@@ -690,7 +703,7 @@ export class ProtoArray {
|
|
|
690
703
|
* Determine if we should extend the payload (prefer FULL over EMPTY)
|
|
691
704
|
* Spec: gloas/fork-choice.md#new-should_extend_payload
|
|
692
705
|
*
|
|
693
|
-
* Returns true if:
|
|
706
|
+
* Returns true if payload is verified (FULL variant exists) AND:
|
|
694
707
|
* 1. Payload is timely, OR
|
|
695
708
|
* 2. No proposer boost root (empty/zero hash), OR
|
|
696
709
|
* 3. Proposer boost root's parent is not this block, OR
|
|
@@ -700,6 +713,10 @@ export class ProtoArray {
|
|
|
700
713
|
* @param proposerBoostRoot - Current proposer boost root (from ForkChoice)
|
|
701
714
|
*/
|
|
702
715
|
shouldExtendPayload(blockRoot: RootHex, proposerBoostRoot: RootHex | null): boolean {
|
|
716
|
+
if (!this.hasPayload(blockRoot)) {
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
|
|
703
720
|
// Condition 1: Payload is timely
|
|
704
721
|
if (this.isPayloadTimely(blockRoot)) {
|
|
705
722
|
return true;
|
|
@@ -780,11 +797,15 @@ export class ProtoArray {
|
|
|
780
797
|
// Mark chain ii) as Invalid if LVH is found and non null, else only invalidate invalid_payload
|
|
781
798
|
// if its in fcU.
|
|
782
799
|
//
|
|
783
|
-
const {invalidateFromParentBlockRoot, latestValidExecHash} = execResponse;
|
|
784
|
-
|
|
785
|
-
|
|
800
|
+
const {invalidateFromParentBlockRoot, invalidateFromParentBlockHash, latestValidExecHash} = execResponse;
|
|
801
|
+
const invalidateFromParentIndex = this.getNodeIndexByRootAndBlockHash(
|
|
802
|
+
invalidateFromParentBlockRoot,
|
|
803
|
+
invalidateFromParentBlockHash
|
|
804
|
+
);
|
|
786
805
|
if (invalidateFromParentIndex === undefined) {
|
|
787
|
-
throw Error(
|
|
806
|
+
throw Error(
|
|
807
|
+
`Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} invalidateFromParentBlockHash=${invalidateFromParentBlockHash} in forkChoice`
|
|
808
|
+
);
|
|
788
809
|
}
|
|
789
810
|
const latestValidHashIndex =
|
|
790
811
|
latestValidExecHash !== null ? this.getNodeIndexFromLVH(latestValidExecHash, invalidateFromParentIndex) : null;
|
|
@@ -820,12 +841,6 @@ export class ProtoArray {
|
|
|
820
841
|
if (node.executionStatus === ExecutionStatus.PreMerge || node.executionStatus === ExecutionStatus.Valid) {
|
|
821
842
|
break;
|
|
822
843
|
}
|
|
823
|
-
// If PayloadSeparated, that means the node is either PENDING or EMPTY, there could be
|
|
824
|
-
// some ancestor still has syncing status.
|
|
825
|
-
if (node.executionStatus === ExecutionStatus.PayloadSeparated) {
|
|
826
|
-
nodeIndex = node.parent;
|
|
827
|
-
continue;
|
|
828
|
-
}
|
|
829
844
|
this.validateNodeByIndex(nodeIndex);
|
|
830
845
|
nodeIndex = node.parent;
|
|
831
846
|
}
|
|
@@ -923,6 +938,13 @@ export class ProtoArray {
|
|
|
923
938
|
invalidNode.executionStatus = ExecutionStatus.Invalid;
|
|
924
939
|
invalidNode.bestChild = undefined;
|
|
925
940
|
invalidNode.bestDescendant = undefined;
|
|
941
|
+
// Gloas: PENDING and sibling EMPTY share chain status, flip together
|
|
942
|
+
if (invalidNode.payloadStatus === PayloadStatus.PENDING) {
|
|
943
|
+
const variants = this.indices.get(invalidNode.blockRoot);
|
|
944
|
+
if (Array.isArray(variants)) {
|
|
945
|
+
this.invalidateNodeByIndex(variants[PayloadStatus.EMPTY]);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
926
948
|
|
|
927
949
|
return invalidNode;
|
|
928
950
|
}
|
|
@@ -944,6 +966,13 @@ export class ProtoArray {
|
|
|
944
966
|
if (validNode.executionStatus === ExecutionStatus.Syncing) {
|
|
945
967
|
validNode.executionStatus = ExecutionStatus.Valid;
|
|
946
968
|
}
|
|
969
|
+
// Gloas: PENDING and sibling EMPTY share chain status, flip together
|
|
970
|
+
if (validNode.payloadStatus === PayloadStatus.PENDING) {
|
|
971
|
+
const variants = this.indices.get(validNode.blockRoot);
|
|
972
|
+
if (Array.isArray(variants)) {
|
|
973
|
+
this.validateNodeByIndex(variants[PayloadStatus.EMPTY]);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
947
976
|
return validNode;
|
|
948
977
|
}
|
|
949
978
|
|
|
@@ -1655,10 +1684,9 @@ export class ProtoArray {
|
|
|
1655
1684
|
const ancestors: ProtoNode[] = [];
|
|
1656
1685
|
const nonAncestors: ProtoNode[] = [];
|
|
1657
1686
|
|
|
1658
|
-
//
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
}
|
|
1687
|
+
// caller of this method may pass default status
|
|
1688
|
+
// this is the only node that we accept PENDING
|
|
1689
|
+
ancestors.push(node);
|
|
1662
1690
|
|
|
1663
1691
|
let nodeIndex = startIndex;
|
|
1664
1692
|
while (node.parent !== undefined) {
|