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