@lodestar/fork-choice 1.44.0-dev.86fc005b68 → 1.44.0-dev.8d5a6a4fc1
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 +10 -0
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +99 -5
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +4 -0
- package/lib/forkChoice/interface.d.ts.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 +33 -2
- package/lib/forkChoice/store.d.ts.map +1 -1
- package/lib/forkChoice/store.js +37 -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 +11 -0
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +21 -0
- 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 +119 -6
- package/src/forkChoice/interface.ts +5 -0
- package/src/forkChoice/safeBlocks.ts +15 -7
- package/src/forkChoice/store.ts +45 -1
- package/src/index.ts +11 -0
- package/src/metrics.ts +3 -1
- package/src/protoArray/protoArray.ts +25 -0
|
@@ -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
|
|
@@ -998,12 +1021,12 @@ export class ForkChoice implements IForkChoice {
|
|
|
998
1021
|
const previousSlot = this.fcStore.currentSlot;
|
|
999
1022
|
// Note: we are relying upon `onTick` to update `fcStore.time` to ensure we don't get stuck in a loop.
|
|
1000
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();
|
|
1001
1029
|
}
|
|
1002
|
-
|
|
1003
|
-
this.queuedAttestationsPreviousSlot = 0;
|
|
1004
|
-
// Process any attestations that might now be eligible.
|
|
1005
|
-
this.processAttestationQueue();
|
|
1006
|
-
this.validatedAttestationDatas = new Set();
|
|
1007
1030
|
}
|
|
1008
1031
|
|
|
1009
1032
|
getTime(): Slot {
|
|
@@ -1080,6 +1103,14 @@ export class ForkChoice implements IForkChoice {
|
|
|
1080
1103
|
return this.protoArray.getPTCVoteCounts(blockRootHex);
|
|
1081
1104
|
}
|
|
1082
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
|
+
|
|
1083
1114
|
getUnrealizedJustifiedCheckpoint(): CheckpointWithHex {
|
|
1084
1115
|
return this.fcStore.unrealizedJustified.checkpoint;
|
|
1085
1116
|
}
|
|
@@ -1913,6 +1944,88 @@ export class ForkChoice implements IForkChoice {
|
|
|
1913
1944
|
|
|
1914
1945
|
return {prelimProposerHead};
|
|
1915
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
|
+
|
|
1963
|
+
const confirmedBlock = this.getBlockHexDefaultStatus(result.confirmedRoot);
|
|
1964
|
+
if (confirmedBlock === null) {
|
|
1965
|
+
throw new Error(`Fast confirmation produced root not in protoArray: ${result.confirmedRoot}`);
|
|
1966
|
+
}
|
|
1967
|
+
this.fcStore.notifyFastConfirmation?.({
|
|
1968
|
+
block: result.confirmedRoot,
|
|
1969
|
+
slot: confirmedBlock.slot,
|
|
1970
|
+
currentSlot: this.fcStore.currentSlot,
|
|
1971
|
+
});
|
|
1972
|
+
} catch (err) {
|
|
1973
|
+
this.logger?.debug(
|
|
1974
|
+
"Fast confirmation failed",
|
|
1975
|
+
{slot: this.fcStore.currentSlot, head: this.head.blockRoot, confirmedRoot: this.fcStore.confirmedRoot},
|
|
1976
|
+
err as Error
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
private createFastConfirmationContext(): FastConfirmationContext {
|
|
1983
|
+
const confirmationByzantineThreshold = this.config.CONFIRMATION_BYZANTINE_THRESHOLD;
|
|
1984
|
+
if (!confirmationByzantineThreshold) {
|
|
1985
|
+
throw new Error("CONFIRMATION_BYZANTINE_THRESHOLD must be set to use fast confirmation");
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
return {
|
|
1989
|
+
config: {
|
|
1990
|
+
CONFIRMATION_BYZANTINE_THRESHOLD: confirmationByzantineThreshold,
|
|
1991
|
+
PROPOSER_SCORE_BOOST: this.config.PROPOSER_SCORE_BOOST,
|
|
1992
|
+
},
|
|
1993
|
+
getCurrentSlot: () => this.fcStore.currentSlot,
|
|
1994
|
+
getHead: () => this.head,
|
|
1995
|
+
getBlock: (root: RootHex) => this.getBlockHexDefaultStatus(root),
|
|
1996
|
+
getAncestor: (root: RootHex, slot: Slot) => this.getAncestor(root, slot).blockRoot,
|
|
1997
|
+
isDescendant: (ancestor: RootHex, descendant: RootHex) => {
|
|
1998
|
+
const ancestorStatus = this.protoArray.getDefaultVariant(ancestor);
|
|
1999
|
+
const descendantStatus = this.protoArray.getDefaultVariant(descendant);
|
|
2000
|
+
if (ancestorStatus === undefined || descendantStatus === undefined) return false;
|
|
2001
|
+
return this.isDescendant(ancestor, ancestorStatus, descendant, descendantStatus);
|
|
2002
|
+
},
|
|
2003
|
+
getLatestMessage: (validatorIndex: ValidatorIndex) => {
|
|
2004
|
+
const nextIndex = this.voteNextIndices[validatorIndex];
|
|
2005
|
+
if (nextIndex === undefined || nextIndex === NULL_VOTE_INDEX) {
|
|
2006
|
+
return null;
|
|
2007
|
+
}
|
|
2008
|
+
const node = this.protoArray.nodes[nextIndex];
|
|
2009
|
+
if (!node) return null;
|
|
2010
|
+
return {root: node.blockRoot, epoch: computeEpochAtSlot(this.voteNextSlots[validatorIndex])};
|
|
2011
|
+
},
|
|
2012
|
+
getUnrealizedJustified: () => ({
|
|
2013
|
+
checkpoint: this.fcStore.unrealizedJustified.checkpoint,
|
|
2014
|
+
balances: this.fcStore.unrealizedJustified.balances,
|
|
2015
|
+
}),
|
|
2016
|
+
getFinalizedCheckpoint: () => this.fcStore.finalizedCheckpoint,
|
|
2017
|
+
getEquivocatingIndices: () => this.fcStore.equivocatingIndices,
|
|
2018
|
+
getTrackedVotesCount: () => {
|
|
2019
|
+
let count = 0;
|
|
2020
|
+
for (let i = 0; i < this.voteNextIndices.length; i++) {
|
|
2021
|
+
if (this.voteNextIndices[i] !== NULL_VOTE_INDEX) {
|
|
2022
|
+
count++;
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
return count;
|
|
2026
|
+
},
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
1916
2029
|
}
|
|
1917
2030
|
|
|
1918
2031
|
// Approximate https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/fork-choice.md#calculate_committee_fraction
|
|
@@ -107,6 +107,8 @@ export interface IForkChoice {
|
|
|
107
107
|
*/
|
|
108
108
|
getHeadRoot(): RootHex;
|
|
109
109
|
getHead(): ProtoBlock;
|
|
110
|
+
getConfirmedRoot(): RootHex;
|
|
111
|
+
getConfirmedBlock(): ProtoBlock | null;
|
|
110
112
|
updateAndGetHead(mode: UpdateAndGetHeadOpt): {
|
|
111
113
|
head: ProtoBlock;
|
|
112
114
|
isHeadTimely?: boolean;
|
|
@@ -257,6 +259,9 @@ export interface IForkChoice {
|
|
|
257
259
|
payloadPresentCount: number;
|
|
258
260
|
dataAvailableCount: number;
|
|
259
261
|
} | null;
|
|
262
|
+
getPayloadTimelinessVotes(blockRootHex: RootHex): (boolean | null)[] | null;
|
|
263
|
+
getPayloadDataAvailabilityVotes(blockRootHex: RootHex): (boolean | null)[] | null;
|
|
264
|
+
|
|
260
265
|
/**
|
|
261
266
|
* Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root.
|
|
262
267
|
*/
|
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
}
|
package/src/forkChoice/store.ts
CHANGED
|
@@ -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);
|
|
@@ -44,6 +45,7 @@ export interface IForkChoiceStore {
|
|
|
44
45
|
unrealizedFinalizedCheckpoint: CheckpointWithHex;
|
|
45
46
|
justifiedBalancesGetter: JustifiedBalancesGetter;
|
|
46
47
|
equivocatingIndices: Set<ValidatorIndex>;
|
|
48
|
+
notifyFastConfirmation?(data: {block: RootHex; slot: Slot; currentSlot: Slot}): void;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
/**
|
|
@@ -58,19 +60,36 @@ export class ForkChoiceStore implements IForkChoiceStore {
|
|
|
58
60
|
justifiedBalancesGetter: JustifiedBalancesGetter;
|
|
59
61
|
currentSlot: Slot;
|
|
60
62
|
|
|
63
|
+
// Fast Confirmation Rule spec fields
|
|
64
|
+
confirmedRoot: RootHex;
|
|
65
|
+
previousEpochObservedJustifiedCheckpoint: CheckpointWithHex;
|
|
66
|
+
currentEpochObservedJustifiedCheckpoint: CheckpointWithHex;
|
|
67
|
+
previousEpochGreatestUnrealizedCheckpoint: CheckpointWithHex;
|
|
68
|
+
previousSlotHead: RootHex;
|
|
69
|
+
currentSlotHead: RootHex;
|
|
70
|
+
|
|
71
|
+
// Fast Confirmation Rule internal fields
|
|
72
|
+
previousEpochObservedJustifiedBalances: JustifiedBalances;
|
|
73
|
+
currentEpochObservedJustifiedBalances: JustifiedBalances;
|
|
74
|
+
previousEpochGreatestUnrealizedBalances: JustifiedBalances;
|
|
75
|
+
stateGetter: ForkChoiceStateGetter;
|
|
76
|
+
|
|
61
77
|
constructor(
|
|
62
78
|
currentSlot: Slot,
|
|
63
79
|
justifiedCheckpoint: phase0.Checkpoint,
|
|
64
80
|
finalizedCheckpoint: phase0.Checkpoint,
|
|
65
81
|
justifiedBalances: EffectiveBalanceIncrements,
|
|
66
82
|
justifiedBalancesGetter: JustifiedBalancesGetter,
|
|
83
|
+
stateGetter: ForkChoiceStateGetter,
|
|
67
84
|
private readonly events?: {
|
|
68
85
|
onJustified: (cp: CheckpointWithHex) => void;
|
|
69
86
|
onFinalized: (cp: CheckpointWithHex) => void;
|
|
87
|
+
onFastConfirmation?: (data: {block: RootHex; slot: Slot; currentSlot: Slot}) => void;
|
|
70
88
|
}
|
|
71
89
|
) {
|
|
72
90
|
this.justifiedBalancesGetter = justifiedBalancesGetter;
|
|
73
91
|
this.currentSlot = currentSlot;
|
|
92
|
+
this.stateGetter = stateGetter;
|
|
74
93
|
const justified = {
|
|
75
94
|
checkpoint: toCheckpointWithHex(justifiedCheckpoint),
|
|
76
95
|
balances: justifiedBalances,
|
|
@@ -80,6 +99,22 @@ export class ForkChoiceStore implements IForkChoiceStore {
|
|
|
80
99
|
this.unrealizedJustified = justified;
|
|
81
100
|
this._finalizedCheckpoint = toCheckpointWithHex(finalizedCheckpoint);
|
|
82
101
|
this.unrealizedFinalizedCheckpoint = this._finalizedCheckpoint;
|
|
102
|
+
|
|
103
|
+
// Initialize Fast Confirmation fields conservatively from finalized, matching
|
|
104
|
+
// the spec's get_fast_confirmation_store() behavior.
|
|
105
|
+
const finalizedCheckpointWithHex = toCheckpointWithHex(finalizedCheckpoint);
|
|
106
|
+
const finalizedState = stateGetter({checkpoint: finalizedCheckpointWithHex});
|
|
107
|
+
const finalizedBalances = finalizedState?.effectiveBalanceIncrements ?? justifiedBalances;
|
|
108
|
+
const anchorRoot = finalizedCheckpointWithHex.rootHex;
|
|
109
|
+
this.previousEpochObservedJustifiedCheckpoint = finalizedCheckpointWithHex;
|
|
110
|
+
this.currentEpochObservedJustifiedCheckpoint = finalizedCheckpointWithHex;
|
|
111
|
+
this.previousEpochGreatestUnrealizedCheckpoint = finalizedCheckpointWithHex;
|
|
112
|
+
this.confirmedRoot = anchorRoot;
|
|
113
|
+
this.previousEpochObservedJustifiedBalances = finalizedBalances;
|
|
114
|
+
this.currentEpochObservedJustifiedBalances = finalizedBalances;
|
|
115
|
+
this.previousEpochGreatestUnrealizedBalances = finalizedBalances;
|
|
116
|
+
this.previousSlotHead = anchorRoot;
|
|
117
|
+
this.currentSlotHead = anchorRoot;
|
|
83
118
|
}
|
|
84
119
|
|
|
85
120
|
get justified(): CheckpointWithTotalBalance {
|
|
@@ -98,6 +133,15 @@ export class ForkChoiceStore implements IForkChoiceStore {
|
|
|
98
133
|
this._finalizedCheckpoint = cp;
|
|
99
134
|
this.events?.onFinalized(cp);
|
|
100
135
|
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Notify subscribers that the Fast Confirmation Rule executed and produced
|
|
139
|
+
* `data.block` at `data.slot`. Fires once per FCR execution, even when
|
|
140
|
+
* `confirmedRoot` did not change from the prior slot.
|
|
141
|
+
*/
|
|
142
|
+
notifyFastConfirmation(data: {block: RootHex; slot: Slot; currentSlot: Slot}): void {
|
|
143
|
+
this.events?.onFastConfirmation?.(data);
|
|
144
|
+
}
|
|
101
145
|
}
|
|
102
146
|
|
|
103
147
|
export function toCheckpointWithHex(checkpoint: phase0.Checkpoint): CheckpointWithHex {
|
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",
|
|
@@ -745,6 +745,31 @@ export class ProtoArray {
|
|
|
745
745
|
return this.previousProposerBoost?.root ?? HEX_ZERO_HASH;
|
|
746
746
|
}
|
|
747
747
|
|
|
748
|
+
/**
|
|
749
|
+
* Timeliness votes per PTC position, `null` where the member has not attested.
|
|
750
|
+
* Returns `null` if the block is unknown or not a Gloas block.
|
|
751
|
+
*/
|
|
752
|
+
getPayloadTimelinessVotes(blockRootHex: RootHex): (boolean | null)[] | null {
|
|
753
|
+
return this.toAttendanceAwareVotes(this.payloadTimelinessVotes.get(blockRootHex), blockRootHex);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Data-availability votes per PTC position, `null` where the member has not attested.
|
|
758
|
+
* Returns `null` if the block is unknown or not a Gloas block.
|
|
759
|
+
*/
|
|
760
|
+
getPayloadDataAvailabilityVotes(blockRootHex: RootHex): (boolean | null)[] | null {
|
|
761
|
+
return this.toAttendanceAwareVotes(this.payloadDataAvailabilityVotes.get(blockRootHex), blockRootHex);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
private toAttendanceAwareVotes(votes: BitArray | undefined, blockRootHex: RootHex): (boolean | null)[] | null {
|
|
765
|
+
const attended = this.ptcAttested.get(blockRootHex);
|
|
766
|
+
if (votes === undefined || attended === undefined) {
|
|
767
|
+
return null;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return Array.from({length: PTC_SIZE}, (_, i) => (attended.get(i) ? votes.get(i) : null));
|
|
771
|
+
}
|
|
772
|
+
|
|
748
773
|
/**
|
|
749
774
|
* Spec: payload_timeliness(store, root, timely=True)
|
|
750
775
|
*/
|