@lodestar/fork-choice 1.42.0 → 1.43.0-dev.07452fe3b7
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/errors.d.ts +8 -1
- package/lib/forkChoice/errors.d.ts.map +1 -1
- package/lib/forkChoice/errors.js +4 -0
- package/lib/forkChoice/errors.js.map +1 -1
- package/lib/forkChoice/forkChoice.d.ts +27 -20
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +102 -84
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +27 -8
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/forkChoice/store.d.ts +16 -40
- package/lib/forkChoice/store.d.ts.map +1 -1
- package/lib/forkChoice/store.js +4 -22
- 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/protoArray/interface.d.ts +7 -8
- package/lib/protoArray/interface.d.ts.map +1 -1
- package/lib/protoArray/interface.js +3 -4
- package/lib/protoArray/interface.js.map +1 -1
- package/lib/protoArray/protoArray.d.ts +10 -2
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +77 -39
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +8 -8
- package/src/forkChoice/errors.ts +6 -1
- package/src/forkChoice/forkChoice.ts +119 -132
- package/src/forkChoice/interface.ts +28 -9
- package/src/forkChoice/store.ts +20 -52
- package/src/index.ts +3 -9
- package/src/protoArray/interface.ts +8 -7
- package/src/protoArray/protoArray.ts +90 -41
package/src/forkChoice/store.ts
CHANGED
|
@@ -1,8 +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 {
|
|
5
|
-
import {CheckpointWithPayloadAndBalance, CheckpointWithPayloadAndTotalBalance} from "./interface.js";
|
|
4
|
+
import {CheckpointWithBalance, CheckpointWithTotalBalance} from "./interface.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Stores checkpoints in a hybrid format:
|
|
@@ -11,15 +10,6 @@ import {CheckpointWithPayloadAndBalance, CheckpointWithPayloadAndTotalBalance} f
|
|
|
11
10
|
*/
|
|
12
11
|
export type CheckpointWithHex = phase0.Checkpoint & {rootHex: RootHex};
|
|
13
12
|
|
|
14
|
-
/**
|
|
15
|
-
* Checkpoint with payload status for Gloas fork choice.
|
|
16
|
-
* Used to track which variant (EMPTY or FULL) of the finalized/justified block to use.
|
|
17
|
-
*
|
|
18
|
-
* Pre-Gloas: payloadStatus is always FULL (payload embedded in block)
|
|
19
|
-
* Gloas: determined by state.execution_payload_availability
|
|
20
|
-
*/
|
|
21
|
-
export type CheckpointWithPayloadStatus = CheckpointWithHex & {payloadStatus: PayloadStatus};
|
|
22
|
-
|
|
23
13
|
export type JustifiedBalances = EffectiveBalanceIncrements;
|
|
24
14
|
|
|
25
15
|
/**
|
|
@@ -29,7 +19,7 @@ export type JustifiedBalances = EffectiveBalanceIncrements;
|
|
|
29
19
|
* @param blockState state that declares justified checkpoint `checkpoint`
|
|
30
20
|
*/
|
|
31
21
|
export type JustifiedBalancesGetter = (
|
|
32
|
-
checkpoint:
|
|
22
|
+
checkpoint: CheckpointWithHex,
|
|
33
23
|
blockState: IBeaconStateView
|
|
34
24
|
) => JustifiedBalances;
|
|
35
25
|
|
|
@@ -47,11 +37,11 @@ export type JustifiedBalancesGetter = (
|
|
|
47
37
|
*/
|
|
48
38
|
export interface IForkChoiceStore {
|
|
49
39
|
currentSlot: Slot;
|
|
50
|
-
get justified():
|
|
51
|
-
set justified(justified:
|
|
52
|
-
unrealizedJustified:
|
|
53
|
-
finalizedCheckpoint:
|
|
54
|
-
unrealizedFinalizedCheckpoint:
|
|
40
|
+
get justified(): CheckpointWithTotalBalance;
|
|
41
|
+
set justified(justified: CheckpointWithBalance);
|
|
42
|
+
unrealizedJustified: CheckpointWithBalance;
|
|
43
|
+
finalizedCheckpoint: CheckpointWithHex;
|
|
44
|
+
unrealizedFinalizedCheckpoint: CheckpointWithHex;
|
|
55
45
|
justifiedBalancesGetter: JustifiedBalancesGetter;
|
|
56
46
|
equivocatingIndices: Set<ValidatorIndex>;
|
|
57
47
|
}
|
|
@@ -60,10 +50,10 @@ export interface IForkChoiceStore {
|
|
|
60
50
|
* IForkChoiceStore implementer which emits forkChoice events on updated justified and finalized checkpoints.
|
|
61
51
|
*/
|
|
62
52
|
export class ForkChoiceStore implements IForkChoiceStore {
|
|
63
|
-
private _justified:
|
|
64
|
-
unrealizedJustified:
|
|
65
|
-
private _finalizedCheckpoint:
|
|
66
|
-
unrealizedFinalizedCheckpoint:
|
|
53
|
+
private _justified: CheckpointWithTotalBalance;
|
|
54
|
+
unrealizedJustified: CheckpointWithBalance;
|
|
55
|
+
private _finalizedCheckpoint: CheckpointWithHex;
|
|
56
|
+
unrealizedFinalizedCheckpoint: CheckpointWithHex;
|
|
67
57
|
equivocatingIndices = new Set<ValidatorIndex>();
|
|
68
58
|
justifiedBalancesGetter: JustifiedBalancesGetter;
|
|
69
59
|
currentSlot: Slot;
|
|
@@ -74,49 +64,37 @@ export class ForkChoiceStore implements IForkChoiceStore {
|
|
|
74
64
|
finalizedCheckpoint: phase0.Checkpoint,
|
|
75
65
|
justifiedBalances: EffectiveBalanceIncrements,
|
|
76
66
|
justifiedBalancesGetter: JustifiedBalancesGetter,
|
|
77
|
-
/**
|
|
78
|
-
* Payload status for justified checkpoint.
|
|
79
|
-
* Pre-Gloas: always FULL
|
|
80
|
-
* Gloas: determined by state.execution_payload_availability
|
|
81
|
-
*/
|
|
82
|
-
justifiedPayloadStatus: PayloadStatus,
|
|
83
|
-
/**
|
|
84
|
-
* Payload status for finalized checkpoint.
|
|
85
|
-
* Pre-Gloas: always FULL
|
|
86
|
-
* Gloas: determined by state.execution_payload_availability
|
|
87
|
-
*/
|
|
88
|
-
finalizedPayloadStatus: PayloadStatus,
|
|
89
67
|
private readonly events?: {
|
|
90
|
-
onJustified: (cp:
|
|
91
|
-
onFinalized: (cp:
|
|
68
|
+
onJustified: (cp: CheckpointWithHex) => void;
|
|
69
|
+
onFinalized: (cp: CheckpointWithHex) => void;
|
|
92
70
|
}
|
|
93
71
|
) {
|
|
94
72
|
this.justifiedBalancesGetter = justifiedBalancesGetter;
|
|
95
73
|
this.currentSlot = currentSlot;
|
|
96
74
|
const justified = {
|
|
97
|
-
checkpoint:
|
|
75
|
+
checkpoint: toCheckpointWithHex(justifiedCheckpoint),
|
|
98
76
|
balances: justifiedBalances,
|
|
99
77
|
totalBalance: computeTotalBalance(justifiedBalances),
|
|
100
78
|
};
|
|
101
79
|
this._justified = justified;
|
|
102
80
|
this.unrealizedJustified = justified;
|
|
103
|
-
this._finalizedCheckpoint =
|
|
81
|
+
this._finalizedCheckpoint = toCheckpointWithHex(finalizedCheckpoint);
|
|
104
82
|
this.unrealizedFinalizedCheckpoint = this._finalizedCheckpoint;
|
|
105
83
|
}
|
|
106
84
|
|
|
107
|
-
get justified():
|
|
85
|
+
get justified(): CheckpointWithTotalBalance {
|
|
108
86
|
return this._justified;
|
|
109
87
|
}
|
|
110
|
-
set justified(justified:
|
|
88
|
+
set justified(justified: CheckpointWithBalance) {
|
|
111
89
|
this._justified = {...justified, totalBalance: computeTotalBalance(justified.balances)};
|
|
112
90
|
this.events?.onJustified(justified.checkpoint);
|
|
113
91
|
}
|
|
114
92
|
|
|
115
|
-
get finalizedCheckpoint():
|
|
93
|
+
get finalizedCheckpoint(): CheckpointWithHex {
|
|
116
94
|
return this._finalizedCheckpoint;
|
|
117
95
|
}
|
|
118
|
-
set finalizedCheckpoint(checkpoint:
|
|
119
|
-
const cp =
|
|
96
|
+
set finalizedCheckpoint(checkpoint: CheckpointWithHex) {
|
|
97
|
+
const cp = toCheckpointWithHex(checkpoint);
|
|
120
98
|
this._finalizedCheckpoint = cp;
|
|
121
99
|
this.events?.onFinalized(cp);
|
|
122
100
|
}
|
|
@@ -133,16 +111,6 @@ export function toCheckpointWithHex(checkpoint: phase0.Checkpoint): CheckpointWi
|
|
|
133
111
|
};
|
|
134
112
|
}
|
|
135
113
|
|
|
136
|
-
export function toCheckpointWithPayload(
|
|
137
|
-
checkpoint: phase0.Checkpoint,
|
|
138
|
-
payloadStatus: PayloadStatus
|
|
139
|
-
): CheckpointWithPayloadStatus {
|
|
140
|
-
return {
|
|
141
|
-
...toCheckpointWithHex(checkpoint),
|
|
142
|
-
payloadStatus,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
114
|
export function equalCheckpointWithHex(a: CheckpointWithHex, b: CheckpointWithHex): boolean {
|
|
147
115
|
return a.epoch === b.epoch && a.rootHex === b.rootHex;
|
|
148
116
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,17 +6,12 @@ export {
|
|
|
6
6
|
type InvalidBlock,
|
|
7
7
|
InvalidBlockCode,
|
|
8
8
|
} from "./forkChoice/errors.js";
|
|
9
|
-
export {
|
|
10
|
-
ForkChoice,
|
|
11
|
-
type ForkChoiceOpts,
|
|
12
|
-
UpdateHeadOpt,
|
|
13
|
-
getCheckpointPayloadStatus,
|
|
14
|
-
} from "./forkChoice/forkChoice.js";
|
|
9
|
+
export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt} from "./forkChoice/forkChoice.js";
|
|
15
10
|
export {
|
|
16
11
|
type AncestorResult,
|
|
17
12
|
AncestorStatus,
|
|
18
|
-
type
|
|
19
|
-
type
|
|
13
|
+
type CheckpointWithBalance,
|
|
14
|
+
type CheckpointWithTotalBalance,
|
|
20
15
|
EpochDifference,
|
|
21
16
|
type IForkChoice,
|
|
22
17
|
NotReorgedReason,
|
|
@@ -24,7 +19,6 @@ export {
|
|
|
24
19
|
export * from "./forkChoice/safeBlocks.js";
|
|
25
20
|
export {
|
|
26
21
|
type CheckpointWithHex,
|
|
27
|
-
type CheckpointWithPayloadStatus,
|
|
28
22
|
ForkChoiceStore,
|
|
29
23
|
type IForkChoiceStore,
|
|
30
24
|
type JustifiedBalancesGetter,
|
|
@@ -24,16 +24,15 @@ export type VoteIndex = number;
|
|
|
24
24
|
* - Syncing: EL is syncing, payload validity unknown (optimistic sync)
|
|
25
25
|
* - PreMerge: Block is from before The Merge, no execution payload exists
|
|
26
26
|
* - Invalid: Execution payload was invalidated by the EL (post-import status)
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
*
|
|
28
|
+
* For gloas blocks the PENDING/EMPTY variants inherit `executionStatus` from the parent's chain
|
|
29
|
+
* (Valid/Syncing/PreMerge); the FULL variant carries the EL response for this block's own payload.
|
|
30
30
|
*/
|
|
31
31
|
export enum ExecutionStatus {
|
|
32
32
|
Valid = "Valid",
|
|
33
33
|
Syncing = "Syncing",
|
|
34
34
|
PreMerge = "PreMerge",
|
|
35
35
|
Invalid = "Invalid",
|
|
36
|
-
PayloadSeparated = "PayloadSeparated",
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
/**
|
|
@@ -61,19 +60,21 @@ export type LVHInvalidResponse = {
|
|
|
61
60
|
executionStatus: ExecutionStatus.Invalid;
|
|
62
61
|
latestValidExecHash: RootHex | null;
|
|
63
62
|
invalidateFromParentBlockRoot: RootHex;
|
|
63
|
+
// EL block hash from invalid block's bid (gloas) or payload's parentHash (pre-gloas).
|
|
64
|
+
// Disambiguates which variant of the parent (FULL vs EMPTY) to invalidate from.
|
|
65
|
+
invalidateFromParentBlockHash: RootHex;
|
|
64
66
|
};
|
|
65
67
|
export type LVHExecResponse = LVHValidResponse | LVHInvalidResponse;
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
70
|
* Any execution status that is not definitively invalid.
|
|
69
|
-
*
|
|
70
|
-
* Post-Gloas: execution status must be PayloadSeparated (beacon block imported before its payload arrives via SignedExecutionPayloadEnvelope)
|
|
71
|
+
* Valid | Syncing | PreMerge
|
|
71
72
|
*/
|
|
72
73
|
export type BlockExecutionStatus = Exclude<ExecutionStatus, ExecutionStatus.Invalid>;
|
|
73
74
|
|
|
74
75
|
/**
|
|
75
76
|
* Execution status for a block whose execution payload is present and has been submitted to the EL.
|
|
76
|
-
* Used post-Gloas when transitioning a
|
|
77
|
+
* Used post-Gloas when transitioning a PENDING block to FULL via onExecutionPayload().
|
|
77
78
|
*/
|
|
78
79
|
export type PayloadExecutionStatus = ExecutionStatus.Valid | ExecutionStatus.Syncing;
|
|
79
80
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {BitArray} from "@chainsafe/ssz";
|
|
2
2
|
import {GENESIS_EPOCH, PTC_SIZE} from "@lodestar/params";
|
|
3
|
-
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
3
|
+
import {DataAvailabilityStatus, computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
4
4
|
import {Epoch, RootHex, Slot} from "@lodestar/types";
|
|
5
5
|
import {bitCount, toRootHex} from "@lodestar/utils";
|
|
6
6
|
import {ForkChoiceError, ForkChoiceErrorCode} from "../forkChoice/errors.js";
|
|
@@ -108,6 +108,7 @@ export class ProtoArray {
|
|
|
108
108
|
currentSlot,
|
|
109
109
|
null
|
|
110
110
|
);
|
|
111
|
+
|
|
111
112
|
return protoArray;
|
|
112
113
|
}
|
|
113
114
|
|
|
@@ -251,38 +252,43 @@ export class ProtoArray {
|
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
/**
|
|
254
|
-
* Returns an EMPTY or FULL
|
|
255
|
+
* Returns the node index of an EMPTY or FULL variant matching block root and block hash.
|
|
256
|
+
* Pre-gloas: checks the single variant. Post-gloas: prefers FULL, falls back to EMPTY.
|
|
255
257
|
*/
|
|
256
|
-
|
|
258
|
+
getNodeIndexByRootAndBlockHash(blockRoot: RootHex, blockHash: RootHex): number | undefined {
|
|
257
259
|
const variantIndices = this.indices.get(blockRoot);
|
|
258
260
|
if (variantIndices === undefined) {
|
|
259
|
-
return
|
|
261
|
+
return undefined;
|
|
260
262
|
}
|
|
261
263
|
|
|
262
264
|
// Pre-Gloas
|
|
263
265
|
if (!Array.isArray(variantIndices)) {
|
|
264
|
-
|
|
265
|
-
return node.executionPayloadBlockHash === blockHash ? node : null;
|
|
266
|
+
return this.nodes[variantIndices].executionPayloadBlockHash === blockHash ? variantIndices : undefined;
|
|
266
267
|
}
|
|
267
268
|
|
|
268
|
-
// Post-Gloas,
|
|
269
|
+
// Post-Gloas, prefer FULL then EMPTY
|
|
269
270
|
const fullNodeIndex = variantIndices[PayloadStatus.FULL];
|
|
270
|
-
if (fullNodeIndex !== undefined) {
|
|
271
|
-
|
|
272
|
-
if (fullNode && fullNode.executionPayloadBlockHash === blockHash) {
|
|
273
|
-
return fullNode;
|
|
274
|
-
}
|
|
271
|
+
if (fullNodeIndex !== undefined && this.nodes[fullNodeIndex].executionPayloadBlockHash === blockHash) {
|
|
272
|
+
return fullNodeIndex;
|
|
275
273
|
}
|
|
276
274
|
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
279
|
-
return
|
|
275
|
+
const emptyNodeIndex = variantIndices[PayloadStatus.EMPTY];
|
|
276
|
+
if (this.nodes[emptyNodeIndex].executionPayloadBlockHash === blockHash) {
|
|
277
|
+
return emptyNodeIndex;
|
|
280
278
|
}
|
|
281
279
|
|
|
282
280
|
// PENDING is the same to EMPTY so not likely we can return it
|
|
283
281
|
// also it's only specific for fork-choice
|
|
284
282
|
|
|
285
|
-
return
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Returns an EMPTY or FULL `ProtoBlock` that has matching block root and block hash
|
|
288
|
+
*/
|
|
289
|
+
getBlockHexAndBlockHash(blockRoot: RootHex, blockHash: RootHex): ProtoBlock | null {
|
|
290
|
+
const idx = this.getNodeIndexByRootAndBlockHash(blockRoot, blockHash);
|
|
291
|
+
return idx !== undefined ? this.nodes[idx] : null;
|
|
286
292
|
}
|
|
287
293
|
|
|
288
294
|
/**
|
|
@@ -354,9 +360,15 @@ export class ProtoArray {
|
|
|
354
360
|
continue;
|
|
355
361
|
}
|
|
356
362
|
|
|
357
|
-
|
|
363
|
+
// For Gloas blocks, PENDING/EMPTY/FULL all share the same blockRoot.
|
|
364
|
+
// Only apply proposer boost to PENDING (for Gloas) or FULL (for pre-Gloas) — to avoid
|
|
365
|
+
// double-counting the boost across variants during delta back-propagation, and to keep
|
|
366
|
+
// the boost neutral with respect to EMPTY vs FULL selection.
|
|
367
|
+
const isBoostVariant = isGloasBlock(node) ? node.payloadStatus === PayloadStatus.PENDING : true; // pre-Gloas has only FULL, always boost
|
|
368
|
+
const currentBoost =
|
|
369
|
+
proposerBoost && proposerBoost.root === node.blockRoot && isBoostVariant ? proposerBoost.score : 0;
|
|
358
370
|
const previousBoost =
|
|
359
|
-
this.previousProposerBoost && this.previousProposerBoost.root === node.blockRoot
|
|
371
|
+
this.previousProposerBoost && this.previousProposerBoost.root === node.blockRoot && isBoostVariant
|
|
360
372
|
? this.previousProposerBoost.score
|
|
361
373
|
: 0;
|
|
362
374
|
|
|
@@ -541,9 +553,9 @@ export class ProtoArray {
|
|
|
541
553
|
currentSlot: Slot,
|
|
542
554
|
executionPayloadBlockHash: RootHex,
|
|
543
555
|
executionPayloadNumber: number,
|
|
544
|
-
executionPayloadStateRoot: RootHex,
|
|
545
556
|
proposerBoostRoot: RootHex | null,
|
|
546
|
-
executionStatus: PayloadExecutionStatus
|
|
557
|
+
executionStatus: PayloadExecutionStatus,
|
|
558
|
+
dataAvailabilityStatus: DataAvailabilityStatus
|
|
547
559
|
): void {
|
|
548
560
|
// First check if block exists
|
|
549
561
|
const variants = this.indices.get(blockRoot);
|
|
@@ -585,7 +597,7 @@ export class ProtoArray {
|
|
|
585
597
|
});
|
|
586
598
|
}
|
|
587
599
|
|
|
588
|
-
// Create FULL variant as a child of PENDING (sibling to EMPTY)
|
|
600
|
+
// Create FULL variant as a child of PENDING (sibling to EMPTY).
|
|
589
601
|
const fullNode: ProtoNode = {
|
|
590
602
|
...pendingNode,
|
|
591
603
|
parent: pendingIndex, // Points to own PENDING (same as EMPTY)
|
|
@@ -593,11 +605,10 @@ export class ProtoArray {
|
|
|
593
605
|
weight: 0,
|
|
594
606
|
bestChild: undefined,
|
|
595
607
|
bestDescendant: undefined,
|
|
596
|
-
// TODO GLOAS: handle optimistic sync
|
|
597
608
|
executionStatus,
|
|
598
609
|
executionPayloadBlockHash,
|
|
599
610
|
executionPayloadNumber,
|
|
600
|
-
|
|
611
|
+
dataAvailabilityStatus,
|
|
601
612
|
};
|
|
602
613
|
|
|
603
614
|
const fullIndex = this.nodes.length;
|
|
@@ -606,6 +617,12 @@ export class ProtoArray {
|
|
|
606
617
|
// Add FULL variant to the indices array
|
|
607
618
|
variants[PayloadStatus.FULL] = fullIndex;
|
|
608
619
|
|
|
620
|
+
if (executionStatus === ExecutionStatus.Valid) {
|
|
621
|
+
// Walk up from FULL's parent (its own PENDING). FULL is already Valid; the loop breaks
|
|
622
|
+
// immediately if we start at FULL. Same pattern as pre-gloas onBlock at line ~533.
|
|
623
|
+
this.propagateValidExecutionStatusByIndex(pendingIndex);
|
|
624
|
+
}
|
|
625
|
+
|
|
609
626
|
// Update bestChild for PENDING node (may now prefer FULL over EMPTY)
|
|
610
627
|
this.maybeUpdateBestChildAndDescendant(pendingIndex, fullIndex, currentSlot, proposerBoostRoot);
|
|
611
628
|
}
|
|
@@ -634,6 +651,16 @@ export class ProtoArray {
|
|
|
634
651
|
}
|
|
635
652
|
}
|
|
636
653
|
|
|
654
|
+
getPTCVotes(blockRootHex: RootHex): BitArray | null {
|
|
655
|
+
const votes = this.ptcVotes.get(blockRootHex);
|
|
656
|
+
if (votes === undefined) {
|
|
657
|
+
// Block not found or not a Gloas block
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return votes;
|
|
662
|
+
}
|
|
663
|
+
|
|
637
664
|
/**
|
|
638
665
|
* Check if execution payload for a block is timely
|
|
639
666
|
* Spec: gloas/fork-choice.md#new-is_payload_timely
|
|
@@ -676,7 +703,7 @@ export class ProtoArray {
|
|
|
676
703
|
* Determine if we should extend the payload (prefer FULL over EMPTY)
|
|
677
704
|
* Spec: gloas/fork-choice.md#new-should_extend_payload
|
|
678
705
|
*
|
|
679
|
-
* Returns true if:
|
|
706
|
+
* Returns true if payload is verified (FULL variant exists) AND:
|
|
680
707
|
* 1. Payload is timely, OR
|
|
681
708
|
* 2. No proposer boost root (empty/zero hash), OR
|
|
682
709
|
* 3. Proposer boost root's parent is not this block, OR
|
|
@@ -686,6 +713,10 @@ export class ProtoArray {
|
|
|
686
713
|
* @param proposerBoostRoot - Current proposer boost root (from ForkChoice)
|
|
687
714
|
*/
|
|
688
715
|
shouldExtendPayload(blockRoot: RootHex, proposerBoostRoot: RootHex | null): boolean {
|
|
716
|
+
if (!this.hasPayload(blockRoot)) {
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
|
|
689
720
|
// Condition 1: Payload is timely
|
|
690
721
|
if (this.isPayloadTimely(blockRoot)) {
|
|
691
722
|
return true;
|
|
@@ -766,11 +797,15 @@ export class ProtoArray {
|
|
|
766
797
|
// Mark chain ii) as Invalid if LVH is found and non null, else only invalidate invalid_payload
|
|
767
798
|
// if its in fcU.
|
|
768
799
|
//
|
|
769
|
-
const {invalidateFromParentBlockRoot, latestValidExecHash} = execResponse;
|
|
770
|
-
|
|
771
|
-
|
|
800
|
+
const {invalidateFromParentBlockRoot, invalidateFromParentBlockHash, latestValidExecHash} = execResponse;
|
|
801
|
+
const invalidateFromParentIndex = this.getNodeIndexByRootAndBlockHash(
|
|
802
|
+
invalidateFromParentBlockRoot,
|
|
803
|
+
invalidateFromParentBlockHash
|
|
804
|
+
);
|
|
772
805
|
if (invalidateFromParentIndex === undefined) {
|
|
773
|
-
throw Error(
|
|
806
|
+
throw Error(
|
|
807
|
+
`Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} invalidateFromParentBlockHash=${invalidateFromParentBlockHash} in forkChoice`
|
|
808
|
+
);
|
|
774
809
|
}
|
|
775
810
|
const latestValidHashIndex =
|
|
776
811
|
latestValidExecHash !== null ? this.getNodeIndexFromLVH(latestValidExecHash, invalidateFromParentIndex) : null;
|
|
@@ -806,12 +841,6 @@ export class ProtoArray {
|
|
|
806
841
|
if (node.executionStatus === ExecutionStatus.PreMerge || node.executionStatus === ExecutionStatus.Valid) {
|
|
807
842
|
break;
|
|
808
843
|
}
|
|
809
|
-
// If PayloadSeparated, that means the node is either PENDING or EMPTY, there could be
|
|
810
|
-
// some ancestor still has syncing status.
|
|
811
|
-
if (node.executionStatus === ExecutionStatus.PayloadSeparated) {
|
|
812
|
-
nodeIndex = node.parent;
|
|
813
|
-
continue;
|
|
814
|
-
}
|
|
815
844
|
this.validateNodeByIndex(nodeIndex);
|
|
816
845
|
nodeIndex = node.parent;
|
|
817
846
|
}
|
|
@@ -909,6 +938,13 @@ export class ProtoArray {
|
|
|
909
938
|
invalidNode.executionStatus = ExecutionStatus.Invalid;
|
|
910
939
|
invalidNode.bestChild = undefined;
|
|
911
940
|
invalidNode.bestDescendant = undefined;
|
|
941
|
+
// Gloas: PENDING and sibling EMPTY share chain status, flip together
|
|
942
|
+
if (invalidNode.payloadStatus === PayloadStatus.PENDING) {
|
|
943
|
+
const variants = this.indices.get(invalidNode.blockRoot);
|
|
944
|
+
if (Array.isArray(variants)) {
|
|
945
|
+
this.invalidateNodeByIndex(variants[PayloadStatus.EMPTY]);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
912
948
|
|
|
913
949
|
return invalidNode;
|
|
914
950
|
}
|
|
@@ -930,6 +966,13 @@ export class ProtoArray {
|
|
|
930
966
|
if (validNode.executionStatus === ExecutionStatus.Syncing) {
|
|
931
967
|
validNode.executionStatus = ExecutionStatus.Valid;
|
|
932
968
|
}
|
|
969
|
+
// Gloas: PENDING and sibling EMPTY share chain status, flip together
|
|
970
|
+
if (validNode.payloadStatus === PayloadStatus.PENDING) {
|
|
971
|
+
const variants = this.indices.get(validNode.blockRoot);
|
|
972
|
+
if (Array.isArray(variants)) {
|
|
973
|
+
this.validateNodeByIndex(variants[PayloadStatus.EMPTY]);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
933
976
|
return validNode;
|
|
934
977
|
}
|
|
935
978
|
|
|
@@ -1478,9 +1521,16 @@ export class ProtoArray {
|
|
|
1478
1521
|
*/
|
|
1479
1522
|
private getParentNodeIndex(node: ProtoNode): number | undefined {
|
|
1480
1523
|
if (isGloasBlock(node)) {
|
|
1481
|
-
//
|
|
1482
|
-
|
|
1483
|
-
|
|
1524
|
+
// Traversal may reach the finalized ProtoBlock, should not throw error in that case
|
|
1525
|
+
try {
|
|
1526
|
+
const parentPayloadStatus = this.getParentPayloadStatus(node);
|
|
1527
|
+
return this.getNodeIndexByRootAndStatus(node.parentRoot, parentPayloadStatus);
|
|
1528
|
+
} catch (e) {
|
|
1529
|
+
if (e instanceof ProtoArrayError && e.type.code === ProtoArrayErrorCode.UNKNOWN_PARENT_BLOCK) {
|
|
1530
|
+
return undefined;
|
|
1531
|
+
}
|
|
1532
|
+
throw e;
|
|
1533
|
+
}
|
|
1484
1534
|
}
|
|
1485
1535
|
// Simple parent traversal for pre-Gloas blocks (includes fork transition)
|
|
1486
1536
|
return node.parent;
|
|
@@ -1634,10 +1684,9 @@ export class ProtoArray {
|
|
|
1634
1684
|
const ancestors: ProtoNode[] = [];
|
|
1635
1685
|
const nonAncestors: ProtoNode[] = [];
|
|
1636
1686
|
|
|
1637
|
-
//
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
}
|
|
1687
|
+
// caller of this method may pass default status
|
|
1688
|
+
// this is the only node that we accept PENDING
|
|
1689
|
+
ancestors.push(node);
|
|
1641
1690
|
|
|
1642
1691
|
let nodeIndex = startIndex;
|
|
1643
1692
|
while (node.parent !== undefined) {
|