@lodestar/fork-choice 1.50.0-dev.1f129c79a1 → 1.50.0-dev.20062ed60e
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/types.d.ts +3 -1
- package/lib/forkChoice/fastConfirmation/types.d.ts.map +1 -1
- package/lib/forkChoice/fastConfirmation/utils.d.ts +2 -2
- package/lib/forkChoice/fastConfirmation/utils.d.ts.map +1 -1
- package/lib/forkChoice/fastConfirmation/utils.js +8 -3
- package/lib/forkChoice/fastConfirmation/utils.js.map +1 -1
- package/lib/forkChoice/forkChoice.d.ts +2 -1
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +43 -5
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +9 -1
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/forkChoice/interface.js.map +1 -1
- package/lib/forkChoice/store.d.ts +4 -1
- package/lib/forkChoice/store.d.ts.map +1 -1
- package/lib/forkChoice/store.js +3 -0
- package/lib/forkChoice/store.js.map +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/metrics.d.ts +3 -0
- package/lib/metrics.d.ts.map +1 -1
- package/lib/metrics.js +5 -0
- package/lib/metrics.js.map +1 -1
- package/lib/protoArray/interface.d.ts +15 -0
- package/lib/protoArray/interface.d.ts.map +1 -1
- package/lib/protoArray/interface.js +13 -0
- package/lib/protoArray/interface.js.map +1 -1
- package/lib/protoArray/protoArray.d.ts +18 -2
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +57 -3
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +8 -8
- package/src/forkChoice/fastConfirmation/types.ts +3 -2
- package/src/forkChoice/fastConfirmation/utils.ts +12 -5
- package/src/forkChoice/forkChoice.ts +47 -4
- package/src/forkChoice/interface.ts +6 -0
- package/src/forkChoice/store.ts +7 -1
- package/src/index.ts +3 -1
- package/src/metrics.ts +5 -0
- package/src/protoArray/interface.ts +21 -0
- package/src/protoArray/protoArray.ts +64 -2
|
@@ -45,7 +45,9 @@ import {
|
|
|
45
45
|
PayloadStatus,
|
|
46
46
|
ProtoBlock,
|
|
47
47
|
ProtoNode,
|
|
48
|
+
PtcQuorum,
|
|
48
49
|
VoteIndex,
|
|
50
|
+
getPtcVerdict,
|
|
49
51
|
isGloasBlock,
|
|
50
52
|
} from "../protoArray/interface.js";
|
|
51
53
|
import {ProtoArray} from "../protoArray/protoArray.js";
|
|
@@ -646,7 +648,12 @@ export class ForkChoice implements IForkChoice {
|
|
|
646
648
|
// splitting them would let the two passes update weights and recompute best child/descendant
|
|
647
649
|
// once at the end.
|
|
648
650
|
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.14/specs/gloas/fork-choice.md#new-should_apply_proposer_boost
|
|
649
|
-
this.protoArray.applyScoreChanges({
|
|
651
|
+
this.protoArray.applyScoreChanges({
|
|
652
|
+
attestationDeltas,
|
|
653
|
+
proposerBoost: null,
|
|
654
|
+
proposerBoostRoot: this.proposerBoostRoot,
|
|
655
|
+
...checkpoints,
|
|
656
|
+
});
|
|
650
657
|
const proposerBoost = this.shouldApplyProposerBoost() ? this.getProposerBoost() : null;
|
|
651
658
|
// The first pass already rolled back the previous boost and left a coherent tree, so a
|
|
652
659
|
// withheld boost needs no second pass
|
|
@@ -1090,7 +1097,20 @@ export class ForkChoice implements IForkChoice {
|
|
|
1090
1097
|
payloadPresent: boolean,
|
|
1091
1098
|
blobDataAvailable: boolean
|
|
1092
1099
|
): void {
|
|
1100
|
+
const before = this.protoArray.getPtcQuorum(blockRoot);
|
|
1093
1101
|
this.protoArray.notifyPtcMessages(blockRoot, slot, ptcIndices, payloadPresent, blobDataAvailable);
|
|
1102
|
+
const after = this.protoArray.getPtcQuorum(blockRoot);
|
|
1103
|
+
if (before === null || after === null) {
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
const verdict = getPtcVerdict(after);
|
|
1107
|
+
if (verdict === null || verdict === getPtcVerdict(before)) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
this.metrics?.forkChoice.ptcQuorum.inc({verdict: verdict ? "true" : "false"});
|
|
1112
|
+
this.logger?.verbose("PTC quorum reached", {slot, blockRoot, verdict, ...after});
|
|
1113
|
+
this.fcStore.notifyPtcQuorum?.({blockRoot, slot, verdict, ...after});
|
|
1094
1114
|
}
|
|
1095
1115
|
|
|
1096
1116
|
/**
|
|
@@ -1223,6 +1243,10 @@ export class ForkChoice implements IForkChoice {
|
|
|
1223
1243
|
return this.protoArray.getPTCVoteCounts(blockRootHex);
|
|
1224
1244
|
}
|
|
1225
1245
|
|
|
1246
|
+
getPtcQuorum(blockRootHex: RootHex): PtcQuorum | null {
|
|
1247
|
+
return this.protoArray.getPtcQuorum(blockRootHex);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1226
1250
|
getPayloadTimelinessVotes(blockRootHex: RootHex): (boolean | null)[] | null {
|
|
1227
1251
|
return this.protoArray.getPayloadTimelinessVotes(blockRootHex);
|
|
1228
1252
|
}
|
|
@@ -1750,7 +1774,10 @@ export class ForkChoice implements IForkChoice {
|
|
|
1750
1774
|
* Child class can overwrite this for testing purpose.
|
|
1751
1775
|
*/
|
|
1752
1776
|
protected isBlockPtcTimely(block: BeaconBlock, blockDelaySec: number): boolean {
|
|
1753
|
-
const ptcThresholdMs = this.config.getSlotComponentDurationMs(
|
|
1777
|
+
const ptcThresholdMs = this.config.getSlotComponentDurationMs(
|
|
1778
|
+
this.config.getForkName(block.slot),
|
|
1779
|
+
this.config.PAYLOAD_ATTESTATION_DUE_BPS
|
|
1780
|
+
);
|
|
1754
1781
|
return this.fcStore.currentSlot === block.slot && blockDelaySec * 1000 < ptcThresholdMs;
|
|
1755
1782
|
}
|
|
1756
1783
|
|
|
@@ -2163,7 +2190,11 @@ export class ForkChoice implements IForkChoice {
|
|
|
2163
2190
|
}
|
|
2164
2191
|
|
|
2165
2192
|
const existingNextSlot = this.voteNextSlots[validatorIndex];
|
|
2166
|
-
|
|
2193
|
+
// Pre-Gloas a vote is only replaced by one from a later epoch, from Gloas by one from a later slot
|
|
2194
|
+
const isNewerVote = isForkPostGloas(this.config.getForkName(nextSlot))
|
|
2195
|
+
? nextSlot > existingNextSlot
|
|
2196
|
+
: computeEpochAtSlot(nextSlot) > computeEpochAtSlot(existingNextSlot);
|
|
2197
|
+
if (existingNextSlot === INIT_VOTE_SLOT || isNewerVote) {
|
|
2167
2198
|
// nextIndex is transfered to currentIndex in computeDeltas()
|
|
2168
2199
|
this.voteNextIndices[validatorIndex] = nextIndex;
|
|
2169
2200
|
this.voteNextSlots[validatorIndex] = nextSlot;
|
|
@@ -2384,9 +2415,21 @@ export class ForkChoice implements IForkChoice {
|
|
|
2384
2415
|
if (nextIndex === undefined || nextIndex === NULL_VOTE_INDEX) {
|
|
2385
2416
|
return null;
|
|
2386
2417
|
}
|
|
2418
|
+
// The vote is tracked by node index, so the node already is the message's supported node
|
|
2387
2419
|
const node = this.protoArray.nodes[nextIndex];
|
|
2388
2420
|
if (!node) return null;
|
|
2389
|
-
return {
|
|
2421
|
+
return {
|
|
2422
|
+
root: node.blockRoot,
|
|
2423
|
+
payloadStatus: node.payloadStatus,
|
|
2424
|
+
epoch: computeEpochAtSlot(this.voteNextSlots[validatorIndex]),
|
|
2425
|
+
};
|
|
2426
|
+
},
|
|
2427
|
+
getParentNodePayloadStatus: (blockRoot: RootHex) => {
|
|
2428
|
+
const nodeIndex = this.protoArray.getDefaultNodeIndex(blockRoot);
|
|
2429
|
+
if (nodeIndex === undefined) return null;
|
|
2430
|
+
const parentIndex = this.protoArray.nodes[nodeIndex]?.parent;
|
|
2431
|
+
if (parentIndex === undefined) return null;
|
|
2432
|
+
return this.protoArray.nodes[parentIndex]?.payloadStatus ?? null;
|
|
2390
2433
|
},
|
|
2391
2434
|
getUnrealizedJustified: () => ({
|
|
2392
2435
|
checkpoint: this.fcStore.unrealizedJustified.checkpoint,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
PayloadStatus,
|
|
8
8
|
ProtoBlock,
|
|
9
9
|
ProtoNode,
|
|
10
|
+
PtcQuorum,
|
|
10
11
|
} from "../protoArray/interface.js";
|
|
11
12
|
import {IFastConfirmationSpecStore} from "./fastConfirmation/types.js";
|
|
12
13
|
import {UpdateAndGetHeadOpt} from "./forkChoice.js";
|
|
@@ -31,6 +32,9 @@ export type CheckpointWithTotalBalance = CheckpointWithBalance & {
|
|
|
31
32
|
totalBalance: number;
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
/** Emitted when the PTC verdict on a block is decided or flips, see getPtcVerdict */
|
|
36
|
+
export type PtcQuorumEvent = PtcQuorum & {blockRoot: RootHex; slot: Slot; verdict: boolean};
|
|
37
|
+
|
|
34
38
|
export enum EpochDifference {
|
|
35
39
|
current = 0,
|
|
36
40
|
previous = 1,
|
|
@@ -259,6 +263,8 @@ export interface IForkChoice {
|
|
|
259
263
|
payloadPresentCount: number;
|
|
260
264
|
dataAvailableCount: number;
|
|
261
265
|
} | null;
|
|
266
|
+
/** Current PTC majority per vote field; `null` for pre-Gloas roots */
|
|
267
|
+
getPtcQuorum(blockRootHex: RootHex): PtcQuorum | null;
|
|
262
268
|
getPayloadTimelinessVotes(blockRootHex: RootHex): (boolean | null)[] | null;
|
|
263
269
|
getPayloadDataAvailabilityVotes(blockRootHex: RootHex): (boolean | null)[] | null;
|
|
264
270
|
|
package/src/forkChoice/store.ts
CHANGED
|
@@ -2,7 +2,7 @@ import {EffectiveBalanceIncrements, IBeaconStateView} from "@lodestar/state-tran
|
|
|
2
2
|
import {RootHex, Slot, ValidatorIndex, phase0} from "@lodestar/types";
|
|
3
3
|
import {toRootHex} from "@lodestar/utils";
|
|
4
4
|
import {ForkChoiceStateGetter, IFastConfirmationStore} from "./fastConfirmation/types.js";
|
|
5
|
-
import {CheckpointWithBalance, CheckpointWithTotalBalance} from "./interface.js";
|
|
5
|
+
import {CheckpointWithBalance, CheckpointWithTotalBalance, PtcQuorumEvent} from "./interface.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Stores checkpoints in a hybrid format:
|
|
@@ -46,6 +46,7 @@ export interface IForkChoiceStore extends IFastConfirmationStore {
|
|
|
46
46
|
justifiedBalancesGetter: JustifiedBalancesGetter;
|
|
47
47
|
equivocatingIndices: Set<ValidatorIndex>;
|
|
48
48
|
notifyFastConfirmation?(data: {block: RootHex; slot: Slot; currentSlot: Slot}): void;
|
|
49
|
+
notifyPtcQuorum?(data: PtcQuorumEvent): void;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
/**
|
|
@@ -85,6 +86,7 @@ export class ForkChoiceStore implements IForkChoiceStore {
|
|
|
85
86
|
onJustified: (cp: CheckpointWithHex) => void;
|
|
86
87
|
onFinalized: (cp: CheckpointWithHex) => void;
|
|
87
88
|
onFastConfirmation?: (data: {block: RootHex; slot: Slot; currentSlot: Slot}) => void;
|
|
89
|
+
onPtcQuorum?: (data: PtcQuorumEvent) => void;
|
|
88
90
|
}
|
|
89
91
|
) {
|
|
90
92
|
this.justifiedBalancesGetter = justifiedBalancesGetter;
|
|
@@ -142,6 +144,10 @@ export class ForkChoiceStore implements IForkChoiceStore {
|
|
|
142
144
|
notifyFastConfirmation(data: {block: RootHex; slot: Slot; currentSlot: Slot}): void {
|
|
143
145
|
this.events?.onFastConfirmation?.(data);
|
|
144
146
|
}
|
|
147
|
+
|
|
148
|
+
notifyPtcQuorum(data: PtcQuorumEvent): void {
|
|
149
|
+
this.events?.onPtcQuorum?.(data);
|
|
150
|
+
}
|
|
145
151
|
}
|
|
146
152
|
|
|
147
153
|
export function toCheckpointWithHex(checkpoint: phase0.Checkpoint): CheckpointWithHex {
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export {
|
|
|
26
26
|
EpochDifference,
|
|
27
27
|
type IForkChoice,
|
|
28
28
|
NotReorgedReason,
|
|
29
|
+
type PtcQuorumEvent,
|
|
29
30
|
} from "./forkChoice/interface.js";
|
|
30
31
|
export * from "./forkChoice/safeBlocks.js";
|
|
31
32
|
export {
|
|
@@ -43,6 +44,7 @@ export type {
|
|
|
43
44
|
PayloadExecutionStatus,
|
|
44
45
|
ProtoBlock,
|
|
45
46
|
ProtoNode,
|
|
47
|
+
PtcQuorum,
|
|
46
48
|
} from "./protoArray/interface.js";
|
|
47
|
-
export {ExecutionStatus, PayloadStatus, isGloasBlock} from "./protoArray/interface.js";
|
|
49
|
+
export {ExecutionStatus, PayloadStatus, getPtcVerdict, isGloasBlock} from "./protoArray/interface.js";
|
|
48
50
|
export {ProtoArray} from "./protoArray/protoArray.js";
|
package/src/metrics.ts
CHANGED
|
@@ -68,6 +68,11 @@ export function getForkChoiceMetrics(register: MetricsRegisterExtra) {
|
|
|
68
68
|
help: "Reason why the current head is not re-orged out",
|
|
69
69
|
labelNames: ["reason"],
|
|
70
70
|
}),
|
|
71
|
+
ptcQuorum: register.counter<{verdict: "true" | "false"}>({
|
|
72
|
+
name: "beacon_fork_choice_ptc_quorum_total",
|
|
73
|
+
help: "Count of decided PTC verdicts, true once majorities voted present and available, false once a majority voted either against",
|
|
74
|
+
labelNames: ["verdict"],
|
|
75
|
+
}),
|
|
71
76
|
computeDeltas: {
|
|
72
77
|
duration: register.histogram({
|
|
73
78
|
name: "beacon_fork_choice_compute_deltas_seconds",
|
|
@@ -180,3 +180,24 @@ export type ProtoNode = ProtoBlock & {
|
|
|
180
180
|
bestChild?: number;
|
|
181
181
|
bestDescendant?: number;
|
|
182
182
|
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* PTC majority per PayloadAttestationData field: `true` or `false` once more than PTC_SIZE // 2
|
|
186
|
+
* members voted that way, `null` while neither outcome has a majority.
|
|
187
|
+
*/
|
|
188
|
+
export type PtcQuorum = {
|
|
189
|
+
payloadPresent: boolean | null;
|
|
190
|
+
blobDataAvailable: boolean | null;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The PTC verdict fork choice acts on: `false` once either field has a majority against the
|
|
195
|
+
* payload, `true` once both have a majority for it, `null` while undecided. A single majority
|
|
196
|
+
* against is final on its own because should_extend_payload needs both in favour and
|
|
197
|
+
* should_build_on_full moves the proposer onto EMPTY on either.
|
|
198
|
+
*/
|
|
199
|
+
export function getPtcVerdict({payloadPresent, blobDataAvailable}: PtcQuorum): boolean | null {
|
|
200
|
+
if (payloadPresent === false || blobDataAvailable === false) return false;
|
|
201
|
+
if (payloadPresent === true && blobDataAvailable === true) return true;
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
PayloadStatus,
|
|
14
14
|
ProtoBlock,
|
|
15
15
|
ProtoNode,
|
|
16
|
+
PtcQuorum,
|
|
16
17
|
isGloasBlock,
|
|
17
18
|
} from "./interface.js";
|
|
18
19
|
|
|
@@ -52,6 +53,12 @@ export function countNoVotes(attended: BitArray, yes: BitArray): number {
|
|
|
52
53
|
return count;
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
function majorityVote(attended: BitArray, yes: BitArray, threshold: number): boolean | null {
|
|
57
|
+
if (bitCount(yes.uint8Array) > threshold) return true;
|
|
58
|
+
if (countNoVotes(attended, yes) > threshold) return false;
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
export const DEFAULT_PRUNE_THRESHOLD = 0;
|
|
56
63
|
type ProposerBoost = {root: RootHex; score: bigint};
|
|
57
64
|
|
|
@@ -90,6 +97,8 @@ export class ProtoArray {
|
|
|
90
97
|
lvhError?: LVHExecError;
|
|
91
98
|
|
|
92
99
|
private previousProposerBoost: ProposerBoost | null = null;
|
|
100
|
+
/** Root read by shouldExtendPayload(), set even while the boost weight is withheld */
|
|
101
|
+
private proposerBoostRoot: RootHex | null = null;
|
|
93
102
|
|
|
94
103
|
/**
|
|
95
104
|
* PTC (Payload Timeliness Committee) votes per block as bitvectors
|
|
@@ -357,6 +366,7 @@ export class ProtoArray {
|
|
|
357
366
|
applyScoreChanges({
|
|
358
367
|
attestationDeltas,
|
|
359
368
|
proposerBoost,
|
|
369
|
+
proposerBoostRoot,
|
|
360
370
|
justifiedEpoch,
|
|
361
371
|
justifiedRoot,
|
|
362
372
|
finalizedEpoch,
|
|
@@ -365,6 +375,8 @@ export class ProtoArray {
|
|
|
365
375
|
}: {
|
|
366
376
|
attestationDeltas: number[];
|
|
367
377
|
proposerBoost: ProposerBoost | null;
|
|
378
|
+
/** Defaults to the boosted root. Pass it explicitly when the boost weight is withheld */
|
|
379
|
+
proposerBoostRoot?: RootHex | null;
|
|
368
380
|
justifiedEpoch: Epoch;
|
|
369
381
|
justifiedRoot: RootHex;
|
|
370
382
|
finalizedEpoch: Epoch;
|
|
@@ -463,7 +475,9 @@ export class ProtoArray {
|
|
|
463
475
|
// We _must_ perform these functions separate from the weight-updating loop above to ensure
|
|
464
476
|
// that we have a fully coherent set of weights before updating parent
|
|
465
477
|
// best-child/descendant.
|
|
466
|
-
|
|
478
|
+
// should_extend_payload reads store.proposer_boost_root even when should_apply_proposer_boost
|
|
479
|
+
// withholds the boost weight, so the tiebreak root is tracked apart from the applied boost
|
|
480
|
+
this.proposerBoostRoot = proposerBoostRoot ?? proposerBoost?.root ?? null;
|
|
467
481
|
for (let nodeIndex = this.nodes.length - 1; nodeIndex >= 0; nodeIndex--) {
|
|
468
482
|
const node = this.nodes[nodeIndex];
|
|
469
483
|
if (node === undefined) {
|
|
@@ -476,7 +490,7 @@ export class ProtoArray {
|
|
|
476
490
|
// If the node has a parent, try to update its best-child and best-descendant.
|
|
477
491
|
const parentIndex = node.parent;
|
|
478
492
|
if (parentIndex !== undefined) {
|
|
479
|
-
this.maybeUpdateBestChildAndDescendant(parentIndex, nodeIndex, currentSlot, proposerBoostRoot);
|
|
493
|
+
this.maybeUpdateBestChildAndDescendant(parentIndex, nodeIndex, currentSlot, this.proposerBoostRoot);
|
|
480
494
|
}
|
|
481
495
|
}
|
|
482
496
|
// Update the previous proposer boost
|
|
@@ -801,6 +815,23 @@ export class ProtoArray {
|
|
|
801
815
|
};
|
|
802
816
|
}
|
|
803
817
|
|
|
818
|
+
/**
|
|
819
|
+
* PTC majority per vote field from the raw tallies, regardless of whether the payload is locally
|
|
820
|
+
* available. Returns `null` for pre-Gloas (or pruned) roots, which have no vote maps.
|
|
821
|
+
*/
|
|
822
|
+
getPtcQuorum(blockRoot: RootHex): PtcQuorum | null {
|
|
823
|
+
const attended = this.ptcAttested.get(blockRoot);
|
|
824
|
+
const timelinessVotes = this.payloadTimelinessVotes.get(blockRoot);
|
|
825
|
+
const daVotes = this.payloadDataAvailabilityVotes.get(blockRoot);
|
|
826
|
+
if (attended === undefined || timelinessVotes === undefined || daVotes === undefined) {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
payloadPresent: majorityVote(attended, timelinessVotes, PAYLOAD_TIMELY_THRESHOLD),
|
|
831
|
+
blobDataAvailable: majorityVote(attended, daVotes, DATA_AVAILABILITY_TIMELY_THRESHOLD),
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
804
835
|
getPreviousProposerBoostRoot(): RootHex {
|
|
805
836
|
return this.previousProposerBoost?.root ?? HEX_ZERO_HASH;
|
|
806
837
|
}
|
|
@@ -1043,6 +1074,8 @@ export class ProtoArray {
|
|
|
1043
1074
|
|
|
1044
1075
|
private propagateValidExecutionStatusByIndex(validNodeIndex: number): void {
|
|
1045
1076
|
let nodeIndex: number | undefined = validNodeIndex;
|
|
1077
|
+
// Payloads that transitioned to VALID, their gloas dependents are validated below
|
|
1078
|
+
const validatedPayloadHashes = new Set<RootHex>();
|
|
1046
1079
|
// propagate till we keep encountering syncing status
|
|
1047
1080
|
while (nodeIndex !== undefined) {
|
|
1048
1081
|
const node = this.getNodeFromIndex(nodeIndex);
|
|
@@ -1050,8 +1083,36 @@ export class ProtoArray {
|
|
|
1050
1083
|
break;
|
|
1051
1084
|
}
|
|
1052
1085
|
this.validateNodeByIndex(nodeIndex);
|
|
1086
|
+
if (node.payloadStatus === PayloadStatus.FULL && node.executionPayloadBlockHash !== null) {
|
|
1087
|
+
validatedPayloadHashes.add(node.executionPayloadBlockHash);
|
|
1088
|
+
}
|
|
1053
1089
|
nodeIndex = node.parent;
|
|
1054
1090
|
}
|
|
1091
|
+
|
|
1092
|
+
if (validatedPayloadHashes.size > 0) {
|
|
1093
|
+
this.validatePayloadDependents(validatedPayloadHashes);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Gloas PENDING and EMPTY variants have no payload of their own, they inherit the execution status of
|
|
1099
|
+
* the payload they build on, which is their `executionPayloadBlockHash`. Once that payload is VALID, all
|
|
1100
|
+
* of them are, not only the ones on the chain the validation walked up (e.g. siblings, or blocks
|
|
1101
|
+
* building on a sibling's EMPTY variant).
|
|
1102
|
+
*/
|
|
1103
|
+
private validatePayloadDependents(payloadHashes: Set<RootHex>): void {
|
|
1104
|
+
for (let nodeIndex = 0; nodeIndex < this.nodes.length; nodeIndex++) {
|
|
1105
|
+
const node = this.nodes[nodeIndex];
|
|
1106
|
+
if (
|
|
1107
|
+
node.payloadStatus === PayloadStatus.PENDING &&
|
|
1108
|
+
node.executionStatus === ExecutionStatus.Syncing &&
|
|
1109
|
+
node.executionPayloadBlockHash !== null &&
|
|
1110
|
+
payloadHashes.has(node.executionPayloadBlockHash)
|
|
1111
|
+
) {
|
|
1112
|
+
// Also flips the sibling EMPTY variant
|
|
1113
|
+
this.validateNodeByIndex(nodeIndex);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1055
1116
|
}
|
|
1056
1117
|
|
|
1057
1118
|
/**
|
|
@@ -1092,6 +1153,7 @@ export class ProtoArray {
|
|
|
1092
1153
|
this.applyScoreChanges({
|
|
1093
1154
|
attestationDeltas: Array.from({length: this.nodes.length}, () => 0),
|
|
1094
1155
|
proposerBoost: this.previousProposerBoost,
|
|
1156
|
+
proposerBoostRoot: this.proposerBoostRoot,
|
|
1095
1157
|
justifiedEpoch: this.justifiedEpoch,
|
|
1096
1158
|
justifiedRoot: this.justifiedRoot,
|
|
1097
1159
|
finalizedEpoch: this.finalizedEpoch,
|