@lodestar/fork-choice 1.44.0-dev.6ef8199cfa → 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.
- 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 +139 -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 +24 -0
- package/lib/forkChoice/fastConfirmation/metrics.d.ts.map +1 -0
- package/lib/forkChoice/fastConfirmation/metrics.js +55 -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 +114 -0
- package/lib/forkChoice/fastConfirmation/rules.js.map +1 -0
- package/lib/forkChoice/fastConfirmation/types.d.ts +115 -0
- package/lib/forkChoice/fastConfirmation/types.d.ts.map +1 -0
- package/lib/forkChoice/fastConfirmation/types.js +22 -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 +687 -0
- package/lib/forkChoice/fastConfirmation/utils.js.map +1 -0
- package/lib/forkChoice/forkChoice.d.ts +24 -4
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +151 -35
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +21 -8
- 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 +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 +15 -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/interface.d.ts +1 -0
- package/lib/protoArray/interface.d.ts.map +1 -1
- package/lib/protoArray/protoArray.d.ts +68 -21
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +172 -39
- 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 +173 -0
- package/src/forkChoice/fastConfirmation/index.ts +3 -0
- package/src/forkChoice/fastConfirmation/metrics.ts +57 -0
- package/src/forkChoice/fastConfirmation/rules.ts +151 -0
- package/src/forkChoice/fastConfirmation/types.ts +137 -0
- package/src/forkChoice/fastConfirmation/utils.ts +975 -0
- package/src/forkChoice/forkChoice.ts +189 -37
- package/src/forkChoice/interface.ts +38 -7
- 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/interface.ts +8 -0
- package/src/protoArray/protoArray.ts +186 -41
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
phase0,
|
|
26
26
|
ssz,
|
|
27
27
|
} from "@lodestar/types";
|
|
28
|
-
import {Logger, MapDef, fromHex, toRootHex} from "@lodestar/utils";
|
|
28
|
+
import {Logger, MapDef, fromHex, toRootHex, withObservedDuration} from "@lodestar/utils";
|
|
29
29
|
import {ForkChoiceMetrics} from "../metrics.js";
|
|
30
30
|
import {computeDeltas} from "../protoArray/computeDeltas.js";
|
|
31
31
|
import {ProtoArrayError, ProtoArrayErrorCode} from "../protoArray/errors.js";
|
|
@@ -44,6 +44,12 @@ import {
|
|
|
44
44
|
} from "../protoArray/interface.js";
|
|
45
45
|
import {ProtoArray} from "../protoArray/protoArray.js";
|
|
46
46
|
import {ForkChoiceError, ForkChoiceErrorCode, InvalidAttestationCode, InvalidBlockCode} from "./errors.js";
|
|
47
|
+
import {
|
|
48
|
+
type FastConfirmationContext,
|
|
49
|
+
FastConfirmationRule,
|
|
50
|
+
FastConfirmationSteps,
|
|
51
|
+
type IFastConfirmationRule,
|
|
52
|
+
} from "./fastConfirmation/fastConfirmationRule.ts";
|
|
47
53
|
import {
|
|
48
54
|
AncestorResult,
|
|
49
55
|
AncestorStatus,
|
|
@@ -58,6 +64,7 @@ export type ForkChoiceOpts = {
|
|
|
58
64
|
proposerBoost?: boolean;
|
|
59
65
|
proposerBoostReorg?: boolean;
|
|
60
66
|
computeUnrealized?: boolean;
|
|
67
|
+
fastConfirmation?: boolean;
|
|
61
68
|
};
|
|
62
69
|
|
|
63
70
|
export enum UpdateHeadOpt {
|
|
@@ -142,6 +149,9 @@ export class ForkChoice implements IForkChoice {
|
|
|
142
149
|
private justifiedProposerBoostScore: number | null = null;
|
|
143
150
|
/** The current effective balances */
|
|
144
151
|
private balances: EffectiveBalanceIncrements;
|
|
152
|
+
/** Optional fast confirmation rule implementation */
|
|
153
|
+
private readonly fastConfirmationRule?: IFastConfirmationRule;
|
|
154
|
+
private readonly fastConfirmationContext?: FastConfirmationContext;
|
|
145
155
|
/**
|
|
146
156
|
* Instantiates a Fork Choice from some existing components
|
|
147
157
|
*
|
|
@@ -167,6 +177,11 @@ export class ForkChoice implements IForkChoice {
|
|
|
167
177
|
this.head = this.updateHead();
|
|
168
178
|
this.balances = this.fcStore.justified.balances;
|
|
169
179
|
|
|
180
|
+
if (this.opts?.fastConfirmation) {
|
|
181
|
+
this.fastConfirmationRule = new FastConfirmationRule(this.fcStore, metrics, this.logger);
|
|
182
|
+
this.fastConfirmationContext = this.createFastConfirmationContext();
|
|
183
|
+
}
|
|
184
|
+
|
|
170
185
|
metrics?.forkChoice.votes.addCollect(() => {
|
|
171
186
|
metrics.forkChoice.votes.set(this.voteNextSlots.length);
|
|
172
187
|
metrics.forkChoice.queuedAttestations.set(this.queuedAttestationsPreviousSlot);
|
|
@@ -207,6 +222,14 @@ export class ForkChoice implements IForkChoice {
|
|
|
207
222
|
return this.head;
|
|
208
223
|
}
|
|
209
224
|
|
|
225
|
+
getConfirmedRoot(): RootHex {
|
|
226
|
+
return this.fastConfirmationRule?.getConfirmedRoot() ?? this.fcStore.justified.checkpoint.rootHex;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
getConfirmedBlock(): ProtoBlock | null {
|
|
230
|
+
return this.getBlockHexDefaultStatus(this.getConfirmedRoot());
|
|
231
|
+
}
|
|
232
|
+
|
|
210
233
|
/**
|
|
211
234
|
*
|
|
212
235
|
* A multiplexer to wrap around the traditional `updateHead()` according to the scenario
|
|
@@ -310,6 +333,10 @@ export class ForkChoice implements IForkChoice {
|
|
|
310
333
|
return this.proposerBoostRoot ?? HEX_ZERO_HASH;
|
|
311
334
|
}
|
|
312
335
|
|
|
336
|
+
getPreviousProposerBoostRoot(): RootHex {
|
|
337
|
+
return this.protoArray.getPreviousProposerBoostRoot();
|
|
338
|
+
}
|
|
339
|
+
|
|
313
340
|
/**
|
|
314
341
|
* Decides whether to extend an available payload from the previous slot,
|
|
315
342
|
* corresponding to the beacon block `blockRoot`.
|
|
@@ -318,6 +345,11 @@ export class ForkChoice implements IForkChoice {
|
|
|
318
345
|
return this.protoArray.shouldExtendPayload(blockRoot, this.proposerBoostRoot);
|
|
319
346
|
}
|
|
320
347
|
|
|
348
|
+
/** Spec: should_build_on_full(store, head) */
|
|
349
|
+
shouldBuildOnFull(head: ProtoBlock, slot: Slot): boolean {
|
|
350
|
+
return this.protoArray.shouldBuildOnFull(head, slot);
|
|
351
|
+
}
|
|
352
|
+
|
|
321
353
|
/**
|
|
322
354
|
* To predict the proposer head of the next slot. That is, to predict if proposer-boost-reorg could happen.
|
|
323
355
|
* Reason why we can't be certain is because information of the head block is not fully available yet
|
|
@@ -596,7 +628,11 @@ export class ForkChoice implements IForkChoice {
|
|
|
596
628
|
blockDelaySec: number,
|
|
597
629
|
currentSlot: Slot,
|
|
598
630
|
executionStatus: BlockExecutionStatus,
|
|
599
|
-
dataAvailabilityStatus: DataAvailabilityStatus
|
|
631
|
+
dataAvailabilityStatus: DataAvailabilityStatus,
|
|
632
|
+
// The expected proposer index on the canonical chain we are following.
|
|
633
|
+
// Calculated by our head state. We use it as part of the proposer
|
|
634
|
+
// boost decision making. No boost will be set if this is null.
|
|
635
|
+
expectedProposerIndex: ValidatorIndex | null
|
|
600
636
|
): ProtoBlock {
|
|
601
637
|
const {parentRoot, slot} = block;
|
|
602
638
|
const parentRootHex = toRootHex(parentRoot);
|
|
@@ -669,7 +705,9 @@ export class ForkChoice implements IForkChoice {
|
|
|
669
705
|
this.opts?.proposerBoost &&
|
|
670
706
|
isTimely &&
|
|
671
707
|
// only boost the first block we see
|
|
672
|
-
this.proposerBoostRoot === null
|
|
708
|
+
this.proposerBoostRoot === null &&
|
|
709
|
+
expectedProposerIndex !== null &&
|
|
710
|
+
block.proposerIndex === expectedProposerIndex
|
|
673
711
|
) {
|
|
674
712
|
this.proposerBoostRoot = blockRootHex;
|
|
675
713
|
}
|
|
@@ -764,33 +802,32 @@ export class ForkChoice implements IForkChoice {
|
|
|
764
802
|
unrealizedFinalizedRoot: unrealizedFinalizedCheckpoint.rootHex,
|
|
765
803
|
|
|
766
804
|
...(isGloasBeaconBlock(block)
|
|
767
|
-
? {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
}
|
|
805
|
+
? (() => {
|
|
806
|
+
// post-gloas, we don't know payload hash until we import execution payload. Set to
|
|
807
|
+
// parent payload hash for now, along with the gas limit/number of that parent payload
|
|
808
|
+
// (which is what bids built on top of this block will reference until a payload arrives).
|
|
809
|
+
// we also use parent hash to pass to EL via fcu
|
|
810
|
+
// see https://github.com/ethereum/consensus-specs/pull/5197
|
|
811
|
+
const parentBlockHashFromBid = toRootHex(block.body.signedExecutionPayloadBid.message.parentBlockHash);
|
|
812
|
+
|
|
813
|
+
// Inherit parent payload's (number, gasLimit) for the PENDING/EMPTY variants.
|
|
814
|
+
// `parentBlock` is already the variant matching `parentBlockHashFromBid` —
|
|
815
|
+
// `getParent` (called above) resolves Gloas parents via
|
|
816
|
+
// `getBlockHexAndBlockHash(parentRoot, parentBlockHash)`, and pre-Gloas parents
|
|
817
|
+
// have a single variant. Pre-merge parents have null payload hash and zero values.
|
|
818
|
+
const parentMeta: {number: number; gasLimit: number} =
|
|
819
|
+
parentBlock.executionPayloadBlockHash === null
|
|
820
|
+
? {number: 0, gasLimit: 0}
|
|
821
|
+
: {number: parentBlock.executionPayloadNumber, gasLimit: parentBlock.executionPayloadGasLimit};
|
|
822
|
+
|
|
823
|
+
return {
|
|
824
|
+
executionPayloadBlockHash: parentBlockHashFromBid,
|
|
825
|
+
executionPayloadNumber: parentMeta.number,
|
|
826
|
+
executionPayloadGasLimit: parentMeta.gasLimit,
|
|
827
|
+
executionStatus: this.getPostMergeExecStatus(executionStatus),
|
|
828
|
+
dataAvailabilityStatus,
|
|
829
|
+
};
|
|
830
|
+
})()
|
|
794
831
|
: isExecutionBlockBodyType(block.body) &&
|
|
795
832
|
isStatePostBellatrix(state) &&
|
|
796
833
|
state.isExecutionStateType &&
|
|
@@ -798,6 +835,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
798
835
|
? {
|
|
799
836
|
executionPayloadBlockHash: toRootHex(block.body.executionPayload.blockHash),
|
|
800
837
|
executionPayloadNumber: block.body.executionPayload.blockNumber,
|
|
838
|
+
executionPayloadGasLimit: block.body.executionPayload.gasLimit,
|
|
801
839
|
executionStatus: this.getPostMergeExecStatus(executionStatus),
|
|
802
840
|
dataAvailabilityStatus,
|
|
803
841
|
}
|
|
@@ -934,8 +972,14 @@ export class ForkChoice implements IForkChoice {
|
|
|
934
972
|
* Updates the PTC votes for multiple validators attesting to a block
|
|
935
973
|
* Spec: gloas/fork-choice.md#new-on_payload_attestation_message
|
|
936
974
|
*/
|
|
937
|
-
notifyPtcMessages(
|
|
938
|
-
|
|
975
|
+
notifyPtcMessages(
|
|
976
|
+
blockRoot: RootHex,
|
|
977
|
+
slot: Slot,
|
|
978
|
+
ptcIndices: number[],
|
|
979
|
+
payloadPresent: boolean,
|
|
980
|
+
blobDataAvailable: boolean
|
|
981
|
+
): void {
|
|
982
|
+
this.protoArray.notifyPtcMessages(blockRoot, slot, ptcIndices, payloadPresent, blobDataAvailable);
|
|
939
983
|
}
|
|
940
984
|
|
|
941
985
|
/**
|
|
@@ -947,6 +991,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
947
991
|
blockRoot: RootHex,
|
|
948
992
|
executionPayloadBlockHash: RootHex,
|
|
949
993
|
executionPayloadNumber: number,
|
|
994
|
+
executionPayloadGasLimit: number,
|
|
950
995
|
executionStatus: PayloadExecutionStatus,
|
|
951
996
|
dataAvailabilityStatus: DataAvailabilityStatus
|
|
952
997
|
): void {
|
|
@@ -955,6 +1000,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
955
1000
|
this.fcStore.currentSlot,
|
|
956
1001
|
executionPayloadBlockHash,
|
|
957
1002
|
executionPayloadNumber,
|
|
1003
|
+
executionPayloadGasLimit,
|
|
958
1004
|
this.proposerBoostRoot,
|
|
959
1005
|
executionStatus,
|
|
960
1006
|
dataAvailabilityStatus
|
|
@@ -975,12 +1021,12 @@ export class ForkChoice implements IForkChoice {
|
|
|
975
1021
|
const previousSlot = this.fcStore.currentSlot;
|
|
976
1022
|
// Note: we are relying upon `onTick` to update `fcStore.time` to ensure we don't get stuck in a loop.
|
|
977
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();
|
|
978
1029
|
}
|
|
979
|
-
|
|
980
|
-
this.queuedAttestationsPreviousSlot = 0;
|
|
981
|
-
// Process any attestations that might now be eligible.
|
|
982
|
-
this.processAttestationQueue();
|
|
983
|
-
this.validatedAttestationDatas = new Set();
|
|
984
1030
|
}
|
|
985
1031
|
|
|
986
1032
|
getTime(): Slot {
|
|
@@ -1049,6 +1095,30 @@ export class ForkChoice implements IForkChoice {
|
|
|
1049
1095
|
return votes.toBoolArray().map((v) => v ?? null);
|
|
1050
1096
|
}
|
|
1051
1097
|
|
|
1098
|
+
getPTCVoteCounts(blockRootHex: RootHex): {
|
|
1099
|
+
attesterCount: number;
|
|
1100
|
+
payloadPresentCount: number;
|
|
1101
|
+
dataAvailableCount: number;
|
|
1102
|
+
} | null {
|
|
1103
|
+
return this.protoArray.getPTCVoteCounts(blockRootHex);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
getPayloadTimelinessVotes(blockRootHex: RootHex): (boolean | null)[] | null {
|
|
1107
|
+
return this.protoArray.getPayloadTimelinessVotes(blockRootHex);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
getPayloadDataAvailabilityVotes(blockRootHex: RootHex): (boolean | null)[] | null {
|
|
1111
|
+
return this.protoArray.getPayloadDataAvailabilityVotes(blockRootHex);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
getUnrealizedJustifiedCheckpoint(): CheckpointWithHex {
|
|
1115
|
+
return this.fcStore.unrealizedJustified.checkpoint;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
getUnrealizedFinalizedCheckpoint(): CheckpointWithHex {
|
|
1119
|
+
return this.fcStore.unrealizedFinalizedCheckpoint;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1052
1122
|
/**
|
|
1053
1123
|
* Returns a MUTABLE `ProtoBlock` if the block is known **and** a descendant of the finalized root.
|
|
1054
1124
|
*/
|
|
@@ -1874,6 +1944,88 @@ export class ForkChoice implements IForkChoice {
|
|
|
1874
1944
|
|
|
1875
1945
|
return {prelimProposerHead};
|
|
1876
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
|
+
}
|
|
1877
2029
|
}
|
|
1878
2030
|
|
|
1879
2031
|
// Approximate https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/fork-choice.md#calculate_committee_fraction
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import {DataAvailabilityStatus, EffectiveBalanceIncrements, IBeaconStateView} from "@lodestar/state-transition";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AttesterSlashing,
|
|
4
|
+
BeaconBlock,
|
|
5
|
+
Epoch,
|
|
6
|
+
IndexedAttestation,
|
|
7
|
+
Root,
|
|
8
|
+
RootHex,
|
|
9
|
+
Slot,
|
|
10
|
+
ValidatorIndex,
|
|
11
|
+
} from "@lodestar/types";
|
|
3
12
|
import {
|
|
4
13
|
BlockExecutionStatus,
|
|
5
14
|
LVHExecResponse,
|
|
@@ -98,6 +107,8 @@ export interface IForkChoice {
|
|
|
98
107
|
*/
|
|
99
108
|
getHeadRoot(): RootHex;
|
|
100
109
|
getHead(): ProtoBlock;
|
|
110
|
+
getConfirmedRoot(): RootHex;
|
|
111
|
+
getConfirmedBlock(): ProtoBlock | null;
|
|
101
112
|
updateAndGetHead(mode: UpdateAndGetHeadOpt): {
|
|
102
113
|
head: ProtoBlock;
|
|
103
114
|
isHeadTimely?: boolean;
|
|
@@ -123,6 +134,10 @@ export interface IForkChoice {
|
|
|
123
134
|
getAllNodes(): ProtoNode[];
|
|
124
135
|
getFinalizedCheckpoint(): CheckpointWithHex;
|
|
125
136
|
getJustifiedCheckpoint(): CheckpointWithHex;
|
|
137
|
+
getUnrealizedJustifiedCheckpoint(): CheckpointWithHex;
|
|
138
|
+
getUnrealizedFinalizedCheckpoint(): CheckpointWithHex;
|
|
139
|
+
getProposerBoostRoot(): RootHex;
|
|
140
|
+
getPreviousProposerBoostRoot(): RootHex;
|
|
126
141
|
/**
|
|
127
142
|
* Add `block` to the fork choice DAG.
|
|
128
143
|
*
|
|
@@ -145,7 +160,8 @@ export interface IForkChoice {
|
|
|
145
160
|
blockDelaySec: number,
|
|
146
161
|
currentSlot: Slot,
|
|
147
162
|
executionStatus: BlockExecutionStatus,
|
|
148
|
-
dataAvailabilityStatus: DataAvailabilityStatus
|
|
163
|
+
dataAvailabilityStatus: DataAvailabilityStatus,
|
|
164
|
+
expectedProposerIndex: ValidatorIndex | null
|
|
149
165
|
): ProtoBlock;
|
|
150
166
|
/**
|
|
151
167
|
* Register `attestation` with the fork choice DAG so that it may influence future calls to `getHead`.
|
|
@@ -181,12 +197,14 @@ export interface IForkChoice {
|
|
|
181
197
|
* ## Specification
|
|
182
198
|
*
|
|
183
199
|
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/gloas/fork-choice.md#new-notify_ptc_messages
|
|
184
|
-
*
|
|
185
|
-
* @param blockRoot - The beacon block root being attested
|
|
186
|
-
* @param ptcIndices - Array of PTC committee indices that voted
|
|
187
|
-
* @param payloadPresent - Whether validators attest the payload is present
|
|
188
200
|
*/
|
|
189
|
-
notifyPtcMessages(
|
|
201
|
+
notifyPtcMessages(
|
|
202
|
+
blockRoot: RootHex,
|
|
203
|
+
slot: Slot,
|
|
204
|
+
ptcIndices: number[],
|
|
205
|
+
payloadPresent: boolean,
|
|
206
|
+
blobDataAvailable: boolean
|
|
207
|
+
): void;
|
|
190
208
|
/**
|
|
191
209
|
* Notify fork choice that an execution payload has arrived (Gloas fork)
|
|
192
210
|
* Creates the FULL variant of a Gloas block when the payload becomes available
|
|
@@ -198,11 +216,13 @@ export interface IForkChoice {
|
|
|
198
216
|
* @param blockRoot - The beacon block root for which the payload arrived
|
|
199
217
|
* @param executionPayloadBlockHash - The block hash of the execution payload
|
|
200
218
|
* @param executionPayloadNumber - The block number of the execution payload
|
|
219
|
+
* @param executionPayloadGasLimit - The gas limit of the execution payload
|
|
201
220
|
*/
|
|
202
221
|
onExecutionPayload(
|
|
203
222
|
blockRoot: RootHex,
|
|
204
223
|
executionPayloadBlockHash: RootHex,
|
|
205
224
|
executionPayloadNumber: number,
|
|
225
|
+
executionPayloadGasLimit: number,
|
|
206
226
|
executionStatus: PayloadExecutionStatus,
|
|
207
227
|
dataAvailabilityStatus: DataAvailabilityStatus
|
|
208
228
|
): void;
|
|
@@ -233,6 +253,15 @@ export interface IForkChoice {
|
|
|
233
253
|
hasPayloadHexUnsafe(blockRoot: RootHex): boolean;
|
|
234
254
|
getSlotsPresent(windowStart: number): number;
|
|
235
255
|
getPTCVotes(blockRootHex: RootHex): (boolean | null)[] | null;
|
|
256
|
+
/** Raw PTC vote tallies for the debug fork choice endpoint; `null` for pre-Gloas roots. */
|
|
257
|
+
getPTCVoteCounts(blockRootHex: RootHex): {
|
|
258
|
+
attesterCount: number;
|
|
259
|
+
payloadPresentCount: number;
|
|
260
|
+
dataAvailableCount: number;
|
|
261
|
+
} | null;
|
|
262
|
+
getPayloadTimelinessVotes(blockRootHex: RootHex): (boolean | null)[] | null;
|
|
263
|
+
getPayloadDataAvailabilityVotes(blockRootHex: RootHex): (boolean | null)[] | null;
|
|
264
|
+
|
|
236
265
|
/**
|
|
237
266
|
* Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root.
|
|
238
267
|
*/
|
|
@@ -242,6 +271,8 @@ export interface IForkChoice {
|
|
|
242
271
|
getBlockHexDefaultStatus(blockRoot: RootHex): ProtoBlock | null;
|
|
243
272
|
getBlockHexAndBlockHash(blockRoot: RootHex, blockHash: RootHex): ProtoBlock | null;
|
|
244
273
|
shouldExtendPayload(blockRoot: RootHex): boolean;
|
|
274
|
+
/** Spec: should_build_on_full(store, head) */
|
|
275
|
+
shouldBuildOnFull(head: ProtoBlock, slot: Slot): boolean;
|
|
245
276
|
getFinalizedBlock(): ProtoBlock;
|
|
246
277
|
getJustifiedBlock(): ProtoBlock;
|
|
247
278
|
getFinalizedCheckpointSlot(): Slot;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {ZERO_HASH_HEX} from "@lodestar/params";
|
|
2
2
|
import {Root, RootHex} from "@lodestar/types";
|
|
3
|
+
import {fromHex} from "@lodestar/utils";
|
|
3
4
|
import {IForkChoice} from "./interface.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -7,21 +8,28 @@ import {IForkChoice} from "./interface.js";
|
|
|
7
8
|
* that is safe from re-orgs. Normally this block is pretty close to the head of canonical
|
|
8
9
|
* chain which makes it valuable to expose a safe block to users.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
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",
|
|
@@ -87,6 +87,14 @@ export type BlockExtraMeta =
|
|
|
87
87
|
// - payload block hash for FULL variant
|
|
88
88
|
executionPayloadBlockHash: RootHex;
|
|
89
89
|
executionPayloadNumber: UintNum64;
|
|
90
|
+
// Gas limit of the executed payload identified by executionPayloadBlockHash. Set on
|
|
91
|
+
// pre-Gloas blocks (from block.body.executionPayload.gasLimit) and on Gloas variants:
|
|
92
|
+
// - PENDING/EMPTY: inherited from the parent payload that the bid commits to extend
|
|
93
|
+
// (matches executionPayloadBlockHash, which also points to that parent payload)
|
|
94
|
+
// - FULL: the actual delivered payload's gasLimit (set in onExecutionPayload)
|
|
95
|
+
// Consumers (e.g. Gloas bid gas-limit validation) can read this without re-deriving from
|
|
96
|
+
// state.
|
|
97
|
+
executionPayloadGasLimit: UintNum64;
|
|
90
98
|
executionStatus: Exclude<ExecutionStatus, ExecutionStatus.PreMerge>;
|
|
91
99
|
dataAvailabilityStatus: DataAvailabilityStatus;
|
|
92
100
|
}
|