@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.
@@ -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 `ProtoBlock` that has matching block root and block hash
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
- getBlockHexAndBlockHash(blockRoot: RootHex, blockHash: RootHex): ProtoBlock | null {
263
+ getNodeIndexByRootAndBlockHash(blockRoot: RootHex, blockHash: RootHex): number | undefined {
265
264
  const variantIndices = this.indices.get(blockRoot);
266
265
  if (variantIndices === undefined) {
267
- return null;
266
+ return undefined;
268
267
  }
269
268
 
270
269
  // Pre-Gloas
271
270
  if (!Array.isArray(variantIndices)) {
272
- const node = this.nodes[variantIndices];
273
- return node.executionPayloadBlockHash === blockHash ? node : null;
271
+ return this.nodes[variantIndices].executionPayloadBlockHash === blockHash ? variantIndices : undefined;
274
272
  }
275
273
 
276
- // Post-Gloas, check empty and full variants
274
+ // Post-Gloas, prefer FULL then EMPTY
277
275
  const fullNodeIndex = variantIndices[PayloadStatus.FULL];
278
- if (fullNodeIndex !== undefined) {
279
- const fullNode = this.nodes[fullNodeIndex];
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 emptyNode = this.nodes[variantIndices[PayloadStatus.EMPTY]];
286
- if (emptyNode && emptyNode.executionPayloadBlockHash === blockHash) {
287
- return emptyNode;
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 null;
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
- // TODO GLOAS: verify if getting the default/canonical node index is correct here
787
- const invalidateFromParentIndex = this.getDefaultNodeIndex(invalidateFromParentBlockRoot);
805
+ const {invalidateFromParentBlockRoot, invalidateFromParentBlockHash, latestValidExecHash} = execResponse;
806
+ const invalidateFromParentIndex = this.getNodeIndexByRootAndBlockHash(
807
+ invalidateFromParentBlockRoot,
808
+ invalidateFromParentBlockHash
809
+ );
788
810
  if (invalidateFromParentIndex === undefined) {
789
- throw Error(`Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} in forkChoice`);
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