@lodestar/fork-choice 1.44.0-dev.985999b30c → 1.44.0-dev.a879adb124

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 (70) hide show
  1. package/lib/forkChoice/fastConfirmation/data.d.ts +4 -0
  2. package/lib/forkChoice/fastConfirmation/data.d.ts.map +1 -0
  3. package/lib/forkChoice/fastConfirmation/data.js +31 -0
  4. package/lib/forkChoice/fastConfirmation/data.js.map +1 -0
  5. package/lib/forkChoice/fastConfirmation/fastConfirmationRule.d.ts +17 -0
  6. package/lib/forkChoice/fastConfirmation/fastConfirmationRule.d.ts.map +1 -0
  7. package/lib/forkChoice/fastConfirmation/fastConfirmationRule.js +129 -0
  8. package/lib/forkChoice/fastConfirmation/fastConfirmationRule.js.map +1 -0
  9. package/lib/forkChoice/fastConfirmation/index.d.ts +4 -0
  10. package/lib/forkChoice/fastConfirmation/index.d.ts.map +1 -0
  11. package/lib/forkChoice/fastConfirmation/index.js +4 -0
  12. package/lib/forkChoice/fastConfirmation/index.js.map +1 -0
  13. package/lib/forkChoice/fastConfirmation/metrics.d.ts +21 -0
  14. package/lib/forkChoice/fastConfirmation/metrics.d.ts.map +1 -0
  15. package/lib/forkChoice/fastConfirmation/metrics.js +42 -0
  16. package/lib/forkChoice/fastConfirmation/metrics.js.map +1 -0
  17. package/lib/forkChoice/fastConfirmation/rules.d.ts +9 -0
  18. package/lib/forkChoice/fastConfirmation/rules.d.ts.map +1 -0
  19. package/lib/forkChoice/fastConfirmation/rules.js +91 -0
  20. package/lib/forkChoice/fastConfirmation/rules.js.map +1 -0
  21. package/lib/forkChoice/fastConfirmation/types.d.ts +101 -0
  22. package/lib/forkChoice/fastConfirmation/types.d.ts.map +1 -0
  23. package/lib/forkChoice/fastConfirmation/types.js +12 -0
  24. package/lib/forkChoice/fastConfirmation/types.js.map +1 -0
  25. package/lib/forkChoice/fastConfirmation/utils.d.ts +47 -0
  26. package/lib/forkChoice/fastConfirmation/utils.d.ts.map +1 -0
  27. package/lib/forkChoice/fastConfirmation/utils.js +681 -0
  28. package/lib/forkChoice/fastConfirmation/utils.js.map +1 -0
  29. package/lib/forkChoice/forkChoice.d.ts +23 -3
  30. package/lib/forkChoice/forkChoice.d.ts.map +1 -1
  31. package/lib/forkChoice/forkChoice.js +116 -9
  32. package/lib/forkChoice/forkChoice.js.map +1 -1
  33. package/lib/forkChoice/interface.d.ts +19 -7
  34. package/lib/forkChoice/interface.d.ts.map +1 -1
  35. package/lib/forkChoice/interface.js.map +1 -1
  36. package/lib/forkChoice/safeBlocks.d.ts +2 -6
  37. package/lib/forkChoice/safeBlocks.d.ts.map +1 -1
  38. package/lib/forkChoice/safeBlocks.js +15 -7
  39. package/lib/forkChoice/safeBlocks.js.map +1 -1
  40. package/lib/forkChoice/store.d.ts +13 -2
  41. package/lib/forkChoice/store.d.ts.map +1 -1
  42. package/lib/forkChoice/store.js +29 -1
  43. package/lib/forkChoice/store.js.map +1 -1
  44. package/lib/index.d.ts +1 -0
  45. package/lib/index.d.ts.map +1 -1
  46. package/lib/index.js +1 -0
  47. package/lib/index.js.map +1 -1
  48. package/lib/metrics.d.ts +12 -1
  49. package/lib/metrics.d.ts.map +1 -1
  50. package/lib/metrics.js +2 -0
  51. package/lib/metrics.js.map +1 -1
  52. package/lib/protoArray/protoArray.d.ts +67 -20
  53. package/lib/protoArray/protoArray.d.ts.map +1 -1
  54. package/lib/protoArray/protoArray.js +170 -38
  55. package/lib/protoArray/protoArray.js.map +1 -1
  56. package/package.json +7 -7
  57. package/src/forkChoice/fastConfirmation/data.ts +43 -0
  58. package/src/forkChoice/fastConfirmation/fastConfirmationRule.ts +159 -0
  59. package/src/forkChoice/fastConfirmation/index.ts +3 -0
  60. package/src/forkChoice/fastConfirmation/metrics.ts +44 -0
  61. package/src/forkChoice/fastConfirmation/rules.ts +124 -0
  62. package/src/forkChoice/fastConfirmation/types.ts +111 -0
  63. package/src/forkChoice/fastConfirmation/utils.ts +968 -0
  64. package/src/forkChoice/forkChoice.ts +150 -10
  65. package/src/forkChoice/interface.ts +36 -7
  66. package/src/forkChoice/safeBlocks.ts +15 -7
  67. package/src/forkChoice/store.ts +34 -1
  68. package/src/index.ts +11 -0
  69. package/src/metrics.ts +3 -1
  70. package/src/protoArray/protoArray.ts +184 -41
@@ -25,7 +25,7 @@ import {
25
25
  phase0,
26
26
  ssz,
27
27
  } from "@lodestar/types";
28
- import {Logger, MapDef, fromHex, toRootHex} from "@lodestar/utils";
28
+ import {Logger, MapDef, fromHex, toRootHex, withObservedDuration} from "@lodestar/utils";
29
29
  import {ForkChoiceMetrics} from "../metrics.js";
30
30
  import {computeDeltas} from "../protoArray/computeDeltas.js";
31
31
  import {ProtoArrayError, ProtoArrayErrorCode} from "../protoArray/errors.js";
@@ -44,6 +44,12 @@ import {
44
44
  } from "../protoArray/interface.js";
45
45
  import {ProtoArray} from "../protoArray/protoArray.js";
46
46
  import {ForkChoiceError, ForkChoiceErrorCode, InvalidAttestationCode, InvalidBlockCode} from "./errors.js";
47
+ import {
48
+ type FastConfirmationContext,
49
+ FastConfirmationRule,
50
+ FastConfirmationSteps,
51
+ type IFastConfirmationRule,
52
+ } from "./fastConfirmation/fastConfirmationRule.ts";
47
53
  import {
48
54
  AncestorResult,
49
55
  AncestorStatus,
@@ -58,6 +64,7 @@ export type ForkChoiceOpts = {
58
64
  proposerBoost?: boolean;
59
65
  proposerBoostReorg?: boolean;
60
66
  computeUnrealized?: boolean;
67
+ fastConfirmation?: boolean;
61
68
  };
62
69
 
63
70
  export enum UpdateHeadOpt {
@@ -142,6 +149,9 @@ export class ForkChoice implements IForkChoice {
142
149
  private justifiedProposerBoostScore: number | null = null;
143
150
  /** The current effective balances */
144
151
  private balances: EffectiveBalanceIncrements;
152
+ /** Optional fast confirmation rule implementation */
153
+ private readonly fastConfirmationRule?: IFastConfirmationRule;
154
+ private readonly fastConfirmationContext?: FastConfirmationContext;
145
155
  /**
146
156
  * Instantiates a Fork Choice from some existing components
147
157
  *
@@ -167,6 +177,11 @@ export class ForkChoice implements IForkChoice {
167
177
  this.head = this.updateHead();
168
178
  this.balances = this.fcStore.justified.balances;
169
179
 
180
+ if (this.opts?.fastConfirmation) {
181
+ this.fastConfirmationRule = new FastConfirmationRule(this.fcStore, metrics, this.logger);
182
+ this.fastConfirmationContext = this.createFastConfirmationContext();
183
+ }
184
+
170
185
  metrics?.forkChoice.votes.addCollect(() => {
171
186
  metrics.forkChoice.votes.set(this.voteNextSlots.length);
172
187
  metrics.forkChoice.queuedAttestations.set(this.queuedAttestationsPreviousSlot);
@@ -207,6 +222,14 @@ export class ForkChoice implements IForkChoice {
207
222
  return this.head;
208
223
  }
209
224
 
225
+ getConfirmedRoot(): RootHex {
226
+ return this.fastConfirmationRule?.getConfirmedRoot() ?? this.fcStore.justified.checkpoint.rootHex;
227
+ }
228
+
229
+ getConfirmedBlock(): ProtoBlock | null {
230
+ return this.getBlockHexDefaultStatus(this.getConfirmedRoot());
231
+ }
232
+
210
233
  /**
211
234
  *
212
235
  * A multiplexer to wrap around the traditional `updateHead()` according to the scenario
@@ -310,6 +333,10 @@ export class ForkChoice implements IForkChoice {
310
333
  return this.proposerBoostRoot ?? HEX_ZERO_HASH;
311
334
  }
312
335
 
336
+ getPreviousProposerBoostRoot(): RootHex {
337
+ return this.protoArray.getPreviousProposerBoostRoot();
338
+ }
339
+
313
340
  /**
314
341
  * Decides whether to extend an available payload from the previous slot,
315
342
  * corresponding to the beacon block `blockRoot`.
@@ -318,6 +345,11 @@ export class ForkChoice implements IForkChoice {
318
345
  return this.protoArray.shouldExtendPayload(blockRoot, this.proposerBoostRoot);
319
346
  }
320
347
 
348
+ /** Spec: should_build_on_full(store, head) */
349
+ shouldBuildOnFull(head: ProtoBlock, slot: Slot): boolean {
350
+ return this.protoArray.shouldBuildOnFull(head, slot);
351
+ }
352
+
321
353
  /**
322
354
  * To predict the proposer head of the next slot. That is, to predict if proposer-boost-reorg could happen.
323
355
  * Reason why we can't be certain is because information of the head block is not fully available yet
@@ -596,7 +628,11 @@ export class ForkChoice implements IForkChoice {
596
628
  blockDelaySec: number,
597
629
  currentSlot: Slot,
598
630
  executionStatus: BlockExecutionStatus,
599
- dataAvailabilityStatus: DataAvailabilityStatus
631
+ dataAvailabilityStatus: DataAvailabilityStatus,
632
+ // The expected proposer index on the canonical chain we are following.
633
+ // Calculated by our head state. We use it as part of the proposer
634
+ // boost decision making. No boost will be set if this is null.
635
+ expectedProposerIndex: ValidatorIndex | null
600
636
  ): ProtoBlock {
601
637
  const {parentRoot, slot} = block;
602
638
  const parentRootHex = toRootHex(parentRoot);
@@ -669,7 +705,9 @@ export class ForkChoice implements IForkChoice {
669
705
  this.opts?.proposerBoost &&
670
706
  isTimely &&
671
707
  // only boost the first block we see
672
- this.proposerBoostRoot === null
708
+ this.proposerBoostRoot === null &&
709
+ expectedProposerIndex !== null &&
710
+ block.proposerIndex === expectedProposerIndex
673
711
  ) {
674
712
  this.proposerBoostRoot = blockRootHex;
675
713
  }
@@ -934,8 +972,14 @@ export class ForkChoice implements IForkChoice {
934
972
  * Updates the PTC votes for multiple validators attesting to a block
935
973
  * Spec: gloas/fork-choice.md#new-on_payload_attestation_message
936
974
  */
937
- notifyPtcMessages(blockRoot: RootHex, ptcIndices: number[], payloadPresent: boolean): void {
938
- this.protoArray.notifyPtcMessages(blockRoot, ptcIndices, payloadPresent);
975
+ notifyPtcMessages(
976
+ blockRoot: RootHex,
977
+ slot: Slot,
978
+ ptcIndices: number[],
979
+ payloadPresent: boolean,
980
+ blobDataAvailable: boolean
981
+ ): void {
982
+ this.protoArray.notifyPtcMessages(blockRoot, slot, ptcIndices, payloadPresent, blobDataAvailable);
939
983
  }
940
984
 
941
985
  /**
@@ -977,12 +1021,12 @@ export class ForkChoice implements IForkChoice {
977
1021
  const previousSlot = this.fcStore.currentSlot;
978
1022
  // Note: we are relying upon `onTick` to update `fcStore.time` to ensure we don't get stuck in a loop.
979
1023
  this.onTick(previousSlot + 1);
1024
+ this.queuedAttestationsPreviousSlot = 0;
1025
+ // Process any attestations that might now be eligible before running FCR for this slot.
1026
+ this.processAttestationQueue();
1027
+ this.runFastConfirmation();
1028
+ this.validatedAttestationDatas = new Set();
980
1029
  }
981
-
982
- this.queuedAttestationsPreviousSlot = 0;
983
- // Process any attestations that might now be eligible.
984
- this.processAttestationQueue();
985
- this.validatedAttestationDatas = new Set();
986
1030
  }
987
1031
 
988
1032
  getTime(): Slot {
@@ -1051,6 +1095,30 @@ export class ForkChoice implements IForkChoice {
1051
1095
  return votes.toBoolArray().map((v) => v ?? null);
1052
1096
  }
1053
1097
 
1098
+ getPTCVoteCounts(blockRootHex: RootHex): {
1099
+ attesterCount: number;
1100
+ payloadPresentCount: number;
1101
+ dataAvailableCount: number;
1102
+ } | null {
1103
+ return this.protoArray.getPTCVoteCounts(blockRootHex);
1104
+ }
1105
+
1106
+ getPayloadTimelinessVotes(blockRootHex: RootHex): (boolean | null)[] | null {
1107
+ return this.protoArray.getPayloadTimelinessVotes(blockRootHex);
1108
+ }
1109
+
1110
+ getPayloadDataAvailabilityVotes(blockRootHex: RootHex): (boolean | null)[] | null {
1111
+ return this.protoArray.getPayloadDataAvailabilityVotes(blockRootHex);
1112
+ }
1113
+
1114
+ getUnrealizedJustifiedCheckpoint(): CheckpointWithHex {
1115
+ return this.fcStore.unrealizedJustified.checkpoint;
1116
+ }
1117
+
1118
+ getUnrealizedFinalizedCheckpoint(): CheckpointWithHex {
1119
+ return this.fcStore.unrealizedFinalizedCheckpoint;
1120
+ }
1121
+
1054
1122
  /**
1055
1123
  * Returns a MUTABLE `ProtoBlock` if the block is known **and** a descendant of the finalized root.
1056
1124
  */
@@ -1876,6 +1944,78 @@ export class ForkChoice implements IForkChoice {
1876
1944
 
1877
1945
  return {prelimProposerHead};
1878
1946
  }
1947
+
1948
+ private runFastConfirmation(): void {
1949
+ withObservedDuration(this.metrics?.fastConfirmation.totalDuration.startTimer(), () => {
1950
+ if (!this.fastConfirmationRule || !this.fastConfirmationContext) return;
1951
+
1952
+ try {
1953
+ withObservedDuration(
1954
+ this.metrics?.fastConfirmation.stepsDuration.startTimer({
1955
+ step: FastConfirmationSteps.updateHead,
1956
+ }),
1957
+ () => this.updateHead()
1958
+ );
1959
+
1960
+ const result = this.fastConfirmationRule.onSlotStartAfterPastAttestationsApplied(this.fastConfirmationContext);
1961
+ this.fcStore.confirmedRoot = result.confirmedRoot;
1962
+ } catch (err) {
1963
+ this.logger?.debug(
1964
+ "Fast confirmation failed",
1965
+ {slot: this.fcStore.currentSlot, head: this.head.blockRoot, confirmedRoot: this.fcStore.confirmedRoot},
1966
+ err as Error
1967
+ );
1968
+ }
1969
+ });
1970
+ }
1971
+
1972
+ private createFastConfirmationContext(): FastConfirmationContext {
1973
+ const confirmationByzantineThreshold = this.config.CONFIRMATION_BYZANTINE_THRESHOLD;
1974
+ if (!confirmationByzantineThreshold) {
1975
+ throw new Error("CONFIRMATION_BYZANTINE_THRESHOLD must be set to use fast confirmation");
1976
+ }
1977
+
1978
+ return {
1979
+ config: {
1980
+ CONFIRMATION_BYZANTINE_THRESHOLD: confirmationByzantineThreshold,
1981
+ PROPOSER_SCORE_BOOST: this.config.PROPOSER_SCORE_BOOST,
1982
+ },
1983
+ getCurrentSlot: () => this.fcStore.currentSlot,
1984
+ getHead: () => this.head,
1985
+ getBlock: (root: RootHex) => this.getBlockHexDefaultStatus(root),
1986
+ getAncestor: (root: RootHex, slot: Slot) => this.getAncestor(root, slot).blockRoot,
1987
+ isDescendant: (ancestor: RootHex, descendant: RootHex) => {
1988
+ const ancestorStatus = this.protoArray.getDefaultVariant(ancestor);
1989
+ const descendantStatus = this.protoArray.getDefaultVariant(descendant);
1990
+ if (ancestorStatus === undefined || descendantStatus === undefined) return false;
1991
+ return this.isDescendant(ancestor, ancestorStatus, descendant, descendantStatus);
1992
+ },
1993
+ getLatestMessage: (validatorIndex: ValidatorIndex) => {
1994
+ const nextIndex = this.voteNextIndices[validatorIndex];
1995
+ if (nextIndex === undefined || nextIndex === NULL_VOTE_INDEX) {
1996
+ return null;
1997
+ }
1998
+ const node = this.protoArray.nodes[nextIndex];
1999
+ if (!node) return null;
2000
+ return {root: node.blockRoot, epoch: computeEpochAtSlot(this.voteNextSlots[validatorIndex])};
2001
+ },
2002
+ getUnrealizedJustified: () => ({
2003
+ checkpoint: this.fcStore.unrealizedJustified.checkpoint,
2004
+ balances: this.fcStore.unrealizedJustified.balances,
2005
+ }),
2006
+ getFinalizedCheckpoint: () => this.fcStore.finalizedCheckpoint,
2007
+ getEquivocatingIndices: () => this.fcStore.equivocatingIndices,
2008
+ getTrackedVotesCount: () => {
2009
+ let count = 0;
2010
+ for (let i = 0; i < this.voteNextIndices.length; i++) {
2011
+ if (this.voteNextIndices[i] !== NULL_VOTE_INDEX) {
2012
+ count++;
2013
+ }
2014
+ }
2015
+ return count;
2016
+ },
2017
+ };
2018
+ }
1879
2019
  }
1880
2020
 
1881
2021
  // Approximate https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/fork-choice.md#calculate_committee_fraction
@@ -1,5 +1,14 @@
1
1
  import {DataAvailabilityStatus, EffectiveBalanceIncrements, IBeaconStateView} from "@lodestar/state-transition";
2
- import {AttesterSlashing, BeaconBlock, Epoch, IndexedAttestation, Root, RootHex, Slot} from "@lodestar/types";
2
+ import {
3
+ AttesterSlashing,
4
+ BeaconBlock,
5
+ Epoch,
6
+ IndexedAttestation,
7
+ Root,
8
+ RootHex,
9
+ Slot,
10
+ ValidatorIndex,
11
+ } from "@lodestar/types";
3
12
  import {
4
13
  BlockExecutionStatus,
5
14
  LVHExecResponse,
@@ -98,6 +107,8 @@ export interface IForkChoice {
98
107
  */
99
108
  getHeadRoot(): RootHex;
100
109
  getHead(): ProtoBlock;
110
+ getConfirmedRoot(): RootHex;
111
+ getConfirmedBlock(): ProtoBlock | null;
101
112
  updateAndGetHead(mode: UpdateAndGetHeadOpt): {
102
113
  head: ProtoBlock;
103
114
  isHeadTimely?: boolean;
@@ -123,6 +134,10 @@ export interface IForkChoice {
123
134
  getAllNodes(): ProtoNode[];
124
135
  getFinalizedCheckpoint(): CheckpointWithHex;
125
136
  getJustifiedCheckpoint(): CheckpointWithHex;
137
+ getUnrealizedJustifiedCheckpoint(): CheckpointWithHex;
138
+ getUnrealizedFinalizedCheckpoint(): CheckpointWithHex;
139
+ getProposerBoostRoot(): RootHex;
140
+ getPreviousProposerBoostRoot(): RootHex;
126
141
  /**
127
142
  * Add `block` to the fork choice DAG.
128
143
  *
@@ -145,7 +160,8 @@ export interface IForkChoice {
145
160
  blockDelaySec: number,
146
161
  currentSlot: Slot,
147
162
  executionStatus: BlockExecutionStatus,
148
- dataAvailabilityStatus: DataAvailabilityStatus
163
+ dataAvailabilityStatus: DataAvailabilityStatus,
164
+ expectedProposerIndex: ValidatorIndex | null
149
165
  ): ProtoBlock;
150
166
  /**
151
167
  * Register `attestation` with the fork choice DAG so that it may influence future calls to `getHead`.
@@ -181,12 +197,14 @@ export interface IForkChoice {
181
197
  * ## Specification
182
198
  *
183
199
  * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/gloas/fork-choice.md#new-notify_ptc_messages
184
- *
185
- * @param blockRoot - The beacon block root being attested
186
- * @param ptcIndices - Array of PTC committee indices that voted
187
- * @param payloadPresent - Whether validators attest the payload is present
188
200
  */
189
- notifyPtcMessages(blockRoot: RootHex, ptcIndices: number[], payloadPresent: boolean): void;
201
+ notifyPtcMessages(
202
+ blockRoot: RootHex,
203
+ slot: Slot,
204
+ ptcIndices: number[],
205
+ payloadPresent: boolean,
206
+ blobDataAvailable: boolean
207
+ ): void;
190
208
  /**
191
209
  * Notify fork choice that an execution payload has arrived (Gloas fork)
192
210
  * Creates the FULL variant of a Gloas block when the payload becomes available
@@ -235,6 +253,15 @@ export interface IForkChoice {
235
253
  hasPayloadHexUnsafe(blockRoot: RootHex): boolean;
236
254
  getSlotsPresent(windowStart: number): number;
237
255
  getPTCVotes(blockRootHex: RootHex): (boolean | null)[] | null;
256
+ /** Raw PTC vote tallies for the debug fork choice endpoint; `null` for pre-Gloas roots. */
257
+ getPTCVoteCounts(blockRootHex: RootHex): {
258
+ attesterCount: number;
259
+ payloadPresentCount: number;
260
+ dataAvailableCount: number;
261
+ } | null;
262
+ getPayloadTimelinessVotes(blockRootHex: RootHex): (boolean | null)[] | null;
263
+ getPayloadDataAvailabilityVotes(blockRootHex: RootHex): (boolean | null)[] | null;
264
+
238
265
  /**
239
266
  * Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root.
240
267
  */
@@ -244,6 +271,8 @@ export interface IForkChoice {
244
271
  getBlockHexDefaultStatus(blockRoot: RootHex): ProtoBlock | null;
245
272
  getBlockHexAndBlockHash(blockRoot: RootHex, blockHash: RootHex): ProtoBlock | null;
246
273
  shouldExtendPayload(blockRoot: RootHex): boolean;
274
+ /** Spec: should_build_on_full(store, head) */
275
+ shouldBuildOnFull(head: ProtoBlock, slot: Slot): boolean;
247
276
  getFinalizedBlock(): ProtoBlock;
248
277
  getJustifiedBlock(): ProtoBlock;
249
278
  getFinalizedCheckpointSlot(): Slot;
@@ -1,5 +1,6 @@
1
1
  import {ZERO_HASH_HEX} from "@lodestar/params";
2
2
  import {Root, RootHex} from "@lodestar/types";
3
+ import {fromHex} from "@lodestar/utils";
3
4
  import {IForkChoice} from "./interface.js";
4
5
 
5
6
  /**
@@ -7,21 +8,28 @@ import {IForkChoice} from "./interface.js";
7
8
  * that is safe from re-orgs. Normally this block is pretty close to the head of canonical
8
9
  * chain which makes it valuable to expose a safe block to users.
9
10
  *
10
- * https://github.com/ethereum/consensus-specs/blob/v1.6.0/fork_choice/safe-block.md#get_safe_beacon_block_root
11
+ * @deprecated The merged fast-confirmation spec only defines `get_safe_execution_block_hash`.
11
12
  */
12
13
  export function getSafeBeaconBlockRoot(fc: IForkChoice): Root {
14
+ const confirmedRoot = fc.getConfirmedRoot();
15
+ if (confirmedRoot && fc.hasBlockHex(confirmedRoot)) {
16
+ return fromHex(confirmedRoot);
17
+ }
13
18
  return fc.getJustifiedCheckpoint().root;
14
19
  }
15
20
 
16
21
  /**
17
22
  * Get execution payload hash for the safe block
18
- * This function assumes that safe block is post Bellatrix and function should not be called otherwise.
19
23
  *
20
- * As our existing usage is aligned with above condition so not adding fork-check inside this function
21
- *
22
- *
23
- * https://github.com/ethereum/consensus-specs/blob/v1.6.0/fork_choice/safe-block.md#get_safe_execution_block_hash
24
+ * https://github.com/ethereum/consensus-specs/blob/master/fork_choice/safe-block.md#get_safe_execution_block_hash
24
25
  */
25
26
  export function getSafeExecutionBlockHash(forkChoice: IForkChoice): RootHex {
26
- return forkChoice.getJustifiedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
27
+ const confirmedRoot = forkChoice.getConfirmedRoot();
28
+ if (confirmedRoot) {
29
+ const confirmedBlock = forkChoice.getBlockHexDefaultStatus(confirmedRoot);
30
+ if (confirmedBlock?.executionPayloadBlockHash) {
31
+ return confirmedBlock.executionPayloadBlockHash;
32
+ }
33
+ }
34
+ return ZERO_HASH_HEX;
27
35
  }
@@ -1,6 +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
5
  import {CheckpointWithBalance, CheckpointWithTotalBalance} from "./interface.js";
5
6
 
6
7
  /**
@@ -35,7 +36,7 @@ export type JustifiedBalancesGetter = (
35
36
  * - The actual block DAG in `ProtoArray`.
36
37
  * - `time` is represented using `Slot` instead of UNIX epoch `u64`.
37
38
  */
38
- export interface IForkChoiceStore {
39
+ export interface IForkChoiceStore extends IFastConfirmationStore {
39
40
  currentSlot: Slot;
40
41
  get justified(): CheckpointWithTotalBalance;
41
42
  set justified(justified: CheckpointWithBalance);
@@ -58,12 +59,27 @@ export class ForkChoiceStore implements IForkChoiceStore {
58
59
  justifiedBalancesGetter: JustifiedBalancesGetter;
59
60
  currentSlot: Slot;
60
61
 
62
+ // Fast Confirmation Rule spec fields
63
+ confirmedRoot: RootHex;
64
+ previousEpochObservedJustifiedCheckpoint: CheckpointWithHex;
65
+ currentEpochObservedJustifiedCheckpoint: CheckpointWithHex;
66
+ previousEpochGreatestUnrealizedCheckpoint: CheckpointWithHex;
67
+ previousSlotHead: RootHex;
68
+ currentSlotHead: RootHex;
69
+
70
+ // Fast Confirmation Rule internal fields
71
+ previousEpochObservedJustifiedBalances: JustifiedBalances;
72
+ currentEpochObservedJustifiedBalances: JustifiedBalances;
73
+ previousEpochGreatestUnrealizedBalances: JustifiedBalances;
74
+ stateGetter: ForkChoiceStateGetter;
75
+
61
76
  constructor(
62
77
  currentSlot: Slot,
63
78
  justifiedCheckpoint: phase0.Checkpoint,
64
79
  finalizedCheckpoint: phase0.Checkpoint,
65
80
  justifiedBalances: EffectiveBalanceIncrements,
66
81
  justifiedBalancesGetter: JustifiedBalancesGetter,
82
+ stateGetter: ForkChoiceStateGetter,
67
83
  private readonly events?: {
68
84
  onJustified: (cp: CheckpointWithHex) => void;
69
85
  onFinalized: (cp: CheckpointWithHex) => void;
@@ -71,6 +87,7 @@ export class ForkChoiceStore implements IForkChoiceStore {
71
87
  ) {
72
88
  this.justifiedBalancesGetter = justifiedBalancesGetter;
73
89
  this.currentSlot = currentSlot;
90
+ this.stateGetter = stateGetter;
74
91
  const justified = {
75
92
  checkpoint: toCheckpointWithHex(justifiedCheckpoint),
76
93
  balances: justifiedBalances,
@@ -80,6 +97,22 @@ export class ForkChoiceStore implements IForkChoiceStore {
80
97
  this.unrealizedJustified = justified;
81
98
  this._finalizedCheckpoint = toCheckpointWithHex(finalizedCheckpoint);
82
99
  this.unrealizedFinalizedCheckpoint = this._finalizedCheckpoint;
100
+
101
+ // Initialize Fast Confirmation fields conservatively from finalized, matching
102
+ // the spec's get_fast_confirmation_store() behavior.
103
+ const finalizedCheckpointWithHex = toCheckpointWithHex(finalizedCheckpoint);
104
+ const finalizedState = stateGetter({checkpoint: finalizedCheckpointWithHex});
105
+ const finalizedBalances = finalizedState?.effectiveBalanceIncrements ?? justifiedBalances;
106
+ const anchorRoot = finalizedCheckpointWithHex.rootHex;
107
+ this.previousEpochObservedJustifiedCheckpoint = finalizedCheckpointWithHex;
108
+ this.currentEpochObservedJustifiedCheckpoint = finalizedCheckpointWithHex;
109
+ this.previousEpochGreatestUnrealizedCheckpoint = finalizedCheckpointWithHex;
110
+ this.confirmedRoot = anchorRoot;
111
+ this.previousEpochObservedJustifiedBalances = finalizedBalances;
112
+ this.currentEpochObservedJustifiedBalances = finalizedBalances;
113
+ this.previousEpochGreatestUnrealizedBalances = finalizedBalances;
114
+ this.previousSlotHead = anchorRoot;
115
+ this.currentSlotHead = anchorRoot;
83
116
  }
84
117
 
85
118
  get justified(): CheckpointWithTotalBalance {
package/src/index.ts CHANGED
@@ -6,6 +6,17 @@ export {
6
6
  type InvalidBlock,
7
7
  InvalidBlockCode,
8
8
  } from "./forkChoice/errors.js";
9
+ export {
10
+ type FastConfirmationBalanceSource,
11
+ type FastConfirmationContext,
12
+ type FastConfirmationMetrics,
13
+ type FastConfirmationResult,
14
+ FastConfirmationRule,
15
+ type ForkChoiceStateGetter,
16
+ type IFastConfirmationRule,
17
+ type IFastConfirmationStore,
18
+ getFastConfirmationMetrics,
19
+ } from "./forkChoice/fastConfirmation/fastConfirmationRule.ts";
9
20
  export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt} from "./forkChoice/forkChoice.js";
10
21
  export {
11
22
  type AncestorResult,
package/src/metrics.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import {MetricsRegisterExtra} from "@lodestar/utils";
2
+ import {FastConfirmationMetrics, getFastConfirmationMetrics} from "./forkChoice/fastConfirmation/metrics.ts";
2
3
  import {UpdateHeadOpt} from "./forkChoice/forkChoice.js";
3
4
  import {NotReorgedReason} from "./forkChoice/interface.js";
4
5
 
5
- export type ForkChoiceMetrics = ReturnType<typeof getForkChoiceMetrics>;
6
+ export type ForkChoiceMetrics = ReturnType<typeof getForkChoiceMetrics> & FastConfirmationMetrics;
6
7
 
7
8
  export function getForkChoiceMetrics(register: MetricsRegisterExtra) {
8
9
  return {
10
+ ...getFastConfirmationMetrics(register),
9
11
  forkChoice: {
10
12
  findHead: register.histogram<{caller: string}>({
11
13
  name: "beacon_fork_choice_find_head_seconds",