@lodestar/fork-choice 1.44.0-dev.7418ddd9f7 → 1.44.0-dev.762dd5f186

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 +139 -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 +114 -0
  20. package/lib/forkChoice/fastConfirmation/rules.js.map +1 -0
  21. package/lib/forkChoice/fastConfirmation/types.d.ts +115 -0
  22. package/lib/forkChoice/fastConfirmation/types.d.ts.map +1 -0
  23. package/lib/forkChoice/fastConfirmation/types.js +22 -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 +27 -5
  52. package/lib/protoArray/protoArray.d.ts.map +1 -1
  53. package/lib/protoArray/protoArray.js +73 -17
  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 +173 -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 +151 -0
  61. package/src/forkChoice/fastConfirmation/types.ts +137 -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 +85 -16
@@ -0,0 +1,151 @@
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
+ isResetReason,
14
+ } from "./types.ts";
15
+ import {findLatestConfirmedDescendant, getBlock, isAncestor, isConfirmedChainSafe} from "./utils.ts";
16
+
17
+ export const resetIfConfirmedUnavailable: FastConfirmationRule = (snapshot, ctx, _store, cache, decision) => {
18
+ const confirmedBlock = getBlock(ctx, cache, decision.confirmedRoot);
19
+ if (!confirmedBlock) {
20
+ return {
21
+ confirmedRoot: snapshot.finalizedRoot,
22
+ didReset: true,
23
+ reason: FastConfirmationDecisionReason.ConfirmedNotFound,
24
+ };
25
+ }
26
+ return decision;
27
+ };
28
+
29
+ export const resetIfBehindOrNotAncestorOrUnsafe: FastConfirmationRule = (
30
+ snapshot,
31
+ ctx,
32
+ store,
33
+ cache,
34
+ decision,
35
+ logger
36
+ ) => {
37
+ const confirmedBlock = getBlock(ctx, cache, decision.confirmedRoot);
38
+ if (!confirmedBlock) return decision;
39
+ const confirmedEpoch = computeEpochAtSlot(confirmedBlock.slot);
40
+
41
+ const confirmedEpochBehindHead = confirmedEpoch + 1 < snapshot.currentEpoch;
42
+ const notAncestorOfHead = !isAncestor(ctx, cache, snapshot.headRoot, decision.confirmedRoot);
43
+ const allChildrenNotConfirmed =
44
+ isStartSlotOfEpoch(snapshot.currentSlot) &&
45
+ !isConfirmedChainSafe(ctx, store, cache, decision.confirmedRoot, logger);
46
+
47
+ if (confirmedEpochBehindHead || notAncestorOfHead || allChildrenNotConfirmed) {
48
+ const didReset = decision.didReset || decision.confirmedRoot !== snapshot.finalizedRoot;
49
+ const reason = confirmedEpochBehindHead
50
+ ? FastConfirmationDecisionReason.ResetBehind
51
+ : notAncestorOfHead
52
+ ? FastConfirmationDecisionReason.ResetNotAncestor
53
+ : FastConfirmationDecisionReason.ResetChainUnsafe;
54
+ return {confirmedRoot: snapshot.finalizedRoot, didReset, reason};
55
+ }
56
+ return decision;
57
+ };
58
+
59
+ export const advanceIfObservedJustified: FastConfirmationRule = (snapshot, ctx, store, cache, decision) => {
60
+ if (!isStartSlotOfEpoch(snapshot.currentSlot)) return decision;
61
+ if (store.currentEpochObservedJustifiedCheckpoint.epoch + 1 !== snapshot.currentEpoch) return decision;
62
+ if (!snapshot.headUnrealized) return decision;
63
+ if (!equalCheckpointWithHex(store.currentEpochObservedJustifiedCheckpoint, snapshot.headUnrealized)) return decision;
64
+ const observedBlock = getBlock(ctx, cache, store.currentEpochObservedJustifiedCheckpoint.rootHex);
65
+ if (!observedBlock || computeEpochAtSlot(observedBlock.slot) + 1 < snapshot.currentEpoch) return decision;
66
+
67
+ const confirmedSlot = getBlock(ctx, cache, decision.confirmedRoot)?.slot ?? null;
68
+ const observedSlot = observedBlock.slot;
69
+ if (confirmedSlot !== null && observedSlot !== null && confirmedSlot < observedSlot) {
70
+ return {
71
+ ...decision,
72
+ confirmedRoot: store.currentEpochObservedJustifiedCheckpoint.rootHex,
73
+ reason: FastConfirmationDecisionReason.ObservedJustified,
74
+ };
75
+ }
76
+ return decision;
77
+ };
78
+
79
+ export const advanceToLatestConfirmedDescendant: FastConfirmationRule = (
80
+ snapshot,
81
+ ctx,
82
+ store,
83
+ cache,
84
+ decision,
85
+ logger
86
+ ) => {
87
+ const confirmedBlock = getBlock(ctx, cache, decision.confirmedRoot);
88
+ const confirmedEpoch = confirmedBlock ? computeEpochAtSlot(confirmedBlock.slot) : null;
89
+ if (confirmedEpoch !== null && confirmedEpoch + 1 >= snapshot.currentEpoch) {
90
+ const newConfirmed = findLatestConfirmedDescendant(snapshot, ctx, store, cache, decision.confirmedRoot, logger);
91
+ return {
92
+ ...decision,
93
+ confirmedRoot: newConfirmed,
94
+ reason: FastConfirmationDecisionReason.ConfirmedDescendant,
95
+ };
96
+ }
97
+ return decision;
98
+ };
99
+
100
+ export const FAST_CONFIRMATION_RULES: FastConfirmationRule[] = [
101
+ resetIfConfirmedUnavailable,
102
+ resetIfBehindOrNotAncestorOrUnsafe,
103
+ advanceIfObservedJustified,
104
+ advanceToLatestConfirmedDescendant,
105
+ ];
106
+
107
+ // Spec mapping: this rule runner implements the `get_latest_confirmed` decision flow
108
+ // over Lodestar's snapshot/store/cache abstractions.
109
+ export function runFastConfirmationRules(
110
+ snapshot: FastConfirmationSnapshot,
111
+ ctx: FastConfirmationContext,
112
+ store: IFastConfirmationStore,
113
+ cache: FastConfirmationCache,
114
+ logger?: Logger
115
+ ): FastConfirmationRunResult {
116
+ let decision: FastConfirmationDecision = {
117
+ confirmedRoot: snapshot.confirmedRoot,
118
+ didReset: false,
119
+ reason: FastConfirmationDecisionReason.Unchanged,
120
+ };
121
+
122
+ const reasonTrail: FastConfirmationDecisionReason[] = [];
123
+ for (const rule of FAST_CONFIRMATION_RULES) {
124
+ const previousDecision = decision;
125
+ decision = rule(snapshot, ctx, store, cache, decision, logger);
126
+ if (decision !== previousDecision && decision.reason !== FastConfirmationDecisionReason.Unchanged) {
127
+ reasonTrail.push(decision.reason);
128
+ }
129
+ }
130
+ logger?.debug("Fast confirmation rule outcomes", {reasonTrail: reasonTrail.join(",")});
131
+
132
+ // Detect a reorg directly from ancestry instead of the reset reason: when the confirmed block is
133
+ // both epoch-behind and not an ancestor of head, `resetIfBehindOrNotAncestorOrUnsafe` records
134
+ // `ResetBehind` (it takes precedence), so keying off `ResetNotAncestor` alone would miss reorgs
135
+ // that cross an epoch boundary.
136
+ const initialConfirmedBlock = getBlock(ctx, cache, snapshot.confirmedRoot);
137
+ const didReorg = initialConfirmedBlock !== null && !isAncestor(ctx, cache, snapshot.headRoot, snapshot.confirmedRoot);
138
+
139
+ return {
140
+ confirmedRoot: decision.confirmedRoot,
141
+ didReset: decision.didReset,
142
+ reason: decision.reason,
143
+ // Reset cause: the first reset-classified reason in the trail (only when an actual reset occurred).
144
+ resetReason: decision.didReset ? reasonTrail.find(isResetReason) : undefined,
145
+ didReorg,
146
+ // A fallback is a revert to finality: a reset whose final confirmed root is the finalized
147
+ // checkpoint. A later rule may advance the confirmed root forward, which is not a fallback.
148
+ didFallback: decision.didReset && decision.confirmedRoot === snapshot.finalizedRoot,
149
+ didRestart: reasonTrail.includes(FastConfirmationDecisionReason.ObservedJustified),
150
+ };
151
+ }
@@ -0,0 +1,137 @@
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
+ /** Reasons where the confirmed root is pulled backward (a reset), as opposed to advanced forward. */
62
+ const RESET_REASONS = new Set<FastConfirmationDecisionReason>([
63
+ FastConfirmationDecisionReason.ConfirmedNotFound,
64
+ FastConfirmationDecisionReason.ResetBehind,
65
+ FastConfirmationDecisionReason.ResetNotAncestor,
66
+ FastConfirmationDecisionReason.ResetChainUnsafe,
67
+ ]);
68
+
69
+ export function isResetReason(reason: FastConfirmationDecisionReason): boolean {
70
+ return RESET_REASONS.has(reason);
71
+ }
72
+
73
+ export type FastConfirmationDecision = {
74
+ confirmedRoot: RootHex;
75
+ didReset: boolean;
76
+ reason: FastConfirmationDecisionReason;
77
+ };
78
+
79
+ export type FastConfirmationRunResult = FastConfirmationDecision & {
80
+ /**
81
+ * Cause of the reset, when one occurred.
82
+ * The `reason` attribute reports the final disposition and can be different.
83
+ */
84
+ resetReason?: FastConfirmationDecisionReason;
85
+ /** Confirmed block became non-canonical (no longer an ancestor of head) */
86
+ didReorg: boolean;
87
+ /** Confirmed block reverted to the finalized checkpoint */
88
+ didFallback: boolean;
89
+ /** Restarted confirmation from the observed unrealized justified checkpoint */
90
+ didRestart: boolean;
91
+ };
92
+
93
+ export type FastConfirmationRule = (
94
+ snapshot: FastConfirmationSnapshot,
95
+ ctx: FastConfirmationContext,
96
+ store: IFastConfirmationStore,
97
+ cache: FastConfirmationCache,
98
+ decision: FastConfirmationDecision,
99
+ logger?: Logger
100
+ ) => FastConfirmationDecision;
101
+
102
+ export type BalanceSourceKey = "current" | "previous";
103
+
104
+ // This cache is created once per slot
105
+ export type FastConfirmationCache = {
106
+ blockByRoot: Map<RootHex, ProtoBlock | null>;
107
+ ancestorRoots: Map<string, RootHex[] | null>;
108
+ committeeBySlot: Map<Slot, Set<ValidatorIndex>>;
109
+ isDescendantByRootPair: Map<string, boolean>;
110
+ /** voteRoot -> totalWeight, keyed by sourceKey */
111
+ voteWeightBySource: Map<BalanceSourceKey, Map<RootHex, number>>;
112
+ headState?: IBeaconStateView;
113
+ pulledUpHeadState?: IBeaconStateView;
114
+ checkpointStateByKey: Map<string, IBeaconStateView | null>;
115
+ };
116
+
117
+ export type FastConfirmationContext = {
118
+ config: {
119
+ CONFIRMATION_BYZANTINE_THRESHOLD: number;
120
+ PROPOSER_SCORE_BOOST: number;
121
+ };
122
+ getCurrentSlot(): Slot;
123
+ getHead(): ProtoBlock;
124
+ getBlock(root: RootHex): ProtoBlock | null;
125
+ getAncestor(root: RootHex, slot: Slot): RootHex;
126
+ isDescendant(ancestor: RootHex, descendant: RootHex): boolean;
127
+ getLatestMessage(validatorIndex: ValidatorIndex): {root: RootHex; epoch: Epoch} | null;
128
+ getUnrealizedJustified(): {checkpoint: CheckpointWithHex; balances: EffectiveBalanceIncrements};
129
+ getFinalizedCheckpoint(): CheckpointWithHex;
130
+ getEquivocatingIndices(): Set<ValidatorIndex>;
131
+ getTrackedVotesCount(): number;
132
+ };
133
+
134
+ export interface IFastConfirmationRule {
135
+ getConfirmedRoot(): RootHex;
136
+ onSlotStartAfterPastAttestationsApplied(ctx: FastConfirmationContext): FastConfirmationResult;
137
+ }