@lodestar/fork-choice 1.43.0-dev.6b7eebbf6d → 1.43.0-dev.6f485b1b61
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 +6 -5
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +16 -16
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +2 -1
- package/lib/forkChoice/interface.d.ts.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 +9 -1
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +56 -34
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +7 -7
- package/src/forkChoice/forkChoice.ts +18 -19
- package/src/forkChoice/interface.ts +3 -1
- package/src/protoArray/interface.ts +8 -7
- package/src/protoArray/protoArray.ts +68 -36
|
@@ -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.5/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
|
|
|
@@ -221,6 +214,11 @@ export class ProtoArray {
|
|
|
221
214
|
return PayloadStatus.FULL;
|
|
222
215
|
}
|
|
223
216
|
|
|
217
|
+
// Genesis block has no parent in the proto array
|
|
218
|
+
if (block.parentRoot === HEX_ZERO_HASH) {
|
|
219
|
+
return PayloadStatus.FULL;
|
|
220
|
+
}
|
|
221
|
+
|
|
224
222
|
const parentBlock = this.getBlockHexAndBlockHash(block.parentRoot, parentBlockHash);
|
|
225
223
|
if (parentBlock == null) {
|
|
226
224
|
throw new ProtoArrayError({
|
|
@@ -259,38 +257,43 @@ export class ProtoArray {
|
|
|
259
257
|
}
|
|
260
258
|
|
|
261
259
|
/**
|
|
262
|
-
* Returns an EMPTY or FULL
|
|
260
|
+
* Returns the node index of an EMPTY or FULL variant matching block root and block hash.
|
|
261
|
+
* Pre-gloas: checks the single variant. Post-gloas: prefers FULL, falls back to EMPTY.
|
|
263
262
|
*/
|
|
264
|
-
|
|
263
|
+
getNodeIndexByRootAndBlockHash(blockRoot: RootHex, blockHash: RootHex): number | undefined {
|
|
265
264
|
const variantIndices = this.indices.get(blockRoot);
|
|
266
265
|
if (variantIndices === undefined) {
|
|
267
|
-
return
|
|
266
|
+
return undefined;
|
|
268
267
|
}
|
|
269
268
|
|
|
270
269
|
// Pre-Gloas
|
|
271
270
|
if (!Array.isArray(variantIndices)) {
|
|
272
|
-
|
|
273
|
-
return node.executionPayloadBlockHash === blockHash ? node : null;
|
|
271
|
+
return this.nodes[variantIndices].executionPayloadBlockHash === blockHash ? variantIndices : undefined;
|
|
274
272
|
}
|
|
275
273
|
|
|
276
|
-
// Post-Gloas,
|
|
274
|
+
// Post-Gloas, prefer FULL then EMPTY
|
|
277
275
|
const fullNodeIndex = variantIndices[PayloadStatus.FULL];
|
|
278
|
-
if (fullNodeIndex !== undefined) {
|
|
279
|
-
|
|
280
|
-
if (fullNode && fullNode.executionPayloadBlockHash === blockHash) {
|
|
281
|
-
return fullNode;
|
|
282
|
-
}
|
|
276
|
+
if (fullNodeIndex !== undefined && this.nodes[fullNodeIndex].executionPayloadBlockHash === blockHash) {
|
|
277
|
+
return fullNodeIndex;
|
|
283
278
|
}
|
|
284
279
|
|
|
285
|
-
const
|
|
286
|
-
if (
|
|
287
|
-
return
|
|
280
|
+
const emptyNodeIndex = variantIndices[PayloadStatus.EMPTY];
|
|
281
|
+
if (this.nodes[emptyNodeIndex].executionPayloadBlockHash === blockHash) {
|
|
282
|
+
return emptyNodeIndex;
|
|
288
283
|
}
|
|
289
284
|
|
|
290
285
|
// PENDING is the same to EMPTY so not likely we can return it
|
|
291
286
|
// also it's only specific for fork-choice
|
|
292
287
|
|
|
293
|
-
return
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Returns an EMPTY or FULL `ProtoBlock` that has matching block root and block hash
|
|
293
|
+
*/
|
|
294
|
+
getBlockHexAndBlockHash(blockRoot: RootHex, blockHash: RootHex): ProtoBlock | null {
|
|
295
|
+
const idx = this.getNodeIndexByRootAndBlockHash(blockRoot, blockHash);
|
|
296
|
+
return idx !== undefined ? this.nodes[idx] : null;
|
|
294
297
|
}
|
|
295
298
|
|
|
296
299
|
/**
|
|
@@ -556,7 +559,8 @@ export class ProtoArray {
|
|
|
556
559
|
executionPayloadBlockHash: RootHex,
|
|
557
560
|
executionPayloadNumber: number,
|
|
558
561
|
proposerBoostRoot: RootHex | null,
|
|
559
|
-
executionStatus: PayloadExecutionStatus
|
|
562
|
+
executionStatus: PayloadExecutionStatus,
|
|
563
|
+
dataAvailabilityStatus: DataAvailabilityStatus
|
|
560
564
|
): void {
|
|
561
565
|
// First check if block exists
|
|
562
566
|
const variants = this.indices.get(blockRoot);
|
|
@@ -598,7 +602,7 @@ export class ProtoArray {
|
|
|
598
602
|
});
|
|
599
603
|
}
|
|
600
604
|
|
|
601
|
-
// Create FULL variant as a child of PENDING (sibling to EMPTY)
|
|
605
|
+
// Create FULL variant as a child of PENDING (sibling to EMPTY).
|
|
602
606
|
const fullNode: ProtoNode = {
|
|
603
607
|
...pendingNode,
|
|
604
608
|
parent: pendingIndex, // Points to own PENDING (same as EMPTY)
|
|
@@ -606,10 +610,10 @@ export class ProtoArray {
|
|
|
606
610
|
weight: 0,
|
|
607
611
|
bestChild: undefined,
|
|
608
612
|
bestDescendant: undefined,
|
|
609
|
-
// TODO GLOAS: handle optimistic sync
|
|
610
613
|
executionStatus,
|
|
611
614
|
executionPayloadBlockHash,
|
|
612
615
|
executionPayloadNumber,
|
|
616
|
+
dataAvailabilityStatus,
|
|
613
617
|
};
|
|
614
618
|
|
|
615
619
|
const fullIndex = this.nodes.length;
|
|
@@ -618,6 +622,12 @@ export class ProtoArray {
|
|
|
618
622
|
// Add FULL variant to the indices array
|
|
619
623
|
variants[PayloadStatus.FULL] = fullIndex;
|
|
620
624
|
|
|
625
|
+
if (executionStatus === ExecutionStatus.Valid) {
|
|
626
|
+
// Walk up from FULL's parent (its own PENDING). FULL is already Valid; the loop breaks
|
|
627
|
+
// immediately if we start at FULL. Same pattern as pre-gloas onBlock at line ~533.
|
|
628
|
+
this.propagateValidExecutionStatusByIndex(pendingIndex);
|
|
629
|
+
}
|
|
630
|
+
|
|
621
631
|
// Update bestChild for PENDING node (may now prefer FULL over EMPTY)
|
|
622
632
|
this.maybeUpdateBestChildAndDescendant(pendingIndex, fullIndex, currentSlot, proposerBoostRoot);
|
|
623
633
|
}
|
|
@@ -646,6 +656,16 @@ export class ProtoArray {
|
|
|
646
656
|
}
|
|
647
657
|
}
|
|
648
658
|
|
|
659
|
+
getPTCVotes(blockRootHex: RootHex): BitArray | null {
|
|
660
|
+
const votes = this.ptcVotes.get(blockRootHex);
|
|
661
|
+
if (votes === undefined) {
|
|
662
|
+
// Block not found or not a Gloas block
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return votes;
|
|
667
|
+
}
|
|
668
|
+
|
|
649
669
|
/**
|
|
650
670
|
* Check if execution payload for a block is timely
|
|
651
671
|
* Spec: gloas/fork-choice.md#new-is_payload_timely
|
|
@@ -782,11 +802,15 @@ export class ProtoArray {
|
|
|
782
802
|
// Mark chain ii) as Invalid if LVH is found and non null, else only invalidate invalid_payload
|
|
783
803
|
// if its in fcU.
|
|
784
804
|
//
|
|
785
|
-
const {invalidateFromParentBlockRoot, latestValidExecHash} = execResponse;
|
|
786
|
-
|
|
787
|
-
|
|
805
|
+
const {invalidateFromParentBlockRoot, invalidateFromParentBlockHash, latestValidExecHash} = execResponse;
|
|
806
|
+
const invalidateFromParentIndex = this.getNodeIndexByRootAndBlockHash(
|
|
807
|
+
invalidateFromParentBlockRoot,
|
|
808
|
+
invalidateFromParentBlockHash
|
|
809
|
+
);
|
|
788
810
|
if (invalidateFromParentIndex === undefined) {
|
|
789
|
-
throw Error(
|
|
811
|
+
throw Error(
|
|
812
|
+
`Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} invalidateFromParentBlockHash=${invalidateFromParentBlockHash} in forkChoice`
|
|
813
|
+
);
|
|
790
814
|
}
|
|
791
815
|
const latestValidHashIndex =
|
|
792
816
|
latestValidExecHash !== null ? this.getNodeIndexFromLVH(latestValidExecHash, invalidateFromParentIndex) : null;
|
|
@@ -822,12 +846,6 @@ export class ProtoArray {
|
|
|
822
846
|
if (node.executionStatus === ExecutionStatus.PreMerge || node.executionStatus === ExecutionStatus.Valid) {
|
|
823
847
|
break;
|
|
824
848
|
}
|
|
825
|
-
// If PayloadSeparated, that means the node is either PENDING or EMPTY, there could be
|
|
826
|
-
// some ancestor still has syncing status.
|
|
827
|
-
if (node.executionStatus === ExecutionStatus.PayloadSeparated) {
|
|
828
|
-
nodeIndex = node.parent;
|
|
829
|
-
continue;
|
|
830
|
-
}
|
|
831
849
|
this.validateNodeByIndex(nodeIndex);
|
|
832
850
|
nodeIndex = node.parent;
|
|
833
851
|
}
|
|
@@ -925,6 +943,13 @@ export class ProtoArray {
|
|
|
925
943
|
invalidNode.executionStatus = ExecutionStatus.Invalid;
|
|
926
944
|
invalidNode.bestChild = undefined;
|
|
927
945
|
invalidNode.bestDescendant = undefined;
|
|
946
|
+
// Gloas: PENDING and sibling EMPTY share chain status, flip together
|
|
947
|
+
if (invalidNode.payloadStatus === PayloadStatus.PENDING) {
|
|
948
|
+
const variants = this.indices.get(invalidNode.blockRoot);
|
|
949
|
+
if (Array.isArray(variants)) {
|
|
950
|
+
this.invalidateNodeByIndex(variants[PayloadStatus.EMPTY]);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
928
953
|
|
|
929
954
|
return invalidNode;
|
|
930
955
|
}
|
|
@@ -946,6 +971,13 @@ export class ProtoArray {
|
|
|
946
971
|
if (validNode.executionStatus === ExecutionStatus.Syncing) {
|
|
947
972
|
validNode.executionStatus = ExecutionStatus.Valid;
|
|
948
973
|
}
|
|
974
|
+
// Gloas: PENDING and sibling EMPTY share chain status, flip together
|
|
975
|
+
if (validNode.payloadStatus === PayloadStatus.PENDING) {
|
|
976
|
+
const variants = this.indices.get(validNode.blockRoot);
|
|
977
|
+
if (Array.isArray(variants)) {
|
|
978
|
+
this.validateNodeByIndex(variants[PayloadStatus.EMPTY]);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
949
981
|
return validNode;
|
|
950
982
|
}
|
|
951
983
|
|