@lodestar/fork-choice 1.45.0-dev.f2645825a8 → 1.45.0-dev.f6c521a123
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/forkChoice/fastConfirmation/metrics.d.ts +1 -0
- package/lib/forkChoice/fastConfirmation/metrics.d.ts.map +1 -1
- package/lib/forkChoice/fastConfirmation/metrics.js +4 -0
- package/lib/forkChoice/fastConfirmation/metrics.js.map +1 -1
- package/lib/forkChoice/forkChoice.d.ts +46 -7
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +166 -49
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +14 -2
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/forkChoice/interface.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/metrics.d.ts +1 -0
- package/lib/metrics.d.ts.map +1 -1
- package/lib/protoArray/computeDeltas.d.ts +3 -3
- package/lib/protoArray/computeDeltas.d.ts.map +1 -1
- package/lib/protoArray/computeDeltas.js +12 -12
- package/lib/protoArray/computeDeltas.js.map +1 -1
- package/lib/protoArray/interface.d.ts +6 -0
- package/lib/protoArray/interface.d.ts.map +1 -1
- package/lib/protoArray/protoArray.d.ts +22 -2
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +93 -13
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +9 -9
- package/src/forkChoice/fastConfirmation/metrics.ts +4 -0
- package/src/forkChoice/forkChoice.ts +181 -50
- package/src/forkChoice/interface.ts +11 -12
- package/src/index.ts +1 -1
- package/src/protoArray/computeDeltas.ts +13 -13
- package/src/protoArray/interface.ts +6 -0
- package/src/protoArray/protoArray.ts +102 -17
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
-
import {MIN_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
2
|
+
import {MIN_SEED_LOOKAHEAD, SLOTS_PER_EPOCH, isForkPostGloas} from "@lodestar/params";
|
|
3
3
|
import {
|
|
4
4
|
DataAvailabilityStatus,
|
|
5
5
|
EffectiveBalanceIncrements,
|
|
@@ -152,6 +152,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
152
152
|
/** Optional fast confirmation rule implementation */
|
|
153
153
|
private readonly fastConfirmationRule?: IFastConfirmationRule;
|
|
154
154
|
private readonly fastConfirmationContext?: FastConfirmationContext;
|
|
155
|
+
private fastConfirmationPaused = false;
|
|
155
156
|
/**
|
|
156
157
|
* Instantiates a Fork Choice from some existing components
|
|
157
158
|
*
|
|
@@ -180,6 +181,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
180
181
|
if (this.opts?.fastConfirmation) {
|
|
181
182
|
this.fastConfirmationRule = new FastConfirmationRule(this.fcStore, metrics, this.logger);
|
|
182
183
|
this.fastConfirmationContext = this.createFastConfirmationContext();
|
|
184
|
+
metrics?.fastConfirmation.paused.set(0);
|
|
183
185
|
}
|
|
184
186
|
|
|
185
187
|
metrics?.forkChoice.votes.addCollect(() => {
|
|
@@ -230,6 +232,47 @@ export class ForkChoice implements IForkChoice {
|
|
|
230
232
|
return this.getBlockHexDefaultStatus(this.getConfirmedRoot());
|
|
231
233
|
}
|
|
232
234
|
|
|
235
|
+
resumeFastConfirmation(): void {
|
|
236
|
+
this.toggleFastConfirmation(false);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
pauseFastConfirmation(): void {
|
|
240
|
+
this.toggleFastConfirmation(true);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private toggleFastConfirmation(paused: boolean): void {
|
|
244
|
+
if (!this.fastConfirmationRule) return;
|
|
245
|
+
if (paused === this.fastConfirmationPaused) return;
|
|
246
|
+
this.fastConfirmationPaused = paused;
|
|
247
|
+
if (paused) {
|
|
248
|
+
// Pin immediately: block imports report the safe block hash to the EL before the next slot tick
|
|
249
|
+
this.fcStore.confirmedRoot = this.fcStore.finalizedCheckpoint.rootHex;
|
|
250
|
+
try {
|
|
251
|
+
this.notifyConfirmedRoot();
|
|
252
|
+
} catch (err) {
|
|
253
|
+
// Callers run in clock/network handler context with no catch above
|
|
254
|
+
this.logger?.debug("Fast confirmation notify failed", {slot: this.fcStore.currentSlot}, err as Error);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
this.metrics?.fastConfirmation.paused.set(paused ? 1 : 0);
|
|
258
|
+
this.logger?.info(paused ? "Paused fast confirmation" : "Resumed fast confirmation", {
|
|
259
|
+
slot: this.fcStore.currentSlot,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private notifyConfirmedRoot(): void {
|
|
264
|
+
const confirmedRoot = this.fcStore.confirmedRoot;
|
|
265
|
+
const confirmedBlock = this.getBlockHexDefaultStatus(confirmedRoot);
|
|
266
|
+
if (confirmedBlock === null) {
|
|
267
|
+
throw new Error(`Fast confirmation produced root not in protoArray: ${confirmedRoot}`);
|
|
268
|
+
}
|
|
269
|
+
this.fcStore.notifyFastConfirmation?.({
|
|
270
|
+
block: confirmedRoot,
|
|
271
|
+
slot: confirmedBlock.slot,
|
|
272
|
+
currentSlot: this.fcStore.currentSlot,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
233
276
|
/**
|
|
234
277
|
*
|
|
235
278
|
* A multiplexer to wrap around the traditional `updateHead()` according to the scenario
|
|
@@ -455,30 +498,12 @@ export class ForkChoice implements IForkChoice {
|
|
|
455
498
|
}
|
|
456
499
|
|
|
457
500
|
// No reorg if headBlock is "not weak" ie. headBlock's weight exceeds (REORG_HEAD_WEIGHT_THRESHOLD = 20)% of total attester weight
|
|
458
|
-
|
|
459
|
-
const reorgThreshold = getCommitteeFraction(this.fcStore.justified.totalBalance, {
|
|
460
|
-
slotsPerEpoch: SLOTS_PER_EPOCH,
|
|
461
|
-
committeePercent: this.config.REORG_HEAD_WEIGHT_THRESHOLD,
|
|
462
|
-
});
|
|
463
|
-
const headNode = this.protoArray.getNode(headBlock.blockRoot, headBlock.payloadStatus);
|
|
464
|
-
// If headNode is unavailable, give up reorg
|
|
465
|
-
if (headNode === undefined || headNode.weight >= reorgThreshold) {
|
|
501
|
+
if (!this.isHeadWeak(headBlock.blockRoot)) {
|
|
466
502
|
return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.HeadBlockNotWeak};
|
|
467
503
|
}
|
|
468
504
|
|
|
469
505
|
// No reorg if parentBlock is "not strong" ie. parentBlock's weight is less than or equal to (REORG_PARENT_WEIGHT_THRESHOLD = 160)% of total attester weight
|
|
470
|
-
|
|
471
|
-
// For Gloas: measure support for the parent beacon block root regardless of its payload status by
|
|
472
|
-
// looking up the PENDING variant.
|
|
473
|
-
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.11/specs/gloas/fork-choice.md#modified-is_parent_strong
|
|
474
|
-
const parentThreshold = getCommitteeFraction(this.fcStore.justified.totalBalance, {
|
|
475
|
-
slotsPerEpoch: SLOTS_PER_EPOCH,
|
|
476
|
-
committeePercent: this.config.REORG_PARENT_WEIGHT_THRESHOLD,
|
|
477
|
-
});
|
|
478
|
-
const parentStrongVariant = isGloasBlock(parentBlock) ? PayloadStatus.PENDING : PayloadStatus.FULL;
|
|
479
|
-
const parentNode = this.protoArray.getNode(parentBlock.blockRoot, parentStrongVariant);
|
|
480
|
-
// If parentNode is unavailable, give up reorg
|
|
481
|
-
if (parentNode === undefined || parentNode.weight <= parentThreshold) {
|
|
506
|
+
if (!this.isParentStrong(parentBlock.blockRoot)) {
|
|
482
507
|
return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ParentBlockNotStrong};
|
|
483
508
|
}
|
|
484
509
|
|
|
@@ -521,7 +546,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
521
546
|
|
|
522
547
|
const timer = computeDeltasMetrics?.duration.startTimer();
|
|
523
548
|
const {
|
|
524
|
-
|
|
549
|
+
attestationDeltas,
|
|
525
550
|
equivocatingValidators,
|
|
526
551
|
oldInactiveValidators,
|
|
527
552
|
newInactiveValidators,
|
|
@@ -537,8 +562,8 @@ export class ForkChoice implements IForkChoice {
|
|
|
537
562
|
);
|
|
538
563
|
timer?.();
|
|
539
564
|
|
|
540
|
-
computeDeltasMetrics?.deltasCount.set(
|
|
541
|
-
computeDeltasMetrics?.zeroDeltasCount.set(
|
|
565
|
+
computeDeltasMetrics?.deltasCount.set(attestationDeltas.length);
|
|
566
|
+
computeDeltasMetrics?.zeroDeltasCount.set(attestationDeltas.filter((d) => d === 0).length);
|
|
542
567
|
computeDeltasMetrics?.equivocatingValidators.set(equivocatingValidators);
|
|
543
568
|
computeDeltasMetrics?.oldInactiveValidators.set(oldInactiveValidators);
|
|
544
569
|
computeDeltasMetrics?.newInactiveValidators.set(newInactiveValidators);
|
|
@@ -564,7 +589,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
564
589
|
|
|
565
590
|
const currentSlot = this.fcStore.currentSlot;
|
|
566
591
|
this.protoArray.applyScoreChanges({
|
|
567
|
-
|
|
592
|
+
attestationDeltas,
|
|
568
593
|
proposerBoost,
|
|
569
594
|
justifiedEpoch: this.fcStore.justified.checkpoint.epoch,
|
|
570
595
|
justifiedRoot: this.fcStore.justified.checkpoint.rootHex,
|
|
@@ -589,11 +614,31 @@ export class ForkChoice implements IForkChoice {
|
|
|
589
614
|
return this.protoArray.nodes.filter((node) => node.slot > windowStart).length;
|
|
590
615
|
}
|
|
591
616
|
|
|
617
|
+
getPayloadRevealCounts(fromSlot: Slot, toSlot: Slot): {blocksPresent: number; payloadsRevealed: number} {
|
|
618
|
+
return this.protoArray.getPayloadRevealCounts(fromSlot, toSlot);
|
|
619
|
+
}
|
|
620
|
+
|
|
592
621
|
/** Very expensive function, iterates the entire ProtoArray. Called only in debug API */
|
|
593
622
|
getHeads(): ProtoBlock[] {
|
|
594
623
|
return this.protoArray.nodes.filter((node) => node.bestChild === undefined);
|
|
595
624
|
}
|
|
596
625
|
|
|
626
|
+
/**
|
|
627
|
+
* weight is in EFFECTIVE_BALANCE_INCREMENTS not gwei.
|
|
628
|
+
* For compliance test use only
|
|
629
|
+
*/
|
|
630
|
+
getViableHeads(): {root: RootHex; payloadStatus: PayloadStatus; weight: number}[] {
|
|
631
|
+
return this.protoArray.getViableHeads(this.fcStore.currentSlot);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* The cached justified total active balance, in EFFECTIVE_BALANCE_INCREMENT units.
|
|
636
|
+
* For compliance test use only
|
|
637
|
+
*/
|
|
638
|
+
getJustifiedTotalActiveBalanceByIncrement(): number {
|
|
639
|
+
return this.fcStore.justified.totalBalance;
|
|
640
|
+
}
|
|
641
|
+
|
|
597
642
|
/** This is for the debug API only */
|
|
598
643
|
getAllNodes(): ProtoNode[] {
|
|
599
644
|
return this.protoArray.nodes;
|
|
@@ -632,11 +677,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
632
677
|
blockDelaySec: number,
|
|
633
678
|
currentSlot: Slot,
|
|
634
679
|
executionStatus: BlockExecutionStatus,
|
|
635
|
-
dataAvailabilityStatus: DataAvailabilityStatus
|
|
636
|
-
// The expected proposer index on the canonical chain we are following.
|
|
637
|
-
// Calculated by our head state. We use it as part of the proposer
|
|
638
|
-
// boost decision making. No boost will be set if this is null.
|
|
639
|
-
expectedProposerIndex: ValidatorIndex | null
|
|
680
|
+
dataAvailabilityStatus: DataAvailabilityStatus
|
|
640
681
|
): ProtoBlock {
|
|
641
682
|
const {parentRoot, slot} = block;
|
|
642
683
|
const parentRootHex = toRootHex(parentRoot);
|
|
@@ -711,8 +752,6 @@ export class ForkChoice implements IForkChoice {
|
|
|
711
752
|
isTimely &&
|
|
712
753
|
// only boost the first block we see
|
|
713
754
|
this.proposerBoostRoot === null &&
|
|
714
|
-
expectedProposerIndex !== null &&
|
|
715
|
-
block.proposerIndex === expectedProposerIndex &&
|
|
716
755
|
this.isProposerBoostSameDependentRoot(this.head.blockRoot, parentRootHex);
|
|
717
756
|
// Candidate boost root used for protoArray.onBlock's best-child weighting. Committed to the
|
|
718
757
|
// store only after the insertion succeeds.
|
|
@@ -1505,11 +1544,11 @@ export class ForkChoice implements IForkChoice {
|
|
|
1505
1544
|
}
|
|
1506
1545
|
|
|
1507
1546
|
/**
|
|
1508
|
-
* Spec: phase0/fork-choice.md#update_proposer_boost_root (`is_same_dependent_root` condition,
|
|
1509
|
-
* consensus-specs
|
|
1510
|
-
* proposer-shuffling dependent root for the current epoch as the canonical head
|
|
1511
|
-
* the block was imported. This withholds the boost from a block built on a different
|
|
1512
|
-
* branch than the head.
|
|
1547
|
+
* Spec: phase0/fork-choice.md#update_proposer_boost_root (`is_same_dependent_root` condition, added in
|
|
1548
|
+
* https://github.com/ethereum/consensus-specs/pull/5306). Proposer boost is only granted when the imported
|
|
1549
|
+
* block shares the same proposer-shuffling dependent root for the current epoch as the canonical head
|
|
1550
|
+
* computed before the block was imported. This withholds the boost from a block built on a different
|
|
1551
|
+
* shuffling branch than the head.
|
|
1513
1552
|
*
|
|
1514
1553
|
* The block is not yet in the proto-array when this runs, so its dependent root is traced from
|
|
1515
1554
|
* its parent
|
|
@@ -1532,6 +1571,93 @@ export class ForkChoice implements IForkChoice {
|
|
|
1532
1571
|
return headDependentRoot === blockDependentRoot;
|
|
1533
1572
|
}
|
|
1534
1573
|
|
|
1574
|
+
/**
|
|
1575
|
+
* Return true if the block is "weak" ie. its weight is below REORG_HEAD_WEIGHT_THRESHOLD of the
|
|
1576
|
+
* total attester weight per slot.
|
|
1577
|
+
*
|
|
1578
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.11/specs/phase0/fork-choice.md#is_head_weak
|
|
1579
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.11/specs/gloas/fork-choice.md#modified-is_head_weak
|
|
1580
|
+
*/
|
|
1581
|
+
private isHeadWeak(blockRoot: RootHex): boolean {
|
|
1582
|
+
// The default variant is PENDING for gloas, FULL pre-gloas. PENDING is the variant gloas measures
|
|
1583
|
+
// support on, ie. support for the beacon block root regardless of its payload status.
|
|
1584
|
+
// Only ever called on a block already in fork choice, so a miss is a broken invariant.
|
|
1585
|
+
const node = this.protoArray.getNodeDefaultStatus(blockRoot);
|
|
1586
|
+
if (node === undefined) {
|
|
1587
|
+
// this is called for head so we should always have this in forkchoice, otherwise we have a serious error
|
|
1588
|
+
throw new ForkChoiceError({code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK, root: blockRoot});
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
const reorgThreshold = getCommitteeFraction(this.fcStore.justified.totalBalance, {
|
|
1592
|
+
slotsPerEpoch: SLOTS_PER_EPOCH,
|
|
1593
|
+
committeePercent: this.config.REORG_HEAD_WEIGHT_THRESHOLD,
|
|
1594
|
+
});
|
|
1595
|
+
|
|
1596
|
+
if (!isForkPostGloas(this.config.getForkName(node.slot))) {
|
|
1597
|
+
return node.weight < reorgThreshold;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
let headWeight = node.attestationScore;
|
|
1601
|
+
|
|
1602
|
+
const {equivocatingIndices} = this.fcStore;
|
|
1603
|
+
// Equivocators are extremely rare (none in normal operation), and with none the added weight is
|
|
1604
|
+
// always 0. Return before fetching the state and walking the block's committees.
|
|
1605
|
+
if (equivocatingIndices.size > 0) {
|
|
1606
|
+
const state = this.fcStore.stateGetter({stateRoot: node.stateRoot});
|
|
1607
|
+
// Only ever called on the head, so the state is always cached.
|
|
1608
|
+
// A miss is a broken invariant, not a recoverable state.
|
|
1609
|
+
if (state === null) {
|
|
1610
|
+
throw new ForkChoiceError({
|
|
1611
|
+
code: ForkChoiceErrorCode.BEACON_STATE_ERROR,
|
|
1612
|
+
error: new Error(`Missing state for isHeadWeak, blockRoot=${blockRoot} stateRoot=${node.stateRoot}`),
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
const epoch = computeEpochAtSlot(node.slot);
|
|
1617
|
+
for (let index = 0; index < state.getBeaconCommitteeCountPerSlot(epoch); index++) {
|
|
1618
|
+
for (const validatorIndex of state.getBeaconCommittee(node.slot, index)) {
|
|
1619
|
+
if (equivocatingIndices.has(validatorIndex)) {
|
|
1620
|
+
// the spec specifies to use effective_balance of the justified state
|
|
1621
|
+
let balance = this.fcStore.justified.balances[validatorIndex];
|
|
1622
|
+
if (!balance) {
|
|
1623
|
+
// 0 (zeroed by getEffectiveBalanceIncrementsZeroInactive) or undefined (validator not in
|
|
1624
|
+
// the justified state) - fall back to the head state's effective balance
|
|
1625
|
+
balance = state.effectiveBalanceIncrements[validatorIndex];
|
|
1626
|
+
}
|
|
1627
|
+
headWeight += balance;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
return headWeight < reorgThreshold;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
/**
|
|
1637
|
+
* Return true if the parent block is "strong" ie. its weight exceeds REORG_PARENT_WEIGHT_THRESHOLD
|
|
1638
|
+
* of the total attester weight per slot.
|
|
1639
|
+
*
|
|
1640
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/phase0/fork-choice.md#is_parent_strong
|
|
1641
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/gloas/fork-choice.md#modified-is_parent_strong
|
|
1642
|
+
*/
|
|
1643
|
+
private isParentStrong(parentRoot: RootHex): boolean {
|
|
1644
|
+
const node = this.protoArray.getNodeDefaultStatus(parentRoot);
|
|
1645
|
+
// If parentNode is unavailable, give up reorg
|
|
1646
|
+
if (node === undefined) {
|
|
1647
|
+
return false;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
const parentThreshold = getCommitteeFraction(this.fcStore.justified.totalBalance, {
|
|
1651
|
+
slotsPerEpoch: SLOTS_PER_EPOCH,
|
|
1652
|
+
committeePercent: this.config.REORG_PARENT_WEIGHT_THRESHOLD,
|
|
1653
|
+
});
|
|
1654
|
+
|
|
1655
|
+
// pre-gloas uses get_weight() (boost-inclusive), gloas uses get_attestation_score() (boost-excluded)
|
|
1656
|
+
const parentWeight = isForkPostGloas(this.config.getForkName(node.slot)) ? node.attestationScore : node.weight;
|
|
1657
|
+
|
|
1658
|
+
return parentWeight > parentThreshold;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1535
1661
|
/**
|
|
1536
1662
|
* Return true if the block is timely for the current slot.
|
|
1537
1663
|
* Child class can overwrite this for testing purpose.
|
|
@@ -1984,9 +2110,23 @@ export class ForkChoice implements IForkChoice {
|
|
|
1984
2110
|
}
|
|
1985
2111
|
|
|
1986
2112
|
private runFastConfirmation(): void {
|
|
1987
|
-
|
|
1988
|
-
|
|
2113
|
+
const fastConfirmationRule = this.fastConfirmationRule;
|
|
2114
|
+
const fastConfirmationContext = this.fastConfirmationContext;
|
|
2115
|
+
if (!fastConfirmationRule || !fastConfirmationContext) return;
|
|
1989
2116
|
|
|
2117
|
+
if (this.fastConfirmationPaused) {
|
|
2118
|
+
// Keep consumers on a safe, available root while the rule is paused
|
|
2119
|
+
this.fcStore.confirmedRoot = this.fcStore.finalizedCheckpoint.rootHex;
|
|
2120
|
+
try {
|
|
2121
|
+
this.notifyConfirmedRoot();
|
|
2122
|
+
} catch (err) {
|
|
2123
|
+
// Runs outside the timed try/catch below; a throw would escape to the clock listener
|
|
2124
|
+
this.logger?.debug("Fast confirmation notify failed", {slot: this.fcStore.currentSlot}, err as Error);
|
|
2125
|
+
}
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
withObservedDuration(this.metrics?.fastConfirmation.totalDuration.startTimer(), () => {
|
|
1990
2130
|
try {
|
|
1991
2131
|
withObservedDuration(
|
|
1992
2132
|
this.metrics?.fastConfirmation.stepsDuration.startTimer({
|
|
@@ -1995,18 +2135,9 @@ export class ForkChoice implements IForkChoice {
|
|
|
1995
2135
|
() => this.updateHead()
|
|
1996
2136
|
);
|
|
1997
2137
|
|
|
1998
|
-
const result =
|
|
2138
|
+
const result = fastConfirmationRule.onSlotStartAfterPastAttestationsApplied(fastConfirmationContext);
|
|
1999
2139
|
this.fcStore.confirmedRoot = result.confirmedRoot;
|
|
2000
|
-
|
|
2001
|
-
const confirmedBlock = this.getBlockHexDefaultStatus(result.confirmedRoot);
|
|
2002
|
-
if (confirmedBlock === null) {
|
|
2003
|
-
throw new Error(`Fast confirmation produced root not in protoArray: ${result.confirmedRoot}`);
|
|
2004
|
-
}
|
|
2005
|
-
this.fcStore.notifyFastConfirmation?.({
|
|
2006
|
-
block: result.confirmedRoot,
|
|
2007
|
-
slot: confirmedBlock.slot,
|
|
2008
|
-
currentSlot: this.fcStore.currentSlot,
|
|
2009
|
-
});
|
|
2140
|
+
this.notifyConfirmedRoot();
|
|
2010
2141
|
} catch (err) {
|
|
2011
2142
|
this.logger?.debug(
|
|
2012
2143
|
"Fast confirmation failed",
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import {DataAvailabilityStatus, EffectiveBalanceIncrements, IBeaconStateView} from "@lodestar/state-transition";
|
|
2
|
-
import {
|
|
3
|
-
AttesterSlashing,
|
|
4
|
-
BeaconBlock,
|
|
5
|
-
Epoch,
|
|
6
|
-
IndexedAttestation,
|
|
7
|
-
Root,
|
|
8
|
-
RootHex,
|
|
9
|
-
Slot,
|
|
10
|
-
ValidatorIndex,
|
|
11
|
-
} from "@lodestar/types";
|
|
2
|
+
import {AttesterSlashing, BeaconBlock, Epoch, IndexedAttestation, Root, RootHex, Slot} from "@lodestar/types";
|
|
12
3
|
import {
|
|
13
4
|
BlockExecutionStatus,
|
|
14
5
|
LVHExecResponse,
|
|
@@ -109,6 +100,10 @@ export interface IForkChoice {
|
|
|
109
100
|
getHead(): ProtoBlock;
|
|
110
101
|
getConfirmedRoot(): RootHex;
|
|
111
102
|
getConfirmedBlock(): ProtoBlock | null;
|
|
103
|
+
/** Resume the fast confirmation rule; restarts from the finalized root on the next slot tick */
|
|
104
|
+
resumeFastConfirmation(): void;
|
|
105
|
+
/** Pause the fast confirmation rule (e.g. while syncing); pins the confirmed root to the finalized root */
|
|
106
|
+
pauseFastConfirmation(): void;
|
|
112
107
|
updateAndGetHead(mode: UpdateAndGetHeadOpt): {
|
|
113
108
|
head: ProtoBlock;
|
|
114
109
|
isHeadTimely?: boolean;
|
|
@@ -160,8 +155,7 @@ export interface IForkChoice {
|
|
|
160
155
|
blockDelaySec: number,
|
|
161
156
|
currentSlot: Slot,
|
|
162
157
|
executionStatus: BlockExecutionStatus,
|
|
163
|
-
dataAvailabilityStatus: DataAvailabilityStatus
|
|
164
|
-
expectedProposerIndex: ValidatorIndex | null
|
|
158
|
+
dataAvailabilityStatus: DataAvailabilityStatus
|
|
165
159
|
): ProtoBlock;
|
|
166
160
|
/**
|
|
167
161
|
* Register `attestation` with the fork choice DAG so that it may influence future calls to `getHead`.
|
|
@@ -252,6 +246,11 @@ export interface IForkChoice {
|
|
|
252
246
|
hasPayloadUnsafe(blockRoot: Root): boolean;
|
|
253
247
|
hasPayloadHexUnsafe(blockRoot: RootHex): boolean;
|
|
254
248
|
getSlotsPresent(windowStart: number): number;
|
|
249
|
+
/**
|
|
250
|
+
* Count gloas blocks with fromSlot <= slot <= toSlot and how many of them have a revealed
|
|
251
|
+
* payload (FULL variant exists). Used by the builder circuit breaker.
|
|
252
|
+
*/
|
|
253
|
+
getPayloadRevealCounts(fromSlot: Slot, toSlot: Slot): {blocksPresent: number; payloadsRevealed: number};
|
|
255
254
|
getPTCVotes(blockRootHex: RootHex): (boolean | null)[] | null;
|
|
256
255
|
/** Raw PTC vote tallies for the debug fork choice endpoint; `null` for pre-Gloas roots. */
|
|
257
256
|
getPTCVoteCounts(blockRootHex: RootHex): {
|
package/src/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ export {
|
|
|
17
17
|
type IFastConfirmationStore,
|
|
18
18
|
getFastConfirmationMetrics,
|
|
19
19
|
} from "./forkChoice/fastConfirmation/fastConfirmationRule.ts";
|
|
20
|
-
export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt} from "./forkChoice/forkChoice.js";
|
|
20
|
+
export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt, getCommitteeFraction} from "./forkChoice/forkChoice.js";
|
|
21
21
|
export {
|
|
22
22
|
type AncestorResult,
|
|
23
23
|
AncestorStatus,
|
|
@@ -4,10 +4,10 @@ import {ProtoArrayError, ProtoArrayErrorCode} from "./errors.js";
|
|
|
4
4
|
import {NULL_VOTE_INDEX, VoteIndex} from "./interface.js";
|
|
5
5
|
|
|
6
6
|
// reuse arrays to avoid memory reallocation and gc
|
|
7
|
-
const
|
|
7
|
+
const attestationDeltas = new Array<number>();
|
|
8
8
|
|
|
9
9
|
export type DeltasResult = {
|
|
10
|
-
|
|
10
|
+
attestationDeltas: number[];
|
|
11
11
|
equivocatingValidators: number;
|
|
12
12
|
// inactive validators before beacon node started
|
|
13
13
|
oldInactiveValidators: number;
|
|
@@ -19,9 +19,9 @@ export type DeltasResult = {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Returns a list of `
|
|
22
|
+
* Returns a list of `attestationDeltas`, where there is one delta for each of the indices in `indices`
|
|
23
23
|
*
|
|
24
|
-
* The
|
|
24
|
+
* The attestationDeltas are formed by a change between `oldBalances` and `newBalances`, and/or a change of vote in `votes`.
|
|
25
25
|
*
|
|
26
26
|
* ## Errors
|
|
27
27
|
*
|
|
@@ -46,8 +46,8 @@ export function computeDeltas(
|
|
|
46
46
|
throw new Error(`numProtoNodes must be less than NULL_VOTE_INDEX: ${numProtoNodes} >= ${NULL_VOTE_INDEX}`);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
attestationDeltas.length = numProtoNodes;
|
|
50
|
+
attestationDeltas.fill(0);
|
|
51
51
|
|
|
52
52
|
// avoid creating new variables in the loop to potentially reduce GC pressure
|
|
53
53
|
let oldBalance: number, newBalance: number;
|
|
@@ -80,7 +80,7 @@ export function computeDeltas(
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
oldBalance = oldBalances[vIndex] ?? 0;
|
|
83
|
-
|
|
83
|
+
attestationDeltas[currentIndex] -= oldBalance;
|
|
84
84
|
}
|
|
85
85
|
voteCurrentIndices[vIndex] = NULL_VOTE_INDEX;
|
|
86
86
|
equivocatingIndex++;
|
|
@@ -125,7 +125,7 @@ export function computeDeltas(
|
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
attestationDeltas[currentIndex] -= oldBalance;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// We ignore the vote if it is not known in `indices .
|
|
@@ -138,7 +138,7 @@ export function computeDeltas(
|
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
attestationDeltas[nextIndex] += newBalance;
|
|
142
142
|
}
|
|
143
143
|
voteCurrentIndices[vIndex] = nextIndex;
|
|
144
144
|
newVoteValidators++;
|
|
@@ -147,13 +147,13 @@ export function computeDeltas(
|
|
|
147
147
|
}
|
|
148
148
|
} // end validator loop
|
|
149
149
|
|
|
150
|
-
if (
|
|
151
|
-
//
|
|
152
|
-
throw new Error(`
|
|
150
|
+
if (attestationDeltas.length !== numProtoNodes) {
|
|
151
|
+
// attestationDeltas array could be growed in the loop, especially if we mistakenly set the [NULL_VOTE_INDEX] to it , just to be safe
|
|
152
|
+
throw new Error(`attestationDeltas length mismatch: expected ${numProtoNodes}, got ${attestationDeltas.length}`);
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
return {
|
|
156
|
-
|
|
156
|
+
attestationDeltas,
|
|
157
157
|
equivocatingValidators,
|
|
158
158
|
oldInactiveValidators,
|
|
159
159
|
newInactiveValidators,
|
|
@@ -160,7 +160,13 @@ export type ProtoBlock = BlockExtraMeta & {
|
|
|
160
160
|
*/
|
|
161
161
|
export type ProtoNode = ProtoBlock & {
|
|
162
162
|
parent?: number;
|
|
163
|
+
/** Total weight, ie. attestationScore plus the proposer boost credited to this node */
|
|
163
164
|
weight: number;
|
|
165
|
+
/**
|
|
166
|
+
* Weight from attester votes only, excluding proposer boost.
|
|
167
|
+
* Spec: get_attestation_score
|
|
168
|
+
*/
|
|
169
|
+
attestationScore: number;
|
|
164
170
|
bestChild?: number;
|
|
165
171
|
bestDescendant?: number;
|
|
166
172
|
};
|