From 589c65901af921487b21a0056dd23ee875cf2a76 Mon Sep 17 00:00:00 2001 From: lodekeeper Date: Sun, 15 Mar 2026 12:22:27 +0000 Subject: [PATCH] feat: reorder PayloadStatus enum (consensus-specs#4948) Reorder PayloadStatus enum values to match spec change: - EMPTY=0, FULL=1, PENDING=2 (was PENDING=0, EMPTY=1, FULL=2) Update GloasVariantIndices type to fixed-length tuple: - [EMPTY, FULL|undefined, PENDING] indexed by enum values All variant array accesses now use explicit PayloadStatus.* enum values instead of raw numeric indices for clarity and correctness. --- .../fork-choice/src/protoArray/interface.ts | 6 +-- .../fork-choice/src/protoArray/protoArray.ts | 39 +++++++++---------- .../test/unit/forkChoice/forkChoice.test.ts | 2 +- .../protoArray/executionStatusUpdates.test.ts | 2 +- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/packages/fork-choice/src/protoArray/interface.ts b/packages/fork-choice/src/protoArray/interface.ts index 5c42e6f33c03..87952a894183 100644 --- a/packages/fork-choice/src/protoArray/interface.ts +++ b/packages/fork-choice/src/protoArray/interface.ts @@ -41,9 +41,9 @@ export enum ExecutionStatus { * Spec: gloas/fork-choice.md#constants */ export enum PayloadStatus { - PENDING = 0, - EMPTY = 1, - FULL = 2, + EMPTY = 0, + FULL = 1, + PENDING = 2, } /** diff --git a/packages/fork-choice/src/protoArray/protoArray.ts b/packages/fork-choice/src/protoArray/protoArray.ts index 683ba6b38145..39019018e03f 100644 --- a/packages/fork-choice/src/protoArray/protoArray.ts +++ b/packages/fork-choice/src/protoArray/protoArray.ts @@ -29,11 +29,12 @@ const ZERO_HASH_HEX = toRootHex(Buffer.alloc(32, 0)); /** Pre-Gloas: single element, FULL index (for backward compatibility) */ type PreGloasVariantIndex = number; /** - * Post-Gloas: array length is 2 or 3 - * - Length 2: [PENDING_INDEX, EMPTY_INDEX] when payload hasn't arrived yet - * - Length 3: [PENDING_INDEX, EMPTY_INDEX, FULL_INDEX] when payload has arrived + * Post-Gloas: always length 3, indexed by PayloadStatus enum value + * - [EMPTY_INDEX, FULL_INDEX | undefined, PENDING_INDEX] + * - PayloadStatus.EMPTY=0, PayloadStatus.FULL=1, PayloadStatus.PENDING=2 + * - FULL starts as undefined until execution payload arrives */ -type GloasVariantIndices = [number, number] | [number, number, number]; +type GloasVariantIndices = [number, number | undefined, number]; type VariantIndices = PreGloasVariantIndex | GloasVariantIndices; export class ProtoArray { @@ -48,10 +49,10 @@ export class ProtoArray { /** * Maps block root to array of node indices for each payload status variant * - * Array structure: [PENDING, EMPTY, FULL] where indices correspond to PayloadStatus enum values - * - number[0] = PENDING variant index (PayloadStatus.PENDING = 0) - * - number[1] = EMPTY variant index (PayloadStatus.EMPTY = 1) - * - number[2] = FULL variant index (PayloadStatus.FULL = 2) + * Array structure: [EMPTY, FULL, PENDING] where indices correspond to PayloadStatus enum values + * - number[0] = EMPTY variant index (PayloadStatus.EMPTY = 0) + * - number[1] = FULL variant index (PayloadStatus.FULL = 1) + * - number[2] = PENDING variant index (PayloadStatus.PENDING = 2) * * Note: undefined array elements indicate that variant doesn't exist for this block */ @@ -439,11 +440,11 @@ export class ProtoArray { // Check if parent exists by getting variants array const parentVariants = this.indices.get(block.parentRoot); if (parentVariants != null) { - const anyParentIndex = Array.isArray(parentVariants) ? parentVariants[0] : parentVariants; + const anyParentIndex = Array.isArray(parentVariants) ? parentVariants[PayloadStatus.EMPTY] : parentVariants; const anyParentNode = this.nodes[anyParentIndex]; if (!isGloasBlock(anyParentNode)) { - // Fork transition: parent is pre-Gloas, so it only has FULL variant at variants[0] + // Fork transition: parent is pre-Gloas, so it only has FULL variant parentIndex = anyParentIndex; } else { // Both blocks are Gloas: determine which parent payload status to extend @@ -479,9 +480,9 @@ export class ProtoArray { const emptyIndex = this.nodes.length; this.nodes.push(emptyNode); - // Store both variants in the indices array - // [PENDING, EMPTY, undefined] - FULL will be added later if payload arrives - this.indices.set(block.blockRoot, [pendingIndex, emptyIndex]); + // Store variants in the indices array indexed by PayloadStatus enum values + // [EMPTY=0, FULL=1(undefined), PENDING=2] - FULL will be added when payload arrives + this.indices.set(block.blockRoot, [emptyIndex, undefined, pendingIndex]); // Update bestChild pointers if (parentIndex !== undefined) { @@ -1397,7 +1398,7 @@ export class ProtoArray { * Gloas: Returns (root, payloadStatus) based on actual node state */ getAncestor(blockRoot: RootHex, ancestorSlot: Slot): ProtoNode { - // Get any variant to check the block (use variants[0]) + // Get any variant to check the block (EMPTY for Gloas, FULL for pre-Gloas) const variantOrArr = this.indices.get(blockRoot); if (variantOrArr == null) { throw new ForkChoiceError({ @@ -1406,13 +1407,11 @@ export class ProtoArray { }); } - const blockIndex = Array.isArray(variantOrArr) ? variantOrArr[0] : variantOrArr; + const blockIndex = Array.isArray(variantOrArr) ? variantOrArr[PayloadStatus.EMPTY] : variantOrArr; const block = this.nodes[blockIndex]; - // If block is at or before queried slot, return PENDING variant (or FULL for pre-Gloas) + // If block is at or before queried slot, return any variant (EMPTY for Gloas, FULL for pre-Gloas) if (block.slot <= ancestorSlot) { - // For pre-Gloas: only FULL exists at variants[0] - // For Gloas: PENDING is at variants[0] return block; } @@ -1428,7 +1427,7 @@ export class ProtoArray { }); } - let parentIndex = Array.isArray(parentVariants) ? parentVariants[0] : parentVariants; + let parentIndex = Array.isArray(parentVariants) ? parentVariants[PayloadStatus.EMPTY] : parentVariants; let parentBlock = this.nodes[parentIndex]; // Walk backwards while parent.slot > ancestorSlot @@ -1444,7 +1443,7 @@ export class ProtoArray { }); } - parentIndex = Array.isArray(nextParentVariants) ? nextParentVariants[0] : nextParentVariants; + parentIndex = Array.isArray(nextParentVariants) ? nextParentVariants[PayloadStatus.EMPTY] : nextParentVariants; parentBlock = this.nodes[parentIndex]; } diff --git a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts index c2ab249d3093..e773976c712f 100644 --- a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts @@ -161,7 +161,7 @@ describe("Forkchoice", () => { bestDescendant: undefined, parent: 0, weight: 0, - payloadStatus: 2, // Pre-Gloas blocks always have PAYLOAD_STATUS_FULL + payloadStatus: PayloadStatus.FULL, // Pre-Gloas blocks always have PAYLOAD_STATUS_FULL }); }); diff --git a/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts b/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts index 9e8b79261ad0..30ed1916d648 100644 --- a/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts +++ b/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts @@ -304,7 +304,7 @@ describe("executionStatus / invalidate all postmerge chain", () => { // findHead returns ProtoNode // For pre-Gloas blocks, this should have blockRoot "0" and payloadStatus FULL (2) expect(fcHead.blockRoot).toBe("0"); - expect(fcHead.payloadStatus).toBe(2); // PayloadStatus.FULL + expect(fcHead.payloadStatus).toBe(PayloadStatus.FULL); }); });