@lodestar/fork-choice 1.43.0-dev.3d6ae250a4 → 1.43.0-dev.3fe3b04cbd

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";
@@ -252,38 +252,43 @@ export class ProtoArray {
252
252
  }
253
253
 
254
254
  /**
255
- * Returns an EMPTY or FULL `ProtoBlock` that has matching block root and block hash
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.
256
257
  */
257
- getBlockHexAndBlockHash(blockRoot: RootHex, blockHash: RootHex): ProtoBlock | null {
258
+ getNodeIndexByRootAndBlockHash(blockRoot: RootHex, blockHash: RootHex): number | undefined {
258
259
  const variantIndices = this.indices.get(blockRoot);
259
260
  if (variantIndices === undefined) {
260
- return null;
261
+ return undefined;
261
262
  }
262
263
 
263
264
  // Pre-Gloas
264
265
  if (!Array.isArray(variantIndices)) {
265
- const node = this.nodes[variantIndices];
266
- return node.executionPayloadBlockHash === blockHash ? node : null;
266
+ return this.nodes[variantIndices].executionPayloadBlockHash === blockHash ? variantIndices : undefined;
267
267
  }
268
268
 
269
- // Post-Gloas, check empty and full variants
269
+ // Post-Gloas, prefer FULL then EMPTY
270
270
  const fullNodeIndex = variantIndices[PayloadStatus.FULL];
271
- if (fullNodeIndex !== undefined) {
272
- const fullNode = this.nodes[fullNodeIndex];
273
- if (fullNode && fullNode.executionPayloadBlockHash === blockHash) {
274
- return fullNode;
275
- }
271
+ if (fullNodeIndex !== undefined && this.nodes[fullNodeIndex].executionPayloadBlockHash === blockHash) {
272
+ return fullNodeIndex;
276
273
  }
277
274
 
278
- const emptyNode = this.nodes[variantIndices[PayloadStatus.EMPTY]];
279
- if (emptyNode && emptyNode.executionPayloadBlockHash === blockHash) {
280
- return emptyNode;
275
+ const emptyNodeIndex = variantIndices[PayloadStatus.EMPTY];
276
+ if (this.nodes[emptyNodeIndex].executionPayloadBlockHash === blockHash) {
277
+ return emptyNodeIndex;
281
278
  }
282
279
 
283
280
  // PENDING is the same to EMPTY so not likely we can return it
284
281
  // also it's only specific for fork-choice
285
282
 
286
- return null;
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;
287
292
  }
288
293
 
289
294
  /**
@@ -549,7 +554,8 @@ export class ProtoArray {
549
554
  executionPayloadBlockHash: RootHex,
550
555
  executionPayloadNumber: number,
551
556
  proposerBoostRoot: RootHex | null,
552
- executionStatus: PayloadExecutionStatus
557
+ executionStatus: PayloadExecutionStatus,
558
+ dataAvailabilityStatus: DataAvailabilityStatus
553
559
  ): void {
554
560
  // First check if block exists
555
561
  const variants = this.indices.get(blockRoot);
@@ -591,7 +597,7 @@ export class ProtoArray {
591
597
  });
592
598
  }
593
599
 
594
- // Create FULL variant as a child of PENDING (sibling to EMPTY)
600
+ // Create FULL variant as a child of PENDING (sibling to EMPTY).
595
601
  const fullNode: ProtoNode = {
596
602
  ...pendingNode,
597
603
  parent: pendingIndex, // Points to own PENDING (same as EMPTY)
@@ -599,10 +605,10 @@ export class ProtoArray {
599
605
  weight: 0,
600
606
  bestChild: undefined,
601
607
  bestDescendant: undefined,
602
- // TODO GLOAS: handle optimistic sync
603
608
  executionStatus,
604
609
  executionPayloadBlockHash,
605
610
  executionPayloadNumber,
611
+ dataAvailabilityStatus,
606
612
  };
607
613
 
608
614
  const fullIndex = this.nodes.length;
@@ -611,6 +617,12 @@ export class ProtoArray {
611
617
  // Add FULL variant to the indices array
612
618
  variants[PayloadStatus.FULL] = fullIndex;
613
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
+
614
626
  // Update bestChild for PENDING node (may now prefer FULL over EMPTY)
615
627
  this.maybeUpdateBestChildAndDescendant(pendingIndex, fullIndex, currentSlot, proposerBoostRoot);
616
628
  }
@@ -639,6 +651,16 @@ export class ProtoArray {
639
651
  }
640
652
  }
641
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
+
642
664
  /**
643
665
  * Check if execution payload for a block is timely
644
666
  * Spec: gloas/fork-choice.md#new-is_payload_timely
@@ -775,11 +797,15 @@ export class ProtoArray {
775
797
  // Mark chain ii) as Invalid if LVH is found and non null, else only invalidate invalid_payload
776
798
  // if its in fcU.
777
799
  //
778
- const {invalidateFromParentBlockRoot, latestValidExecHash} = execResponse;
779
- // TODO GLOAS: verify if getting the default/canonical node index is correct here
780
- const invalidateFromParentIndex = this.getDefaultNodeIndex(invalidateFromParentBlockRoot);
800
+ const {invalidateFromParentBlockRoot, invalidateFromParentBlockHash, latestValidExecHash} = execResponse;
801
+ const invalidateFromParentIndex = this.getNodeIndexByRootAndBlockHash(
802
+ invalidateFromParentBlockRoot,
803
+ invalidateFromParentBlockHash
804
+ );
781
805
  if (invalidateFromParentIndex === undefined) {
782
- throw Error(`Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} in forkChoice`);
806
+ throw Error(
807
+ `Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} invalidateFromParentBlockHash=${invalidateFromParentBlockHash} in forkChoice`
808
+ );
783
809
  }
784
810
  const latestValidHashIndex =
785
811
  latestValidExecHash !== null ? this.getNodeIndexFromLVH(latestValidExecHash, invalidateFromParentIndex) : null;
@@ -815,12 +841,6 @@ export class ProtoArray {
815
841
  if (node.executionStatus === ExecutionStatus.PreMerge || node.executionStatus === ExecutionStatus.Valid) {
816
842
  break;
817
843
  }
818
- // If PayloadSeparated, that means the node is either PENDING or EMPTY, there could be
819
- // some ancestor still has syncing status.
820
- if (node.executionStatus === ExecutionStatus.PayloadSeparated) {
821
- nodeIndex = node.parent;
822
- continue;
823
- }
824
844
  this.validateNodeByIndex(nodeIndex);
825
845
  nodeIndex = node.parent;
826
846
  }
@@ -918,6 +938,13 @@ export class ProtoArray {
918
938
  invalidNode.executionStatus = ExecutionStatus.Invalid;
919
939
  invalidNode.bestChild = undefined;
920
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
+ }
921
948
 
922
949
  return invalidNode;
923
950
  }
@@ -939,6 +966,13 @@ export class ProtoArray {
939
966
  if (validNode.executionStatus === ExecutionStatus.Syncing) {
940
967
  validNode.executionStatus = ExecutionStatus.Valid;
941
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
+ }
942
976
  return validNode;
943
977
  }
944
978