Skip to content

Commit 2740f92

Browse files
authored
fix: correct proposer boost logic and getParentNodeIndex for gloas (#9165)
**Motivation** Two fork-choice bugs in the Gloas (ePBS) protoArray implementation: 1. **Proposer boost double-counting** — In Gloas, PENDING/EMPTY/FULL variants share the same blockRoot. The proposer boost was applied to every matching variant, causing a 2x boost that propagated up to PENDING via delta back-propagation. 2. **getParentNodeIndex() crash at finalized boundary** — getParentPayloadStatus() throws UNKNOWN_PARENT_BLOCK when traversal reaches the finalized ProtoBlock (whose parent has been pruned). This caused unexpected errors during score propagation and ancestor iteration. **Description** - Only apply proposer boost to the EMPTY variant (for Gloas) or FULL (for pre-Gloas) — the variant that validator votes target — to avoid double-counting. - Catch UNKNOWN_PARENT_BLOCK in getParentNodeIndex() and return undefined (consistent with the method's number | undefined return type), re-throw all other errors. **AI Assistance Disclosure** Used Claude Code to assist with the PR creation. --------- Co-authored-by: Tuyen Nguyen <[email protected]>
1 parent aef3645 commit 2740f92

1 file changed

Lines changed: 18 additions & 5 deletions

File tree

packages/fork-choice/src/protoArray/protoArray.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -362,9 +362,15 @@ export class ProtoArray {
362362
continue;
363363
}
364364

365-
const currentBoost = proposerBoost && proposerBoost.root === node.blockRoot ? proposerBoost.score : 0;
365+
// For Gloas blocks, PENDING/EMPTY/FULL all share the same blockRoot.
366+
// Only apply proposer boost to PENDING (for Gloas) or FULL (for pre-Gloas) — to avoid
367+
// double-counting the boost across variants during delta back-propagation, and to keep
368+
// the boost neutral with respect to EMPTY vs FULL selection.
369+
const isBoostVariant = isGloasBlock(node) ? node.payloadStatus === PayloadStatus.PENDING : true; // pre-Gloas has only FULL, always boost
370+
const currentBoost =
371+
proposerBoost && proposerBoost.root === node.blockRoot && isBoostVariant ? proposerBoost.score : 0;
366372
const previousBoost =
367-
this.previousProposerBoost && this.previousProposerBoost.root === node.blockRoot
373+
this.previousProposerBoost && this.previousProposerBoost.root === node.blockRoot && isBoostVariant
368374
? this.previousProposerBoost.score
369375
: 0;
370376

@@ -1486,9 +1492,16 @@ export class ProtoArray {
14861492
*/
14871493
private getParentNodeIndex(node: ProtoNode): number | undefined {
14881494
if (isGloasBlock(node)) {
1489-
// Use getParentPayloadStatus for Gloas blocks to get correct EMPTY/FULL variant
1490-
const parentPayloadStatus = this.getParentPayloadStatus(node);
1491-
return this.getNodeIndexByRootAndStatus(node.parentRoot, parentPayloadStatus);
1495+
// Traversal may reach the finalized ProtoBlock, should not throw error in that case
1496+
try {
1497+
const parentPayloadStatus = this.getParentPayloadStatus(node);
1498+
return this.getNodeIndexByRootAndStatus(node.parentRoot, parentPayloadStatus);
1499+
} catch (e) {
1500+
if (e instanceof ProtoArrayError && e.type.code === ProtoArrayErrorCode.UNKNOWN_PARENT_BLOCK) {
1501+
return undefined;
1502+
}
1503+
throw e;
1504+
}
14921505
}
14931506
// Simple parent traversal for pre-Gloas blocks (includes fork transition)
14941507
return node.parent;

0 commit comments

Comments
 (0)