@lodestar/fork-choice 1.43.0-dev.dfb984e779 → 1.43.0-dev.e3c96e7a79

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";
@@ -214,6 +214,11 @@ export class ProtoArray {
214
214
  return PayloadStatus.FULL;
215
215
  }
216
216
 
217
+ // Genesis block has no parent in the proto array
218
+ if (block.parentRoot === HEX_ZERO_HASH) {
219
+ return PayloadStatus.FULL;
220
+ }
221
+
217
222
  const parentBlock = this.getBlockHexAndBlockHash(block.parentRoot, parentBlockHash);
218
223
  if (parentBlock == null) {
219
224
  throw new ProtoArrayError({
@@ -252,38 +257,43 @@ export class ProtoArray {
252
257
  }
253
258
 
254
259
  /**
255
- * 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.
256
262
  */
257
- getBlockHexAndBlockHash(blockRoot: RootHex, blockHash: RootHex): ProtoBlock | null {
263
+ getNodeIndexByRootAndBlockHash(blockRoot: RootHex, blockHash: RootHex): number | undefined {
258
264
  const variantIndices = this.indices.get(blockRoot);
259
265
  if (variantIndices === undefined) {
260
- return null;
266
+ return undefined;
261
267
  }
262
268
 
263
269
  // Pre-Gloas
264
270
  if (!Array.isArray(variantIndices)) {
265
- const node = this.nodes[variantIndices];
266
- return node.executionPayloadBlockHash === blockHash ? node : null;
271
+ return this.nodes[variantIndices].executionPayloadBlockHash === blockHash ? variantIndices : undefined;
267
272
  }
268
273
 
269
- // Post-Gloas, check empty and full variants
274
+ // Post-Gloas, prefer FULL then EMPTY
270
275
  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
- }
276
+ if (fullNodeIndex !== undefined && this.nodes[fullNodeIndex].executionPayloadBlockHash === blockHash) {
277
+ return fullNodeIndex;
276
278
  }
277
279
 
278
- const emptyNode = this.nodes[variantIndices[PayloadStatus.EMPTY]];
279
- if (emptyNode && emptyNode.executionPayloadBlockHash === blockHash) {
280
- return emptyNode;
280
+ const emptyNodeIndex = variantIndices[PayloadStatus.EMPTY];
281
+ if (this.nodes[emptyNodeIndex].executionPayloadBlockHash === blockHash) {
282
+ return emptyNodeIndex;
281
283
  }
282
284
 
283
285
  // PENDING is the same to EMPTY so not likely we can return it
284
286
  // also it's only specific for fork-choice
285
287
 
286
- 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;
287
297
  }
288
298
 
289
299
  /**
@@ -549,7 +559,8 @@ export class ProtoArray {
549
559
  executionPayloadBlockHash: RootHex,
550
560
  executionPayloadNumber: number,
551
561
  proposerBoostRoot: RootHex | null,
552
- executionStatus: PayloadExecutionStatus
562
+ executionStatus: PayloadExecutionStatus,
563
+ dataAvailabilityStatus: DataAvailabilityStatus
553
564
  ): void {
554
565
  // First check if block exists
555
566
  const variants = this.indices.get(blockRoot);
@@ -591,7 +602,7 @@ export class ProtoArray {
591
602
  });
592
603
  }
593
604
 
594
- // Create FULL variant as a child of PENDING (sibling to EMPTY)
605
+ // Create FULL variant as a child of PENDING (sibling to EMPTY).
595
606
  const fullNode: ProtoNode = {
596
607
  ...pendingNode,
597
608
  parent: pendingIndex, // Points to own PENDING (same as EMPTY)
@@ -599,10 +610,10 @@ export class ProtoArray {
599
610
  weight: 0,
600
611
  bestChild: undefined,
601
612
  bestDescendant: undefined,
602
- // TODO GLOAS: handle optimistic sync
603
613
  executionStatus,
604
614
  executionPayloadBlockHash,
605
615
  executionPayloadNumber,
616
+ dataAvailabilityStatus,
606
617
  };
607
618
 
608
619
  const fullIndex = this.nodes.length;
@@ -611,6 +622,12 @@ export class ProtoArray {
611
622
  // Add FULL variant to the indices array
612
623
  variants[PayloadStatus.FULL] = fullIndex;
613
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
+
614
631
  // Update bestChild for PENDING node (may now prefer FULL over EMPTY)
615
632
  this.maybeUpdateBestChildAndDescendant(pendingIndex, fullIndex, currentSlot, proposerBoostRoot);
616
633
  }
@@ -639,6 +656,16 @@ export class ProtoArray {
639
656
  }
640
657
  }
641
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
+
642
669
  /**
643
670
  * Check if execution payload for a block is timely
644
671
  * Spec: gloas/fork-choice.md#new-is_payload_timely
@@ -775,11 +802,15 @@ export class ProtoArray {
775
802
  // Mark chain ii) as Invalid if LVH is found and non null, else only invalidate invalid_payload
776
803
  // if its in fcU.
777
804
  //
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);
805
+ const {invalidateFromParentBlockRoot, invalidateFromParentBlockHash, latestValidExecHash} = execResponse;
806
+ const invalidateFromParentIndex = this.getNodeIndexByRootAndBlockHash(
807
+ invalidateFromParentBlockRoot,
808
+ invalidateFromParentBlockHash
809
+ );
781
810
  if (invalidateFromParentIndex === undefined) {
782
- throw Error(`Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} in forkChoice`);
811
+ throw Error(
812
+ `Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} invalidateFromParentBlockHash=${invalidateFromParentBlockHash} in forkChoice`
813
+ );
783
814
  }
784
815
  const latestValidHashIndex =
785
816
  latestValidExecHash !== null ? this.getNodeIndexFromLVH(latestValidExecHash, invalidateFromParentIndex) : null;
@@ -815,12 +846,6 @@ export class ProtoArray {
815
846
  if (node.executionStatus === ExecutionStatus.PreMerge || node.executionStatus === ExecutionStatus.Valid) {
816
847
  break;
817
848
  }
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
849
  this.validateNodeByIndex(nodeIndex);
825
850
  nodeIndex = node.parent;
826
851
  }
@@ -918,6 +943,13 @@ export class ProtoArray {
918
943
  invalidNode.executionStatus = ExecutionStatus.Invalid;
919
944
  invalidNode.bestChild = undefined;
920
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
+ }
921
953
 
922
954
  return invalidNode;
923
955
  }
@@ -939,6 +971,13 @@ export class ProtoArray {
939
971
  if (validNode.executionStatus === ExecutionStatus.Syncing) {
940
972
  validNode.executionStatus = ExecutionStatus.Valid;
941
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
+ }
942
981
  return validNode;
943
982
  }
944
983