@lodestar/fork-choice 1.44.0-dev.847e824a6a → 1.44.0-dev.848b01419b

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 (69) 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 +138 -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 +24 -0
  14. package/lib/forkChoice/fastConfirmation/metrics.d.ts.map +1 -0
  15. package/lib/forkChoice/fastConfirmation/metrics.js +55 -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 +110 -0
  20. package/lib/forkChoice/fastConfirmation/rules.js.map +1 -0
  21. package/lib/forkChoice/fastConfirmation/types.d.ts +109 -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 +687 -0
  28. package/lib/forkChoice/fastConfirmation/utils.js.map +1 -0
  29. package/lib/forkChoice/forkChoice.d.ts +20 -2
  30. package/lib/forkChoice/forkChoice.d.ts.map +1 -1
  31. package/lib/forkChoice/forkChoice.js +115 -9
  32. package/lib/forkChoice/forkChoice.js.map +1 -1
  33. package/lib/forkChoice/interface.d.ts +16 -2
  34. package/lib/forkChoice/interface.d.ts.map +1 -1
  35. package/lib/forkChoice/safeBlocks.d.ts +2 -6
  36. package/lib/forkChoice/safeBlocks.d.ts.map +1 -1
  37. package/lib/forkChoice/safeBlocks.js +15 -7
  38. package/lib/forkChoice/safeBlocks.js.map +1 -1
  39. package/lib/forkChoice/store.d.ts +33 -2
  40. package/lib/forkChoice/store.d.ts.map +1 -1
  41. package/lib/forkChoice/store.js +37 -1
  42. package/lib/forkChoice/store.js.map +1 -1
  43. package/lib/index.d.ts +1 -0
  44. package/lib/index.d.ts.map +1 -1
  45. package/lib/index.js +1 -0
  46. package/lib/index.js.map +1 -1
  47. package/lib/metrics.d.ts +15 -1
  48. package/lib/metrics.d.ts.map +1 -1
  49. package/lib/metrics.js +2 -0
  50. package/lib/metrics.js.map +1 -1
  51. package/lib/protoArray/protoArray.d.ts +28 -6
  52. package/lib/protoArray/protoArray.d.ts.map +1 -1
  53. package/lib/protoArray/protoArray.js +76 -20
  54. package/lib/protoArray/protoArray.js.map +1 -1
  55. package/package.json +7 -7
  56. package/src/forkChoice/fastConfirmation/data.ts +43 -0
  57. package/src/forkChoice/fastConfirmation/fastConfirmationRule.ts +172 -0
  58. package/src/forkChoice/fastConfirmation/index.ts +3 -0
  59. package/src/forkChoice/fastConfirmation/metrics.ts +57 -0
  60. package/src/forkChoice/fastConfirmation/rules.ts +146 -0
  61. package/src/forkChoice/fastConfirmation/types.ts +120 -0
  62. package/src/forkChoice/fastConfirmation/utils.ts +975 -0
  63. package/src/forkChoice/forkChoice.ts +143 -9
  64. package/src/forkChoice/interface.ts +17 -1
  65. package/src/forkChoice/safeBlocks.ts +15 -7
  66. package/src/forkChoice/store.ts +45 -1
  67. package/src/index.ts +11 -0
  68. package/src/metrics.ts +3 -1
  69. package/src/protoArray/protoArray.ts +88 -19
@@ -0,0 +1,3 @@
1
+ export * from "./fastConfirmationRule.ts";
2
+ export * from "./metrics.ts";
3
+ export * from "./types.ts";
@@ -0,0 +1,57 @@
1
+ import {MetricsRegisterExtra} from "@lodestar/utils";
2
+
3
+ export type FastConfirmationMetrics = ReturnType<typeof getFastConfirmationMetrics>;
4
+
5
+ export enum FastConfirmationSteps {
6
+ updateHead = "updateHead",
7
+ buildCache = "buildCache",
8
+ buildSnapshot = "buildSnapshot",
9
+ runRules = "runRules",
10
+ }
11
+
12
+ export function getFastConfirmationMetrics(register: MetricsRegisterExtra) {
13
+ return {
14
+ fastConfirmation: {
15
+ totalDuration: register.histogram({
16
+ name: "lodestar_fast_confirmation_duration_seconds",
17
+ help: "Time to run Fast Confirmation Rule algorithm",
18
+ buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 2],
19
+ }),
20
+ stepsDuration: register.histogram<{step: FastConfirmationSteps}>({
21
+ name: "lodestar_fast_confirmation_steps_duration_seconds",
22
+ help: "Time to run Fast Confirmation Steps",
23
+ buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 2],
24
+ labelNames: ["step"],
25
+ }),
26
+ confirmedEpoch: register.gauge({
27
+ name: "lodestar_fast_confirmation_confirmed_epoch",
28
+ help: "Current confirmed epoch from fast confirmation",
29
+ }),
30
+ votesTracked: register.gauge({
31
+ name: "lodestar_fast_confirmation_votes_tracked",
32
+ help: "Number of checkpoint votes tracked by fast confirmation",
33
+ }),
34
+ resets: register.counter({
35
+ name: "lodestar_fast_confirmation_resets_total",
36
+ help: "Total number of fast confirmation resets (any cause)",
37
+ }),
38
+ // Standardized Fast Confirmation metrics
39
+ slot: register.gauge({
40
+ name: "beacon_fast_confirmation_slot",
41
+ help: "Slot of the most recent confirmed block",
42
+ }),
43
+ reorgs: register.counter({
44
+ name: "beacon_fast_confirmation_reorgs_total",
45
+ help: "Total number of confirmed block reorganizations",
46
+ }),
47
+ fallbacks: register.counter({
48
+ name: "beacon_fast_confirmation_fallbacks_total",
49
+ help: "Total number of fallbacks to finality",
50
+ }),
51
+ restarts: register.counter({
52
+ name: "beacon_fast_confirmation_restarts_total",
53
+ help: "Total number of restarts from a safe unrealized justified block",
54
+ }),
55
+ },
56
+ };
57
+ }
@@ -0,0 +1,146 @@
1
+ import {computeEpochAtSlot, isStartSlotOfEpoch} from "@lodestar/state-transition";
2
+ import {Logger} from "@lodestar/utils";
3
+ import {equalCheckpointWithHex} from "../store.ts";
4
+ import {
5
+ FastConfirmationCache,
6
+ FastConfirmationContext,
7
+ FastConfirmationDecision,
8
+ FastConfirmationDecisionReason,
9
+ FastConfirmationRule,
10
+ FastConfirmationRunResult,
11
+ FastConfirmationSnapshot,
12
+ IFastConfirmationStore,
13
+ } from "./types.ts";
14
+ import {findLatestConfirmedDescendant, getBlock, isAncestor, isConfirmedChainSafe} from "./utils.ts";
15
+
16
+ export const resetIfConfirmedUnavailable: FastConfirmationRule = (snapshot, ctx, _store, cache, decision) => {
17
+ const confirmedBlock = getBlock(ctx, cache, decision.confirmedRoot);
18
+ if (!confirmedBlock) {
19
+ return {
20
+ confirmedRoot: snapshot.finalizedRoot,
21
+ didReset: true,
22
+ reason: FastConfirmationDecisionReason.ConfirmedNotFound,
23
+ };
24
+ }
25
+ return decision;
26
+ };
27
+
28
+ export const resetIfBehindOrNotAncestorOrUnsafe: FastConfirmationRule = (
29
+ snapshot,
30
+ ctx,
31
+ store,
32
+ cache,
33
+ decision,
34
+ logger
35
+ ) => {
36
+ const confirmedBlock = getBlock(ctx, cache, decision.confirmedRoot);
37
+ if (!confirmedBlock) return decision;
38
+ const confirmedEpoch = computeEpochAtSlot(confirmedBlock.slot);
39
+
40
+ const confirmedEpochBehindHead = confirmedEpoch + 1 < snapshot.currentEpoch;
41
+ const notAncestorOfHead = !isAncestor(ctx, cache, snapshot.headRoot, decision.confirmedRoot);
42
+ const allChildrenNotConfirmed =
43
+ isStartSlotOfEpoch(snapshot.currentSlot) &&
44
+ !isConfirmedChainSafe(ctx, store, cache, decision.confirmedRoot, logger);
45
+
46
+ if (confirmedEpochBehindHead || notAncestorOfHead || allChildrenNotConfirmed) {
47
+ const didReset = decision.didReset || decision.confirmedRoot !== snapshot.finalizedRoot;
48
+ const reason = confirmedEpochBehindHead
49
+ ? FastConfirmationDecisionReason.ResetBehind
50
+ : notAncestorOfHead
51
+ ? FastConfirmationDecisionReason.ResetNotAncestor
52
+ : FastConfirmationDecisionReason.ResetChainUnsafe;
53
+ return {confirmedRoot: snapshot.finalizedRoot, didReset, reason};
54
+ }
55
+ return decision;
56
+ };
57
+
58
+ export const advanceIfObservedJustified: FastConfirmationRule = (snapshot, ctx, store, cache, decision) => {
59
+ if (!isStartSlotOfEpoch(snapshot.currentSlot)) return decision;
60
+ if (store.currentEpochObservedJustifiedCheckpoint.epoch + 1 !== snapshot.currentEpoch) return decision;
61
+ if (!snapshot.headUnrealized) return decision;
62
+ if (!equalCheckpointWithHex(store.currentEpochObservedJustifiedCheckpoint, snapshot.headUnrealized)) return decision;
63
+ const observedBlock = getBlock(ctx, cache, store.currentEpochObservedJustifiedCheckpoint.rootHex);
64
+ if (!observedBlock || computeEpochAtSlot(observedBlock.slot) + 1 < snapshot.currentEpoch) return decision;
65
+
66
+ const confirmedSlot = getBlock(ctx, cache, decision.confirmedRoot)?.slot ?? null;
67
+ const observedSlot = observedBlock.slot;
68
+ if (confirmedSlot !== null && observedSlot !== null && confirmedSlot < observedSlot) {
69
+ return {
70
+ ...decision,
71
+ confirmedRoot: store.currentEpochObservedJustifiedCheckpoint.rootHex,
72
+ reason: FastConfirmationDecisionReason.ObservedJustified,
73
+ };
74
+ }
75
+ return decision;
76
+ };
77
+
78
+ export const advanceToLatestConfirmedDescendant: FastConfirmationRule = (
79
+ snapshot,
80
+ ctx,
81
+ store,
82
+ cache,
83
+ decision,
84
+ logger
85
+ ) => {
86
+ const confirmedBlock = getBlock(ctx, cache, decision.confirmedRoot);
87
+ const confirmedEpoch = confirmedBlock ? computeEpochAtSlot(confirmedBlock.slot) : null;
88
+ if (confirmedEpoch !== null && confirmedEpoch + 1 >= snapshot.currentEpoch) {
89
+ const newConfirmed = findLatestConfirmedDescendant(snapshot, ctx, store, cache, decision.confirmedRoot, logger);
90
+ return {
91
+ ...decision,
92
+ confirmedRoot: newConfirmed,
93
+ reason: FastConfirmationDecisionReason.ConfirmedDescendant,
94
+ };
95
+ }
96
+ return decision;
97
+ };
98
+
99
+ export const FAST_CONFIRMATION_RULES: FastConfirmationRule[] = [
100
+ resetIfConfirmedUnavailable,
101
+ resetIfBehindOrNotAncestorOrUnsafe,
102
+ advanceIfObservedJustified,
103
+ advanceToLatestConfirmedDescendant,
104
+ ];
105
+
106
+ // Spec mapping: this rule runner implements the `get_latest_confirmed` decision flow
107
+ // over Lodestar's snapshot/store/cache abstractions.
108
+ export function runFastConfirmationRules(
109
+ snapshot: FastConfirmationSnapshot,
110
+ ctx: FastConfirmationContext,
111
+ store: IFastConfirmationStore,
112
+ cache: FastConfirmationCache,
113
+ logger?: Logger
114
+ ): FastConfirmationRunResult {
115
+ let decision: FastConfirmationDecision = {
116
+ confirmedRoot: snapshot.confirmedRoot,
117
+ didReset: false,
118
+ reason: FastConfirmationDecisionReason.Unchanged,
119
+ };
120
+
121
+ // Track every reason a rule decided on, the final `decision.reason` is overwritten by
122
+ // later rules (eg. a restart is followed by advancing to the latest confirmed descendant)
123
+ const reasons = new Set<FastConfirmationDecisionReason>();
124
+ for (const rule of FAST_CONFIRMATION_RULES) {
125
+ decision = rule(snapshot, ctx, store, cache, decision, logger);
126
+ reasons.add(decision.reason);
127
+ }
128
+
129
+ // Detect a reorg directly from ancestry instead of the reset reason: when the confirmed block is
130
+ // both epoch-behind and not an ancestor of head, `resetIfBehindOrNotAncestorOrUnsafe` records
131
+ // `ResetBehind` (it takes precedence), so keying off `ResetNotAncestor` alone would miss reorgs
132
+ // that cross an epoch boundary.
133
+ const initialConfirmedBlock = getBlock(ctx, cache, snapshot.confirmedRoot);
134
+ const didReorg = initialConfirmedBlock !== null && !isAncestor(ctx, cache, snapshot.headRoot, snapshot.confirmedRoot);
135
+
136
+ return {
137
+ confirmedRoot: decision.confirmedRoot,
138
+ didReset: decision.didReset,
139
+ reason: decision.reason,
140
+ didReorg,
141
+ // A fallback is a revert to finality: a reset whose final confirmed root is the finalized
142
+ // checkpoint. A later rule may advance the confirmed root forward, which is not a fallback.
143
+ didFallback: decision.didReset && decision.confirmedRoot === snapshot.finalizedRoot,
144
+ didRestart: reasons.has(FastConfirmationDecisionReason.ObservedJustified),
145
+ };
146
+ }
@@ -0,0 +1,120 @@
1
+ import {EffectiveBalanceIncrements, IBeaconStateView} from "@lodestar/state-transition";
2
+ import {Epoch, RootHex, Slot, ValidatorIndex} from "@lodestar/types";
3
+ import {Logger} from "@lodestar/utils";
4
+ import {ProtoBlock} from "../../protoArray/interface.ts";
5
+ import {CheckpointWithHex} from "../store.ts";
6
+
7
+ export type FastConfirmationBalanceSource = {
8
+ state: IBeaconStateView | null;
9
+ balances: EffectiveBalanceIncrements;
10
+ };
11
+
12
+ export type ForkChoiceStateGetter = (
13
+ opts: {stateRoot: RootHex; checkpoint?: never} | {stateRoot?: never; checkpoint: CheckpointWithHex}
14
+ ) => IBeaconStateView | null;
15
+
16
+ type IFastConfirmationSpecStore = {
17
+ confirmedRoot: RootHex;
18
+ previousEpochObservedJustifiedCheckpoint: CheckpointWithHex;
19
+ currentEpochObservedJustifiedCheckpoint: CheckpointWithHex;
20
+ previousEpochGreatestUnrealizedCheckpoint: CheckpointWithHex;
21
+ previousSlotHead: RootHex;
22
+ currentSlotHead: RootHex;
23
+ };
24
+
25
+ type IFastConfirmationAuxStore = {
26
+ previousEpochObservedJustifiedBalances: EffectiveBalanceIncrements;
27
+ currentEpochObservedJustifiedBalances: EffectiveBalanceIncrements;
28
+ previousEpochGreatestUnrealizedBalances: EffectiveBalanceIncrements;
29
+ stateGetter: ForkChoiceStateGetter;
30
+ };
31
+
32
+ export type IFastConfirmationStore = IFastConfirmationSpecStore & IFastConfirmationAuxStore;
33
+
34
+ export type FastConfirmationResult = {
35
+ confirmedRoot: RootHex;
36
+ didReset?: boolean;
37
+ };
38
+
39
+ export type FastConfirmationSnapshot = {
40
+ currentSlot: Slot;
41
+ currentEpoch: Epoch;
42
+ headRoot: RootHex;
43
+ confirmedRoot: RootHex;
44
+ confirmedEpoch: Epoch | null;
45
+ confirmedSlot: Slot | null;
46
+ observedJustified: CheckpointWithHex;
47
+ headUnrealized: CheckpointWithHex | null;
48
+ finalizedRoot: RootHex;
49
+ };
50
+
51
+ export enum FastConfirmationDecisionReason {
52
+ Unchanged = "unchanged",
53
+ ConfirmedNotFound = "confirmed_not_found",
54
+ ResetBehind = "reset_behind",
55
+ ResetNotAncestor = "reset_not_ancestor",
56
+ ResetChainUnsafe = "reset_chain_unsafe",
57
+ ObservedJustified = "observed_justified",
58
+ ConfirmedDescendant = "confirmed_descendant",
59
+ }
60
+
61
+ export type FastConfirmationDecision = {
62
+ confirmedRoot: RootHex;
63
+ didReset: boolean;
64
+ reason: FastConfirmationDecisionReason;
65
+ };
66
+
67
+ export type FastConfirmationRunResult = FastConfirmationDecision & {
68
+ /** Confirmed block became non-canonical (no longer an ancestor of head) */
69
+ didReorg: boolean;
70
+ /** Confirmed block reverted to the finalized checkpoint */
71
+ didFallback: boolean;
72
+ /** Restarted confirmation from the observed unrealized justified checkpoint */
73
+ didRestart: boolean;
74
+ };
75
+
76
+ export type FastConfirmationRule = (
77
+ snapshot: FastConfirmationSnapshot,
78
+ ctx: FastConfirmationContext,
79
+ store: IFastConfirmationStore,
80
+ cache: FastConfirmationCache,
81
+ decision: FastConfirmationDecision,
82
+ logger?: Logger
83
+ ) => FastConfirmationDecision;
84
+
85
+ export type BalanceSourceKey = "current" | "previous";
86
+
87
+ // This cache is created once per slot
88
+ export type FastConfirmationCache = {
89
+ blockByRoot: Map<RootHex, ProtoBlock | null>;
90
+ ancestorRoots: Map<string, RootHex[] | null>;
91
+ committeeBySlot: Map<Slot, Set<ValidatorIndex>>;
92
+ isDescendantByRootPair: Map<string, boolean>;
93
+ /** voteRoot -> totalWeight, keyed by sourceKey */
94
+ voteWeightBySource: Map<BalanceSourceKey, Map<RootHex, number>>;
95
+ headState?: IBeaconStateView;
96
+ pulledUpHeadState?: IBeaconStateView;
97
+ checkpointStateByKey: Map<string, IBeaconStateView | null>;
98
+ };
99
+
100
+ export type FastConfirmationContext = {
101
+ config: {
102
+ CONFIRMATION_BYZANTINE_THRESHOLD: number;
103
+ PROPOSER_SCORE_BOOST: number;
104
+ };
105
+ getCurrentSlot(): Slot;
106
+ getHead(): ProtoBlock;
107
+ getBlock(root: RootHex): ProtoBlock | null;
108
+ getAncestor(root: RootHex, slot: Slot): RootHex;
109
+ isDescendant(ancestor: RootHex, descendant: RootHex): boolean;
110
+ getLatestMessage(validatorIndex: ValidatorIndex): {root: RootHex; epoch: Epoch} | null;
111
+ getUnrealizedJustified(): {checkpoint: CheckpointWithHex; balances: EffectiveBalanceIncrements};
112
+ getFinalizedCheckpoint(): CheckpointWithHex;
113
+ getEquivocatingIndices(): Set<ValidatorIndex>;
114
+ getTrackedVotesCount(): number;
115
+ };
116
+
117
+ export interface IFastConfirmationRule {
118
+ getConfirmedRoot(): RootHex;
119
+ onSlotStartAfterPastAttestationsApplied(ctx: FastConfirmationContext): FastConfirmationResult;
120
+ }