@lodestar/fork-choice 1.43.0-dev.ca1fc40294 → 1.43.0-dev.d901f86f98
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 -2
- 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 -43
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +7 -7
- package/src/forkChoice/forkChoice.ts +18 -21
- package/src/forkChoice/interface.ts +3 -3
- package/src/protoArray/interface.ts +8 -7
- package/src/protoArray/protoArray.ts +68 -55
|
@@ -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,30 +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
|
-
// In the spec, we have payload_states = {anchor_root: anchor_state.copy()}
|
|
119
|
-
// which means the anchor's "payload" is considered received
|
|
120
|
-
// Without FULL, blocks extending FULL from the anchor would be orphaned.
|
|
121
|
-
// TODO GLOAS: This is a bug in the spec. Keep this to pass the current spec test
|
|
122
|
-
// for now. Need to remove this when we work on v1.7.0-alpha.5
|
|
123
|
-
if (block.executionPayloadBlockHash !== null) {
|
|
124
|
-
protoArray.onExecutionPayload(
|
|
125
|
-
block.blockRoot,
|
|
126
|
-
currentSlot,
|
|
127
|
-
block.executionPayloadBlockHash,
|
|
128
|
-
(block as {executionPayloadNumber?: number}).executionPayloadNumber ?? 0,
|
|
129
|
-
block.stateRoot,
|
|
130
|
-
null,
|
|
131
|
-
ExecutionStatus.Valid
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
112
|
return protoArray;
|
|
137
113
|
}
|
|
138
114
|
|
|
@@ -238,6 +214,11 @@ export class ProtoArray {
|
|
|
238
214
|
return PayloadStatus.FULL;
|
|
239
215
|
}
|
|
240
216
|
|
|
217
|
+
// Genesis block has no parent in the proto array
|
|
218
|
+
if (block.parentRoot === HEX_ZERO_HASH) {
|
|
219
|
+
return PayloadStatus.FULL;
|
|
220
|
+
}
|
|
221
|
+
|
|
241
222
|
const parentBlock = this.getBlockHexAndBlockHash(block.parentRoot, parentBlockHash);
|
|
242
223
|
if (parentBlock == null) {
|
|
243
224
|
throw new ProtoArrayError({
|
|
@@ -276,38 +257,43 @@ export class ProtoArray {
|
|
|
276
257
|
}
|
|
277
258
|
|
|
278
259
|
/**
|
|
279
|
-
* 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.
|
|
280
262
|
*/
|
|
281
|
-
|
|
263
|
+
getNodeIndexByRootAndBlockHash(blockRoot: RootHex, blockHash: RootHex): number | undefined {
|
|
282
264
|
const variantIndices = this.indices.get(blockRoot);
|
|
283
265
|
if (variantIndices === undefined) {
|
|
284
|
-
return
|
|
266
|
+
return undefined;
|
|
285
267
|
}
|
|
286
268
|
|
|
287
269
|
// Pre-Gloas
|
|
288
270
|
if (!Array.isArray(variantIndices)) {
|
|
289
|
-
|
|
290
|
-
return node.executionPayloadBlockHash === blockHash ? node : null;
|
|
271
|
+
return this.nodes[variantIndices].executionPayloadBlockHash === blockHash ? variantIndices : undefined;
|
|
291
272
|
}
|
|
292
273
|
|
|
293
|
-
// Post-Gloas,
|
|
274
|
+
// Post-Gloas, prefer FULL then EMPTY
|
|
294
275
|
const fullNodeIndex = variantIndices[PayloadStatus.FULL];
|
|
295
|
-
if (fullNodeIndex !== undefined) {
|
|
296
|
-
|
|
297
|
-
if (fullNode && fullNode.executionPayloadBlockHash === blockHash) {
|
|
298
|
-
return fullNode;
|
|
299
|
-
}
|
|
276
|
+
if (fullNodeIndex !== undefined && this.nodes[fullNodeIndex].executionPayloadBlockHash === blockHash) {
|
|
277
|
+
return fullNodeIndex;
|
|
300
278
|
}
|
|
301
279
|
|
|
302
|
-
const
|
|
303
|
-
if (
|
|
304
|
-
return
|
|
280
|
+
const emptyNodeIndex = variantIndices[PayloadStatus.EMPTY];
|
|
281
|
+
if (this.nodes[emptyNodeIndex].executionPayloadBlockHash === blockHash) {
|
|
282
|
+
return emptyNodeIndex;
|
|
305
283
|
}
|
|
306
284
|
|
|
307
285
|
// PENDING is the same to EMPTY so not likely we can return it
|
|
308
286
|
// also it's only specific for fork-choice
|
|
309
287
|
|
|
310
|
-
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;
|
|
311
297
|
}
|
|
312
298
|
|
|
313
299
|
/**
|
|
@@ -572,9 +558,9 @@ export class ProtoArray {
|
|
|
572
558
|
currentSlot: Slot,
|
|
573
559
|
executionPayloadBlockHash: RootHex,
|
|
574
560
|
executionPayloadNumber: number,
|
|
575
|
-
executionPayloadStateRoot: RootHex,
|
|
576
561
|
proposerBoostRoot: RootHex | null,
|
|
577
|
-
executionStatus: PayloadExecutionStatus
|
|
562
|
+
executionStatus: PayloadExecutionStatus,
|
|
563
|
+
dataAvailabilityStatus: DataAvailabilityStatus
|
|
578
564
|
): void {
|
|
579
565
|
// First check if block exists
|
|
580
566
|
const variants = this.indices.get(blockRoot);
|
|
@@ -616,7 +602,7 @@ export class ProtoArray {
|
|
|
616
602
|
});
|
|
617
603
|
}
|
|
618
604
|
|
|
619
|
-
// Create FULL variant as a child of PENDING (sibling to EMPTY)
|
|
605
|
+
// Create FULL variant as a child of PENDING (sibling to EMPTY).
|
|
620
606
|
const fullNode: ProtoNode = {
|
|
621
607
|
...pendingNode,
|
|
622
608
|
parent: pendingIndex, // Points to own PENDING (same as EMPTY)
|
|
@@ -624,11 +610,10 @@ export class ProtoArray {
|
|
|
624
610
|
weight: 0,
|
|
625
611
|
bestChild: undefined,
|
|
626
612
|
bestDescendant: undefined,
|
|
627
|
-
// TODO GLOAS: handle optimistic sync
|
|
628
613
|
executionStatus,
|
|
629
614
|
executionPayloadBlockHash,
|
|
630
615
|
executionPayloadNumber,
|
|
631
|
-
|
|
616
|
+
dataAvailabilityStatus,
|
|
632
617
|
};
|
|
633
618
|
|
|
634
619
|
const fullIndex = this.nodes.length;
|
|
@@ -637,6 +622,12 @@ export class ProtoArray {
|
|
|
637
622
|
// Add FULL variant to the indices array
|
|
638
623
|
variants[PayloadStatus.FULL] = fullIndex;
|
|
639
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
|
+
|
|
640
631
|
// Update bestChild for PENDING node (may now prefer FULL over EMPTY)
|
|
641
632
|
this.maybeUpdateBestChildAndDescendant(pendingIndex, fullIndex, currentSlot, proposerBoostRoot);
|
|
642
633
|
}
|
|
@@ -665,6 +656,16 @@ export class ProtoArray {
|
|
|
665
656
|
}
|
|
666
657
|
}
|
|
667
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
|
+
|
|
668
669
|
/**
|
|
669
670
|
* Check if execution payload for a block is timely
|
|
670
671
|
* Spec: gloas/fork-choice.md#new-is_payload_timely
|
|
@@ -801,11 +802,15 @@ export class ProtoArray {
|
|
|
801
802
|
// Mark chain ii) as Invalid if LVH is found and non null, else only invalidate invalid_payload
|
|
802
803
|
// if its in fcU.
|
|
803
804
|
//
|
|
804
|
-
const {invalidateFromParentBlockRoot, latestValidExecHash} = execResponse;
|
|
805
|
-
|
|
806
|
-
|
|
805
|
+
const {invalidateFromParentBlockRoot, invalidateFromParentBlockHash, latestValidExecHash} = execResponse;
|
|
806
|
+
const invalidateFromParentIndex = this.getNodeIndexByRootAndBlockHash(
|
|
807
|
+
invalidateFromParentBlockRoot,
|
|
808
|
+
invalidateFromParentBlockHash
|
|
809
|
+
);
|
|
807
810
|
if (invalidateFromParentIndex === undefined) {
|
|
808
|
-
throw Error(
|
|
811
|
+
throw Error(
|
|
812
|
+
`Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} invalidateFromParentBlockHash=${invalidateFromParentBlockHash} in forkChoice`
|
|
813
|
+
);
|
|
809
814
|
}
|
|
810
815
|
const latestValidHashIndex =
|
|
811
816
|
latestValidExecHash !== null ? this.getNodeIndexFromLVH(latestValidExecHash, invalidateFromParentIndex) : null;
|
|
@@ -841,12 +846,6 @@ export class ProtoArray {
|
|
|
841
846
|
if (node.executionStatus === ExecutionStatus.PreMerge || node.executionStatus === ExecutionStatus.Valid) {
|
|
842
847
|
break;
|
|
843
848
|
}
|
|
844
|
-
// If PayloadSeparated, that means the node is either PENDING or EMPTY, there could be
|
|
845
|
-
// some ancestor still has syncing status.
|
|
846
|
-
if (node.executionStatus === ExecutionStatus.PayloadSeparated) {
|
|
847
|
-
nodeIndex = node.parent;
|
|
848
|
-
continue;
|
|
849
|
-
}
|
|
850
849
|
this.validateNodeByIndex(nodeIndex);
|
|
851
850
|
nodeIndex = node.parent;
|
|
852
851
|
}
|
|
@@ -944,6 +943,13 @@ export class ProtoArray {
|
|
|
944
943
|
invalidNode.executionStatus = ExecutionStatus.Invalid;
|
|
945
944
|
invalidNode.bestChild = undefined;
|
|
946
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
|
+
}
|
|
947
953
|
|
|
948
954
|
return invalidNode;
|
|
949
955
|
}
|
|
@@ -965,6 +971,13 @@ export class ProtoArray {
|
|
|
965
971
|
if (validNode.executionStatus === ExecutionStatus.Syncing) {
|
|
966
972
|
validNode.executionStatus = ExecutionStatus.Valid;
|
|
967
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
|
+
}
|
|
968
981
|
return validNode;
|
|
969
982
|
}
|
|
970
983
|
|