@lodestar/fork-choice 1.43.0-dev.6641fd750e → 1.43.0-dev.66d2c102e3

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.
@@ -9,7 +9,7 @@ import {
9
9
  ProtoNode,
10
10
  } from "../protoArray/interface.js";
11
11
  import {UpdateAndGetHeadOpt} from "./forkChoice.js";
12
- import {CheckpointWithHex, CheckpointWithPayloadStatus} from "./store.js";
12
+ import {CheckpointWithHex} from "./store.js";
13
13
 
14
14
  export type CheckpointHex = {
15
15
  epoch: Epoch;
@@ -21,12 +21,12 @@ export type CheckpointsWithHex = {
21
21
  finalizedCheckpoint: CheckpointWithHex;
22
22
  };
23
23
 
24
- export type CheckpointWithPayloadAndBalance = {
25
- checkpoint: CheckpointWithPayloadStatus;
24
+ export type CheckpointWithBalance = {
25
+ checkpoint: CheckpointWithHex;
26
26
  balances: EffectiveBalanceIncrements;
27
27
  };
28
28
 
29
- export type CheckpointWithPayloadAndTotalBalance = CheckpointWithPayloadAndBalance & {
29
+ export type CheckpointWithTotalBalance = CheckpointWithBalance & {
30
30
  totalBalance: number;
31
31
  };
32
32
 
@@ -121,8 +121,8 @@ export interface IForkChoice {
121
121
  * Retrieve all nodes for the debug API.
122
122
  */
123
123
  getAllNodes(): ProtoNode[];
124
- getFinalizedCheckpoint(): CheckpointWithPayloadStatus;
125
- getJustifiedCheckpoint(): CheckpointWithPayloadStatus;
124
+ getFinalizedCheckpoint(): CheckpointWithHex;
125
+ getJustifiedCheckpoint(): CheckpointWithHex;
126
126
  /**
127
127
  * Add `block` to the fork choice DAG.
128
128
  *
@@ -198,13 +198,11 @@ export interface IForkChoice {
198
198
  * @param blockRoot - The beacon block root for which the payload arrived
199
199
  * @param executionPayloadBlockHash - The block hash of the execution payload
200
200
  * @param executionPayloadNumber - The block number of the execution payload
201
- * @param executionPayloadStateRoot - The execution payload state root ie. the root of post-state after processExecutionPayloadEnvelope()
202
201
  */
203
202
  onExecutionPayload(
204
203
  blockRoot: RootHex,
205
204
  executionPayloadBlockHash: RootHex,
206
205
  executionPayloadNumber: number,
207
- executionPayloadStateRoot: RootHex,
208
206
  executionStatus: PayloadExecutionStatus
209
207
  ): void;
210
208
  /**
@@ -241,6 +239,7 @@ export interface IForkChoice {
241
239
  getBlockDefaultStatus(blockRoot: Root): ProtoBlock | null;
242
240
  getBlockHexDefaultStatus(blockRoot: RootHex): ProtoBlock | null;
243
241
  getBlockHexAndBlockHash(blockRoot: RootHex, blockHash: RootHex): ProtoBlock | null;
242
+ shouldExtendPayload(blockRoot: RootHex): boolean;
244
243
  getFinalizedBlock(): ProtoBlock;
245
244
  getJustifiedBlock(): ProtoBlock;
246
245
  getFinalizedCheckpointSlot(): Slot;
@@ -272,11 +271,23 @@ export interface IForkChoice {
272
271
  getAllNonAncestorBlocks(blockRoot: RootHex, payloadStatus: PayloadStatus): ProtoBlock[];
273
272
  /**
274
273
  * Returns both ancestor and non-ancestor blocks in a single traversal.
274
+ *
275
+ * `ancestors` is the raw walk and includes the previous finalized block as its last element —
276
+ * callers that don't want the boundary should slice it off themselves.
275
277
  */
276
278
  getAllAncestorAndNonAncestorBlocks(
277
279
  blockRoot: RootHex,
278
280
  payloadStatus: PayloadStatus
279
281
  ): {ancestors: ProtoBlock[]; nonAncestors: ProtoBlock[]};
282
+ /**
283
+ * Same as `getAllAncestorAndNonAncestorBlocks` but resolves the default payload-status variant
284
+ * (FULL pre-Gloas, PENDING for Gloas) for the given root. Use when the caller holds a
285
+ * `CheckpointWithHex` / finalized root without a specific payload-status variant in mind.
286
+ */
287
+ getAllAncestorAndNonAncestorBlocksDefaultStatus(blockRoot: RootHex): {
288
+ ancestors: ProtoBlock[];
289
+ nonAncestors: ProtoBlock[];
290
+ };
280
291
  getCanonicalBlockByRoot(blockRoot: Root): ProtoBlock | null;
281
292
  getCanonicalBlockAtSlot(slot: Slot): ProtoBlock | null;
282
293
  getCanonicalBlockClosestLteSlot(slot: Slot): ProtoBlock | null;
@@ -288,6 +299,12 @@ export interface IForkChoice {
288
299
  * Iterates forward descendants of blockRoot. Does not yield blockRoot itself
289
300
  */
290
301
  forwardIterateDescendants(blockRoot: RootHex, payloadStatus: PayloadStatus): IterableIterator<ProtoBlock>;
302
+ /**
303
+ * Same as `forwardIterateDescendants` but resolves the default payload-status variant
304
+ * (FULL pre-Gloas, PENDING for Gloas) for the given root. Use when the caller holds a
305
+ * `CheckpointWithHex` / finalized root without a specific payload-status variant in mind.
306
+ */
307
+ forwardIterateDescendantsDefaultStatus(blockRoot: RootHex): IterableIterator<ProtoBlock>;
291
308
  getBlockSummariesByParentRoot(parentRoot: RootHex): ProtoBlock[];
292
309
  getBlockSummariesAtSlot(slot: Slot): ProtoBlock[];
293
310
  /** Returns the distance of common ancestor of nodes to the max of the newNode and the prevNode. */
@@ -1,8 +1,7 @@
1
1
  import {EffectiveBalanceIncrements, IBeaconStateView} from "@lodestar/state-transition";
2
2
  import {RootHex, Slot, ValidatorIndex, phase0} from "@lodestar/types";
3
3
  import {toRootHex} from "@lodestar/utils";
4
- import {PayloadStatus} from "../protoArray/interface.js";
5
- import {CheckpointWithPayloadAndBalance, CheckpointWithPayloadAndTotalBalance} from "./interface.js";
4
+ import {CheckpointWithBalance, CheckpointWithTotalBalance} from "./interface.js";
6
5
 
7
6
  /**
8
7
  * Stores checkpoints in a hybrid format:
@@ -11,15 +10,6 @@ import {CheckpointWithPayloadAndBalance, CheckpointWithPayloadAndTotalBalance} f
11
10
  */
12
11
  export type CheckpointWithHex = phase0.Checkpoint & {rootHex: RootHex};
13
12
 
14
- /**
15
- * Checkpoint with payload status for Gloas fork choice.
16
- * Used to track which variant (EMPTY or FULL) of the finalized/justified block to use.
17
- *
18
- * Pre-Gloas: payloadStatus is always FULL (payload embedded in block)
19
- * Gloas: determined by state.execution_payload_availability
20
- */
21
- export type CheckpointWithPayloadStatus = CheckpointWithHex & {payloadStatus: PayloadStatus};
22
-
23
13
  export type JustifiedBalances = EffectiveBalanceIncrements;
24
14
 
25
15
  /**
@@ -29,7 +19,7 @@ export type JustifiedBalances = EffectiveBalanceIncrements;
29
19
  * @param blockState state that declares justified checkpoint `checkpoint`
30
20
  */
31
21
  export type JustifiedBalancesGetter = (
32
- checkpoint: CheckpointWithPayloadStatus,
22
+ checkpoint: CheckpointWithHex,
33
23
  blockState: IBeaconStateView
34
24
  ) => JustifiedBalances;
35
25
 
@@ -47,11 +37,11 @@ export type JustifiedBalancesGetter = (
47
37
  */
48
38
  export interface IForkChoiceStore {
49
39
  currentSlot: Slot;
50
- get justified(): CheckpointWithPayloadAndTotalBalance;
51
- set justified(justified: CheckpointWithPayloadAndBalance);
52
- unrealizedJustified: CheckpointWithPayloadAndBalance;
53
- finalizedCheckpoint: CheckpointWithPayloadStatus;
54
- unrealizedFinalizedCheckpoint: CheckpointWithPayloadStatus;
40
+ get justified(): CheckpointWithTotalBalance;
41
+ set justified(justified: CheckpointWithBalance);
42
+ unrealizedJustified: CheckpointWithBalance;
43
+ finalizedCheckpoint: CheckpointWithHex;
44
+ unrealizedFinalizedCheckpoint: CheckpointWithHex;
55
45
  justifiedBalancesGetter: JustifiedBalancesGetter;
56
46
  equivocatingIndices: Set<ValidatorIndex>;
57
47
  }
@@ -60,10 +50,10 @@ export interface IForkChoiceStore {
60
50
  * IForkChoiceStore implementer which emits forkChoice events on updated justified and finalized checkpoints.
61
51
  */
62
52
  export class ForkChoiceStore implements IForkChoiceStore {
63
- private _justified: CheckpointWithPayloadAndTotalBalance;
64
- unrealizedJustified: CheckpointWithPayloadAndBalance;
65
- private _finalizedCheckpoint: CheckpointWithPayloadStatus;
66
- unrealizedFinalizedCheckpoint: CheckpointWithPayloadStatus;
53
+ private _justified: CheckpointWithTotalBalance;
54
+ unrealizedJustified: CheckpointWithBalance;
55
+ private _finalizedCheckpoint: CheckpointWithHex;
56
+ unrealizedFinalizedCheckpoint: CheckpointWithHex;
67
57
  equivocatingIndices = new Set<ValidatorIndex>();
68
58
  justifiedBalancesGetter: JustifiedBalancesGetter;
69
59
  currentSlot: Slot;
@@ -74,49 +64,37 @@ export class ForkChoiceStore implements IForkChoiceStore {
74
64
  finalizedCheckpoint: phase0.Checkpoint,
75
65
  justifiedBalances: EffectiveBalanceIncrements,
76
66
  justifiedBalancesGetter: JustifiedBalancesGetter,
77
- /**
78
- * Payload status for justified checkpoint.
79
- * Pre-Gloas: always FULL
80
- * Gloas: determined by state.execution_payload_availability
81
- */
82
- justifiedPayloadStatus: PayloadStatus,
83
- /**
84
- * Payload status for finalized checkpoint.
85
- * Pre-Gloas: always FULL
86
- * Gloas: determined by state.execution_payload_availability
87
- */
88
- finalizedPayloadStatus: PayloadStatus,
89
67
  private readonly events?: {
90
- onJustified: (cp: CheckpointWithPayloadStatus) => void;
91
- onFinalized: (cp: CheckpointWithPayloadStatus) => void;
68
+ onJustified: (cp: CheckpointWithHex) => void;
69
+ onFinalized: (cp: CheckpointWithHex) => void;
92
70
  }
93
71
  ) {
94
72
  this.justifiedBalancesGetter = justifiedBalancesGetter;
95
73
  this.currentSlot = currentSlot;
96
74
  const justified = {
97
- checkpoint: toCheckpointWithPayload(justifiedCheckpoint, justifiedPayloadStatus),
75
+ checkpoint: toCheckpointWithHex(justifiedCheckpoint),
98
76
  balances: justifiedBalances,
99
77
  totalBalance: computeTotalBalance(justifiedBalances),
100
78
  };
101
79
  this._justified = justified;
102
80
  this.unrealizedJustified = justified;
103
- this._finalizedCheckpoint = toCheckpointWithPayload(finalizedCheckpoint, finalizedPayloadStatus);
81
+ this._finalizedCheckpoint = toCheckpointWithHex(finalizedCheckpoint);
104
82
  this.unrealizedFinalizedCheckpoint = this._finalizedCheckpoint;
105
83
  }
106
84
 
107
- get justified(): CheckpointWithPayloadAndTotalBalance {
85
+ get justified(): CheckpointWithTotalBalance {
108
86
  return this._justified;
109
87
  }
110
- set justified(justified: CheckpointWithPayloadAndBalance) {
88
+ set justified(justified: CheckpointWithBalance) {
111
89
  this._justified = {...justified, totalBalance: computeTotalBalance(justified.balances)};
112
90
  this.events?.onJustified(justified.checkpoint);
113
91
  }
114
92
 
115
- get finalizedCheckpoint(): CheckpointWithPayloadStatus {
93
+ get finalizedCheckpoint(): CheckpointWithHex {
116
94
  return this._finalizedCheckpoint;
117
95
  }
118
- set finalizedCheckpoint(checkpoint: CheckpointWithPayloadStatus) {
119
- const cp = toCheckpointWithPayload(checkpoint, checkpoint.payloadStatus);
96
+ set finalizedCheckpoint(checkpoint: CheckpointWithHex) {
97
+ const cp = toCheckpointWithHex(checkpoint);
120
98
  this._finalizedCheckpoint = cp;
121
99
  this.events?.onFinalized(cp);
122
100
  }
@@ -133,16 +111,6 @@ export function toCheckpointWithHex(checkpoint: phase0.Checkpoint): CheckpointWi
133
111
  };
134
112
  }
135
113
 
136
- export function toCheckpointWithPayload(
137
- checkpoint: phase0.Checkpoint,
138
- payloadStatus: PayloadStatus
139
- ): CheckpointWithPayloadStatus {
140
- return {
141
- ...toCheckpointWithHex(checkpoint),
142
- payloadStatus,
143
- };
144
- }
145
-
146
114
  export function equalCheckpointWithHex(a: CheckpointWithHex, b: CheckpointWithHex): boolean {
147
115
  return a.epoch === b.epoch && a.rootHex === b.rootHex;
148
116
  }
package/src/index.ts CHANGED
@@ -6,17 +6,12 @@ export {
6
6
  type InvalidBlock,
7
7
  InvalidBlockCode,
8
8
  } from "./forkChoice/errors.js";
9
- export {
10
- ForkChoice,
11
- type ForkChoiceOpts,
12
- UpdateHeadOpt,
13
- getCheckpointPayloadStatus,
14
- } from "./forkChoice/forkChoice.js";
9
+ export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt} from "./forkChoice/forkChoice.js";
15
10
  export {
16
11
  type AncestorResult,
17
12
  AncestorStatus,
18
- type CheckpointWithPayloadAndBalance,
19
- type CheckpointWithPayloadAndTotalBalance,
13
+ type CheckpointWithBalance,
14
+ type CheckpointWithTotalBalance,
20
15
  EpochDifference,
21
16
  type IForkChoice,
22
17
  NotReorgedReason,
@@ -24,7 +19,6 @@ export {
24
19
  export * from "./forkChoice/safeBlocks.js";
25
20
  export {
26
21
  type CheckpointWithHex,
27
- type CheckpointWithPayloadStatus,
28
22
  ForkChoiceStore,
29
23
  type IForkChoiceStore,
30
24
  type JustifiedBalancesGetter,
@@ -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.4/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
 
@@ -362,9 +355,15 @@ export class ProtoArray {
362
355
  continue;
363
356
  }
364
357
 
365
- const currentBoost = proposerBoost && proposerBoost.root === node.blockRoot ? proposerBoost.score : 0;
358
+ // For Gloas blocks, PENDING/EMPTY/FULL all share the same blockRoot.
359
+ // Only apply proposer boost to PENDING (for Gloas) or FULL (for pre-Gloas) — to avoid
360
+ // double-counting the boost across variants during delta back-propagation, and to keep
361
+ // the boost neutral with respect to EMPTY vs FULL selection.
362
+ const isBoostVariant = isGloasBlock(node) ? node.payloadStatus === PayloadStatus.PENDING : true; // pre-Gloas has only FULL, always boost
363
+ const currentBoost =
364
+ proposerBoost && proposerBoost.root === node.blockRoot && isBoostVariant ? proposerBoost.score : 0;
366
365
  const previousBoost =
367
- this.previousProposerBoost && this.previousProposerBoost.root === node.blockRoot
366
+ this.previousProposerBoost && this.previousProposerBoost.root === node.blockRoot && isBoostVariant
368
367
  ? this.previousProposerBoost.score
369
368
  : 0;
370
369
 
@@ -549,7 +548,6 @@ export class ProtoArray {
549
548
  currentSlot: Slot,
550
549
  executionPayloadBlockHash: RootHex,
551
550
  executionPayloadNumber: number,
552
- executionPayloadStateRoot: RootHex,
553
551
  proposerBoostRoot: RootHex | null,
554
552
  executionStatus: PayloadExecutionStatus
555
553
  ): void {
@@ -605,7 +603,6 @@ export class ProtoArray {
605
603
  executionStatus,
606
604
  executionPayloadBlockHash,
607
605
  executionPayloadNumber,
608
- stateRoot: executionPayloadStateRoot,
609
606
  };
610
607
 
611
608
  const fullIndex = this.nodes.length;
@@ -684,7 +681,7 @@ export class ProtoArray {
684
681
  * Determine if we should extend the payload (prefer FULL over EMPTY)
685
682
  * Spec: gloas/fork-choice.md#new-should_extend_payload
686
683
  *
687
- * Returns true if:
684
+ * Returns true if payload is verified (FULL variant exists) AND:
688
685
  * 1. Payload is timely, OR
689
686
  * 2. No proposer boost root (empty/zero hash), OR
690
687
  * 3. Proposer boost root's parent is not this block, OR
@@ -694,6 +691,10 @@ export class ProtoArray {
694
691
  * @param proposerBoostRoot - Current proposer boost root (from ForkChoice)
695
692
  */
696
693
  shouldExtendPayload(blockRoot: RootHex, proposerBoostRoot: RootHex | null): boolean {
694
+ if (!this.hasPayload(blockRoot)) {
695
+ return false;
696
+ }
697
+
697
698
  // Condition 1: Payload is timely
698
699
  if (this.isPayloadTimely(blockRoot)) {
699
700
  return true;
@@ -1486,9 +1487,16 @@ export class ProtoArray {
1486
1487
  */
1487
1488
  private getParentNodeIndex(node: ProtoNode): number | undefined {
1488
1489
  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);
1490
+ // Traversal may reach the finalized ProtoBlock, should not throw error in that case
1491
+ try {
1492
+ const parentPayloadStatus = this.getParentPayloadStatus(node);
1493
+ return this.getNodeIndexByRootAndStatus(node.parentRoot, parentPayloadStatus);
1494
+ } catch (e) {
1495
+ if (e instanceof ProtoArrayError && e.type.code === ProtoArrayErrorCode.UNKNOWN_PARENT_BLOCK) {
1496
+ return undefined;
1497
+ }
1498
+ throw e;
1499
+ }
1492
1500
  }
1493
1501
  // Simple parent traversal for pre-Gloas blocks (includes fork transition)
1494
1502
  return node.parent;
@@ -1642,10 +1650,9 @@ export class ProtoArray {
1642
1650
  const ancestors: ProtoNode[] = [];
1643
1651
  const nonAncestors: ProtoNode[] = [];
1644
1652
 
1645
- // Include starting node if it's not PENDING (i.e., pre-Gloas or EMPTY/FULL variant post-Gloas)
1646
- if (node.payloadStatus !== PayloadStatus.PENDING) {
1647
- ancestors.push(node);
1648
- }
1653
+ // caller of this method may pass default status
1654
+ // this is the only node that we accept PENDING
1655
+ ancestors.push(node);
1649
1656
 
1650
1657
  let nodeIndex = startIndex;
1651
1658
  while (node.parent !== undefined) {