@lodestar/fork-choice 1.47.0-dev.0bcaaaebb5 → 1.47.0-dev.101e2c290d

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,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,
@@ -68,6 +74,8 @@ export type ForkChoiceOpts = {
68
74
  fastConfirmation?: boolean;
69
75
  };
70
76
 
77
+ const EFFECTIVE_BALANCE_INCREMENT_BIGINT = BigInt(EFFECTIVE_BALANCE_INCREMENT);
78
+
71
79
  export enum UpdateHeadOpt {
72
80
  GetCanonicalHead = "getCanonicalHead", // Skip getProposerHead
73
81
  GetProposerHead = "getProposerHead", // With getProposerHead
@@ -147,7 +155,7 @@ export class ForkChoice implements IForkChoice {
147
155
  /** Boost the entire branch with this proposer root as the leaf */
148
156
  private proposerBoostRoot: RootHex | null = null;
149
157
  /** Score to use in proposer boost, evaluated lazily from justified balances */
150
- private justifiedProposerBoostScore: number | null = null;
158
+ private justifiedProposerBoostScore: bigint | null = null;
151
159
  /** The current effective balances */
152
160
  private balances: EffectiveBalanceIncrements;
153
161
  /** Optional fast confirmation rule implementation */
@@ -583,32 +591,48 @@ export class ForkChoice implements IForkChoice {
583
591
  computeDeltasMetrics?.newVoteValidators.set(newVoteValidators);
584
592
 
585
593
  this.balances = newBalances;
586
- /**
587
- * The structure in line with deltas to propagate boost up the branch
588
- * starting from the proposerIndex
589
- */
590
- let proposerBoost: {root: RootHex; score: number} | null = null;
591
- if (this.opts?.proposerBoost && this.proposerBoostRoot) {
592
- const proposerBoostScore =
593
- this.justifiedProposerBoostScore ??
594
- getCommitteeFraction(this.fcStore.justified.totalBalance, {
595
- slotsPerEpoch: SLOTS_PER_EPOCH,
596
- committeePercent: this.config.PROPOSER_SCORE_BOOST,
597
- });
598
- proposerBoost = {root: this.proposerBoostRoot, score: proposerBoostScore};
599
- this.justifiedProposerBoostScore = proposerBoostScore;
600
- }
601
594
 
602
595
  const currentSlot = this.fcStore.currentSlot;
603
- this.protoArray.applyScoreChanges({
604
- attestationDeltas,
605
- proposerBoost,
596
+ const checkpoints = {
606
597
  justifiedEpoch: this.fcStore.justified.checkpoint.epoch,
607
598
  justifiedRoot: this.fcStore.justified.checkpoint.rootHex,
608
599
  finalizedEpoch: this.fcStore.finalizedCheckpoint.epoch,
609
600
  finalizedRoot: this.fcStore.finalizedCheckpoint.rootHex,
610
601
  currentSlot,
611
- });
602
+ };
603
+
604
+ const boostedBlock =
605
+ this.opts?.proposerBoost && this.proposerBoostRoot ? this.getBlockHexDefaultStatus(this.proposerBoostRoot) : null;
606
+
607
+ if (boostedBlock && isGloasBlock(boostedBlock)) {
608
+ // should_apply_proposer_boost judges the parent against the attestations known to the store,
609
+ // via is_head_weak (which also assumes equivocators' votes were already discounted before
610
+ // their balance is added back). Apply the attestation deltas first so the decision reads
611
+ // post-delta scores, then apply the boost in a second pass.
612
+ // TODO GLOAS: applyScoreChanges() updates weights and best child/descendant in one call;
613
+ // splitting them would let the two passes update weights and recompute best child/descendant
614
+ // once at the end.
615
+ // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.14/specs/gloas/fork-choice.md#new-should_apply_proposer_boost
616
+ this.protoArray.applyScoreChanges({attestationDeltas, proposerBoost: null, ...checkpoints});
617
+ const proposerBoost = this.shouldApplyProposerBoost() ? this.getProposerBoost() : null;
618
+ // The first pass already rolled back the previous boost and left a coherent tree, so a
619
+ // withheld boost needs no second pass
620
+ if (proposerBoost !== null) {
621
+ this.protoArray.applyScoreChanges({
622
+ attestationDeltas: new Array<number>(this.protoArray.nodes.length).fill(0),
623
+ proposerBoost,
624
+ ...checkpoints,
625
+ });
626
+ }
627
+ } else {
628
+ // Pre-gloas the boost is unconditional, so attestation deltas and boost deltas can propagate
629
+ // up the branch in a single pass
630
+ this.protoArray.applyScoreChanges({
631
+ attestationDeltas,
632
+ proposerBoost: boostedBlock ? this.getProposerBoost() : null,
633
+ ...checkpoints,
634
+ });
635
+ }
612
636
 
613
637
  // findHead returns the ProtoNode representing the head
614
638
  const head = this.protoArray.findHead(this.fcStore.justified.checkpoint.rootHex, currentSlot);
@@ -626,8 +650,8 @@ export class ForkChoice implements IForkChoice {
626
650
  return this.protoArray.nodes.filter((node) => node.slot > windowStart).length;
627
651
  }
628
652
 
629
- getPayloadRevealCounts(fromSlot: Slot, toSlot: Slot): {blocksPresent: number; payloadsRevealed: number} {
630
- return this.protoArray.getPayloadRevealCounts(fromSlot, toSlot);
653
+ getCanonicalPayloadCounts(fromSlot: Slot, toSlot: Slot, head: ProtoBlock): {full: number; empty: number} {
654
+ return this.protoArray.getCanonicalPayloadCounts(fromSlot, toSlot, head.blockRoot, head.payloadStatus);
631
655
  }
632
656
 
633
657
  /** Very expensive function, iterates the entire ProtoArray. Called only in debug API */
@@ -635,22 +659,11 @@ export class ForkChoice implements IForkChoice {
635
659
  return this.protoArray.nodes.filter((node) => node.bestChild === undefined);
636
660
  }
637
661
 
638
- /**
639
- * weight is in EFFECTIVE_BALANCE_INCREMENTS not gwei.
640
- * For compliance test use only
641
- */
642
- getViableHeads(): {root: RootHex; payloadStatus: PayloadStatus; weight: number}[] {
662
+ /** Returns exact Gwei weights for the compliance test. */
663
+ getViableHeads(): {root: RootHex; payloadStatus: PayloadStatus; weight: bigint}[] {
643
664
  return this.protoArray.getViableHeads(this.fcStore.currentSlot);
644
665
  }
645
666
 
646
- /**
647
- * The cached justified total active balance, in EFFECTIVE_BALANCE_INCREMENT units.
648
- * For compliance test use only
649
- */
650
- getJustifiedTotalActiveBalanceByIncrement(): number {
651
- return this.fcStore.justified.totalBalance;
652
- }
653
-
654
667
  /** This is for the debug API only */
655
668
  getAllNodes(): ProtoNode[] {
656
669
  return this.protoArray.nodes;
@@ -829,6 +842,8 @@ export class ForkChoice implements IForkChoice {
829
842
  targetRoot: toRootHex(targetRoot),
830
843
  stateRoot: toRootHex(block.stateRoot),
831
844
  timeliness: isTimely,
845
+ ptcTimeliness: this.isBlockPtcTimely(block, blockDelaySec),
846
+ proposerIndex: block.proposerIndex,
832
847
 
833
848
  justifiedEpoch: stateJustifiedEpoch,
834
849
  justifiedRoot: toRootHex(state.currentJustifiedCheckpoint.root),
@@ -1081,11 +1096,20 @@ export class ForkChoice implements IForkChoice {
1081
1096
  while (this.fcStore.currentSlot < currentSlot) {
1082
1097
  const previousSlot = this.fcStore.currentSlot;
1083
1098
  // Note: we are relying upon `onTick` to update `fcStore.time` to ensure we don't get stuck in a loop.
1084
- this.onTick(previousSlot + 1);
1099
+ const didUpdateCheckpoints = this.onTick(previousSlot + 1);
1085
1100
  this.queuedAttestationsPreviousSlot = 0;
1086
1101
  // Process any attestations that might now be eligible before running FCR for this slot.
1087
1102
  this.processAttestationQueue();
1088
- this.runFastConfirmation();
1103
+ const didRecomputeHead = this.runFastConfirmation();
1104
+
1105
+ // An epoch-boundary checkpoint pull-up can move the head's dependent root and stale the cached
1106
+ // head before block 0 of the new epoch is imported, making isProposerBoostSameDependentRoot()
1107
+ // wrong for that block. Recompute the head so it reflects the new checkpoint and the queued
1108
+ // votes — unless fast confirmation already did, to avoid a redundant head calculation.
1109
+ if (didUpdateCheckpoints && !didRecomputeHead) {
1110
+ this.updateHead();
1111
+ }
1112
+
1089
1113
  this.validatedAttestationDatas = new Set();
1090
1114
  }
1091
1115
  }
@@ -1596,7 +1620,8 @@ export class ForkChoice implements IForkChoice {
1596
1620
  // Only ever called on a block already in fork choice, so a miss is a broken invariant.
1597
1621
  const node = this.protoArray.getNodeDefaultStatus(blockRoot);
1598
1622
  if (node === undefined) {
1599
- // this is called for head so we should always have this in forkchoice, otherwise we have a serious error
1623
+ // this is called for head or the boosted block's parent, both of which should always be
1624
+ // in forkchoice, otherwise we have a serious error
1600
1625
  throw new ForkChoiceError({code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK, root: blockRoot});
1601
1626
  }
1602
1627
 
@@ -1616,7 +1641,7 @@ export class ForkChoice implements IForkChoice {
1616
1641
  // always 0. Return before fetching the state and walking the block's committees.
1617
1642
  if (equivocatingIndices.size > 0) {
1618
1643
  const state = this.fcStore.stateGetter({stateRoot: node.stateRoot});
1619
- // Only ever called on the head, so the state is always cached.
1644
+ // Only ever called on the head or the boosted block's parent, so the state is always cached.
1620
1645
  // A miss is a broken invariant, not a recoverable state.
1621
1646
  if (state === null) {
1622
1647
  throw new ForkChoiceError({
@@ -1636,7 +1661,7 @@ export class ForkChoice implements IForkChoice {
1636
1661
  // the justified state) - fall back to the head state's effective balance
1637
1662
  balance = state.effectiveBalanceIncrements[validatorIndex];
1638
1663
  }
1639
- headWeight += balance;
1664
+ headWeight += BigInt(balance) * EFFECTIVE_BALANCE_INCREMENT_BIGINT;
1640
1665
  }
1641
1666
  }
1642
1667
  }
@@ -1666,7 +1691,6 @@ export class ForkChoice implements IForkChoice {
1666
1691
 
1667
1692
  // pre-gloas uses get_weight() (boost-inclusive), gloas uses get_attestation_score() (boost-excluded)
1668
1693
  const parentWeight = isForkPostGloas(this.config.getForkName(node.slot)) ? node.attestationScore : node.weight;
1669
-
1670
1694
  return parentWeight > parentThreshold;
1671
1695
  }
1672
1696
 
@@ -1680,6 +1704,81 @@ export class ForkChoice implements IForkChoice {
1680
1704
  return this.fcStore.currentSlot === block.slot && isBeforeLateBlockCutoff;
1681
1705
  }
1682
1706
 
1707
+ /**
1708
+ * Return true if the block arrived before the PTC (payload-timeliness committee) deadline,
1709
+ * ie. block_timeliness[PTC_TIMELINESS_INDEX]. should_apply_proposer_boost uses this as the
1710
+ * definition of an "early" proposer equivocation: only a sibling seen by the PTC deadline
1711
+ * proves the proposer equivocated soon enough that honest nodes could have withheld the boost.
1712
+ *
1713
+ * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.14/specs/gloas/fork-choice.md#modified-record_block_timeliness
1714
+ *
1715
+ * Child class can overwrite this for testing purpose.
1716
+ */
1717
+ protected isBlockPtcTimely(block: BeaconBlock, blockDelaySec: number): boolean {
1718
+ const ptcThresholdMs = this.config.getSlotComponentDurationMs(this.config.PAYLOAD_ATTESTATION_DUE_BPS);
1719
+ return this.fcStore.currentSlot === block.slot && blockDelaySec * 1000 < ptcThresholdMs;
1720
+ }
1721
+
1722
+ /**
1723
+ * Determine whether proposer boost should be applied to `proposerBoostRoot`.
1724
+ *
1725
+ * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.14/specs/gloas/fork-choice.md#new-should_apply_proposer_boost
1726
+ *
1727
+ * Pre-gloas blocks always receive the boost (unconditional, backward compatible). For gloas
1728
+ * blocks the boost is withheld when the parent is a weak block from the previous slot and the
1729
+ * proposer of that parent equivocated (published another PTC-timely block at the same slot).
1730
+ */
1731
+ private shouldApplyProposerBoost(): boolean {
1732
+ if (!this.proposerBoostRoot) {
1733
+ return false;
1734
+ }
1735
+
1736
+ const boostedBlock = this.getBlockHexDefaultStatus(this.proposerBoostRoot);
1737
+ // Pre-gloas blocks always get boost
1738
+ if (!boostedBlock || !isGloasBlock(boostedBlock)) {
1739
+ return true;
1740
+ }
1741
+
1742
+ const parentBlock = this.getBlockHexDefaultStatus(boostedBlock.parentRoot);
1743
+ if (!parentBlock) {
1744
+ return true;
1745
+ }
1746
+
1747
+ // Apply proposer boost if parent is not from the previous slot
1748
+ if (parentBlock.slot + 1 < boostedBlock.slot) {
1749
+ return true;
1750
+ }
1751
+
1752
+ // Apply proposer boost if parent is not weak
1753
+ if (!this.isHeadWeak(parentBlock.blockRoot)) {
1754
+ return true;
1755
+ }
1756
+
1757
+ // Parent is weak and from the previous slot: apply boost only if there are no early
1758
+ // equivocations, ie. no other PTC-timely block at the parent's slot from the same proposer.
1759
+ return !this.protoArray.hasEquivocatingBlock(parentBlock.proposerIndex, parentBlock.slot, parentBlock.blockRoot);
1760
+ }
1761
+
1762
+ /**
1763
+ * The proposer boost to apply to `proposerBoostRoot`, propagated up the branch by
1764
+ * applyScoreChanges() together with the deltas.
1765
+ */
1766
+ private getProposerBoost(): {root: RootHex; score: bigint} | null {
1767
+ if (!this.proposerBoostRoot) {
1768
+ return null;
1769
+ }
1770
+
1771
+ const proposerBoostScore =
1772
+ this.justifiedProposerBoostScore ??
1773
+ getCommitteeFraction(this.fcStore.justified.totalBalance, {
1774
+ slotsPerEpoch: SLOTS_PER_EPOCH,
1775
+ committeePercent: this.config.PROPOSER_SCORE_BOOST,
1776
+ });
1777
+ this.justifiedProposerBoostScore = proposerBoostScore;
1778
+
1779
+ return {root: this.proposerBoostRoot, score: proposerBoostScore};
1780
+ }
1781
+
1683
1782
  /**
1684
1783
  * https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/fork-choice.md#is_proposing_on_time
1685
1784
  */
@@ -1732,23 +1831,31 @@ export class ForkChoice implements IForkChoice {
1732
1831
  * May need the justified balances of:
1733
1832
  * - unrealizedJustified: Already available in `CheckpointWithBalance`
1734
1833
  * Since this balances are already available the getter is just `() => balances`, without cache interaction
1834
+ *
1835
+ * @returns Whether either checkpoint was updated.
1735
1836
  */
1736
1837
  private updateCheckpoints(
1737
1838
  justifiedCheckpoint: CheckpointWithHex,
1738
1839
  finalizedCheckpoint: CheckpointWithHex,
1739
1840
  getJustifiedBalances: () => JustifiedBalances
1740
- ): void {
1841
+ ): boolean {
1842
+ let updated = false;
1843
+
1741
1844
  // Update justified checkpoint.
1742
1845
  if (justifiedCheckpoint.epoch > this.fcStore.justified.checkpoint.epoch) {
1743
1846
  this.fcStore.justified = {checkpoint: justifiedCheckpoint, balances: getJustifiedBalances()};
1744
1847
  this.justifiedProposerBoostScore = null;
1848
+ updated = true;
1745
1849
  }
1746
1850
 
1747
1851
  // Update finalized checkpoint.
1748
1852
  if (finalizedCheckpoint.epoch > this.fcStore.finalizedCheckpoint.epoch) {
1749
1853
  this.fcStore.finalizedCheckpoint = finalizedCheckpoint;
1750
1854
  this.justifiedProposerBoostScore = null;
1855
+ updated = true;
1751
1856
  }
1857
+
1858
+ return updated;
1752
1859
  }
1753
1860
 
1754
1861
  /**
@@ -2034,8 +2141,10 @@ export class ForkChoice implements IForkChoice {
2034
2141
  * Equivalent to:
2035
2142
  *
2036
2143
  * https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#on_tick
2144
+ *
2145
+ * @returns Whether an epoch-boundary checkpoint was updated.
2037
2146
  */
2038
- private onTick(time: Slot): void {
2147
+ private onTick(time: Slot): boolean {
2039
2148
  const previousSlot = this.fcStore.currentSlot;
2040
2149
 
2041
2150
  if (time > previousSlot + 1) {
@@ -2056,11 +2165,11 @@ export class ForkChoice implements IForkChoice {
2056
2165
 
2057
2166
  // Not a new epoch, return.
2058
2167
  if (computeSlotsSinceEpochStart(time) !== 0) {
2059
- return;
2168
+ return false;
2060
2169
  }
2061
2170
 
2062
- // If a new epoch, pull-up justification and finalization from previous epoch
2063
- this.updateCheckpoints(
2171
+ // If a new epoch, pull-up justification and finalization from previous epoch.
2172
+ return this.updateCheckpoints(
2064
2173
  this.fcStore.unrealizedJustified.checkpoint,
2065
2174
  this.fcStore.unrealizedFinalizedCheckpoint,
2066
2175
  () => this.fcStore.unrealizedJustified.balances
@@ -2086,11 +2195,9 @@ export class ForkChoice implements IForkChoice {
2086
2195
  return {prelimProposerHead, prelimNotReorgedReason: NotReorgedReason.HeadBlockIsTimely};
2087
2196
  }
2088
2197
 
2089
- // No reorg if we are at an epoch boundary
2090
- // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/phase0/fork-choice.md#is_not_epoch_boundary
2091
- const isAtEpochBoundary = slot % SLOTS_PER_EPOCH === 0;
2092
- if (isAtEpochBoundary) {
2093
- return {prelimProposerHead, prelimNotReorgedReason: NotReorgedReason.AtEpochBoundary};
2198
+ const isShufflingStable = isForkPostFulu(this.config.getForkName(slot)) || slot % SLOTS_PER_EPOCH !== 0;
2199
+ if (!isShufflingStable) {
2200
+ return {prelimProposerHead, prelimNotReorgedReason: NotReorgedReason.NotShufflingStable};
2094
2201
  }
2095
2202
 
2096
2203
  // No reorg if headBlock and parentBlock are not ffg competitive
@@ -2121,10 +2228,11 @@ export class ForkChoice implements IForkChoice {
2121
2228
  return {prelimProposerHead};
2122
2229
  }
2123
2230
 
2124
- private runFastConfirmation(): void {
2231
+ /** Returns whether it recomputed the head, so the caller can avoid a redundant `updateHead()`. */
2232
+ private runFastConfirmation(): boolean {
2125
2233
  const fastConfirmationRule = this.fastConfirmationRule;
2126
2234
  const fastConfirmationContext = this.fastConfirmationContext;
2127
- if (!fastConfirmationRule || !fastConfirmationContext) return;
2235
+ if (!fastConfirmationRule || !fastConfirmationContext) return false;
2128
2236
 
2129
2237
  if (this.fastConfirmationPaused) {
2130
2238
  // Keep consumers on a safe, available root while the rule is paused
@@ -2135,7 +2243,7 @@ export class ForkChoice implements IForkChoice {
2135
2243
  // Runs outside the timed try/catch below; a throw would escape to the clock listener
2136
2244
  this.logger?.debug("Fast confirmation notify failed", {slot: this.fcStore.currentSlot}, err as Error);
2137
2245
  }
2138
- return;
2246
+ return false;
2139
2247
  }
2140
2248
 
2141
2249
  withObservedDuration(this.metrics?.fastConfirmation.totalDuration.startTimer(), () => {
@@ -2158,6 +2266,8 @@ export class ForkChoice implements IForkChoice {
2158
2266
  );
2159
2267
  }
2160
2268
  });
2269
+
2270
+ return true;
2161
2271
  }
2162
2272
 
2163
2273
  private createFastConfirmationContext(): FastConfirmationContext {
@@ -2209,12 +2319,14 @@ export class ForkChoice implements IForkChoice {
2209
2319
  }
2210
2320
  }
2211
2321
 
2212
- // Approximate https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/fork-choice.md#calculate_committee_fraction
2322
+ // https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/fork-choice.md#calculate_committee_fraction
2213
2323
  // Calculates proposer boost score when committeePercent = config.PROPOSER_SCORE_BOOST
2214
- export function getCommitteeFraction(
2324
+ function getCommitteeFraction(
2215
2325
  justifiedTotalActiveBalanceByIncrement: number,
2216
2326
  config: {slotsPerEpoch: number; committeePercent: number}
2217
- ): number {
2218
- const committeeWeight = Math.floor(justifiedTotalActiveBalanceByIncrement / config.slotsPerEpoch);
2219
- return Math.floor((committeeWeight * config.committeePercent) / 100);
2327
+ ): bigint {
2328
+ const committeeWeightGwei =
2329
+ (BigInt(justifiedTotalActiveBalanceByIncrement) * EFFECTIVE_BALANCE_INCREMENT_BIGINT) /
2330
+ BigInt(config.slotsPerEpoch);
2331
+ return (committeeWeightGwei * BigInt(config.committeePercent)) / 100n;
2220
2332
  }
@@ -54,7 +54,7 @@ export enum NotReorgedReason {
54
54
  HeadBlockIsTimely = "headBlockIsTimely",
55
55
  ParentBlockNotAvailable = "parentBlockNotAvailable",
56
56
  ProposerBoostReorgDisabled = "proposerBoostReorgDisabled",
57
- AtEpochBoundary = "atEpochBoundary",
57
+ NotShufflingStable = "notShufflingStable",
58
58
  NotFFGCompetitive = "notFFGCompetitive",
59
59
  ChainLongUnfinality = "chainLongUnfinality",
60
60
  ParentBlockDistanceMoreThanOneSlot = "parentBlockDistanceMoreThanOneSlot",
@@ -249,11 +249,8 @@ export interface IForkChoice {
249
249
  hasPayloadUnsafe(blockRoot: Root): boolean;
250
250
  hasPayloadHexUnsafe(blockRoot: RootHex): boolean;
251
251
  getSlotsPresent(windowStart: number): number;
252
- /**
253
- * Count gloas blocks with fromSlot <= slot <= toSlot and how many of them have a revealed
254
- * payload (FULL variant exists). Used by the builder circuit breaker.
255
- */
256
- 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};
257
254
  getPTCVotes(blockRootHex: RootHex): (boolean | null)[] | null;
258
255
  /** Raw PTC vote tallies for the debug fork choice endpoint; `null` for pre-Gloas roots. */
259
256
  getPTCVoteCounts(blockRootHex: RootHex): {
@@ -13,7 +13,7 @@ import {IForkChoice} from "./interface.js";
13
13
  * payload may not yet be confirmed canonical, so we report the parent EL block which has
14
14
  * been (the bid commits to extending it).
15
15
  *
16
- * 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
17
17
  */
18
18
  export function getSafeExecutionBlockHash(forkChoice: IForkChoice, logger?: Pick<Logger, LogLevel.debug>): RootHex {
19
19
  const confirmedRoot = forkChoice.getConfirmedRoot();
@@ -36,7 +36,7 @@ export function getSafeExecutionBlockHash(forkChoice: IForkChoice, logger?: Pick
36
36
  * Get execution payload hash to report as `finalizedBlockHash` in `engine_forkchoiceUpdated`.
37
37
  * Mirrors `getSafeExecutionBlockHash`: post-Gloas returns the bid `parent_block_hash`.
38
38
  *
39
- * 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
40
40
  */
41
41
  export function getFinalizedExecutionBlockHash(forkChoice: IForkChoice): RootHex {
42
42
  return getExecutionBlockHash(forkChoice.getFinalizedBlock());
package/src/index.ts CHANGED
@@ -17,7 +17,7 @@ export {
17
17
  type IFastConfirmationStore,
18
18
  getFastConfirmationMetrics,
19
19
  } from "./forkChoice/fastConfirmation/fastConfirmationRule.js";
20
- export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt, getCommitteeFraction} from "./forkChoice/forkChoice.js";
20
+ export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt} from "./forkChoice/forkChoice.js";
21
21
  export {
22
22
  type AncestorResult,
23
23
  AncestorStatus,
@@ -1,5 +1,5 @@
1
1
  import {DataAvailabilityStatus} from "@lodestar/state-transition";
2
- import {Epoch, RootHex, Slot, UintNum64} from "@lodestar/types";
2
+ import {Epoch, RootHex, Slot, UintNum64, ValidatorIndex} from "@lodestar/types";
3
3
 
4
4
  // RootHex is a root as a hex string
5
5
  // Used for lightweight and easy comparison
@@ -144,6 +144,13 @@ export type ProtoBlock = BlockExtraMeta & {
144
144
  // Indicate whether block arrives in a timely manner ie. before the 4 second mark
145
145
  timeliness: boolean;
146
146
 
147
+ // Indicate whether block arrives before the PTC deadline
148
+ // Spec: gloas/fork-choice.md#modified-record_block_timeliness (block_timeliness[PTC_TIMELINESS_INDEX])
149
+ ptcTimeliness: boolean;
150
+
151
+ // The index of the block proposer. Used by should_apply_proposer_boost to detect proposer equivocations
152
+ proposerIndex: ValidatorIndex;
153
+
147
154
  /** Payload status for this node (Gloas fork). Always FULL in pre-gloas */
148
155
  payloadStatus: PayloadStatus;
149
156
 
@@ -160,13 +167,13 @@ export type ProtoBlock = BlockExtraMeta & {
160
167
  */
161
168
  export type ProtoNode = ProtoBlock & {
162
169
  parent?: number;
163
- /** Total weight, ie. attestationScore plus the proposer boost credited to this node */
164
- weight: number;
170
+ /** Total weight in Gwei, ie. attestationScore plus the proposer boost credited to this node */
171
+ weight: bigint;
165
172
  /**
166
- * Weight from attester votes only, excluding proposer boost.
173
+ * Weight in Gwei from attester votes only, excluding proposer boost.
167
174
  * Spec: get_attestation_score
168
175
  */
169
- attestationScore: number;
176
+ attestationScore: bigint;
170
177
  bestChild?: number;
171
178
  bestDescendant?: number;
172
179
  };