@lodestar/fork-choice 1.41.0-dev.afd446235e → 1.41.0-dev.bb273175f2
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 +9 -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 +74 -18
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +302 -117
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +53 -20
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/forkChoice/interface.js.map +1 -1
- package/lib/forkChoice/store.d.ts +40 -16
- package/lib/forkChoice/store.d.ts.map +1 -1
- package/lib/forkChoice/store.js +22 -4
- package/lib/forkChoice/store.js.map +1 -1
- package/lib/index.d.ts +4 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/protoArray/computeDeltas.d.ts.map +1 -1
- package/lib/protoArray/computeDeltas.js +3 -0
- package/lib/protoArray/computeDeltas.js.map +1 -1
- package/lib/protoArray/errors.d.ts +15 -2
- package/lib/protoArray/errors.d.ts.map +1 -1
- package/lib/protoArray/errors.js +3 -0
- package/lib/protoArray/errors.js.map +1 -1
- package/lib/protoArray/interface.d.ts +33 -3
- package/lib/protoArray/interface.d.ts.map +1 -1
- package/lib/protoArray/interface.js +28 -0
- package/lib/protoArray/interface.js.map +1 -1
- package/lib/protoArray/protoArray.d.ts +217 -22
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +754 -133
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +7 -7
- package/src/forkChoice/errors.ts +7 -2
- package/src/forkChoice/forkChoice.ts +387 -127
- package/src/forkChoice/interface.ts +72 -20
- package/src/forkChoice/store.ts +52 -20
- package/src/index.ts +10 -2
- package/src/protoArray/computeDeltas.ts +6 -0
- package/src/protoArray/errors.ts +7 -1
- package/src/protoArray/interface.ts +48 -3
- package/src/protoArray/protoArray.ts +886 -134
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT } from "@lodestar/params";
|
|
1
|
+
import { ForkSeq, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT } from "@lodestar/params";
|
|
2
2
|
import { DataAvailabilityStatus, ZERO_HASH, computeEpochAtSlot, computeSlotsSinceEpochStart, computeStartSlotAtEpoch, getAttesterSlashableIndices, isExecutionBlockBodyType, isExecutionEnabled, isExecutionStateType, } from "@lodestar/state-transition";
|
|
3
3
|
import { computeUnrealizedCheckpoints } from "@lodestar/state-transition/epoch";
|
|
4
4
|
import { isGloasBeaconBlock, ssz, } from "@lodestar/types";
|
|
5
5
|
import { MapDef, fromHex, toRootHex } from "@lodestar/utils";
|
|
6
6
|
import { computeDeltas } from "../protoArray/computeDeltas.js";
|
|
7
7
|
import { ProtoArrayError, ProtoArrayErrorCode } from "../protoArray/errors.js";
|
|
8
|
-
import { ExecutionStatus, HEX_ZERO_HASH, NULL_VOTE_INDEX, } from "../protoArray/interface.js";
|
|
8
|
+
import { ExecutionStatus, HEX_ZERO_HASH, NULL_VOTE_INDEX, PayloadStatus, isGloasBlock, } from "../protoArray/interface.js";
|
|
9
9
|
import { ForkChoiceError, ForkChoiceErrorCode, InvalidAttestationCode, InvalidBlockCode } from "./errors.js";
|
|
10
10
|
import { AncestorStatus, NotReorgedReason, } from "./interface.js";
|
|
11
|
-
import {
|
|
11
|
+
import { toCheckpointWithPayload } from "./store.js";
|
|
12
12
|
export var UpdateHeadOpt;
|
|
13
13
|
(function (UpdateHeadOpt) {
|
|
14
14
|
UpdateHeadOpt["GetCanonicalHead"] = "getCanonicalHead";
|
|
@@ -16,7 +16,7 @@ export var UpdateHeadOpt;
|
|
|
16
16
|
UpdateHeadOpt["GetPredictedProposerHead"] = "getPredictedProposerHead";
|
|
17
17
|
})(UpdateHeadOpt || (UpdateHeadOpt = {}));
|
|
18
18
|
// the initial vote epoch for all validators
|
|
19
|
-
const
|
|
19
|
+
const INIT_VOTE_SLOT = 0;
|
|
20
20
|
/**
|
|
21
21
|
* Provides an implementation of "Ethereum Consensus -- Beacon Chain Fork Choice":
|
|
22
22
|
*
|
|
@@ -44,16 +44,26 @@ export class ForkChoice {
|
|
|
44
44
|
irrecoverableError;
|
|
45
45
|
/**
|
|
46
46
|
* Votes currently tracked in the protoArray. Instead of tracking a VoteTracker of currentIndex, nextIndex and epoch,
|
|
47
|
-
* we decompose the struct and track them in
|
|
47
|
+
* we decompose the struct and track them in separate arrays for performance reason.
|
|
48
|
+
*
|
|
49
|
+
* For Gloas (ePBS), LatestMessage tracks slot instead of epoch and includes payload_present flag.
|
|
50
|
+
* Spec: gloas/fork-choice.md#modified-latestmessage
|
|
51
|
+
*
|
|
52
|
+
* IMPORTANT: voteCurrentIndices and voteNextIndices point to the EXACT variant node index.
|
|
53
|
+
* The payload status is encoded in the node index itself (different variants have different indices).
|
|
54
|
+
* For example, if a validator votes for the EMPTY variant, voteNextIndices[i] points to that specific EMPTY node.
|
|
48
55
|
*/
|
|
49
56
|
voteCurrentIndices;
|
|
50
57
|
voteNextIndices;
|
|
51
|
-
|
|
58
|
+
voteNextSlots;
|
|
52
59
|
/**
|
|
53
60
|
* Attestations that arrived at the current slot and must be queued for later processing.
|
|
54
61
|
* NOT currently tracked in the protoArray
|
|
62
|
+
*
|
|
63
|
+
* Modified for Gloas to track PayloadStatus per validator.
|
|
64
|
+
* Maps: Slot -> BlockRoot -> ValidatorIndex -> PayloadStatus
|
|
55
65
|
*/
|
|
56
|
-
queuedAttestations = new MapDef(() => new MapDef(() => new
|
|
66
|
+
queuedAttestations = new MapDef(() => new MapDef(() => new Map()));
|
|
57
67
|
/**
|
|
58
68
|
* It's inconsistent to count number of queued attestations at different intervals of slot.
|
|
59
69
|
* Instead of that, we count number of queued attestations at the previous slot.
|
|
@@ -93,11 +103,11 @@ export class ForkChoice {
|
|
|
93
103
|
this.voteCurrentIndices = new Array(validatorCount).fill(NULL_VOTE_INDEX);
|
|
94
104
|
this.voteNextIndices = new Array(validatorCount).fill(NULL_VOTE_INDEX);
|
|
95
105
|
// when compute deltas, we ignore epoch if voteNextIndex is NULL_VOTE_INDEX anyway
|
|
96
|
-
this.
|
|
106
|
+
this.voteNextSlots = new Array(validatorCount).fill(0);
|
|
97
107
|
this.head = this.updateHead();
|
|
98
108
|
this.balances = this.fcStore.justified.balances;
|
|
99
109
|
metrics?.forkChoice.votes.addCollect(() => {
|
|
100
|
-
metrics.forkChoice.votes.set(this.
|
|
110
|
+
metrics.forkChoice.votes.set(this.voteNextSlots.length);
|
|
101
111
|
metrics.forkChoice.queuedAttestations.set(this.queuedAttestationsPreviousSlot);
|
|
102
112
|
metrics.forkChoice.validatedAttestationDatas.set(this.validatedAttestationDatas.size);
|
|
103
113
|
metrics.forkChoice.balancesLength.set(this.balances.length);
|
|
@@ -163,8 +173,7 @@ export class ForkChoice {
|
|
|
163
173
|
// Return true if the given block passes all criteria to be re-orged out
|
|
164
174
|
// Return false otherwise.
|
|
165
175
|
// Note when proposer boost reorg is disabled, it always returns false
|
|
166
|
-
shouldOverrideForkChoiceUpdate(
|
|
167
|
-
const headBlock = this.getBlockHex(blockRoot);
|
|
176
|
+
shouldOverrideForkChoiceUpdate(headBlock, secFromSlot, currentSlot) {
|
|
168
177
|
if (headBlock === null) {
|
|
169
178
|
// should not happen because this block just got imported. Fall back to no-reorg.
|
|
170
179
|
return { shouldOverrideFcu: false, reason: NotReorgedReason.HeadBlockNotAvailable };
|
|
@@ -179,7 +188,7 @@ export class ForkChoice {
|
|
|
179
188
|
});
|
|
180
189
|
return { shouldOverrideFcu: false, reason: NotReorgedReason.ProposerBoostReorgDisabled };
|
|
181
190
|
}
|
|
182
|
-
const parentBlock = this.protoArray.getBlock(headBlock.parentRoot);
|
|
191
|
+
const parentBlock = this.protoArray.getBlock(headBlock.parentRoot, this.protoArray.getParentPayloadStatus(headBlock));
|
|
183
192
|
const proposalSlot = headBlock.slot + 1;
|
|
184
193
|
// No reorg if parentBlock isn't available
|
|
185
194
|
if (parentBlock === undefined) {
|
|
@@ -194,7 +203,10 @@ export class ForkChoice {
|
|
|
194
203
|
if (!currentTimeOk) {
|
|
195
204
|
return { shouldOverrideFcu: false, reason: NotReorgedReason.ReorgMoreThanOneSlot };
|
|
196
205
|
}
|
|
197
|
-
this.logger?.verbose("Block is weak. Should override forkchoice update", {
|
|
206
|
+
this.logger?.verbose("Block is weak. Should override forkchoice update", {
|
|
207
|
+
blockRoot: headBlock.blockRoot,
|
|
208
|
+
slot: currentSlot,
|
|
209
|
+
});
|
|
198
210
|
return { shouldOverrideFcu: true, parentBlock };
|
|
199
211
|
}
|
|
200
212
|
/**
|
|
@@ -225,7 +237,7 @@ export class ForkChoice {
|
|
|
225
237
|
return headBlock;
|
|
226
238
|
}
|
|
227
239
|
const blockRoot = headBlock.blockRoot;
|
|
228
|
-
const result = this.shouldOverrideForkChoiceUpdate(
|
|
240
|
+
const result = this.shouldOverrideForkChoiceUpdate(headBlock, secFromSlot, currentSlot);
|
|
229
241
|
if (result.shouldOverrideFcu) {
|
|
230
242
|
this.logger?.verbose("Current head is weak. Predicting next block to be built on parent of head.", {
|
|
231
243
|
slot: currentSlot,
|
|
@@ -262,7 +274,7 @@ export class ForkChoice {
|
|
|
262
274
|
});
|
|
263
275
|
return { proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ProposerBoostReorgDisabled };
|
|
264
276
|
}
|
|
265
|
-
const parentBlock = this.protoArray.getBlock(headBlock.parentRoot);
|
|
277
|
+
const parentBlock = this.protoArray.getBlock(headBlock.parentRoot, this.protoArray.getParentPayloadStatus(headBlock));
|
|
266
278
|
// No reorg if parentBlock isn't available
|
|
267
279
|
if (parentBlock === undefined) {
|
|
268
280
|
return { proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ParentBlockNotAvailable };
|
|
@@ -292,7 +304,7 @@ export class ForkChoice {
|
|
|
292
304
|
slotsPerEpoch: SLOTS_PER_EPOCH,
|
|
293
305
|
committeePercent: this.config.REORG_HEAD_WEIGHT_THRESHOLD,
|
|
294
306
|
});
|
|
295
|
-
const headNode = this.protoArray.getNode(headBlock.blockRoot);
|
|
307
|
+
const headNode = this.protoArray.getNode(headBlock.blockRoot, headBlock.payloadStatus);
|
|
296
308
|
// If headNode is unavailable, give up reorg
|
|
297
309
|
if (headNode === undefined || headNode.weight >= reorgThreshold) {
|
|
298
310
|
return { proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.HeadBlockNotWeak };
|
|
@@ -303,7 +315,7 @@ export class ForkChoice {
|
|
|
303
315
|
slotsPerEpoch: SLOTS_PER_EPOCH,
|
|
304
316
|
committeePercent: this.config.REORG_PARENT_WEIGHT_THRESHOLD,
|
|
305
317
|
});
|
|
306
|
-
const parentNode = this.protoArray.getNode(parentBlock.blockRoot);
|
|
318
|
+
const parentNode = this.protoArray.getNode(parentBlock.blockRoot, parentBlock.payloadStatus);
|
|
307
319
|
// If parentNode is unavailable, give up reorg
|
|
308
320
|
if (parentNode === undefined || parentNode.weight <= parentThreshold) {
|
|
309
321
|
return { proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ParentBlockNotStrong };
|
|
@@ -375,22 +387,9 @@ export class ForkChoice {
|
|
|
375
387
|
finalizedRoot: this.fcStore.finalizedCheckpoint.rootHex,
|
|
376
388
|
currentSlot,
|
|
377
389
|
});
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
throw new ForkChoiceError({
|
|
382
|
-
code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK,
|
|
383
|
-
root: headRoot,
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
const headNode = this.protoArray.nodes[headIndex];
|
|
387
|
-
if (headNode === undefined) {
|
|
388
|
-
throw new ForkChoiceError({
|
|
389
|
-
code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK,
|
|
390
|
-
root: headRoot,
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
this.head = headNode;
|
|
390
|
+
// findHead returns the ProtoNode representing the head
|
|
391
|
+
const head = this.protoArray.findHead(this.fcStore.justified.checkpoint.rootHex, currentSlot);
|
|
392
|
+
this.head = head;
|
|
394
393
|
return this.head;
|
|
395
394
|
}
|
|
396
395
|
/**
|
|
@@ -437,14 +436,18 @@ export class ForkChoice {
|
|
|
437
436
|
onBlock(block, state, blockDelaySec, currentSlot, executionStatus, dataAvailabilityStatus) {
|
|
438
437
|
const { parentRoot, slot } = block;
|
|
439
438
|
const parentRootHex = toRootHex(parentRoot);
|
|
440
|
-
// Parent block must be known
|
|
441
|
-
const
|
|
439
|
+
// Parent block must be known because state_transition would have failed otherwise.
|
|
440
|
+
const parentHashHex = isGloasBeaconBlock(block)
|
|
441
|
+
? toRootHex(block.body.signedExecutionPayloadBid.message.parentBlockHash)
|
|
442
|
+
: null;
|
|
443
|
+
const parentBlock = this.protoArray.getParent(parentRootHex, parentHashHex);
|
|
442
444
|
if (!parentBlock) {
|
|
443
445
|
throw new ForkChoiceError({
|
|
444
446
|
code: ForkChoiceErrorCode.INVALID_BLOCK,
|
|
445
447
|
err: {
|
|
446
448
|
code: InvalidBlockCode.UNKNOWN_PARENT,
|
|
447
449
|
root: parentRootHex,
|
|
450
|
+
hash: parentHashHex,
|
|
448
451
|
},
|
|
449
452
|
});
|
|
450
453
|
}
|
|
@@ -476,15 +479,16 @@ export class ForkChoice {
|
|
|
476
479
|
});
|
|
477
480
|
}
|
|
478
481
|
// Check block is a descendant of the finalized block at the checkpoint finalized slot.
|
|
479
|
-
const
|
|
480
|
-
const
|
|
481
|
-
if (
|
|
482
|
+
const blockAncestorNode = this.getAncestor(parentRootHex, finalizedSlot);
|
|
483
|
+
const fcStoreFinalized = this.fcStore.finalizedCheckpoint;
|
|
484
|
+
if (blockAncestorNode.blockRoot !== fcStoreFinalized.rootHex ||
|
|
485
|
+
blockAncestorNode.payloadStatus !== fcStoreFinalized.payloadStatus) {
|
|
482
486
|
throw new ForkChoiceError({
|
|
483
487
|
code: ForkChoiceErrorCode.INVALID_BLOCK,
|
|
484
488
|
err: {
|
|
485
489
|
code: InvalidBlockCode.NOT_FINALIZED_DESCENDANT,
|
|
486
|
-
finalizedRoot,
|
|
487
|
-
blockAncestor:
|
|
490
|
+
finalizedRoot: fcStoreFinalized.rootHex,
|
|
491
|
+
blockAncestor: blockAncestorNode.blockRoot,
|
|
488
492
|
},
|
|
489
493
|
});
|
|
490
494
|
}
|
|
@@ -499,9 +503,13 @@ export class ForkChoice {
|
|
|
499
503
|
this.proposerBoostRoot === null) {
|
|
500
504
|
this.proposerBoostRoot = blockRootHex;
|
|
501
505
|
}
|
|
502
|
-
|
|
503
|
-
const
|
|
506
|
+
// Get justified checkpoint with payload status for Gloas
|
|
507
|
+
const justifiedPayloadStatus = getCheckpointPayloadStatus(state, state.currentJustifiedCheckpoint.epoch);
|
|
508
|
+
const justifiedCheckpoint = toCheckpointWithPayload(state.currentJustifiedCheckpoint, justifiedPayloadStatus);
|
|
504
509
|
const stateJustifiedEpoch = justifiedCheckpoint.epoch;
|
|
510
|
+
// Get finalized checkpoint with payload status for Gloas
|
|
511
|
+
const finalizedPayloadStatus = getCheckpointPayloadStatus(state, state.finalizedCheckpoint.epoch);
|
|
512
|
+
const finalizedCheckpoint = toCheckpointWithPayload(state.finalizedCheckpoint, finalizedPayloadStatus);
|
|
505
513
|
// Justified balances for `justifiedCheckpoint` are new to the fork-choice. Compute them on demand only if
|
|
506
514
|
// the justified checkpoint changes
|
|
507
515
|
this.updateCheckpoints(justifiedCheckpoint, finalizedCheckpoint, () => this.fcStore.justifiedBalancesGetter(justifiedCheckpoint, state));
|
|
@@ -524,22 +532,32 @@ export class ForkChoice {
|
|
|
524
532
|
if (parentBlock.unrealizedJustifiedEpoch === blockEpoch &&
|
|
525
533
|
parentBlock.unrealizedFinalizedEpoch + 1 >= blockEpoch) {
|
|
526
534
|
// reuse from parent, happens at 1/3 last blocks of epoch as monitored in mainnet
|
|
535
|
+
// Get payload status for unrealized justified checkpoint
|
|
536
|
+
const unrealizedJustifiedPayloadStatus = getCheckpointPayloadStatus(state, parentBlock.unrealizedJustifiedEpoch);
|
|
527
537
|
unrealizedJustifiedCheckpoint = {
|
|
528
538
|
epoch: parentBlock.unrealizedJustifiedEpoch,
|
|
529
539
|
root: fromHex(parentBlock.unrealizedJustifiedRoot),
|
|
530
540
|
rootHex: parentBlock.unrealizedJustifiedRoot,
|
|
541
|
+
payloadStatus: unrealizedJustifiedPayloadStatus,
|
|
531
542
|
};
|
|
543
|
+
// Get payload status for unrealized finalized checkpoint
|
|
544
|
+
const unrealizedFinalizedPayloadStatus = getCheckpointPayloadStatus(state, parentBlock.unrealizedFinalizedEpoch);
|
|
532
545
|
unrealizedFinalizedCheckpoint = {
|
|
533
546
|
epoch: parentBlock.unrealizedFinalizedEpoch,
|
|
534
547
|
root: fromHex(parentBlock.unrealizedFinalizedRoot),
|
|
535
548
|
rootHex: parentBlock.unrealizedFinalizedRoot,
|
|
549
|
+
payloadStatus: unrealizedFinalizedPayloadStatus,
|
|
536
550
|
};
|
|
537
551
|
}
|
|
538
552
|
else {
|
|
539
553
|
// compute new, happens 2/3 first blocks of epoch as monitored in mainnet
|
|
540
554
|
const unrealized = computeUnrealizedCheckpoints(state);
|
|
541
|
-
|
|
542
|
-
|
|
555
|
+
// Get payload status for unrealized justified checkpoint
|
|
556
|
+
const unrealizedJustifiedPayloadStatus = getCheckpointPayloadStatus(state, unrealized.justifiedCheckpoint.epoch);
|
|
557
|
+
unrealizedJustifiedCheckpoint = toCheckpointWithPayload(unrealized.justifiedCheckpoint, unrealizedJustifiedPayloadStatus);
|
|
558
|
+
// Get payload status for unrealized finalized checkpoint
|
|
559
|
+
const unrealizedFinalizedPayloadStatus = getCheckpointPayloadStatus(state, unrealized.finalizedCheckpoint.epoch);
|
|
560
|
+
unrealizedFinalizedCheckpoint = toCheckpointWithPayload(unrealized.finalizedCheckpoint, unrealizedFinalizedPayloadStatus);
|
|
543
561
|
}
|
|
544
562
|
}
|
|
545
563
|
else {
|
|
@@ -572,29 +590,51 @@ export class ForkChoice {
|
|
|
572
590
|
unrealizedJustifiedRoot: unrealizedJustifiedCheckpoint.rootHex,
|
|
573
591
|
unrealizedFinalizedEpoch: unrealizedFinalizedCheckpoint.epoch,
|
|
574
592
|
unrealizedFinalizedRoot: unrealizedFinalizedCheckpoint.rootHex,
|
|
575
|
-
...(isExecutionBlockBodyType(block.body) && isExecutionStateType(state) && isExecutionEnabled(state, block)
|
|
576
|
-
? {
|
|
577
|
-
executionPayloadBlockHash: toRootHex(block.body.executionPayload.blockHash),
|
|
578
|
-
executionPayloadNumber: block.body.executionPayload.blockNumber,
|
|
579
|
-
executionStatus: this.getPostMergeExecStatus(executionStatus),
|
|
580
|
-
dataAvailabilityStatus,
|
|
581
|
-
}
|
|
582
|
-
: {
|
|
583
|
-
executionPayloadBlockHash: null,
|
|
584
|
-
executionStatus: this.getPreMergeExecStatus(executionStatus),
|
|
585
|
-
dataAvailabilityStatus: this.getPreMergeDataStatus(dataAvailabilityStatus),
|
|
586
|
-
}),
|
|
587
593
|
...(isGloasBeaconBlock(block)
|
|
588
594
|
? {
|
|
589
|
-
|
|
590
|
-
|
|
595
|
+
executionPayloadBlockHash: toRootHex(block.body.signedExecutionPayloadBid.message.parentBlockHash), // post-gloas, we don't know payload hash until we import execution payload. Set to parent payload hash for now
|
|
596
|
+
executionPayloadNumber: (() => {
|
|
597
|
+
// Determine parent's execution payload number based on which variant the block extends
|
|
598
|
+
const parentBlockHashFromBid = toRootHex(block.body.signedExecutionPayloadBid.message.parentBlockHash);
|
|
599
|
+
// If parent is pre-merge, return 0
|
|
600
|
+
if (parentBlock.executionPayloadBlockHash === null) {
|
|
601
|
+
return 0;
|
|
602
|
+
}
|
|
603
|
+
// If parent is pre-Gloas, it only has FULL variant
|
|
604
|
+
if (parentBlock.parentBlockHash === null) {
|
|
605
|
+
return parentBlock.executionPayloadNumber;
|
|
606
|
+
}
|
|
607
|
+
// Parent is Gloas: get the variant that matches the parentBlockHash from bid
|
|
608
|
+
const parentVariant = this.getBlockHexAndBlockHash(parentRootHex, parentBlockHashFromBid);
|
|
609
|
+
if (parentVariant && parentVariant.executionPayloadBlockHash !== null) {
|
|
610
|
+
return parentVariant.executionPayloadNumber;
|
|
611
|
+
}
|
|
612
|
+
// Fallback to parent block's number (we know it's post-merge from check above)
|
|
613
|
+
return parentBlock.executionPayloadNumber;
|
|
614
|
+
})(),
|
|
615
|
+
executionStatus: this.getPostGloasExecStatus(executionStatus),
|
|
616
|
+
dataAvailabilityStatus,
|
|
591
617
|
}
|
|
592
|
-
:
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
618
|
+
: isExecutionBlockBodyType(block.body) && isExecutionStateType(state) && isExecutionEnabled(state, block)
|
|
619
|
+
? {
|
|
620
|
+
executionPayloadBlockHash: toRootHex(block.body.executionPayload.blockHash),
|
|
621
|
+
executionPayloadNumber: block.body.executionPayload.blockNumber,
|
|
622
|
+
executionStatus: this.getPreGloasExecStatus(executionStatus),
|
|
623
|
+
dataAvailabilityStatus,
|
|
624
|
+
}
|
|
625
|
+
: {
|
|
626
|
+
executionPayloadBlockHash: null,
|
|
627
|
+
executionStatus: this.getPreMergeExecStatus(executionStatus),
|
|
628
|
+
dataAvailabilityStatus: this.getPreMergeDataStatus(dataAvailabilityStatus),
|
|
629
|
+
}),
|
|
630
|
+
payloadStatus: isGloasBeaconBlock(block) ? PayloadStatus.PENDING : PayloadStatus.FULL,
|
|
631
|
+
builderIndex: isGloasBeaconBlock(block) ? block.body.signedExecutionPayloadBid.message.builderIndex : null,
|
|
632
|
+
blockHashFromBid: isGloasBeaconBlock(block)
|
|
633
|
+
? toRootHex(block.body.signedExecutionPayloadBid.message.blockHash)
|
|
634
|
+
: null,
|
|
635
|
+
parentBlockHash: parentHashHex,
|
|
596
636
|
};
|
|
597
|
-
this.protoArray.onBlock(protoBlock, currentSlot);
|
|
637
|
+
this.protoArray.onBlock(protoBlock, currentSlot, this.proposerBoostRoot);
|
|
598
638
|
return protoBlock;
|
|
599
639
|
}
|
|
600
640
|
/**
|
|
@@ -637,10 +677,46 @@ export class ForkChoice {
|
|
|
637
677
|
return;
|
|
638
678
|
}
|
|
639
679
|
this.validateOnAttestation(attestation, slot, blockRootHex, targetEpoch, attDataRoot, forceImport);
|
|
680
|
+
// Pre-gloas: payload is always present
|
|
681
|
+
// Post-gloas:
|
|
682
|
+
// - always add weight to PENDING
|
|
683
|
+
// - if message.slot > block.slot, it also add weights to FULL or EMPTY
|
|
684
|
+
let payloadStatus;
|
|
685
|
+
// We need to retrieve block to check if it's Gloas and to compare slot
|
|
686
|
+
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/fork-choice.md#new-is_supporting_vote
|
|
687
|
+
const block = this.getBlockHexDefaultStatus(blockRootHex);
|
|
688
|
+
if (block && isGloasBlock(block)) {
|
|
689
|
+
// Post-Gloas block: determine FULL/EMPTY/PENDING based on slot and committee index
|
|
690
|
+
// If slot > block.slot, we can determine FULL or EMPTY. Else always PENDING
|
|
691
|
+
if (slot > block.slot) {
|
|
692
|
+
if (attestationData.index === 1) {
|
|
693
|
+
payloadStatus = PayloadStatus.FULL;
|
|
694
|
+
}
|
|
695
|
+
else if (attestationData.index === 0) {
|
|
696
|
+
payloadStatus = PayloadStatus.EMPTY;
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
throw new ForkChoiceError({
|
|
700
|
+
code: ForkChoiceErrorCode.INVALID_ATTESTATION,
|
|
701
|
+
err: {
|
|
702
|
+
code: InvalidAttestationCode.INVALID_DATA_INDEX,
|
|
703
|
+
index: attestationData.index,
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
payloadStatus = PayloadStatus.PENDING;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
// Pre-Gloas block or block not found: always FULL
|
|
714
|
+
payloadStatus = PayloadStatus.FULL;
|
|
715
|
+
}
|
|
640
716
|
if (slot < this.fcStore.currentSlot) {
|
|
641
717
|
for (const validatorIndex of attestation.attestingIndices) {
|
|
642
718
|
if (!this.fcStore.equivocatingIndices.has(validatorIndex)) {
|
|
643
|
-
this.addLatestMessage(validatorIndex,
|
|
719
|
+
this.addLatestMessage(validatorIndex, slot, blockRootHex, payloadStatus);
|
|
644
720
|
}
|
|
645
721
|
}
|
|
646
722
|
}
|
|
@@ -652,10 +728,10 @@ export class ForkChoice {
|
|
|
652
728
|
// Delay consideration in the fork choice until their slot is in the past.
|
|
653
729
|
// ```
|
|
654
730
|
const byRoot = this.queuedAttestations.getOrDefault(slot);
|
|
655
|
-
const
|
|
731
|
+
const validatorVotes = byRoot.getOrDefault(blockRootHex);
|
|
656
732
|
for (const validatorIndex of attestation.attestingIndices) {
|
|
657
733
|
if (!this.fcStore.equivocatingIndices.has(validatorIndex)) {
|
|
658
|
-
|
|
734
|
+
validatorVotes.set(validatorIndex, payloadStatus);
|
|
659
735
|
}
|
|
660
736
|
}
|
|
661
737
|
}
|
|
@@ -672,6 +748,22 @@ export class ForkChoice {
|
|
|
672
748
|
this.fcStore.equivocatingIndices.add(validatorIndex);
|
|
673
749
|
}
|
|
674
750
|
}
|
|
751
|
+
/**
|
|
752
|
+
* Process a PTC (Payload Timeliness Committee) message
|
|
753
|
+
* Updates the PTC votes for multiple validators attesting to a block
|
|
754
|
+
* Spec: gloas/fork-choice.md#new-on_payload_attestation_message
|
|
755
|
+
*/
|
|
756
|
+
notifyPtcMessages(blockRoot, ptcIndices, payloadPresent) {
|
|
757
|
+
this.protoArray.notifyPtcMessages(blockRoot, ptcIndices, payloadPresent);
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Notify fork choice that an execution payload has arrived (Gloas fork)
|
|
761
|
+
* Creates the FULL variant of a Gloas block when the payload becomes available
|
|
762
|
+
* Spec: gloas/fork-choice.md#new-on_execution_payload
|
|
763
|
+
*/
|
|
764
|
+
onExecutionPayload(blockRoot, executionPayloadBlockHash, executionPayloadNumber, executionPayloadStateRoot) {
|
|
765
|
+
this.protoArray.onExecutionPayload(blockRoot, this.fcStore.currentSlot, executionPayloadBlockHash, executionPayloadNumber, executionPayloadStateRoot, this.proposerBoostRoot);
|
|
766
|
+
}
|
|
675
767
|
/**
|
|
676
768
|
* Call `onTick` for all slots between `fcStore.getCurrentSlot()` and the provided `currentSlot`.
|
|
677
769
|
* This should only be called once per slot because:
|
|
@@ -701,14 +793,19 @@ export class ForkChoice {
|
|
|
701
793
|
return this.hasBlockHex(toRootHex(blockRoot));
|
|
702
794
|
}
|
|
703
795
|
/** Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root. */
|
|
704
|
-
getBlock(blockRoot) {
|
|
705
|
-
return this.getBlockHex(toRootHex(blockRoot));
|
|
796
|
+
getBlock(blockRoot, payloadStatus) {
|
|
797
|
+
return this.getBlockHex(toRootHex(blockRoot), payloadStatus);
|
|
798
|
+
}
|
|
799
|
+
getBlockDefaultStatus(blockRoot) {
|
|
800
|
+
return this.getBlockHexDefaultStatus(toRootHex(blockRoot));
|
|
706
801
|
}
|
|
707
802
|
/**
|
|
708
803
|
* Returns `true` if the block is known **and** a descendant of the finalized root.
|
|
804
|
+
* Uses default variant (PENDING for Gloas, FULL for pre-Gloas).
|
|
709
805
|
*/
|
|
710
806
|
hasBlockHex(blockRoot) {
|
|
711
|
-
const
|
|
807
|
+
const defaultStatus = this.protoArray.getDefaultVariant(blockRoot);
|
|
808
|
+
const node = defaultStatus !== undefined ? this.protoArray.getNode(blockRoot, defaultStatus) : undefined;
|
|
712
809
|
if (node === undefined) {
|
|
713
810
|
return false;
|
|
714
811
|
}
|
|
@@ -729,8 +826,8 @@ export class ForkChoice {
|
|
|
729
826
|
/**
|
|
730
827
|
* Returns a MUTABLE `ProtoBlock` if the block is known **and** a descendant of the finalized root.
|
|
731
828
|
*/
|
|
732
|
-
getBlockHex(blockRoot) {
|
|
733
|
-
const node = this.protoArray.getNode(blockRoot);
|
|
829
|
+
getBlockHex(blockRoot, payloadStatus) {
|
|
830
|
+
const node = this.protoArray.getNode(blockRoot, payloadStatus);
|
|
734
831
|
if (!node) {
|
|
735
832
|
return null;
|
|
736
833
|
}
|
|
@@ -741,22 +838,45 @@ export class ForkChoice {
|
|
|
741
838
|
...node,
|
|
742
839
|
};
|
|
743
840
|
}
|
|
841
|
+
/**
|
|
842
|
+
* Returns a `ProtoBlock` with the default variant for the given block root
|
|
843
|
+
* - Pre-Gloas blocks: returns FULL variant (only variant)
|
|
844
|
+
* - Gloas blocks: returns PENDING variant
|
|
845
|
+
*
|
|
846
|
+
* Use this when you need the canonical block reference regardless of payload status.
|
|
847
|
+
* For searching by execution payload hash and variant-specific info, use `getBlockHexAndBlockHash` instead.
|
|
848
|
+
*/
|
|
849
|
+
getBlockHexDefaultStatus(blockRoot) {
|
|
850
|
+
const defaultStatus = this.protoArray.getDefaultVariant(blockRoot);
|
|
851
|
+
if (defaultStatus === undefined) {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
return this.getBlockHex(blockRoot, defaultStatus);
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Returns EMPTY or FULL `ProtoBlock` that has matching block root and block hash
|
|
858
|
+
*/
|
|
859
|
+
getBlockHexAndBlockHash(blockRoot, blockHash) {
|
|
860
|
+
return this.protoArray.getBlockHexAndBlockHash(blockRoot, blockHash);
|
|
861
|
+
}
|
|
744
862
|
getJustifiedBlock() {
|
|
745
|
-
const
|
|
863
|
+
const { rootHex, payloadStatus } = this.fcStore.justified.checkpoint;
|
|
864
|
+
const block = this.getBlockHex(rootHex, payloadStatus);
|
|
746
865
|
if (!block) {
|
|
747
866
|
throw new ForkChoiceError({
|
|
748
867
|
code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK,
|
|
749
|
-
root:
|
|
868
|
+
root: rootHex,
|
|
750
869
|
});
|
|
751
870
|
}
|
|
752
871
|
return block;
|
|
753
872
|
}
|
|
754
873
|
getFinalizedBlock() {
|
|
755
|
-
const
|
|
874
|
+
const { rootHex, payloadStatus } = this.fcStore.finalizedCheckpoint;
|
|
875
|
+
const block = this.getBlockHex(rootHex, payloadStatus);
|
|
756
876
|
if (!block) {
|
|
757
877
|
throw new ForkChoiceError({
|
|
758
878
|
code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK,
|
|
759
|
-
root:
|
|
879
|
+
root: rootHex,
|
|
760
880
|
});
|
|
761
881
|
}
|
|
762
882
|
return block;
|
|
@@ -771,8 +891,8 @@ export class ForkChoice {
|
|
|
771
891
|
* Always returns `false` if either input roots are unknown.
|
|
772
892
|
* Still returns `true` if `ancestorRoot===descendantRoot` (and the roots are known)
|
|
773
893
|
*/
|
|
774
|
-
isDescendant(ancestorRoot, descendantRoot) {
|
|
775
|
-
return this.protoArray.isDescendant(ancestorRoot, descendantRoot);
|
|
894
|
+
isDescendant(ancestorRoot, ancestorPayloadStatus, descendantRoot, descendantPayloadStatus) {
|
|
895
|
+
return this.protoArray.isDescendant(ancestorRoot, ancestorPayloadStatus, descendantRoot, descendantPayloadStatus);
|
|
776
896
|
}
|
|
777
897
|
/**
|
|
778
898
|
* All indices in votes are relative to proto array so always keep it up to date
|
|
@@ -780,7 +900,7 @@ export class ForkChoice {
|
|
|
780
900
|
prune(finalizedRoot) {
|
|
781
901
|
const prunedNodes = this.protoArray.maybePrune(finalizedRoot);
|
|
782
902
|
const prunedCount = prunedNodes.length;
|
|
783
|
-
for (let i = 0; i < this.
|
|
903
|
+
for (let i = 0; i < this.voteNextSlots.length; i++) {
|
|
784
904
|
const currentIndex = this.voteCurrentIndices[i];
|
|
785
905
|
if (currentIndex !== NULL_VOTE_INDEX) {
|
|
786
906
|
if (currentIndex >= prunedCount) {
|
|
@@ -811,35 +931,47 @@ export class ForkChoice {
|
|
|
811
931
|
* Iterates backwards through block summaries, starting from a block root.
|
|
812
932
|
* Return only the non-finalized blocks.
|
|
813
933
|
*/
|
|
814
|
-
iterateAncestorBlocks(blockRoot) {
|
|
815
|
-
return this.protoArray.iterateAncestorNodes(blockRoot);
|
|
934
|
+
iterateAncestorBlocks(blockRoot, payloadStatus) {
|
|
935
|
+
return this.protoArray.iterateAncestorNodes(blockRoot, payloadStatus);
|
|
816
936
|
}
|
|
817
937
|
/**
|
|
818
938
|
* Returns all blocks backwards starting from a block root.
|
|
819
939
|
* Return only the non-finalized blocks.
|
|
820
940
|
*/
|
|
821
|
-
getAllAncestorBlocks(blockRoot) {
|
|
822
|
-
const blocks = this.protoArray.getAllAncestorNodes(blockRoot);
|
|
941
|
+
getAllAncestorBlocks(blockRoot, payloadStatus) {
|
|
942
|
+
const blocks = this.protoArray.getAllAncestorNodes(blockRoot, payloadStatus);
|
|
823
943
|
// the last node is the previous finalized one, it's there to check onBlock finalized checkpoint only.
|
|
824
944
|
return blocks.slice(0, blocks.length - 1);
|
|
825
945
|
}
|
|
826
946
|
/**
|
|
827
947
|
* The same to iterateAncestorBlocks but this gets non-ancestor nodes instead of ancestor nodes.
|
|
828
948
|
*/
|
|
829
|
-
getAllNonAncestorBlocks(blockRoot) {
|
|
830
|
-
return this.protoArray.getAllNonAncestorNodes(blockRoot);
|
|
949
|
+
getAllNonAncestorBlocks(blockRoot, payloadStatus) {
|
|
950
|
+
return this.protoArray.getAllNonAncestorNodes(blockRoot, payloadStatus);
|
|
831
951
|
}
|
|
832
952
|
/**
|
|
833
953
|
* Returns both ancestor and non-ancestor blocks in a single traversal.
|
|
834
954
|
*/
|
|
835
|
-
getAllAncestorAndNonAncestorBlocks(blockRoot) {
|
|
836
|
-
const { ancestors, nonAncestors } = this.protoArray.getAllAncestorAndNonAncestorNodes(blockRoot);
|
|
955
|
+
getAllAncestorAndNonAncestorBlocks(blockRoot, payloadStatus) {
|
|
956
|
+
const { ancestors, nonAncestors } = this.protoArray.getAllAncestorAndNonAncestorNodes(blockRoot, payloadStatus);
|
|
837
957
|
return {
|
|
838
958
|
// the last node is the previous finalized one, it's there to check onBlock finalized checkpoint only.
|
|
839
959
|
ancestors: ancestors.slice(0, ancestors.length - 1),
|
|
840
960
|
nonAncestors,
|
|
841
961
|
};
|
|
842
962
|
}
|
|
963
|
+
getCanonicalBlockByRoot(blockRoot) {
|
|
964
|
+
const blockRootHex = toRootHex(blockRoot);
|
|
965
|
+
if (blockRootHex === this.head.blockRoot) {
|
|
966
|
+
return this.head;
|
|
967
|
+
}
|
|
968
|
+
for (const block of this.protoArray.iterateAncestorNodes(this.head.blockRoot, this.head.payloadStatus)) {
|
|
969
|
+
if (block.blockRoot === blockRootHex) {
|
|
970
|
+
return block;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
843
975
|
getCanonicalBlockAtSlot(slot) {
|
|
844
976
|
if (slot > this.head.slot) {
|
|
845
977
|
return null;
|
|
@@ -847,7 +979,7 @@ export class ForkChoice {
|
|
|
847
979
|
if (slot === this.head.slot) {
|
|
848
980
|
return this.head;
|
|
849
981
|
}
|
|
850
|
-
for (const block of this.protoArray.iterateAncestorNodes(this.head.blockRoot)) {
|
|
982
|
+
for (const block of this.protoArray.iterateAncestorNodes(this.head.blockRoot, this.head.payloadStatus)) {
|
|
851
983
|
if (block.slot === slot) {
|
|
852
984
|
return block;
|
|
853
985
|
}
|
|
@@ -858,7 +990,7 @@ export class ForkChoice {
|
|
|
858
990
|
if (slot >= this.head.slot) {
|
|
859
991
|
return this.head;
|
|
860
992
|
}
|
|
861
|
-
for (const block of this.protoArray.iterateAncestorNodes(this.head.blockRoot)) {
|
|
993
|
+
for (const block of this.protoArray.iterateAncestorNodes(this.head.blockRoot, this.head.payloadStatus)) {
|
|
862
994
|
if (slot >= block.slot) {
|
|
863
995
|
return block;
|
|
864
996
|
}
|
|
@@ -869,9 +1001,9 @@ export class ForkChoice {
|
|
|
869
1001
|
forwarditerateAncestorBlocks() {
|
|
870
1002
|
return this.protoArray.nodes;
|
|
871
1003
|
}
|
|
872
|
-
*forwardIterateDescendants(blockRoot) {
|
|
1004
|
+
*forwardIterateDescendants(blockRoot, payloadStatus) {
|
|
873
1005
|
const rootsInChain = new Set([blockRoot]);
|
|
874
|
-
const blockIndex = this.protoArray.
|
|
1006
|
+
const blockIndex = this.protoArray.getNodeIndexByRootAndStatus(blockRoot, payloadStatus);
|
|
875
1007
|
if (blockIndex === undefined) {
|
|
876
1008
|
throw new ForkChoiceError({
|
|
877
1009
|
code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK,
|
|
@@ -904,8 +1036,8 @@ export class ForkChoice {
|
|
|
904
1036
|
}
|
|
905
1037
|
/** Returns the distance of common ancestor of nodes to the max of the newNode and the prevNode. */
|
|
906
1038
|
getCommonAncestorDepth(prevBlock, newBlock) {
|
|
907
|
-
const prevNode = this.protoArray.getNode(prevBlock.blockRoot);
|
|
908
|
-
const newNode = this.protoArray.getNode(newBlock.blockRoot);
|
|
1039
|
+
const prevNode = this.protoArray.getNode(prevBlock.blockRoot, prevBlock.payloadStatus);
|
|
1040
|
+
const newNode = this.protoArray.getNode(newBlock.blockRoot, newBlock.payloadStatus);
|
|
909
1041
|
if (!prevNode || !newNode) {
|
|
910
1042
|
return { code: AncestorStatus.BlockUnknown };
|
|
911
1043
|
}
|
|
@@ -977,12 +1109,17 @@ export class ForkChoice {
|
|
|
977
1109
|
if (block.slot === beforeSlot) {
|
|
978
1110
|
return block.parentRoot;
|
|
979
1111
|
}
|
|
980
|
-
block
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1112
|
+
// For the first slot of the epoch, a block is it's own target
|
|
1113
|
+
const nextRoot = block.blockRoot === block.targetRoot ? block.parentRoot : block.targetRoot;
|
|
1114
|
+
// Use default variant (PENDING for Gloas, FULL for pre-Gloas)
|
|
1115
|
+
// For Gloas: we search for PENDING blocks because dependent root is determined by the block itself,
|
|
1116
|
+
// not the payload. In state-transition, block parentage is independent of payload status,
|
|
1117
|
+
// so linking by PENDING block in fork-choice is correct.
|
|
1118
|
+
const defaultStatus = this.protoArray.getDefaultVariant(nextRoot);
|
|
1119
|
+
if (defaultStatus === undefined) {
|
|
1120
|
+
throw Error(`No block for root ${nextRoot}`);
|
|
1121
|
+
}
|
|
1122
|
+
block = this.protoArray.getBlockReadonly(nextRoot, defaultStatus);
|
|
986
1123
|
}
|
|
987
1124
|
throw Error(`Not found dependent root for block slot ${block.slot}, epoch difference ${epochDifference}`);
|
|
988
1125
|
}
|
|
@@ -1013,9 +1150,14 @@ export class ForkChoice {
|
|
|
1013
1150
|
throw Error(`Invalid pre-merge data status: expected: ${DataAvailabilityStatus.PreData}, got ${dataAvailabilityStatus}`);
|
|
1014
1151
|
return dataAvailabilityStatus;
|
|
1015
1152
|
}
|
|
1016
|
-
|
|
1017
|
-
if (executionStatus === ExecutionStatus.PreMerge)
|
|
1018
|
-
throw Error(`Invalid post-merge execution status: expected: ${ExecutionStatus.Syncing} or ${ExecutionStatus.Valid}
|
|
1153
|
+
getPreGloasExecStatus(executionStatus) {
|
|
1154
|
+
if (executionStatus === ExecutionStatus.PreMerge || executionStatus === ExecutionStatus.PayloadSeparated)
|
|
1155
|
+
throw Error(`Invalid post-merge execution status: expected: ${ExecutionStatus.Syncing} or ${ExecutionStatus.Valid}, got ${executionStatus}`);
|
|
1156
|
+
return executionStatus;
|
|
1157
|
+
}
|
|
1158
|
+
getPostGloasExecStatus(executionStatus) {
|
|
1159
|
+
if (executionStatus !== ExecutionStatus.PayloadSeparated)
|
|
1160
|
+
throw Error(`Invalid post-gloas execution status: expected: ${ExecutionStatus.PayloadSeparated}, got ${executionStatus}`);
|
|
1019
1161
|
return executionStatus;
|
|
1020
1162
|
}
|
|
1021
1163
|
/**
|
|
@@ -1035,7 +1177,7 @@ export class ForkChoice {
|
|
|
1035
1177
|
*
|
|
1036
1178
|
* **`on_tick`**
|
|
1037
1179
|
* May need the justified balances of:
|
|
1038
|
-
* - unrealizedJustified: Already available in `
|
|
1180
|
+
* - unrealizedJustified: Already available in `CheckpointWithPayloadAndBalance`
|
|
1039
1181
|
* Since this balances are already available the getter is just `() => balances`, without cache interaction
|
|
1040
1182
|
*/
|
|
1041
1183
|
updateCheckpoints(justifiedCheckpoint, finalizedCheckpoint, getJustifiedBalances) {
|
|
@@ -1150,7 +1292,9 @@ export class ForkChoice {
|
|
|
1150
1292
|
//
|
|
1151
1293
|
// Attestations must be for a known block. If the block is unknown, we simply drop the
|
|
1152
1294
|
// attestation and do not delay consideration for later.
|
|
1153
|
-
|
|
1295
|
+
// We don't care which variant it is, just need to find the block
|
|
1296
|
+
const defaultStatus = this.protoArray.getDefaultVariant(beaconBlockRootHex);
|
|
1297
|
+
const block = defaultStatus !== undefined ? this.protoArray.getBlock(beaconBlockRootHex, defaultStatus) : undefined;
|
|
1154
1298
|
if (!block) {
|
|
1155
1299
|
throw new ForkChoiceError({
|
|
1156
1300
|
code: ForkChoiceErrorCode.INVALID_ATTESTATION,
|
|
@@ -1187,30 +1331,48 @@ export class ForkChoice {
|
|
|
1187
1331
|
},
|
|
1188
1332
|
});
|
|
1189
1333
|
}
|
|
1334
|
+
// For Gloas blocks, attestation index must be 0 or 1
|
|
1335
|
+
if (isGloasBlock(block) && attestationData.index !== 0 && attestationData.index !== 1) {
|
|
1336
|
+
throw new ForkChoiceError({
|
|
1337
|
+
code: ForkChoiceErrorCode.INVALID_ATTESTATION,
|
|
1338
|
+
err: {
|
|
1339
|
+
code: InvalidAttestationCode.INVALID_DATA_INDEX,
|
|
1340
|
+
index: attestationData.index,
|
|
1341
|
+
},
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1190
1344
|
this.validatedAttestationDatas.add(attDataRoot);
|
|
1191
1345
|
}
|
|
1192
1346
|
/**
|
|
1193
1347
|
* Add a validator's latest message to the tracked votes.
|
|
1194
1348
|
* Always sync voteCurrentIndices and voteNextIndices so that it'll not throw in computeDeltas()
|
|
1349
|
+
*
|
|
1350
|
+
* Modified for Gloas to accept slot and payloadPresent.
|
|
1351
|
+
* Spec: gloas/fork-choice.md#modified-update_latest_messages
|
|
1352
|
+
*
|
|
1353
|
+
* For backward compatibility with Fulu (pre-Gloas):
|
|
1354
|
+
* - Accepts both epoch-derived and slot parameters
|
|
1355
|
+
* - payloadPresent defaults to true for Fulu (payloads embedded in blocks)
|
|
1195
1356
|
*/
|
|
1196
|
-
addLatestMessage(validatorIndex,
|
|
1357
|
+
addLatestMessage(validatorIndex, nextSlot, nextRoot, nextPayloadStatus) {
|
|
1197
1358
|
// should not happen, attestation is validated before this step
|
|
1198
|
-
|
|
1359
|
+
// Get the node index for the voted block
|
|
1360
|
+
const nextIndex = this.protoArray.getNodeIndexByRootAndStatus(nextRoot, nextPayloadStatus);
|
|
1199
1361
|
if (nextIndex === undefined) {
|
|
1200
|
-
throw new Error(`Could not find proto index for nextRoot ${nextRoot}`);
|
|
1362
|
+
throw new Error(`Could not find proto index for nextRoot ${nextRoot} with payloadStatus ${nextPayloadStatus}`);
|
|
1201
1363
|
}
|
|
1202
1364
|
// ensure there is no undefined entries in Votes arrays
|
|
1203
|
-
if (this.
|
|
1204
|
-
for (let i = this.
|
|
1205
|
-
this.
|
|
1365
|
+
if (this.voteNextSlots.length < validatorIndex + 1) {
|
|
1366
|
+
for (let i = this.voteNextSlots.length; i < validatorIndex + 1; i++) {
|
|
1367
|
+
this.voteNextSlots[i] = INIT_VOTE_SLOT;
|
|
1206
1368
|
this.voteCurrentIndices[i] = this.voteNextIndices[i] = NULL_VOTE_INDEX;
|
|
1207
1369
|
}
|
|
1208
1370
|
}
|
|
1209
|
-
const
|
|
1210
|
-
if (
|
|
1371
|
+
const existingNextSlot = this.voteNextSlots[validatorIndex];
|
|
1372
|
+
if (existingNextSlot === INIT_VOTE_SLOT || computeEpochAtSlot(nextSlot) > computeEpochAtSlot(existingNextSlot)) {
|
|
1211
1373
|
// nextIndex is transfered to currentIndex in computeDeltas()
|
|
1212
1374
|
this.voteNextIndices[validatorIndex] = nextIndex;
|
|
1213
|
-
this.
|
|
1375
|
+
this.voteNextSlots[validatorIndex] = nextSlot;
|
|
1214
1376
|
}
|
|
1215
1377
|
// else its an old vote, don't count it
|
|
1216
1378
|
}
|
|
@@ -1221,17 +1383,16 @@ export class ForkChoice {
|
|
|
1221
1383
|
processAttestationQueue() {
|
|
1222
1384
|
const currentSlot = this.fcStore.currentSlot;
|
|
1223
1385
|
for (const [slot, byRoot] of this.queuedAttestations.entries()) {
|
|
1224
|
-
const targetEpoch = computeEpochAtSlot(slot);
|
|
1225
1386
|
if (slot < currentSlot) {
|
|
1226
1387
|
this.queuedAttestations.delete(slot);
|
|
1227
|
-
for (const [blockRoot,
|
|
1388
|
+
for (const [blockRoot, validatorVotes] of byRoot.entries()) {
|
|
1228
1389
|
const blockRootHex = blockRoot;
|
|
1229
|
-
for (const validatorIndex of
|
|
1390
|
+
for (const [validatorIndex, payloadStatus] of validatorVotes.entries()) {
|
|
1230
1391
|
// equivocatingIndices was checked in onAttestation
|
|
1231
|
-
this.addLatestMessage(validatorIndex,
|
|
1392
|
+
this.addLatestMessage(validatorIndex, slot, blockRootHex, payloadStatus);
|
|
1232
1393
|
}
|
|
1233
1394
|
if (slot === currentSlot - 1) {
|
|
1234
|
-
this.queuedAttestationsPreviousSlot +=
|
|
1395
|
+
this.queuedAttestationsPreviousSlot += validatorVotes.size;
|
|
1235
1396
|
}
|
|
1236
1397
|
}
|
|
1237
1398
|
}
|
|
@@ -1322,4 +1483,28 @@ export function getCommitteeFraction(justifiedTotalActiveBalanceByIncrement, con
|
|
|
1322
1483
|
const committeeWeight = Math.floor(justifiedTotalActiveBalanceByIncrement / config.slotsPerEpoch);
|
|
1323
1484
|
return Math.floor((committeeWeight * config.committeePercent) / 100);
|
|
1324
1485
|
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Get the payload status for a checkpoint.
|
|
1488
|
+
*
|
|
1489
|
+
* Pre-Gloas: always FULL (payload embedded in block)
|
|
1490
|
+
* Gloas: determined by state.execution_payload_availability
|
|
1491
|
+
*
|
|
1492
|
+
* @param state - The state to check execution_payload_availability
|
|
1493
|
+
* @param checkpointEpoch - The epoch of the checkpoint
|
|
1494
|
+
*/
|
|
1495
|
+
export function getCheckpointPayloadStatus(state, checkpointEpoch) {
|
|
1496
|
+
// Compute checkpoint slot first to determine the correct fork
|
|
1497
|
+
const checkpointSlot = computeStartSlotAtEpoch(checkpointEpoch);
|
|
1498
|
+
const fork = state.config.getForkSeq(checkpointSlot);
|
|
1499
|
+
// Pre-Gloas: always FULL
|
|
1500
|
+
if (fork < ForkSeq.gloas) {
|
|
1501
|
+
return PayloadStatus.FULL;
|
|
1502
|
+
}
|
|
1503
|
+
// For Gloas, check state.execution_payload_availability
|
|
1504
|
+
// - For non-skipped slots at checkpoint: returns false (EMPTY) since payload hasn't arrived yet
|
|
1505
|
+
// - For skipped slots at checkpoint: returns the actual availability status from state
|
|
1506
|
+
const gloasState = state;
|
|
1507
|
+
const payloadAvailable = gloasState.executionPayloadAvailability.get(checkpointSlot % SLOTS_PER_HISTORICAL_ROOT);
|
|
1508
|
+
return payloadAvailable ? PayloadStatus.FULL : PayloadStatus.EMPTY;
|
|
1509
|
+
}
|
|
1325
1510
|
//# sourceMappingURL=forkChoice.js.map
|