@lodestar/fork-choice 1.42.0 → 1.43.0-dev.05a33e512f
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 +81 -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 +95 -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
|
|
|
@@ -213,6 +214,11 @@ export class ProtoArray {
|
|
|
213
214
|
return PayloadStatus.FULL;
|
|
214
215
|
}
|
|
215
216
|
|
|
217
|
+
// Genesis block has no parent in the proto array
|
|
218
|
+
if (block.parentRoot === HEX_ZERO_HASH) {
|
|
219
|
+
return PayloadStatus.FULL;
|
|
220
|
+
}
|
|
221
|
+
|
|
216
222
|
const parentBlock = this.getBlockHexAndBlockHash(block.parentRoot, parentBlockHash);
|
|
217
223
|
if (parentBlock == null) {
|
|
218
224
|
throw new ProtoArrayError({
|
|
@@ -251,38 +257,43 @@ export class ProtoArray {
|
|
|
251
257
|
}
|
|
252
258
|
|
|
253
259
|
/**
|
|
254
|
-
* Returns an EMPTY or FULL
|
|
260
|
+
* Returns the node index of an EMPTY or FULL variant matching block root and block hash.
|
|
261
|
+
* Pre-gloas: checks the single variant. Post-gloas: prefers FULL, falls back to EMPTY.
|
|
255
262
|
*/
|
|
256
|
-
|
|
263
|
+
getNodeIndexByRootAndBlockHash(blockRoot: RootHex, blockHash: RootHex): number | undefined {
|
|
257
264
|
const variantIndices = this.indices.get(blockRoot);
|
|
258
265
|
if (variantIndices === undefined) {
|
|
259
|
-
return
|
|
266
|
+
return undefined;
|
|
260
267
|
}
|
|
261
268
|
|
|
262
269
|
// Pre-Gloas
|
|
263
270
|
if (!Array.isArray(variantIndices)) {
|
|
264
|
-
|
|
265
|
-
return node.executionPayloadBlockHash === blockHash ? node : null;
|
|
271
|
+
return this.nodes[variantIndices].executionPayloadBlockHash === blockHash ? variantIndices : undefined;
|
|
266
272
|
}
|
|
267
273
|
|
|
268
|
-
// Post-Gloas,
|
|
274
|
+
// Post-Gloas, prefer FULL then EMPTY
|
|
269
275
|
const fullNodeIndex = variantIndices[PayloadStatus.FULL];
|
|
270
|
-
if (fullNodeIndex !== undefined) {
|
|
271
|
-
|
|
272
|
-
if (fullNode && fullNode.executionPayloadBlockHash === blockHash) {
|
|
273
|
-
return fullNode;
|
|
274
|
-
}
|
|
276
|
+
if (fullNodeIndex !== undefined && this.nodes[fullNodeIndex].executionPayloadBlockHash === blockHash) {
|
|
277
|
+
return fullNodeIndex;
|
|
275
278
|
}
|
|
276
279
|
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
279
|
-
return
|
|
280
|
+
const emptyNodeIndex = variantIndices[PayloadStatus.EMPTY];
|
|
281
|
+
if (this.nodes[emptyNodeIndex].executionPayloadBlockHash === blockHash) {
|
|
282
|
+
return emptyNodeIndex;
|
|
280
283
|
}
|
|
281
284
|
|
|
282
285
|
// PENDING is the same to EMPTY so not likely we can return it
|
|
283
286
|
// also it's only specific for fork-choice
|
|
284
287
|
|
|
285
|
-
return
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Returns an EMPTY or FULL `ProtoBlock` that has matching block root and block hash
|
|
293
|
+
*/
|
|
294
|
+
getBlockHexAndBlockHash(blockRoot: RootHex, blockHash: RootHex): ProtoBlock | null {
|
|
295
|
+
const idx = this.getNodeIndexByRootAndBlockHash(blockRoot, blockHash);
|
|
296
|
+
return idx !== undefined ? this.nodes[idx] : null;
|
|
286
297
|
}
|
|
287
298
|
|
|
288
299
|
/**
|
|
@@ -354,9 +365,15 @@ export class ProtoArray {
|
|
|
354
365
|
continue;
|
|
355
366
|
}
|
|
356
367
|
|
|
357
|
-
|
|
368
|
+
// For Gloas blocks, PENDING/EMPTY/FULL all share the same blockRoot.
|
|
369
|
+
// Only apply proposer boost to PENDING (for Gloas) or FULL (for pre-Gloas) — to avoid
|
|
370
|
+
// double-counting the boost across variants during delta back-propagation, and to keep
|
|
371
|
+
// the boost neutral with respect to EMPTY vs FULL selection.
|
|
372
|
+
const isBoostVariant = isGloasBlock(node) ? node.payloadStatus === PayloadStatus.PENDING : true; // pre-Gloas has only FULL, always boost
|
|
373
|
+
const currentBoost =
|
|
374
|
+
proposerBoost && proposerBoost.root === node.blockRoot && isBoostVariant ? proposerBoost.score : 0;
|
|
358
375
|
const previousBoost =
|
|
359
|
-
this.previousProposerBoost && this.previousProposerBoost.root === node.blockRoot
|
|
376
|
+
this.previousProposerBoost && this.previousProposerBoost.root === node.blockRoot && isBoostVariant
|
|
360
377
|
? this.previousProposerBoost.score
|
|
361
378
|
: 0;
|
|
362
379
|
|
|
@@ -541,9 +558,9 @@ export class ProtoArray {
|
|
|
541
558
|
currentSlot: Slot,
|
|
542
559
|
executionPayloadBlockHash: RootHex,
|
|
543
560
|
executionPayloadNumber: number,
|
|
544
|
-
executionPayloadStateRoot: RootHex,
|
|
545
561
|
proposerBoostRoot: RootHex | null,
|
|
546
|
-
executionStatus: PayloadExecutionStatus
|
|
562
|
+
executionStatus: PayloadExecutionStatus,
|
|
563
|
+
dataAvailabilityStatus: DataAvailabilityStatus
|
|
547
564
|
): void {
|
|
548
565
|
// First check if block exists
|
|
549
566
|
const variants = this.indices.get(blockRoot);
|
|
@@ -585,7 +602,7 @@ export class ProtoArray {
|
|
|
585
602
|
});
|
|
586
603
|
}
|
|
587
604
|
|
|
588
|
-
// Create FULL variant as a child of PENDING (sibling to EMPTY)
|
|
605
|
+
// Create FULL variant as a child of PENDING (sibling to EMPTY).
|
|
589
606
|
const fullNode: ProtoNode = {
|
|
590
607
|
...pendingNode,
|
|
591
608
|
parent: pendingIndex, // Points to own PENDING (same as EMPTY)
|
|
@@ -593,11 +610,10 @@ export class ProtoArray {
|
|
|
593
610
|
weight: 0,
|
|
594
611
|
bestChild: undefined,
|
|
595
612
|
bestDescendant: undefined,
|
|
596
|
-
// TODO GLOAS: handle optimistic sync
|
|
597
613
|
executionStatus,
|
|
598
614
|
executionPayloadBlockHash,
|
|
599
615
|
executionPayloadNumber,
|
|
600
|
-
|
|
616
|
+
dataAvailabilityStatus,
|
|
601
617
|
};
|
|
602
618
|
|
|
603
619
|
const fullIndex = this.nodes.length;
|
|
@@ -606,6 +622,12 @@ export class ProtoArray {
|
|
|
606
622
|
// Add FULL variant to the indices array
|
|
607
623
|
variants[PayloadStatus.FULL] = fullIndex;
|
|
608
624
|
|
|
625
|
+
if (executionStatus === ExecutionStatus.Valid) {
|
|
626
|
+
// Walk up from FULL's parent (its own PENDING). FULL is already Valid; the loop breaks
|
|
627
|
+
// immediately if we start at FULL. Same pattern as pre-gloas onBlock at line ~533.
|
|
628
|
+
this.propagateValidExecutionStatusByIndex(pendingIndex);
|
|
629
|
+
}
|
|
630
|
+
|
|
609
631
|
// Update bestChild for PENDING node (may now prefer FULL over EMPTY)
|
|
610
632
|
this.maybeUpdateBestChildAndDescendant(pendingIndex, fullIndex, currentSlot, proposerBoostRoot);
|
|
611
633
|
}
|
|
@@ -634,6 +656,16 @@ export class ProtoArray {
|
|
|
634
656
|
}
|
|
635
657
|
}
|
|
636
658
|
|
|
659
|
+
getPTCVotes(blockRootHex: RootHex): BitArray | null {
|
|
660
|
+
const votes = this.ptcVotes.get(blockRootHex);
|
|
661
|
+
if (votes === undefined) {
|
|
662
|
+
// Block not found or not a Gloas block
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return votes;
|
|
667
|
+
}
|
|
668
|
+
|
|
637
669
|
/**
|
|
638
670
|
* Check if execution payload for a block is timely
|
|
639
671
|
* Spec: gloas/fork-choice.md#new-is_payload_timely
|
|
@@ -676,7 +708,7 @@ export class ProtoArray {
|
|
|
676
708
|
* Determine if we should extend the payload (prefer FULL over EMPTY)
|
|
677
709
|
* Spec: gloas/fork-choice.md#new-should_extend_payload
|
|
678
710
|
*
|
|
679
|
-
* Returns true if:
|
|
711
|
+
* Returns true if payload is verified (FULL variant exists) AND:
|
|
680
712
|
* 1. Payload is timely, OR
|
|
681
713
|
* 2. No proposer boost root (empty/zero hash), OR
|
|
682
714
|
* 3. Proposer boost root's parent is not this block, OR
|
|
@@ -686,6 +718,10 @@ export class ProtoArray {
|
|
|
686
718
|
* @param proposerBoostRoot - Current proposer boost root (from ForkChoice)
|
|
687
719
|
*/
|
|
688
720
|
shouldExtendPayload(blockRoot: RootHex, proposerBoostRoot: RootHex | null): boolean {
|
|
721
|
+
if (!this.hasPayload(blockRoot)) {
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
|
|
689
725
|
// Condition 1: Payload is timely
|
|
690
726
|
if (this.isPayloadTimely(blockRoot)) {
|
|
691
727
|
return true;
|
|
@@ -766,11 +802,15 @@ export class ProtoArray {
|
|
|
766
802
|
// Mark chain ii) as Invalid if LVH is found and non null, else only invalidate invalid_payload
|
|
767
803
|
// if its in fcU.
|
|
768
804
|
//
|
|
769
|
-
const {invalidateFromParentBlockRoot, latestValidExecHash} = execResponse;
|
|
770
|
-
|
|
771
|
-
|
|
805
|
+
const {invalidateFromParentBlockRoot, invalidateFromParentBlockHash, latestValidExecHash} = execResponse;
|
|
806
|
+
const invalidateFromParentIndex = this.getNodeIndexByRootAndBlockHash(
|
|
807
|
+
invalidateFromParentBlockRoot,
|
|
808
|
+
invalidateFromParentBlockHash
|
|
809
|
+
);
|
|
772
810
|
if (invalidateFromParentIndex === undefined) {
|
|
773
|
-
throw Error(
|
|
811
|
+
throw Error(
|
|
812
|
+
`Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} invalidateFromParentBlockHash=${invalidateFromParentBlockHash} in forkChoice`
|
|
813
|
+
);
|
|
774
814
|
}
|
|
775
815
|
const latestValidHashIndex =
|
|
776
816
|
latestValidExecHash !== null ? this.getNodeIndexFromLVH(latestValidExecHash, invalidateFromParentIndex) : null;
|
|
@@ -806,12 +846,6 @@ export class ProtoArray {
|
|
|
806
846
|
if (node.executionStatus === ExecutionStatus.PreMerge || node.executionStatus === ExecutionStatus.Valid) {
|
|
807
847
|
break;
|
|
808
848
|
}
|
|
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
849
|
this.validateNodeByIndex(nodeIndex);
|
|
816
850
|
nodeIndex = node.parent;
|
|
817
851
|
}
|
|
@@ -909,6 +943,13 @@ export class ProtoArray {
|
|
|
909
943
|
invalidNode.executionStatus = ExecutionStatus.Invalid;
|
|
910
944
|
invalidNode.bestChild = undefined;
|
|
911
945
|
invalidNode.bestDescendant = undefined;
|
|
946
|
+
// Gloas: PENDING and sibling EMPTY share chain status, flip together
|
|
947
|
+
if (invalidNode.payloadStatus === PayloadStatus.PENDING) {
|
|
948
|
+
const variants = this.indices.get(invalidNode.blockRoot);
|
|
949
|
+
if (Array.isArray(variants)) {
|
|
950
|
+
this.invalidateNodeByIndex(variants[PayloadStatus.EMPTY]);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
912
953
|
|
|
913
954
|
return invalidNode;
|
|
914
955
|
}
|
|
@@ -930,6 +971,13 @@ export class ProtoArray {
|
|
|
930
971
|
if (validNode.executionStatus === ExecutionStatus.Syncing) {
|
|
931
972
|
validNode.executionStatus = ExecutionStatus.Valid;
|
|
932
973
|
}
|
|
974
|
+
// Gloas: PENDING and sibling EMPTY share chain status, flip together
|
|
975
|
+
if (validNode.payloadStatus === PayloadStatus.PENDING) {
|
|
976
|
+
const variants = this.indices.get(validNode.blockRoot);
|
|
977
|
+
if (Array.isArray(variants)) {
|
|
978
|
+
this.validateNodeByIndex(variants[PayloadStatus.EMPTY]);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
933
981
|
return validNode;
|
|
934
982
|
}
|
|
935
983
|
|
|
@@ -1478,9 +1526,16 @@ export class ProtoArray {
|
|
|
1478
1526
|
*/
|
|
1479
1527
|
private getParentNodeIndex(node: ProtoNode): number | undefined {
|
|
1480
1528
|
if (isGloasBlock(node)) {
|
|
1481
|
-
//
|
|
1482
|
-
|
|
1483
|
-
|
|
1529
|
+
// Traversal may reach the finalized ProtoBlock, should not throw error in that case
|
|
1530
|
+
try {
|
|
1531
|
+
const parentPayloadStatus = this.getParentPayloadStatus(node);
|
|
1532
|
+
return this.getNodeIndexByRootAndStatus(node.parentRoot, parentPayloadStatus);
|
|
1533
|
+
} catch (e) {
|
|
1534
|
+
if (e instanceof ProtoArrayError && e.type.code === ProtoArrayErrorCode.UNKNOWN_PARENT_BLOCK) {
|
|
1535
|
+
return undefined;
|
|
1536
|
+
}
|
|
1537
|
+
throw e;
|
|
1538
|
+
}
|
|
1484
1539
|
}
|
|
1485
1540
|
// Simple parent traversal for pre-Gloas blocks (includes fork transition)
|
|
1486
1541
|
return node.parent;
|
|
@@ -1634,10 +1689,9 @@ export class ProtoArray {
|
|
|
1634
1689
|
const ancestors: ProtoNode[] = [];
|
|
1635
1690
|
const nonAncestors: ProtoNode[] = [];
|
|
1636
1691
|
|
|
1637
|
-
//
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
}
|
|
1692
|
+
// caller of this method may pass default status
|
|
1693
|
+
// this is the only node that we accept PENDING
|
|
1694
|
+
ancestors.push(node);
|
|
1641
1695
|
|
|
1642
1696
|
let nodeIndex = startIndex;
|
|
1643
1697
|
while (node.parent !== undefined) {
|