@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.
- package/lib/forkChoice/fastConfirmation/data.d.ts +4 -0
- package/lib/forkChoice/fastConfirmation/data.d.ts.map +1 -0
- package/lib/forkChoice/fastConfirmation/data.js +31 -0
- package/lib/forkChoice/fastConfirmation/data.js.map +1 -0
- package/lib/forkChoice/fastConfirmation/fastConfirmationRule.d.ts +17 -0
- package/lib/forkChoice/fastConfirmation/fastConfirmationRule.d.ts.map +1 -0
- package/lib/forkChoice/fastConfirmation/fastConfirmationRule.js +129 -0
- package/lib/forkChoice/fastConfirmation/fastConfirmationRule.js.map +1 -0
- package/lib/forkChoice/fastConfirmation/index.d.ts +4 -0
- package/lib/forkChoice/fastConfirmation/index.d.ts.map +1 -0
- package/lib/forkChoice/fastConfirmation/index.js +4 -0
- package/lib/forkChoice/fastConfirmation/index.js.map +1 -0
- package/lib/forkChoice/fastConfirmation/metrics.d.ts +21 -0
- package/lib/forkChoice/fastConfirmation/metrics.d.ts.map +1 -0
- package/lib/forkChoice/fastConfirmation/metrics.js +42 -0
- package/lib/forkChoice/fastConfirmation/metrics.js.map +1 -0
- package/lib/forkChoice/fastConfirmation/rules.d.ts +9 -0
- package/lib/forkChoice/fastConfirmation/rules.d.ts.map +1 -0
- package/lib/forkChoice/fastConfirmation/rules.js +91 -0
- package/lib/forkChoice/fastConfirmation/rules.js.map +1 -0
- package/lib/forkChoice/fastConfirmation/types.d.ts +101 -0
- package/lib/forkChoice/fastConfirmation/types.d.ts.map +1 -0
- package/lib/forkChoice/fastConfirmation/types.js +12 -0
- package/lib/forkChoice/fastConfirmation/types.js.map +1 -0
- package/lib/forkChoice/fastConfirmation/utils.d.ts +47 -0
- package/lib/forkChoice/fastConfirmation/utils.d.ts.map +1 -0
- package/lib/forkChoice/fastConfirmation/utils.js +681 -0
- package/lib/forkChoice/fastConfirmation/utils.js.map +1 -0
- package/lib/forkChoice/forkChoice.d.ts +23 -3
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +116 -9
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +19 -7
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/forkChoice/interface.js.map +1 -1
- package/lib/forkChoice/safeBlocks.d.ts +2 -6
- package/lib/forkChoice/safeBlocks.d.ts.map +1 -1
- package/lib/forkChoice/safeBlocks.js +15 -7
- package/lib/forkChoice/safeBlocks.js.map +1 -1
- package/lib/forkChoice/store.d.ts +13 -2
- package/lib/forkChoice/store.d.ts.map +1 -1
- package/lib/forkChoice/store.js +29 -1
- package/lib/forkChoice/store.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/metrics.d.ts +12 -1
- package/lib/metrics.d.ts.map +1 -1
- package/lib/metrics.js +2 -0
- package/lib/metrics.js.map +1 -1
- package/lib/protoArray/protoArray.d.ts +67 -20
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +170 -38
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +7 -7
- package/src/forkChoice/fastConfirmation/data.ts +43 -0
- package/src/forkChoice/fastConfirmation/fastConfirmationRule.ts +159 -0
- package/src/forkChoice/fastConfirmation/index.ts +3 -0
- package/src/forkChoice/fastConfirmation/metrics.ts +44 -0
- package/src/forkChoice/fastConfirmation/rules.ts +124 -0
- package/src/forkChoice/fastConfirmation/types.ts +111 -0
- package/src/forkChoice/fastConfirmation/utils.ts +968 -0
- package/src/forkChoice/forkChoice.ts +150 -10
- package/src/forkChoice/interface.ts +36 -7
- package/src/forkChoice/safeBlocks.ts +15 -7
- package/src/forkChoice/store.ts +34 -1
- package/src/index.ts +11 -0
- package/src/metrics.ts +3 -1
- package/src/protoArray/protoArray.ts +184 -41
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
import { SLOTS_PER_EPOCH } from "@lodestar/params";
|
|
2
|
+
import { computeEpochAtSlot, computeSlotsSinceEpochStart, computeStartSlotAtEpoch, isActiveValidator, isStartSlotOfEpoch, } from "@lodestar/state-transition";
|
|
3
|
+
import { fromHex } from "@lodestar/utils";
|
|
4
|
+
import { ExecutionStatus } from "../../protoArray/interface.js";
|
|
5
|
+
import { computeTotalBalance, equalCheckpointWithHex } from "../store.js";
|
|
6
|
+
// Spec: adjust_committee_weight_estimate_to_ensure_safety
|
|
7
|
+
// https://github.com/ethereum/consensus-specs/blob/master/specs/phase0/fast-confirmation.md#adjust_committee_weight_estimate_to_ensure_safety
|
|
8
|
+
const COMMITTEE_WEIGHT_ESTIMATION_ADJUSTMENT_FACTOR = 5;
|
|
9
|
+
const SAFETY_THRESHOLD_UNREACHABLE = Object.freeze({
|
|
10
|
+
threshold: Number.POSITIVE_INFINITY,
|
|
11
|
+
proposerScore: 0,
|
|
12
|
+
maximumSupport: 0,
|
|
13
|
+
supportDiscount: 0,
|
|
14
|
+
adversarialWeight: 0,
|
|
15
|
+
});
|
|
16
|
+
export function getBlock(ctx, cache, root) {
|
|
17
|
+
if (cache.blockByRoot.has(root)) {
|
|
18
|
+
return cache.blockByRoot.get(root) ?? null;
|
|
19
|
+
}
|
|
20
|
+
const block = ctx.getBlock(root);
|
|
21
|
+
cache.blockByRoot.set(root, block);
|
|
22
|
+
return block;
|
|
23
|
+
}
|
|
24
|
+
export function getUnrealizedJustification(ctx, cache, blockRoot) {
|
|
25
|
+
const block = getBlock(ctx, cache, blockRoot);
|
|
26
|
+
if (!block)
|
|
27
|
+
return null;
|
|
28
|
+
return {
|
|
29
|
+
epoch: block.unrealizedJustifiedEpoch,
|
|
30
|
+
root: fromHex(block.unrealizedJustifiedRoot),
|
|
31
|
+
rootHex: block.unrealizedJustifiedRoot,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function getVotingSource(ctx, cache, blockRoot) {
|
|
35
|
+
const block = getBlock(ctx, cache, blockRoot);
|
|
36
|
+
if (!block)
|
|
37
|
+
return null;
|
|
38
|
+
const currentEpoch = computeEpochAtSlot(ctx.getCurrentSlot());
|
|
39
|
+
const isFromPrevEpoch = computeEpochAtSlot(block.slot) < currentEpoch;
|
|
40
|
+
const epoch = isFromPrevEpoch ? block.unrealizedJustifiedEpoch : block.justifiedEpoch;
|
|
41
|
+
const rootHex = isFromPrevEpoch ? block.unrealizedJustifiedRoot : block.justifiedRoot;
|
|
42
|
+
return { epoch, root: fromHex(rootHex), rootHex };
|
|
43
|
+
}
|
|
44
|
+
export function getCheckpointForBlock(ctx, blockRoot, epoch) {
|
|
45
|
+
try {
|
|
46
|
+
const epochStartSlot = computeStartSlotAtEpoch(epoch);
|
|
47
|
+
const rootHex = ctx.getAncestor(blockRoot, epochStartSlot);
|
|
48
|
+
return { epoch, root: fromHex(rootHex), rootHex };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Spec: `get_ancestor_roots`
|
|
55
|
+
// https://github.com/ethereum/consensus-specs/blob/master/specs/phase0/fast-confirmation.md#get_ancestor_roots
|
|
56
|
+
export function getAncestorRoots(ctx, cache, blockRoot, terminalRoot) {
|
|
57
|
+
const cacheKey = `${blockRoot}:${terminalRoot}`;
|
|
58
|
+
if (cache.ancestorRoots.has(cacheKey)) {
|
|
59
|
+
return cache.ancestorRoots.get(cacheKey) ?? [];
|
|
60
|
+
}
|
|
61
|
+
const terminalBlock = getBlock(ctx, cache, terminalRoot);
|
|
62
|
+
if (!terminalBlock) {
|
|
63
|
+
cache.ancestorRoots.set(cacheKey, null);
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
let root = blockRoot;
|
|
67
|
+
const ancestorRoots = [];
|
|
68
|
+
let block = getBlock(ctx, cache, root);
|
|
69
|
+
while (block && block.slot > terminalBlock.slot) {
|
|
70
|
+
ancestorRoots.push(root);
|
|
71
|
+
root = block.parentRoot;
|
|
72
|
+
if (root === terminalRoot) {
|
|
73
|
+
ancestorRoots.reverse();
|
|
74
|
+
cache.ancestorRoots.set(cacheKey, ancestorRoots);
|
|
75
|
+
return ancestorRoots;
|
|
76
|
+
}
|
|
77
|
+
block = getBlock(ctx, cache, root);
|
|
78
|
+
}
|
|
79
|
+
cache.ancestorRoots.set(cacheKey, null);
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
export function isAncestor(ctx, cache, blockRoot, ancestorRoot) {
|
|
83
|
+
const ancestorBlock = getBlock(ctx, cache, ancestorRoot);
|
|
84
|
+
if (!ancestorBlock)
|
|
85
|
+
return false;
|
|
86
|
+
try {
|
|
87
|
+
return ctx.getAncestor(blockRoot, ancestorBlock.slot) === ancestorRoot;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function getHeadState(ctx, store, cache) {
|
|
94
|
+
if (cache.headState !== undefined)
|
|
95
|
+
return cache.headState;
|
|
96
|
+
const headState = store.stateGetter({ stateRoot: ctx.getHead().stateRoot });
|
|
97
|
+
if (!headState)
|
|
98
|
+
throw new Error(`Head state not found for root ${ctx.getHead().stateRoot}`);
|
|
99
|
+
cache.headState = headState;
|
|
100
|
+
return cache.headState;
|
|
101
|
+
}
|
|
102
|
+
export function getPulledUpHeadState(ctx, store, cache) {
|
|
103
|
+
if (cache.pulledUpHeadState !== undefined)
|
|
104
|
+
return cache.pulledUpHeadState;
|
|
105
|
+
const headState = getHeadState(ctx, store, cache);
|
|
106
|
+
const currentEpoch = computeEpochAtSlot(ctx.getCurrentSlot());
|
|
107
|
+
cache.pulledUpHeadState =
|
|
108
|
+
headState.epoch < currentEpoch ? headState.processSlots(computeStartSlotAtEpoch(currentEpoch)) : headState;
|
|
109
|
+
return cache.pulledUpHeadState;
|
|
110
|
+
}
|
|
111
|
+
export function getCheckpointState(store, cache, checkpoint) {
|
|
112
|
+
const key = `${checkpoint.epoch}:${checkpoint.rootHex}`;
|
|
113
|
+
if (cache.checkpointStateByKey.has(key)) {
|
|
114
|
+
return cache.checkpointStateByKey.get(key) ?? null;
|
|
115
|
+
}
|
|
116
|
+
const state = store.stateGetter({ checkpoint });
|
|
117
|
+
cache.checkpointStateByKey.set(key, state ?? null);
|
|
118
|
+
return state ?? null;
|
|
119
|
+
}
|
|
120
|
+
export function getSlotCommittee(cache, state, slot) {
|
|
121
|
+
if (cache.committeeBySlot.has(slot)) {
|
|
122
|
+
return cache.committeeBySlot.get(slot) ?? new Set();
|
|
123
|
+
}
|
|
124
|
+
const epoch = computeEpochAtSlot(slot);
|
|
125
|
+
const committeesCount = state.getBeaconCommitteeCountPerSlot(epoch);
|
|
126
|
+
const participants = new Set();
|
|
127
|
+
for (let i = 0; i < committeesCount; i++) {
|
|
128
|
+
const committee = state.getBeaconCommittee(slot, i);
|
|
129
|
+
for (const index of committee) {
|
|
130
|
+
participants.add(index);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
cache.committeeBySlot.set(slot, participants);
|
|
134
|
+
return participants;
|
|
135
|
+
}
|
|
136
|
+
function getSlotRangeParticipants(ctx, store, cache, startSlot, endSlot) {
|
|
137
|
+
const participants = new Set();
|
|
138
|
+
const headState = getHeadState(ctx, store, cache);
|
|
139
|
+
for (let slot = startSlot; slot <= endSlot; slot++) {
|
|
140
|
+
for (const index of getSlotCommittee(cache, headState, slot)) {
|
|
141
|
+
participants.add(index);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return participants;
|
|
145
|
+
}
|
|
146
|
+
function isDescendantCached(ctx, cache, ancestorRoot, descendantRoot) {
|
|
147
|
+
const cacheKey = `${ancestorRoot}:${descendantRoot}`;
|
|
148
|
+
if (cache.isDescendantByRootPair.has(cacheKey)) {
|
|
149
|
+
return cache.isDescendantByRootPair.get(cacheKey) ?? false;
|
|
150
|
+
}
|
|
151
|
+
const isDescendant = ctx.isDescendant(ancestorRoot, descendantRoot);
|
|
152
|
+
cache.isDescendantByRootPair.set(cacheKey, isDescendant);
|
|
153
|
+
return isDescendant;
|
|
154
|
+
}
|
|
155
|
+
export function getBalanceSource(store, cache, kind) {
|
|
156
|
+
const checkpoint = kind === "previous"
|
|
157
|
+
? store.previousEpochObservedJustifiedCheckpoint
|
|
158
|
+
: store.currentEpochObservedJustifiedCheckpoint;
|
|
159
|
+
const fallbackBalances = kind === "previous" ? store.previousEpochObservedJustifiedBalances : store.currentEpochObservedJustifiedBalances;
|
|
160
|
+
const state = getCheckpointState(store, cache, checkpoint);
|
|
161
|
+
return {
|
|
162
|
+
state,
|
|
163
|
+
balances: state?.effectiveBalanceIncrements ?? fallbackBalances,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export function getCurrentBalanceSource(store, cache) {
|
|
167
|
+
return getBalanceSource(store, cache, "current");
|
|
168
|
+
}
|
|
169
|
+
export function getPreviousBalanceSource(store, cache) {
|
|
170
|
+
return getBalanceSource(store, cache, "previous");
|
|
171
|
+
}
|
|
172
|
+
export function getTotalActiveBalance(balanceSource) {
|
|
173
|
+
if (balanceSource.state) {
|
|
174
|
+
return computeTotalBalance(balanceSource.state.getEffectiveBalanceIncrementsZeroInactive());
|
|
175
|
+
}
|
|
176
|
+
// Fallback balances come from the justified-balance path and already zero inactive
|
|
177
|
+
// validators, so summing them gives the active justified total for this balance source.
|
|
178
|
+
return computeTotalBalance(balanceSource.balances);
|
|
179
|
+
}
|
|
180
|
+
export function estimateCommitteeWeightBetweenSlots(balanceSource, startSlot, endSlot) {
|
|
181
|
+
if (startSlot > endSlot)
|
|
182
|
+
return 0;
|
|
183
|
+
const totalActiveBalance = getTotalActiveBalance(balanceSource);
|
|
184
|
+
const startEpoch = computeEpochAtSlot(startSlot);
|
|
185
|
+
const endEpoch = computeEpochAtSlot(endSlot);
|
|
186
|
+
if (isFullValidatorSetCovered(startSlot, endSlot)) {
|
|
187
|
+
return totalActiveBalance;
|
|
188
|
+
}
|
|
189
|
+
const committeeWeightPerSlot = Math.floor(totalActiveBalance / SLOTS_PER_EPOCH);
|
|
190
|
+
if (startEpoch === endEpoch) {
|
|
191
|
+
return committeeWeightPerSlot * (endSlot - startSlot + 1);
|
|
192
|
+
}
|
|
193
|
+
const numSlotsInStartEpoch = SLOTS_PER_EPOCH - computeSlotsSinceEpochStart(startSlot);
|
|
194
|
+
const numSlotsInEndEpoch = computeSlotsSinceEpochStart(endSlot) + 1;
|
|
195
|
+
const remainingSlotsInEndEpoch = SLOTS_PER_EPOCH - numSlotsInEndEpoch;
|
|
196
|
+
const startEpochWeight = committeeWeightPerSlot * numSlotsInStartEpoch;
|
|
197
|
+
const endEpochWeight = committeeWeightPerSlot * numSlotsInEndEpoch;
|
|
198
|
+
// For ranges that cross exactly one epoch boundary without covering a full epoch,
|
|
199
|
+
// the spec models overlap as:
|
|
200
|
+
// startEpochWeightProRated = startEpochWeight * (1 - numSlotsInEndEpoch / SLOTS_PER_EPOCH)
|
|
201
|
+
// = startEpochWeight * remainingSlotsInEndEpoch / SLOTS_PER_EPOCH
|
|
202
|
+
// We keep the spec's "pro-rate the start epoch" form so integer rounding matches it exactly.
|
|
203
|
+
const startEpochWeightProRated = Math.floor(startEpochWeight / SLOTS_PER_EPOCH) * remainingSlotsInEndEpoch;
|
|
204
|
+
return adjustCommitteeWeightEstimateToEnsureSafety(startEpochWeightProRated + endEpochWeight);
|
|
205
|
+
}
|
|
206
|
+
export function adjustCommitteeWeightEstimateToEnsureSafety(estimate) {
|
|
207
|
+
// The spec applies this adjustment in raw Gwei:
|
|
208
|
+
// ceil(estimate_gwei / 1000) * (1000 + factor)
|
|
209
|
+
//
|
|
210
|
+
// Lodestar carries effective balance increments instead, where:
|
|
211
|
+
// 1 increment = EFFECTIVE_BALANCE_INCREMENT = 1e9 Gwei
|
|
212
|
+
//
|
|
213
|
+
// Since each increment is already far larger than 1000 Gwei, the spec's
|
|
214
|
+
// `ceil(... / 1000)` becomes a no-op at our unit scale. The equivalent
|
|
215
|
+
// conservative adjustment in increments is therefore:
|
|
216
|
+
// ceil(estimate_increments * (1000 + factor) / 1000)
|
|
217
|
+
// The `+ 999` is integer ceiling division by 1000.
|
|
218
|
+
return Math.floor((estimate * (1000 + COMMITTEE_WEIGHT_ESTIMATION_ADJUSTMENT_FACTOR) + 999) / 1000);
|
|
219
|
+
}
|
|
220
|
+
export function isFullValidatorSetCovered(startSlot, endSlot) {
|
|
221
|
+
const startFullEpoch = computeEpochAtSlot(startSlot + (SLOTS_PER_EPOCH - 1));
|
|
222
|
+
const endFullEpoch = computeEpochAtSlot((endSlot + 1));
|
|
223
|
+
return startFullEpoch < endFullEpoch;
|
|
224
|
+
}
|
|
225
|
+
export function computeProposerScore(ctx, balanceSource) {
|
|
226
|
+
const totalActiveBalance = getTotalActiveBalance(balanceSource);
|
|
227
|
+
const committeeWeight = Math.floor(totalActiveBalance / SLOTS_PER_EPOCH);
|
|
228
|
+
return Math.floor((committeeWeight * ctx.config.PROPOSER_SCORE_BOOST) / 100);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Build vote weight map in a single pass over all active validators.
|
|
232
|
+
* Groups validators by their latest vote root, summing their balances.
|
|
233
|
+
* Cached per sourceKey ("current" | "previous").
|
|
234
|
+
*/
|
|
235
|
+
function ensureVoteMaps(ctx, cache, balanceSource, sourceKey) {
|
|
236
|
+
if (cache.voteWeightBySource.has(sourceKey))
|
|
237
|
+
return;
|
|
238
|
+
const voteMap = new Map();
|
|
239
|
+
const balances = balanceSource.balances;
|
|
240
|
+
const state = balanceSource.state;
|
|
241
|
+
const activeIndices = state?.getCurrentShuffling().activeIndices ?? null;
|
|
242
|
+
const equivocating = ctx.getEquivocatingIndices();
|
|
243
|
+
const indices = activeIndices ?? balances.keys();
|
|
244
|
+
const isSlashed = state ? (i) => state.getValidator(i).slashed : () => false;
|
|
245
|
+
for (const i of indices) {
|
|
246
|
+
if (isSlashed(i))
|
|
247
|
+
continue;
|
|
248
|
+
if (equivocating.has(i))
|
|
249
|
+
continue;
|
|
250
|
+
const weight = balances[i] ?? 0;
|
|
251
|
+
if (weight === 0)
|
|
252
|
+
continue;
|
|
253
|
+
const msg = ctx.getLatestMessage(i);
|
|
254
|
+
if (!msg)
|
|
255
|
+
continue;
|
|
256
|
+
voteMap.set(msg.root, (voteMap.get(msg.root) ?? 0) + weight);
|
|
257
|
+
}
|
|
258
|
+
cache.voteWeightBySource.set(sourceKey, voteMap);
|
|
259
|
+
}
|
|
260
|
+
export function getAttestationScore(ctx, cache, balanceSource, blockRoot, sourceKey) {
|
|
261
|
+
ensureVoteMaps(ctx, cache, balanceSource, sourceKey);
|
|
262
|
+
const voteMap = cache.voteWeightBySource.get(sourceKey) ?? new Map();
|
|
263
|
+
let score = 0;
|
|
264
|
+
for (const [voteRoot, weight] of voteMap) {
|
|
265
|
+
if (isDescendantCached(ctx, cache, blockRoot, voteRoot)) {
|
|
266
|
+
score += weight;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return score;
|
|
270
|
+
}
|
|
271
|
+
export function getBlockSupportBetweenSlots(ctx, store, cache, balanceSource, blockRoot, startSlot, endSlot) {
|
|
272
|
+
if (startSlot > endSlot)
|
|
273
|
+
return 0;
|
|
274
|
+
const balances = balanceSource.balances;
|
|
275
|
+
const state = balanceSource.state;
|
|
276
|
+
const stateEpoch = state ? computeEpochAtSlot(state.slot) : null;
|
|
277
|
+
const participants = getSlotRangeParticipants(ctx, store, cache, startSlot, endSlot);
|
|
278
|
+
if (participants.size === 0)
|
|
279
|
+
return 0;
|
|
280
|
+
const equivocating = ctx.getEquivocatingIndices();
|
|
281
|
+
let score = 0;
|
|
282
|
+
for (const i of participants) {
|
|
283
|
+
if (i >= balances.length)
|
|
284
|
+
continue;
|
|
285
|
+
const validator = state?.getValidator(i);
|
|
286
|
+
if (validator?.slashed)
|
|
287
|
+
continue;
|
|
288
|
+
if (validator && stateEpoch !== null && !isActiveValidator(validator, stateEpoch))
|
|
289
|
+
continue;
|
|
290
|
+
if (equivocating.has(i))
|
|
291
|
+
continue;
|
|
292
|
+
const latestMessage = ctx.getLatestMessage(i);
|
|
293
|
+
if (latestMessage?.root === blockRoot) {
|
|
294
|
+
score += balances[i] ?? 0;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return score;
|
|
298
|
+
}
|
|
299
|
+
export function getEquivocationScore(ctx, store, cache, balanceSource, startSlot, endSlot) {
|
|
300
|
+
if (startSlot > endSlot)
|
|
301
|
+
return 0;
|
|
302
|
+
const balances = balanceSource.balances;
|
|
303
|
+
const state = balanceSource.state;
|
|
304
|
+
const stateEpoch = state ? computeEpochAtSlot(state.slot) : null;
|
|
305
|
+
const participants = getSlotRangeParticipants(ctx, store, cache, startSlot, endSlot);
|
|
306
|
+
if (participants.size === 0)
|
|
307
|
+
return 0;
|
|
308
|
+
const equivocating = ctx.getEquivocatingIndices();
|
|
309
|
+
let score = 0;
|
|
310
|
+
for (const i of participants) {
|
|
311
|
+
if (!equivocating.has(i))
|
|
312
|
+
continue;
|
|
313
|
+
if (i >= balances.length)
|
|
314
|
+
continue;
|
|
315
|
+
const validator = state?.getValidator(i);
|
|
316
|
+
if (validator && stateEpoch !== null && !isActiveValidator(validator, stateEpoch))
|
|
317
|
+
continue;
|
|
318
|
+
score += balances[i] ?? 0;
|
|
319
|
+
}
|
|
320
|
+
return score;
|
|
321
|
+
}
|
|
322
|
+
export function computeAdversarialWeight(ctx, store, cache, balanceSource, startSlot, endSlot) {
|
|
323
|
+
const maximumWeight = estimateCommitteeWeightBetweenSlots(balanceSource, startSlot, endSlot);
|
|
324
|
+
// The spec uses raw Gwei and computes `maximum_weight // 100 * threshold`.
|
|
325
|
+
// Lodestar carries effective-balance increments instead, so divide after multiplying to avoid
|
|
326
|
+
// dropping the adversarial budget to zero for small validator sets/minimal presets.
|
|
327
|
+
const maxAdversarialWeight = Math.floor((maximumWeight * ctx.config.CONFIRMATION_BYZANTINE_THRESHOLD) / 100);
|
|
328
|
+
const equivocationScore = getEquivocationScore(ctx, store, cache, balanceSource, startSlot, endSlot);
|
|
329
|
+
return maxAdversarialWeight > equivocationScore ? maxAdversarialWeight - equivocationScore : 0;
|
|
330
|
+
}
|
|
331
|
+
export function getAdversarialWeight(ctx, store, cache, balanceSource, blockRoot) {
|
|
332
|
+
const currentSlot = ctx.getCurrentSlot();
|
|
333
|
+
if (currentSlot === 0)
|
|
334
|
+
return 0;
|
|
335
|
+
const block = getBlock(ctx, cache, blockRoot);
|
|
336
|
+
if (!block)
|
|
337
|
+
return 0;
|
|
338
|
+
const parentBlock = getBlock(ctx, cache, block.parentRoot);
|
|
339
|
+
if (!parentBlock)
|
|
340
|
+
return 0;
|
|
341
|
+
const blockEpoch = computeEpochAtSlot(block.slot);
|
|
342
|
+
const parentEpoch = computeEpochAtSlot(parentBlock.slot);
|
|
343
|
+
if (blockEpoch > parentEpoch) {
|
|
344
|
+
const startSlot = computeStartSlotAtEpoch(blockEpoch);
|
|
345
|
+
return computeAdversarialWeight(ctx, store, cache, balanceSource, startSlot, (currentSlot - 1));
|
|
346
|
+
}
|
|
347
|
+
return computeAdversarialWeight(ctx, store, cache, balanceSource, block.slot, (currentSlot - 1));
|
|
348
|
+
}
|
|
349
|
+
export function computeEmptySlotSupportDiscount(ctx, store, cache, balanceSource, blockRoot) {
|
|
350
|
+
const block = getBlock(ctx, cache, blockRoot);
|
|
351
|
+
if (!block)
|
|
352
|
+
return 0;
|
|
353
|
+
const parentBlock = getBlock(ctx, cache, block.parentRoot);
|
|
354
|
+
if (!parentBlock)
|
|
355
|
+
return 0;
|
|
356
|
+
if (parentBlock.slot + 1 === block.slot) {
|
|
357
|
+
return 0;
|
|
358
|
+
}
|
|
359
|
+
const parentSupportInEmptySlots = getBlockSupportBetweenSlots(ctx, store, cache, balanceSource, block.parentRoot, (parentBlock.slot + 1), (block.slot - 1));
|
|
360
|
+
const adversarialWeight = computeAdversarialWeight(ctx, store, cache, balanceSource, (parentBlock.slot + 1), (block.slot - 1));
|
|
361
|
+
return parentSupportInEmptySlots > adversarialWeight ? parentSupportInEmptySlots - adversarialWeight : 0;
|
|
362
|
+
}
|
|
363
|
+
export function computeSafetyThreshold(ctx, store, cache, balanceSource, blockRoot) {
|
|
364
|
+
const currentSlot = ctx.getCurrentSlot();
|
|
365
|
+
const block = getBlock(ctx, cache, blockRoot);
|
|
366
|
+
if (!block) {
|
|
367
|
+
return SAFETY_THRESHOLD_UNREACHABLE;
|
|
368
|
+
}
|
|
369
|
+
const parentBlock = getBlock(ctx, cache, block.parentRoot);
|
|
370
|
+
if (!parentBlock) {
|
|
371
|
+
return SAFETY_THRESHOLD_UNREACHABLE;
|
|
372
|
+
}
|
|
373
|
+
// Spec: compute_safety_threshold(store, block_root, balance_source)
|
|
374
|
+
// Build the threshold from the same terms used in the paper/spec:
|
|
375
|
+
// max possible committee support, proposer boost, empty-slot discount, and adversarial budget.
|
|
376
|
+
const proposerScore = computeProposerScore(ctx, balanceSource);
|
|
377
|
+
const maximumSupport = estimateCommitteeWeightBetweenSlots(balanceSource, (parentBlock.slot + 1), (currentSlot - 1));
|
|
378
|
+
const supportDiscount = computeEmptySlotSupportDiscount(ctx, store, cache, balanceSource, blockRoot);
|
|
379
|
+
const adversarialWeight = getAdversarialWeight(ctx, store, cache, balanceSource, blockRoot);
|
|
380
|
+
// Spec underflow guard:
|
|
381
|
+
// if the discount alone already exceeds the threshold budget, the safety threshold is zero.
|
|
382
|
+
const threshold = supportDiscount > maximumSupport + proposerScore + 2 * adversarialWeight
|
|
383
|
+
? 0
|
|
384
|
+
: Math.floor((maximumSupport + proposerScore + 2 * adversarialWeight - supportDiscount) / 2);
|
|
385
|
+
return { threshold, proposerScore, maximumSupport, supportDiscount, adversarialWeight };
|
|
386
|
+
}
|
|
387
|
+
export function isOneConfirmed(ctx, store, cache, balanceSource, blockRoot, sourceKey, logger) {
|
|
388
|
+
const currentSlot = ctx.getCurrentSlot();
|
|
389
|
+
if (currentSlot === 0)
|
|
390
|
+
return false;
|
|
391
|
+
const block = getBlock(ctx, cache, blockRoot);
|
|
392
|
+
if (!block)
|
|
393
|
+
return false;
|
|
394
|
+
if (block.executionStatus !== ExecutionStatus.Valid && block.executionStatus !== ExecutionStatus.PreMerge) {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
// Spec: is_one_confirmed(store, balance_source, block_root)
|
|
398
|
+
// Compare actual support for this block against the computed LMD-GHOST safety threshold.
|
|
399
|
+
const support = getAttestationScore(ctx, cache, balanceSource, blockRoot, sourceKey);
|
|
400
|
+
const { threshold, proposerScore, maximumSupport, supportDiscount, adversarialWeight } = computeSafetyThreshold(ctx, store, cache, balanceSource, blockRoot);
|
|
401
|
+
const isConfirmed = support > threshold;
|
|
402
|
+
logger?.debug("Fast confirmation one-confirmed evaluation", {
|
|
403
|
+
blockRoot,
|
|
404
|
+
blockSlot: block.slot,
|
|
405
|
+
currentSlot,
|
|
406
|
+
sourceKey,
|
|
407
|
+
support,
|
|
408
|
+
threshold,
|
|
409
|
+
proposerScore,
|
|
410
|
+
maximumSupport,
|
|
411
|
+
supportDiscount,
|
|
412
|
+
adversarialWeight,
|
|
413
|
+
isConfirmed,
|
|
414
|
+
});
|
|
415
|
+
return isConfirmed;
|
|
416
|
+
}
|
|
417
|
+
export function getCurrentTarget(ctx) {
|
|
418
|
+
const head = ctx.getHead().blockRoot;
|
|
419
|
+
const currentEpoch = computeEpochAtSlot(ctx.getCurrentSlot());
|
|
420
|
+
return getCheckpointForBlock(ctx, head, currentEpoch);
|
|
421
|
+
}
|
|
422
|
+
export function getCurrentEpochState(ctx, store, cache) {
|
|
423
|
+
return getPulledUpHeadState(ctx, store, cache);
|
|
424
|
+
}
|
|
425
|
+
export function getCurrentTargetScore(ctx, store, cache) {
|
|
426
|
+
const target = getCurrentTarget(ctx);
|
|
427
|
+
const targetState = getCurrentEpochState(ctx, store, cache);
|
|
428
|
+
if (!target || !targetState)
|
|
429
|
+
return 0;
|
|
430
|
+
const balances = targetState.effectiveBalanceIncrements;
|
|
431
|
+
const activeIndices = targetState.getCurrentShuffling().activeIndices;
|
|
432
|
+
const equivocating = ctx.getEquivocatingIndices();
|
|
433
|
+
// Group validators by (voteRoot, voteEpoch) to avoid per-validator getCheckpointForBlock calls.
|
|
434
|
+
// On mainnet ~1M validators vote for only ~50 unique (root, epoch) pairs.
|
|
435
|
+
const voteGroups = new Map();
|
|
436
|
+
for (const i of activeIndices) {
|
|
437
|
+
if (targetState.getValidator(i).slashed)
|
|
438
|
+
continue;
|
|
439
|
+
if (equivocating.has(i))
|
|
440
|
+
continue;
|
|
441
|
+
const msg = ctx.getLatestMessage(i);
|
|
442
|
+
if (!msg)
|
|
443
|
+
continue;
|
|
444
|
+
const weight = balances[i] ?? 0;
|
|
445
|
+
if (weight === 0)
|
|
446
|
+
continue;
|
|
447
|
+
let byEpoch = voteGroups.get(msg.root);
|
|
448
|
+
if (!byEpoch) {
|
|
449
|
+
byEpoch = new Map();
|
|
450
|
+
voteGroups.set(msg.root, byEpoch);
|
|
451
|
+
}
|
|
452
|
+
byEpoch.set(msg.epoch, (byEpoch.get(msg.epoch) ?? 0) + weight);
|
|
453
|
+
}
|
|
454
|
+
// Check each unique vote group's checkpoint against the target
|
|
455
|
+
let score = 0;
|
|
456
|
+
for (const [root, byEpoch] of voteGroups) {
|
|
457
|
+
for (const [epoch, weight] of byEpoch) {
|
|
458
|
+
const cp = getCheckpointForBlock(ctx, root, epoch);
|
|
459
|
+
if (cp && cp.epoch === target.epoch && cp.rootHex === target.rootHex) {
|
|
460
|
+
score += weight;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return score;
|
|
465
|
+
}
|
|
466
|
+
function computeHonestFfgSupport(totalActiveBalance, ffgSupport, ffgWeightTillNow, adversarialWeight, byzantineThreshold) {
|
|
467
|
+
const remainingFfgWeight = totalActiveBalance - ffgWeightTillNow;
|
|
468
|
+
// The spec rounds in raw Gwei as `remaining_ffg_weight // 100 * honest_percent`.
|
|
469
|
+
// In Lodestar this value is in effective-balance increments, so multiply before dividing by 100
|
|
470
|
+
// to preserve the same percentage semantics at our coarser unit scale.
|
|
471
|
+
const remainingHonestFfgWeight = Math.floor((remainingFfgWeight * (100 - byzantineThreshold)) / 100);
|
|
472
|
+
const minHonestFfgSupport = ffgSupport - Math.min(adversarialWeight, ffgSupport);
|
|
473
|
+
return minHonestFfgSupport + remainingHonestFfgWeight;
|
|
474
|
+
}
|
|
475
|
+
export function computeHonestFfgSupportForCurrentTarget(ctx, store, cache) {
|
|
476
|
+
const currentSlot = ctx.getCurrentSlot();
|
|
477
|
+
if (currentSlot === 0)
|
|
478
|
+
return 0;
|
|
479
|
+
const currentEpoch = computeEpochAtSlot(currentSlot);
|
|
480
|
+
const targetState = getCurrentEpochState(ctx, store, cache);
|
|
481
|
+
if (!targetState)
|
|
482
|
+
return 0;
|
|
483
|
+
const totalActiveBalance = computeTotalBalance(targetState.getEffectiveBalanceIncrementsZeroInactive());
|
|
484
|
+
const ffgSupport = getCurrentTargetScore(ctx, store, cache);
|
|
485
|
+
const tillNowFFGWeight = estimateCommitteeWeightBetweenSlots({ state: targetState, balances: targetState.effectiveBalanceIncrements }, computeStartSlotAtEpoch(currentEpoch), (currentSlot - 1));
|
|
486
|
+
const adversarialWeight = computeAdversarialWeight(ctx, store, cache, { state: targetState, balances: targetState.effectiveBalanceIncrements }, computeStartSlotAtEpoch(currentEpoch), (currentSlot - 1));
|
|
487
|
+
return computeHonestFfgSupport(totalActiveBalance, ffgSupport, tillNowFFGWeight, adversarialWeight, ctx.config.CONFIRMATION_BYZANTINE_THRESHOLD);
|
|
488
|
+
}
|
|
489
|
+
export function willNoConflictingCheckpointBeJustified(ctx, store, cache) {
|
|
490
|
+
const target = getCurrentTarget(ctx);
|
|
491
|
+
if (!target)
|
|
492
|
+
return false;
|
|
493
|
+
if (equalCheckpointWithHex(target, ctx.getUnrealizedJustified().checkpoint)) {
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
const targetState = getCurrentEpochState(ctx, store, cache);
|
|
497
|
+
if (!targetState)
|
|
498
|
+
return false;
|
|
499
|
+
const totalActiveBalance = computeTotalBalance(targetState.getEffectiveBalanceIncrementsZeroInactive());
|
|
500
|
+
const honestSupport = computeHonestFfgSupportForCurrentTarget(ctx, store, cache);
|
|
501
|
+
return 3 * honestSupport > 1 * totalActiveBalance;
|
|
502
|
+
}
|
|
503
|
+
export function willCurrentTargetBeJustified(ctx, store, cache) {
|
|
504
|
+
const targetState = getCurrentEpochState(ctx, store, cache);
|
|
505
|
+
if (!targetState)
|
|
506
|
+
return false;
|
|
507
|
+
const totalActiveBalance = computeTotalBalance(targetState.getEffectiveBalanceIncrementsZeroInactive());
|
|
508
|
+
const honestSupport = computeHonestFfgSupportForCurrentTarget(ctx, store, cache);
|
|
509
|
+
return 3 * honestSupport >= 2 * totalActiveBalance;
|
|
510
|
+
}
|
|
511
|
+
export function isConfirmedChainSafe(ctx, store, cache, confirmedRoot, logger) {
|
|
512
|
+
const checkpointInConfirmedChain = getCheckpointForBlock(ctx, confirmedRoot, store.currentEpochObservedJustifiedCheckpoint.epoch);
|
|
513
|
+
if (checkpointInConfirmedChain === null ||
|
|
514
|
+
!equalCheckpointWithHex(store.currentEpochObservedJustifiedCheckpoint, checkpointInConfirmedChain)) {
|
|
515
|
+
logger?.debug("Fast confirmation chain-safety failed", {
|
|
516
|
+
confirmedRoot,
|
|
517
|
+
reason: "observed_justified_checkpoint_not_in_confirmed_chain",
|
|
518
|
+
observedJustifiedRoot: store.currentEpochObservedJustifiedCheckpoint.rootHex,
|
|
519
|
+
observedJustifiedEpoch: store.currentEpochObservedJustifiedCheckpoint.epoch,
|
|
520
|
+
checkpointRoot: checkpointInConfirmedChain?.rootHex,
|
|
521
|
+
checkpointEpoch: checkpointInConfirmedChain?.epoch,
|
|
522
|
+
});
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
const currentEpoch = computeEpochAtSlot(ctx.getCurrentSlot());
|
|
526
|
+
let startRoot;
|
|
527
|
+
if (store.currentEpochObservedJustifiedCheckpoint.epoch + 1 >= currentEpoch) {
|
|
528
|
+
startRoot = store.currentEpochObservedJustifiedCheckpoint.rootHex;
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
let ancestorAtPreviousEpochStartRoot;
|
|
532
|
+
try {
|
|
533
|
+
ancestorAtPreviousEpochStartRoot = ctx.getAncestor(confirmedRoot, computeStartSlotAtEpoch((currentEpoch - 1)));
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
const ancestorAtPreviousEpochStart = getBlock(ctx, cache, ancestorAtPreviousEpochStartRoot);
|
|
539
|
+
if (!ancestorAtPreviousEpochStart)
|
|
540
|
+
return false;
|
|
541
|
+
const ancestorEpoch = computeEpochAtSlot(ancestorAtPreviousEpochStart.slot);
|
|
542
|
+
if (ancestorEpoch + 1 === currentEpoch) {
|
|
543
|
+
startRoot = ancestorAtPreviousEpochStart.parentRoot;
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
startRoot = ancestorAtPreviousEpochStartRoot;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const chainRoots = getAncestorRoots(ctx, cache, confirmedRoot, startRoot);
|
|
550
|
+
const previousBalanceSource = getPreviousBalanceSource(store, cache);
|
|
551
|
+
for (const root of chainRoots) {
|
|
552
|
+
if (!isOneConfirmed(ctx, store, cache, previousBalanceSource, root, "previous", logger)) {
|
|
553
|
+
logger?.debug("Fast confirmation chain-safety failed", {
|
|
554
|
+
confirmedRoot,
|
|
555
|
+
reason: "unconfirmed_block_in_chain",
|
|
556
|
+
blockRoot: root,
|
|
557
|
+
});
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return true;
|
|
562
|
+
}
|
|
563
|
+
// Spec: `find_latest_confirmed_descendant`
|
|
564
|
+
// https://github.com/ethereum/consensus-specs/blob/master/specs/phase0/fast-confirmation.md#find_latest_confirmed_descendant
|
|
565
|
+
export function findLatestConfirmedDescendant(snapshot, ctx, store, cache, latestConfirmedRoot, logger) {
|
|
566
|
+
const currentEpoch = snapshot.currentEpoch;
|
|
567
|
+
let confirmedRoot = latestConfirmedRoot;
|
|
568
|
+
const previousSlotVotingSource = getVotingSource(ctx, cache, store.previousSlotHead);
|
|
569
|
+
const prevSlotJustification = getUnrealizedJustification(ctx, cache, store.previousSlotHead);
|
|
570
|
+
const headJustification = snapshot.headUnrealized ?? getUnrealizedJustification(ctx, cache, snapshot.headRoot);
|
|
571
|
+
const currentBalanceSource = getCurrentBalanceSource(store, cache);
|
|
572
|
+
const confirmedBlock = getBlock(ctx, cache, confirmedRoot);
|
|
573
|
+
const confirmedEpoch = confirmedBlock ? computeEpochAtSlot(confirmedBlock.slot) : null;
|
|
574
|
+
const shouldAdvanceThroughPreviousEpoch = confirmedEpoch !== null &&
|
|
575
|
+
confirmedEpoch + 1 === currentEpoch &&
|
|
576
|
+
previousSlotVotingSource !== null &&
|
|
577
|
+
previousSlotVotingSource.epoch + 2 >= currentEpoch &&
|
|
578
|
+
(isStartSlotOfEpoch(snapshot.currentSlot) ||
|
|
579
|
+
(willNoConflictingCheckpointBeJustified(ctx, store, cache) &&
|
|
580
|
+
((prevSlotJustification !== null && prevSlotJustification.epoch + 1 >= currentEpoch) ||
|
|
581
|
+
(headJustification !== null && headJustification.epoch + 1 >= currentEpoch))));
|
|
582
|
+
logger?.debug("Fast confirmation descendant search start", {
|
|
583
|
+
latestConfirmedRoot,
|
|
584
|
+
currentEpoch,
|
|
585
|
+
headRoot: snapshot.headRoot,
|
|
586
|
+
shouldAdvanceThroughPreviousEpoch,
|
|
587
|
+
});
|
|
588
|
+
if (shouldAdvanceThroughPreviousEpoch) {
|
|
589
|
+
const canonicalRoots = getAncestorRoots(ctx, cache, snapshot.headRoot, confirmedRoot);
|
|
590
|
+
for (const blockRoot of canonicalRoots) {
|
|
591
|
+
const block = getBlock(ctx, cache, blockRoot);
|
|
592
|
+
const blockEpoch = block ? computeEpochAtSlot(block.slot) : null;
|
|
593
|
+
const blockSlot = block?.slot;
|
|
594
|
+
if (blockEpoch === null || blockEpoch === currentEpoch) {
|
|
595
|
+
logger?.debug("Fast confirmation previous-epoch loop stopped", {
|
|
596
|
+
reason: "reached_current_epoch_or_unknown_epoch",
|
|
597
|
+
blockRoot,
|
|
598
|
+
blockSlot,
|
|
599
|
+
blockEpoch,
|
|
600
|
+
});
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
if (!isAncestor(ctx, cache, store.previousSlotHead, blockRoot)) {
|
|
604
|
+
logger?.debug("Fast confirmation previous-epoch loop stopped", {
|
|
605
|
+
reason: "not_ancestor_of_previous_slot_head",
|
|
606
|
+
blockRoot,
|
|
607
|
+
blockSlot,
|
|
608
|
+
blockEpoch,
|
|
609
|
+
previousSlotHead: store.previousSlotHead,
|
|
610
|
+
});
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
const isConfirmed = isOneConfirmed(ctx, store, cache, currentBalanceSource, blockRoot, "current", logger);
|
|
614
|
+
if (!isConfirmed) {
|
|
615
|
+
logger?.debug("Fast confirmation previous-epoch loop stopped", {
|
|
616
|
+
reason: "block_not_one_confirmed",
|
|
617
|
+
blockRoot,
|
|
618
|
+
blockSlot,
|
|
619
|
+
blockEpoch,
|
|
620
|
+
});
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
confirmedRoot = blockRoot;
|
|
624
|
+
logger?.debug("Fast confirmation previous-epoch loop advanced", { confirmedRoot });
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const shouldAdvanceThroughCurrentEpoch = isStartSlotOfEpoch(snapshot.currentSlot) ||
|
|
628
|
+
(headJustification !== null && headJustification.epoch + 1 >= currentEpoch);
|
|
629
|
+
if (shouldAdvanceThroughCurrentEpoch) {
|
|
630
|
+
const canonicalRoots = getAncestorRoots(ctx, cache, snapshot.headRoot, confirmedRoot);
|
|
631
|
+
let tentativeConfirmedRoot = confirmedRoot;
|
|
632
|
+
for (const blockRoot of canonicalRoots) {
|
|
633
|
+
const block = getBlock(ctx, cache, blockRoot);
|
|
634
|
+
const blockEpoch = block ? computeEpochAtSlot(block.slot) : null;
|
|
635
|
+
const blockSlot = block?.slot;
|
|
636
|
+
const tentativeBlock = getBlock(ctx, cache, tentativeConfirmedRoot);
|
|
637
|
+
const tentativeEpoch = tentativeBlock ? computeEpochAtSlot(tentativeBlock.slot) : null;
|
|
638
|
+
if (blockEpoch === null || tentativeEpoch === null)
|
|
639
|
+
break;
|
|
640
|
+
if (blockEpoch > tentativeEpoch && !willCurrentTargetBeJustified(ctx, store, cache)) {
|
|
641
|
+
logger?.debug("Fast confirmation current-epoch loop stopped", {
|
|
642
|
+
reason: "current_target_not_justified",
|
|
643
|
+
blockRoot,
|
|
644
|
+
blockSlot,
|
|
645
|
+
blockEpoch,
|
|
646
|
+
tentativeEpoch,
|
|
647
|
+
});
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
const isConfirmed = isOneConfirmed(ctx, store, cache, currentBalanceSource, blockRoot, "current", logger);
|
|
651
|
+
if (!isConfirmed) {
|
|
652
|
+
logger?.debug("Fast confirmation current-epoch loop stopped", {
|
|
653
|
+
reason: "block_not_one_confirmed",
|
|
654
|
+
blockRoot,
|
|
655
|
+
blockSlot,
|
|
656
|
+
blockEpoch,
|
|
657
|
+
});
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
tentativeConfirmedRoot = blockRoot;
|
|
661
|
+
logger?.debug("Fast confirmation current-epoch loop advanced", { tentativeConfirmedRoot });
|
|
662
|
+
}
|
|
663
|
+
const tentativeBlock = getBlock(ctx, cache, tentativeConfirmedRoot);
|
|
664
|
+
const tentativeEpoch = tentativeBlock ? computeEpochAtSlot(tentativeBlock.slot) : null;
|
|
665
|
+
const tentativeVotingSource = getVotingSource(ctx, cache, tentativeConfirmedRoot);
|
|
666
|
+
if (tentativeEpoch !== null &&
|
|
667
|
+
(tentativeEpoch === currentEpoch ||
|
|
668
|
+
(tentativeVotingSource !== null &&
|
|
669
|
+
tentativeVotingSource.epoch + 2 >= currentEpoch &&
|
|
670
|
+
(isStartSlotOfEpoch(snapshot.currentSlot) || willNoConflictingCheckpointBeJustified(ctx, store, cache))))) {
|
|
671
|
+
confirmedRoot = tentativeConfirmedRoot;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
logger?.debug("Fast confirmation descendant search result", {
|
|
675
|
+
latestConfirmedRoot,
|
|
676
|
+
confirmedRoot,
|
|
677
|
+
shouldAdvanceThroughCurrentEpoch,
|
|
678
|
+
});
|
|
679
|
+
return confirmedRoot;
|
|
680
|
+
}
|
|
681
|
+
//# sourceMappingURL=utils.js.map
|