@lodestar/fork-choice 1.46.0 → 1.47.0-dev.02d2dbea03

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.
Files changed (51) hide show
  1. package/lib/forkChoice/errors.d.ts +6 -1
  2. package/lib/forkChoice/errors.d.ts.map +1 -1
  3. package/lib/forkChoice/errors.js +1 -0
  4. package/lib/forkChoice/errors.js.map +1 -1
  5. package/lib/forkChoice/fastConfirmation/data.d.ts +1 -1
  6. package/lib/forkChoice/fastConfirmation/fastConfirmationRule.d.ts +4 -4
  7. package/lib/forkChoice/fastConfirmation/index.d.ts +3 -3
  8. package/lib/forkChoice/fastConfirmation/rules.d.ts +1 -1
  9. package/lib/forkChoice/fastConfirmation/types.d.ts +3 -3
  10. package/lib/forkChoice/fastConfirmation/types.d.ts.map +1 -1
  11. package/lib/forkChoice/fastConfirmation/utils.d.ts +3 -3
  12. package/lib/forkChoice/forkChoice.d.ts +12 -17
  13. package/lib/forkChoice/forkChoice.d.ts.map +1 -1
  14. package/lib/forkChoice/forkChoice.js +48 -31
  15. package/lib/forkChoice/forkChoice.js.map +1 -1
  16. package/lib/forkChoice/interface.d.ts +8 -8
  17. package/lib/forkChoice/interface.d.ts.map +1 -1
  18. package/lib/forkChoice/interface.js +1 -1
  19. package/lib/forkChoice/interface.js.map +1 -1
  20. package/lib/forkChoice/safeBlocks.d.ts +5 -13
  21. package/lib/forkChoice/safeBlocks.d.ts.map +1 -1
  22. package/lib/forkChoice/safeBlocks.js +23 -32
  23. package/lib/forkChoice/safeBlocks.js.map +1 -1
  24. package/lib/forkChoice/store.d.ts +1 -1
  25. package/lib/index.d.ts +2 -2
  26. package/lib/index.d.ts.map +1 -1
  27. package/lib/index.js +1 -1
  28. package/lib/index.js.map +1 -1
  29. package/lib/metrics.d.ts +2 -2
  30. package/lib/protoArray/interface.d.ts +4 -4
  31. package/lib/protoArray/interface.d.ts.map +1 -1
  32. package/lib/protoArray/protoArray.d.ts +7 -10
  33. package/lib/protoArray/protoArray.d.ts.map +1 -1
  34. package/lib/protoArray/protoArray.js +51 -37
  35. package/lib/protoArray/protoArray.js.map +1 -1
  36. package/package.json +8 -8
  37. package/src/forkChoice/errors.ts +3 -1
  38. package/src/forkChoice/fastConfirmation/data.ts +2 -2
  39. package/src/forkChoice/fastConfirmation/fastConfirmationRule.ts +6 -6
  40. package/src/forkChoice/fastConfirmation/index.ts +3 -3
  41. package/src/forkChoice/fastConfirmation/rules.ts +3 -3
  42. package/src/forkChoice/fastConfirmation/types.ts +3 -3
  43. package/src/forkChoice/fastConfirmation/utils.ts +3 -3
  44. package/src/forkChoice/forkChoice.ts +71 -41
  45. package/src/forkChoice/interface.ts +6 -6
  46. package/src/forkChoice/safeBlocks.ts +27 -35
  47. package/src/forkChoice/store.ts +1 -1
  48. package/src/index.ts +2 -2
  49. package/src/metrics.ts +1 -1
  50. package/src/protoArray/interface.ts +4 -4
  51. package/src/protoArray/protoArray.ts +65 -41
@@ -9,8 +9,8 @@ import {
9
9
  } from "@lodestar/state-transition";
10
10
  import {Epoch, RootHex, Slot, ValidatorIndex} from "@lodestar/types";
11
11
  import {Logger, fromHex} from "@lodestar/utils";
12
- import {ExecutionStatus, ProtoBlock} from "../../protoArray/interface.ts";
13
- import {CheckpointWithHex, computeTotalBalance, equalCheckpointWithHex} from "../store.ts";
12
+ import {ExecutionStatus, ProtoBlock} from "../../protoArray/interface.js";
13
+ import {CheckpointWithHex, computeTotalBalance, equalCheckpointWithHex} from "../store.js";
14
14
  import {
15
15
  type BalanceSourceKey,
16
16
  FastConfirmationBalanceSource,
@@ -19,7 +19,7 @@ import {
19
19
  FastConfirmationSnapshot,
20
20
  IFastConfirmationStore,
21
21
  type TotalActiveBalanceCacheKey,
22
- } from "./types.ts";
22
+ } from "./types.js";
23
23
 
24
24
  // Spec: adjust_committee_weight_estimate_to_ensure_safety
25
25
  // https://github.com/ethereum/consensus-specs/blob/master/specs/phase0/fast-confirmation.md#adjust_committee_weight_estimate_to_ensure_safety
@@ -1,5 +1,11 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {MIN_SEED_LOOKAHEAD, SLOTS_PER_EPOCH, isForkPostGloas} from "@lodestar/params";
2
+ import {
3
+ EFFECTIVE_BALANCE_INCREMENT,
4
+ MIN_SEED_LOOKAHEAD,
5
+ SLOTS_PER_EPOCH,
6
+ isForkPostFulu,
7
+ isForkPostGloas,
8
+ } from "@lodestar/params";
3
9
  import {
4
10
  DataAvailabilityStatus,
5
11
  EffectiveBalanceIncrements,
@@ -49,7 +55,8 @@ import {
49
55
  FastConfirmationRule,
50
56
  FastConfirmationSteps,
51
57
  type IFastConfirmationRule,
52
- } from "./fastConfirmation/fastConfirmationRule.ts";
58
+ type IFastConfirmationSpecStore,
59
+ } from "./fastConfirmation/fastConfirmationRule.js";
53
60
  import {
54
61
  AncestorResult,
55
62
  AncestorStatus,
@@ -67,6 +74,8 @@ export type ForkChoiceOpts = {
67
74
  fastConfirmation?: boolean;
68
75
  };
69
76
 
77
+ const EFFECTIVE_BALANCE_INCREMENT_BIGINT = BigInt(EFFECTIVE_BALANCE_INCREMENT);
78
+
70
79
  export enum UpdateHeadOpt {
71
80
  GetCanonicalHead = "getCanonicalHead", // Skip getProposerHead
72
81
  GetProposerHead = "getProposerHead", // With getProposerHead
@@ -146,7 +155,7 @@ export class ForkChoice implements IForkChoice {
146
155
  /** Boost the entire branch with this proposer root as the leaf */
147
156
  private proposerBoostRoot: RootHex | null = null;
148
157
  /** Score to use in proposer boost, evaluated lazily from justified balances */
149
- private justifiedProposerBoostScore: number | null = null;
158
+ private justifiedProposerBoostScore: bigint | null = null;
150
159
  /** The current effective balances */
151
160
  private balances: EffectiveBalanceIncrements;
152
161
  /** Optional fast confirmation rule implementation */
@@ -228,6 +237,17 @@ export class ForkChoice implements IForkChoice {
228
237
  return this.fastConfirmationRule?.getConfirmedRoot() ?? this.fcStore.justified.checkpoint.rootHex;
229
238
  }
230
239
 
240
+ getFastConfirmationStore(): IFastConfirmationSpecStore {
241
+ return {
242
+ confirmedRoot: this.getConfirmedRoot(),
243
+ previousEpochObservedJustifiedCheckpoint: this.fcStore.previousEpochObservedJustifiedCheckpoint,
244
+ currentEpochObservedJustifiedCheckpoint: this.fcStore.currentEpochObservedJustifiedCheckpoint,
245
+ previousEpochGreatestUnrealizedCheckpoint: this.fcStore.previousEpochGreatestUnrealizedCheckpoint,
246
+ previousSlotHead: this.fcStore.previousSlotHead,
247
+ currentSlotHead: this.fcStore.currentSlotHead,
248
+ };
249
+ }
250
+
231
251
  getConfirmedBlock(): ProtoBlock | null {
232
252
  return this.getBlockHexDefaultStatus(this.getConfirmedRoot());
233
253
  }
@@ -575,7 +595,7 @@ export class ForkChoice implements IForkChoice {
575
595
  * The structure in line with deltas to propagate boost up the branch
576
596
  * starting from the proposerIndex
577
597
  */
578
- let proposerBoost: {root: RootHex; score: number} | null = null;
598
+ let proposerBoost: {root: RootHex; score: bigint} | null = null;
579
599
  if (this.opts?.proposerBoost && this.proposerBoostRoot) {
580
600
  const proposerBoostScore =
581
601
  this.justifiedProposerBoostScore ??
@@ -614,8 +634,8 @@ export class ForkChoice implements IForkChoice {
614
634
  return this.protoArray.nodes.filter((node) => node.slot > windowStart).length;
615
635
  }
616
636
 
617
- getPayloadRevealCounts(fromSlot: Slot, toSlot: Slot): {blocksPresent: number; payloadsRevealed: number} {
618
- return this.protoArray.getPayloadRevealCounts(fromSlot, toSlot);
637
+ getCanonicalPayloadCounts(fromSlot: Slot, toSlot: Slot, head: ProtoBlock): {full: number; empty: number} {
638
+ return this.protoArray.getCanonicalPayloadCounts(fromSlot, toSlot, head.blockRoot, head.payloadStatus);
619
639
  }
620
640
 
621
641
  /** Very expensive function, iterates the entire ProtoArray. Called only in debug API */
@@ -623,22 +643,11 @@ export class ForkChoice implements IForkChoice {
623
643
  return this.protoArray.nodes.filter((node) => node.bestChild === undefined);
624
644
  }
625
645
 
626
- /**
627
- * weight is in EFFECTIVE_BALANCE_INCREMENTS not gwei.
628
- * For compliance test use only
629
- */
630
- getViableHeads(): {root: RootHex; payloadStatus: PayloadStatus; weight: number}[] {
646
+ /** Returns exact Gwei weights for the compliance test. */
647
+ getViableHeads(): {root: RootHex; payloadStatus: PayloadStatus; weight: bigint}[] {
631
648
  return this.protoArray.getViableHeads(this.fcStore.currentSlot);
632
649
  }
633
650
 
634
- /**
635
- * The cached justified total active balance, in EFFECTIVE_BALANCE_INCREMENT units.
636
- * For compliance test use only
637
- */
638
- getJustifiedTotalActiveBalanceByIncrement(): number {
639
- return this.fcStore.justified.totalBalance;
640
- }
641
-
642
651
  /** This is for the debug API only */
643
652
  getAllNodes(): ProtoNode[] {
644
653
  return this.protoArray.nodes;
@@ -1069,11 +1078,20 @@ export class ForkChoice implements IForkChoice {
1069
1078
  while (this.fcStore.currentSlot < currentSlot) {
1070
1079
  const previousSlot = this.fcStore.currentSlot;
1071
1080
  // Note: we are relying upon `onTick` to update `fcStore.time` to ensure we don't get stuck in a loop.
1072
- this.onTick(previousSlot + 1);
1081
+ const didUpdateCheckpoints = this.onTick(previousSlot + 1);
1073
1082
  this.queuedAttestationsPreviousSlot = 0;
1074
1083
  // Process any attestations that might now be eligible before running FCR for this slot.
1075
1084
  this.processAttestationQueue();
1076
- this.runFastConfirmation();
1085
+ const didRecomputeHead = this.runFastConfirmation();
1086
+
1087
+ // An epoch-boundary checkpoint pull-up can move the head's dependent root and stale the cached
1088
+ // head before block 0 of the new epoch is imported, making isProposerBoostSameDependentRoot()
1089
+ // wrong for that block. Recompute the head so it reflects the new checkpoint and the queued
1090
+ // votes — unless fast confirmation already did, to avoid a redundant head calculation.
1091
+ if (didUpdateCheckpoints && !didRecomputeHead) {
1092
+ this.updateHead();
1093
+ }
1094
+
1077
1095
  this.validatedAttestationDatas = new Set();
1078
1096
  }
1079
1097
  }
@@ -1624,7 +1642,7 @@ export class ForkChoice implements IForkChoice {
1624
1642
  // the justified state) - fall back to the head state's effective balance
1625
1643
  balance = state.effectiveBalanceIncrements[validatorIndex];
1626
1644
  }
1627
- headWeight += balance;
1645
+ headWeight += BigInt(balance) * EFFECTIVE_BALANCE_INCREMENT_BIGINT;
1628
1646
  }
1629
1647
  }
1630
1648
  }
@@ -1654,7 +1672,6 @@ export class ForkChoice implements IForkChoice {
1654
1672
 
1655
1673
  // pre-gloas uses get_weight() (boost-inclusive), gloas uses get_attestation_score() (boost-excluded)
1656
1674
  const parentWeight = isForkPostGloas(this.config.getForkName(node.slot)) ? node.attestationScore : node.weight;
1657
-
1658
1675
  return parentWeight > parentThreshold;
1659
1676
  }
1660
1677
 
@@ -1720,23 +1737,31 @@ export class ForkChoice implements IForkChoice {
1720
1737
  * May need the justified balances of:
1721
1738
  * - unrealizedJustified: Already available in `CheckpointWithBalance`
1722
1739
  * Since this balances are already available the getter is just `() => balances`, without cache interaction
1740
+ *
1741
+ * @returns Whether either checkpoint was updated.
1723
1742
  */
1724
1743
  private updateCheckpoints(
1725
1744
  justifiedCheckpoint: CheckpointWithHex,
1726
1745
  finalizedCheckpoint: CheckpointWithHex,
1727
1746
  getJustifiedBalances: () => JustifiedBalances
1728
- ): void {
1747
+ ): boolean {
1748
+ let updated = false;
1749
+
1729
1750
  // Update justified checkpoint.
1730
1751
  if (justifiedCheckpoint.epoch > this.fcStore.justified.checkpoint.epoch) {
1731
1752
  this.fcStore.justified = {checkpoint: justifiedCheckpoint, balances: getJustifiedBalances()};
1732
1753
  this.justifiedProposerBoostScore = null;
1754
+ updated = true;
1733
1755
  }
1734
1756
 
1735
1757
  // Update finalized checkpoint.
1736
1758
  if (finalizedCheckpoint.epoch > this.fcStore.finalizedCheckpoint.epoch) {
1737
1759
  this.fcStore.finalizedCheckpoint = finalizedCheckpoint;
1738
1760
  this.justifiedProposerBoostScore = null;
1761
+ updated = true;
1739
1762
  }
1763
+
1764
+ return updated;
1740
1765
  }
1741
1766
 
1742
1767
  /**
@@ -2022,8 +2047,10 @@ export class ForkChoice implements IForkChoice {
2022
2047
  * Equivalent to:
2023
2048
  *
2024
2049
  * https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#on_tick
2050
+ *
2051
+ * @returns Whether an epoch-boundary checkpoint was updated.
2025
2052
  */
2026
- private onTick(time: Slot): void {
2053
+ private onTick(time: Slot): boolean {
2027
2054
  const previousSlot = this.fcStore.currentSlot;
2028
2055
 
2029
2056
  if (time > previousSlot + 1) {
@@ -2044,11 +2071,11 @@ export class ForkChoice implements IForkChoice {
2044
2071
 
2045
2072
  // Not a new epoch, return.
2046
2073
  if (computeSlotsSinceEpochStart(time) !== 0) {
2047
- return;
2074
+ return false;
2048
2075
  }
2049
2076
 
2050
- // If a new epoch, pull-up justification and finalization from previous epoch
2051
- this.updateCheckpoints(
2077
+ // If a new epoch, pull-up justification and finalization from previous epoch.
2078
+ return this.updateCheckpoints(
2052
2079
  this.fcStore.unrealizedJustified.checkpoint,
2053
2080
  this.fcStore.unrealizedFinalizedCheckpoint,
2054
2081
  () => this.fcStore.unrealizedJustified.balances
@@ -2074,11 +2101,9 @@ export class ForkChoice implements IForkChoice {
2074
2101
  return {prelimProposerHead, prelimNotReorgedReason: NotReorgedReason.HeadBlockIsTimely};
2075
2102
  }
2076
2103
 
2077
- // No reorg if we are at an epoch boundary
2078
- // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/phase0/fork-choice.md#is_not_epoch_boundary
2079
- const isAtEpochBoundary = slot % SLOTS_PER_EPOCH === 0;
2080
- if (isAtEpochBoundary) {
2081
- return {prelimProposerHead, prelimNotReorgedReason: NotReorgedReason.AtEpochBoundary};
2104
+ const isShufflingStable = isForkPostFulu(this.config.getForkName(slot)) || slot % SLOTS_PER_EPOCH !== 0;
2105
+ if (!isShufflingStable) {
2106
+ return {prelimProposerHead, prelimNotReorgedReason: NotReorgedReason.NotShufflingStable};
2082
2107
  }
2083
2108
 
2084
2109
  // No reorg if headBlock and parentBlock are not ffg competitive
@@ -2109,10 +2134,11 @@ export class ForkChoice implements IForkChoice {
2109
2134
  return {prelimProposerHead};
2110
2135
  }
2111
2136
 
2112
- private runFastConfirmation(): void {
2137
+ /** Returns whether it recomputed the head, so the caller can avoid a redundant `updateHead()`. */
2138
+ private runFastConfirmation(): boolean {
2113
2139
  const fastConfirmationRule = this.fastConfirmationRule;
2114
2140
  const fastConfirmationContext = this.fastConfirmationContext;
2115
- if (!fastConfirmationRule || !fastConfirmationContext) return;
2141
+ if (!fastConfirmationRule || !fastConfirmationContext) return false;
2116
2142
 
2117
2143
  if (this.fastConfirmationPaused) {
2118
2144
  // Keep consumers on a safe, available root while the rule is paused
@@ -2123,7 +2149,7 @@ export class ForkChoice implements IForkChoice {
2123
2149
  // Runs outside the timed try/catch below; a throw would escape to the clock listener
2124
2150
  this.logger?.debug("Fast confirmation notify failed", {slot: this.fcStore.currentSlot}, err as Error);
2125
2151
  }
2126
- return;
2152
+ return false;
2127
2153
  }
2128
2154
 
2129
2155
  withObservedDuration(this.metrics?.fastConfirmation.totalDuration.startTimer(), () => {
@@ -2146,6 +2172,8 @@ export class ForkChoice implements IForkChoice {
2146
2172
  );
2147
2173
  }
2148
2174
  });
2175
+
2176
+ return true;
2149
2177
  }
2150
2178
 
2151
2179
  private createFastConfirmationContext(): FastConfirmationContext {
@@ -2197,12 +2225,14 @@ export class ForkChoice implements IForkChoice {
2197
2225
  }
2198
2226
  }
2199
2227
 
2200
- // Approximate https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/fork-choice.md#calculate_committee_fraction
2228
+ // https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/fork-choice.md#calculate_committee_fraction
2201
2229
  // Calculates proposer boost score when committeePercent = config.PROPOSER_SCORE_BOOST
2202
- export function getCommitteeFraction(
2230
+ function getCommitteeFraction(
2203
2231
  justifiedTotalActiveBalanceByIncrement: number,
2204
2232
  config: {slotsPerEpoch: number; committeePercent: number}
2205
- ): number {
2206
- const committeeWeight = Math.floor(justifiedTotalActiveBalanceByIncrement / config.slotsPerEpoch);
2207
- return Math.floor((committeeWeight * config.committeePercent) / 100);
2233
+ ): bigint {
2234
+ const committeeWeightGwei =
2235
+ (BigInt(justifiedTotalActiveBalanceByIncrement) * EFFECTIVE_BALANCE_INCREMENT_BIGINT) /
2236
+ BigInt(config.slotsPerEpoch);
2237
+ return (committeeWeightGwei * BigInt(config.committeePercent)) / 100n;
2208
2238
  }
@@ -8,6 +8,7 @@ import {
8
8
  ProtoBlock,
9
9
  ProtoNode,
10
10
  } from "../protoArray/interface.js";
11
+ import {IFastConfirmationSpecStore} from "./fastConfirmation/types.js";
11
12
  import {UpdateAndGetHeadOpt} from "./forkChoice.js";
12
13
  import {CheckpointWithHex} from "./store.js";
13
14
 
@@ -53,7 +54,7 @@ export enum NotReorgedReason {
53
54
  HeadBlockIsTimely = "headBlockIsTimely",
54
55
  ParentBlockNotAvailable = "parentBlockNotAvailable",
55
56
  ProposerBoostReorgDisabled = "proposerBoostReorgDisabled",
56
- AtEpochBoundary = "atEpochBoundary",
57
+ NotShufflingStable = "notShufflingStable",
57
58
  NotFFGCompetitive = "notFFGCompetitive",
58
59
  ChainLongUnfinality = "chainLongUnfinality",
59
60
  ParentBlockDistanceMoreThanOneSlot = "parentBlockDistanceMoreThanOneSlot",
@@ -100,6 +101,8 @@ export interface IForkChoice {
100
101
  getHead(): ProtoBlock;
101
102
  getConfirmedRoot(): RootHex;
102
103
  getConfirmedBlock(): ProtoBlock | null;
104
+ /** Snapshot of the spec `FastConfirmationStore` fields, mirroring `get_fast_confirmation_store` */
105
+ getFastConfirmationStore(): IFastConfirmationSpecStore;
103
106
  /** Resume the fast confirmation rule; restarts from the finalized root on the next slot tick */
104
107
  resumeFastConfirmation(): void;
105
108
  /** Pause the fast confirmation rule (e.g. while syncing); pins the confirmed root to the finalized root */
@@ -246,11 +249,8 @@ export interface IForkChoice {
246
249
  hasPayloadUnsafe(blockRoot: Root): boolean;
247
250
  hasPayloadHexUnsafe(blockRoot: RootHex): boolean;
248
251
  getSlotsPresent(windowStart: number): number;
249
- /**
250
- * Count gloas blocks with fromSlot <= slot <= toSlot and how many of them have a revealed
251
- * payload (FULL variant exists). Used by the builder circuit breaker.
252
- */
253
- getPayloadRevealCounts(fromSlot: Slot, toSlot: Slot): {blocksPresent: number; payloadsRevealed: number};
252
+ /** Count FULL and EMPTY blocks on the supplied head chain in the inclusive slot range. */
253
+ getCanonicalPayloadCounts(fromSlot: Slot, toSlot: Slot, head: ProtoBlock): {full: number; empty: number};
254
254
  getPTCVotes(blockRootHex: RootHex): (boolean | null)[] | null;
255
255
  /** Raw PTC vote tallies for the debug fork choice endpoint; `null` for pre-Gloas roots. */
256
256
  getPTCVoteCounts(blockRootHex: RootHex): {
@@ -1,24 +1,10 @@
1
- import {GENESIS_SLOT, ZERO_HASH_HEX} from "@lodestar/params";
2
- import {Root, RootHex} from "@lodestar/types";
3
- import {LogLevel, Logger, fromHex} from "@lodestar/utils";
4
- import {HEX_ZERO_HASH, ProtoBlock, isGloasBlock} from "../protoArray/interface.js";
1
+ import {GENESIS_SLOT} from "@lodestar/params";
2
+ import {RootHex} from "@lodestar/types";
3
+ import {LogLevel, Logger} from "@lodestar/utils";
4
+ import {ExecutionStatus, HEX_ZERO_HASH, ProtoBlock, isGloasBlock} from "../protoArray/interface.js";
5
+ import {ForkChoiceError, ForkChoiceErrorCode} from "./errors.js";
5
6
  import {IForkChoice} from "./interface.js";
6
7
 
7
- /**
8
- * Under honest majority and certain network synchronicity assumptions there exists a block
9
- * that is safe from re-orgs. Normally this block is pretty close to the head of canonical
10
- * chain which makes it valuable to expose a safe block to users.
11
- *
12
- * @deprecated The merged fast-confirmation spec only defines `get_safe_execution_block_hash`.
13
- */
14
- export function getSafeBeaconBlockRoot(fc: IForkChoice): Root {
15
- const confirmedRoot = fc.getConfirmedRoot();
16
- if (confirmedRoot && fc.hasBlockHex(confirmedRoot)) {
17
- return fromHex(confirmedRoot);
18
- }
19
- return fc.getJustifiedCheckpoint().root;
20
- }
21
-
22
8
  /**
23
9
  * Get execution payload hash to report as `safeBlockHash` in `engine_forkchoiceUpdated`.
24
10
  *
@@ -27,31 +13,36 @@ export function getSafeBeaconBlockRoot(fc: IForkChoice): Root {
27
13
  * payload may not yet be confirmed canonical, so we report the parent EL block which has
28
14
  * been (the bid commits to extending it).
29
15
  *
30
- * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/bellatrix/fast-confirmation.md#new-get_safe_execution_block_hash
16
+ * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.14/specs/bellatrix/fast-confirmation.md#new-get_safe_execution_block_hash
31
17
  */
32
- export function getSafeExecutionBlockHash(forkChoice: IForkChoice, logger?: Pick<Logger, LogLevel.warn>): RootHex {
18
+ export function getSafeExecutionBlockHash(forkChoice: IForkChoice, logger?: Pick<Logger, LogLevel.debug>): RootHex {
19
+ const confirmedRoot = forkChoice.getConfirmedRoot();
33
20
  const confirmedBlock = forkChoice.getConfirmedBlock();
34
21
  if (confirmedBlock === null) {
35
- logger?.warn("Confirmed block not found, using zero safe execution block hash", {
36
- confirmedRoot: forkChoice.getConfirmedRoot(),
22
+ throw new ForkChoiceError({code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK, root: confirmedRoot});
23
+ }
24
+
25
+ if (confirmedBlock.blockRoot === forkChoice.getFinalizedBlock().blockRoot) {
26
+ logger?.debug("Confirmed block is the finalized block", {
27
+ blockRoot: confirmedBlock.blockRoot,
28
+ slot: confirmedBlock.slot,
37
29
  });
38
- return ZERO_HASH_HEX;
39
30
  }
40
31
 
41
- return getExecutionBlockHash(confirmedBlock, logger);
32
+ return getExecutionBlockHash(confirmedBlock);
42
33
  }
43
34
 
44
35
  /**
45
36
  * Get execution payload hash to report as `finalizedBlockHash` in `engine_forkchoiceUpdated`.
46
37
  * Mirrors `getSafeExecutionBlockHash`: post-Gloas returns the bid `parent_block_hash`.
47
38
  *
48
- * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/gloas/fork-choice.md#notify_forkchoice_updated
39
+ * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.14/specs/gloas/fork-choice.md#notify_forkchoice_updated
49
40
  */
50
- export function getFinalizedExecutionBlockHash(forkChoice: IForkChoice, logger?: Pick<Logger, LogLevel.warn>): RootHex {
51
- return getExecutionBlockHash(forkChoice.getFinalizedBlock(), logger);
41
+ export function getFinalizedExecutionBlockHash(forkChoice: IForkChoice): RootHex {
42
+ return getExecutionBlockHash(forkChoice.getFinalizedBlock());
52
43
  }
53
44
 
54
- function getExecutionBlockHash(block: ProtoBlock, logger?: Pick<Logger, LogLevel.warn>): RootHex {
45
+ function getExecutionBlockHash(block: ProtoBlock): RootHex {
55
46
  if (isGloasBlock(block)) {
56
47
  return block.parentBlockHash;
57
48
  }
@@ -63,13 +54,14 @@ function getExecutionBlockHash(block: ProtoBlock, logger?: Pick<Logger, LogLevel
63
54
  return HEX_ZERO_HASH;
64
55
  }
65
56
 
66
- if (block.executionPayloadBlockHash === null) {
67
- logger?.warn("Execution payload block hash not found, using zero hash", {
68
- blockRoot: block.blockRoot,
69
- slot: block.slot,
57
+ const {blockRoot, slot} = block;
58
+ if (block.executionPayloadBlockHash === null && block.executionStatus !== ExecutionStatus.PreMerge) {
59
+ throw new ForkChoiceError({
60
+ code: ForkChoiceErrorCode.MISSING_EXECUTION_PAYLOAD_BLOCK_HASH,
61
+ root: blockRoot,
62
+ slot,
70
63
  });
71
- return HEX_ZERO_HASH;
72
64
  }
73
65
 
74
- return block.executionPayloadBlockHash;
66
+ return block.executionPayloadBlockHash ?? HEX_ZERO_HASH;
75
67
  }
@@ -1,7 +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 {ForkChoiceStateGetter, IFastConfirmationStore} from "./fastConfirmation/types.ts";
4
+ import {ForkChoiceStateGetter, IFastConfirmationStore} from "./fastConfirmation/types.js";
5
5
  import {CheckpointWithBalance, CheckpointWithTotalBalance} from "./interface.js";
6
6
 
7
7
  /**
package/src/index.ts CHANGED
@@ -16,8 +16,8 @@ export {
16
16
  type IFastConfirmationRule,
17
17
  type IFastConfirmationStore,
18
18
  getFastConfirmationMetrics,
19
- } from "./forkChoice/fastConfirmation/fastConfirmationRule.ts";
20
- export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt, getCommitteeFraction} from "./forkChoice/forkChoice.js";
19
+ } from "./forkChoice/fastConfirmation/fastConfirmationRule.js";
20
+ export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt} from "./forkChoice/forkChoice.js";
21
21
  export {
22
22
  type AncestorResult,
23
23
  AncestorStatus,
package/src/metrics.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import {MetricsRegisterExtra} from "@lodestar/utils";
2
- import {FastConfirmationMetrics, getFastConfirmationMetrics} from "./forkChoice/fastConfirmation/metrics.ts";
2
+ import {FastConfirmationMetrics, getFastConfirmationMetrics} from "./forkChoice/fastConfirmation/metrics.js";
3
3
  import {UpdateHeadOpt} from "./forkChoice/forkChoice.js";
4
4
  import {NotReorgedReason} from "./forkChoice/interface.js";
5
5
 
@@ -160,13 +160,13 @@ export type ProtoBlock = BlockExtraMeta & {
160
160
  */
161
161
  export type ProtoNode = ProtoBlock & {
162
162
  parent?: number;
163
- /** Total weight, ie. attestationScore plus the proposer boost credited to this node */
164
- weight: number;
163
+ /** Total weight in Gwei, ie. attestationScore plus the proposer boost credited to this node */
164
+ weight: bigint;
165
165
  /**
166
- * Weight from attester votes only, excluding proposer boost.
166
+ * Weight in Gwei from attester votes only, excluding proposer boost.
167
167
  * Spec: get_attestation_score
168
168
  */
169
- attestationScore: number;
169
+ attestationScore: bigint;
170
170
  bestChild?: number;
171
171
  bestDescendant?: number;
172
172
  };