@lodestar/fork-choice 1.45.0-dev.0cff4e6916 → 1.45.0-dev.17a48050e6
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/data.d.ts.map +1 -1
- package/lib/forkChoice/fastConfirmation/data.js +1 -0
- package/lib/forkChoice/fastConfirmation/data.js.map +1 -1
- package/lib/forkChoice/fastConfirmation/types.d.ts +3 -0
- package/lib/forkChoice/fastConfirmation/types.d.ts.map +1 -1
- package/lib/forkChoice/fastConfirmation/types.js.map +1 -1
- package/lib/forkChoice/fastConfirmation/utils.d.ts +3 -3
- package/lib/forkChoice/fastConfirmation/utils.d.ts.map +1 -1
- package/lib/forkChoice/fastConfirmation/utils.js +27 -19
- package/lib/forkChoice/fastConfirmation/utils.js.map +1 -1
- package/lib/forkChoice/forkChoice.d.ts +11 -7
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +9 -12
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +10 -2
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/forkChoice/interface.js.map +1 -1
- package/lib/protoArray/protoArray.d.ts +8 -0
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +28 -0
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +8 -8
- package/src/forkChoice/fastConfirmation/data.ts +1 -0
- package/src/forkChoice/fastConfirmation/types.ts +4 -0
- package/src/forkChoice/fastConfirmation/utils.ts +39 -16
- package/src/forkChoice/forkChoice.ts +10 -12
- package/src/forkChoice/interface.ts +7 -12
- package/src/protoArray/protoArray.ts +29 -0
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/ChainSafe/lodestar/issues"
|
|
13
13
|
},
|
|
14
|
-
"version": "1.45.0-dev.
|
|
14
|
+
"version": "1.45.0-dev.17a48050e6",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
"check-readme": "pnpm exec ts-node ../../scripts/check_readme.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@chainsafe/ssz": "^1.
|
|
43
|
-
"@lodestar/config": "^1.45.0-dev.
|
|
44
|
-
"@lodestar/params": "^1.45.0-dev.
|
|
45
|
-
"@lodestar/state-transition": "^1.45.0-dev.
|
|
46
|
-
"@lodestar/types": "^1.45.0-dev.
|
|
47
|
-
"@lodestar/utils": "^1.45.0-dev.
|
|
42
|
+
"@chainsafe/ssz": "^1.6.2",
|
|
43
|
+
"@lodestar/config": "^1.45.0-dev.17a48050e6",
|
|
44
|
+
"@lodestar/params": "^1.45.0-dev.17a48050e6",
|
|
45
|
+
"@lodestar/state-transition": "^1.45.0-dev.17a48050e6",
|
|
46
|
+
"@lodestar/types": "^1.45.0-dev.17a48050e6",
|
|
47
|
+
"@lodestar/utils": "^1.45.0-dev.17a48050e6"
|
|
48
48
|
},
|
|
49
49
|
"keywords": [
|
|
50
50
|
"ethereum",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"beacon",
|
|
53
53
|
"blockchain"
|
|
54
54
|
],
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "740e743dd01a58366a3bce4bc843678fd447a6f0"
|
|
56
56
|
}
|
|
@@ -9,6 +9,8 @@ export type FastConfirmationBalanceSource = {
|
|
|
9
9
|
balances: EffectiveBalanceIncrements;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
export type TotalActiveBalanceCacheKey = IBeaconStateView | EffectiveBalanceIncrements;
|
|
13
|
+
|
|
12
14
|
export type ForkChoiceStateGetter = (
|
|
13
15
|
opts: {stateRoot: RootHex; checkpoint?: never} | {stateRoot?: never; checkpoint: CheckpointWithHex}
|
|
14
16
|
) => IBeaconStateView | null;
|
|
@@ -109,6 +111,8 @@ export type FastConfirmationCache = {
|
|
|
109
111
|
isDescendantByRootPair: Map<string, boolean>;
|
|
110
112
|
/** voteRoot -> totalWeight, keyed by sourceKey */
|
|
111
113
|
voteWeightBySource: Map<BalanceSourceKey, Map<RootHex, number>>;
|
|
114
|
+
/** total active balance, keyed by the balance source's state or balances reference */
|
|
115
|
+
totalActiveBalanceByKey: Map<TotalActiveBalanceCacheKey, number>;
|
|
112
116
|
headState?: IBeaconStateView;
|
|
113
117
|
pulledUpHeadState?: IBeaconStateView;
|
|
114
118
|
checkpointStateByKey: Map<string, IBeaconStateView | null>;
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
FastConfirmationContext,
|
|
19
19
|
FastConfirmationSnapshot,
|
|
20
20
|
IFastConfirmationStore,
|
|
21
|
+
type TotalActiveBalanceCacheKey,
|
|
21
22
|
} from "./types.ts";
|
|
22
23
|
|
|
23
24
|
// Spec: adjust_committee_weight_estimate_to_ensure_safety
|
|
@@ -266,22 +267,34 @@ export function getPreviousBalanceSource(
|
|
|
266
267
|
return getBalanceSource(store, cache, "previous");
|
|
267
268
|
}
|
|
268
269
|
|
|
269
|
-
export function getTotalActiveBalance(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
|
|
270
|
+
export function getTotalActiveBalance(
|
|
271
|
+
balanceSource: FastConfirmationBalanceSource,
|
|
272
|
+
cache: FastConfirmationCache
|
|
273
|
+
): number {
|
|
274
|
+
// Invariant per balance source within a run, but read many times per is_one_confirmed evaluation
|
|
275
|
+
// across the epoch-boundary chain walk; memoize so the full validator-set scan runs once per run.
|
|
276
|
+
const key: TotalActiveBalanceCacheKey = balanceSource.state ?? balanceSource.balances;
|
|
277
|
+
const cached = cache.totalActiveBalanceByKey.get(key);
|
|
278
|
+
if (cached !== undefined) return cached;
|
|
279
|
+
|
|
280
|
+
const total = balanceSource.state
|
|
281
|
+
? computeTotalBalance(balanceSource.state.getEffectiveBalanceIncrementsZeroInactive())
|
|
282
|
+
: // Fallback balances come from the justified-balance path and already zero inactive
|
|
283
|
+
// validators, so summing them gives the active justified total for this balance source.
|
|
284
|
+
computeTotalBalance(balanceSource.balances);
|
|
285
|
+
|
|
286
|
+
cache.totalActiveBalanceByKey.set(key, total);
|
|
287
|
+
return total;
|
|
276
288
|
}
|
|
277
289
|
|
|
278
290
|
export function estimateCommitteeWeightBetweenSlots(
|
|
279
291
|
balanceSource: FastConfirmationBalanceSource,
|
|
292
|
+
cache: FastConfirmationCache,
|
|
280
293
|
startSlot: Slot,
|
|
281
294
|
endSlot: Slot
|
|
282
295
|
): number {
|
|
283
296
|
if (startSlot > endSlot) return 0;
|
|
284
|
-
const totalActiveBalance = getTotalActiveBalance(balanceSource);
|
|
297
|
+
const totalActiveBalance = getTotalActiveBalance(balanceSource, cache);
|
|
285
298
|
const startEpoch = computeEpochAtSlot(startSlot);
|
|
286
299
|
const endEpoch = computeEpochAtSlot(endSlot);
|
|
287
300
|
|
|
@@ -334,9 +347,10 @@ export function isFullValidatorSetCovered(startSlot: Slot, endSlot: Slot): boole
|
|
|
334
347
|
|
|
335
348
|
export function computeProposerScore(
|
|
336
349
|
ctx: FastConfirmationContext,
|
|
337
|
-
balanceSource: FastConfirmationBalanceSource
|
|
350
|
+
balanceSource: FastConfirmationBalanceSource,
|
|
351
|
+
cache: FastConfirmationCache
|
|
338
352
|
): number {
|
|
339
|
-
const totalActiveBalance = getTotalActiveBalance(balanceSource);
|
|
353
|
+
const totalActiveBalance = getTotalActiveBalance(balanceSource, cache);
|
|
340
354
|
const committeeWeight = Math.floor(totalActiveBalance / SLOTS_PER_EPOCH);
|
|
341
355
|
return Math.floor((committeeWeight * ctx.config.PROPOSER_SCORE_BOOST) / 100);
|
|
342
356
|
}
|
|
@@ -469,7 +483,7 @@ export function computeAdversarialWeight(
|
|
|
469
483
|
startSlot: Slot,
|
|
470
484
|
endSlot: Slot
|
|
471
485
|
): number {
|
|
472
|
-
const maximumWeight = estimateCommitteeWeightBetweenSlots(balanceSource, startSlot, endSlot);
|
|
486
|
+
const maximumWeight = estimateCommitteeWeightBetweenSlots(balanceSource, cache, startSlot, endSlot);
|
|
473
487
|
// The spec uses raw Gwei and computes `maximum_weight // 100 * threshold`.
|
|
474
488
|
// Lodestar carries effective-balance increments instead, so divide after multiplying to avoid
|
|
475
489
|
// dropping the adversarial budget to zero for small validator sets/minimal presets.
|
|
@@ -564,9 +578,10 @@ export function computeSafetyThreshold(
|
|
|
564
578
|
// Spec: compute_safety_threshold(store, block_root, balance_source)
|
|
565
579
|
// Build the threshold from the same terms used in the paper/spec:
|
|
566
580
|
// max possible committee support, proposer boost, empty-slot discount, and adversarial budget.
|
|
567
|
-
const proposerScore = computeProposerScore(ctx, balanceSource);
|
|
581
|
+
const proposerScore = computeProposerScore(ctx, balanceSource, cache);
|
|
568
582
|
const maximumSupport = estimateCommitteeWeightBetweenSlots(
|
|
569
583
|
balanceSource,
|
|
584
|
+
cache,
|
|
570
585
|
(parentBlock.slot + 1) as Slot,
|
|
571
586
|
(currentSlot - 1) as Slot
|
|
572
587
|
);
|
|
@@ -711,10 +726,12 @@ export function computeHonestFfgSupportForCurrentTarget(
|
|
|
711
726
|
const currentEpoch = computeEpochAtSlot(currentSlot);
|
|
712
727
|
const targetState = getCurrentEpochState(ctx, store, cache);
|
|
713
728
|
if (!targetState) return 0;
|
|
714
|
-
const
|
|
729
|
+
const targetBalanceSource = {state: targetState, balances: targetState.effectiveBalanceIncrements};
|
|
730
|
+
const totalActiveBalance = getTotalActiveBalance(targetBalanceSource, cache);
|
|
715
731
|
const ffgSupport = getCurrentTargetScore(ctx, store, cache);
|
|
716
732
|
const tillNowFFGWeight = estimateCommitteeWeightBetweenSlots(
|
|
717
|
-
|
|
733
|
+
targetBalanceSource,
|
|
734
|
+
cache,
|
|
718
735
|
computeStartSlotAtEpoch(currentEpoch),
|
|
719
736
|
(currentSlot - 1) as Slot
|
|
720
737
|
);
|
|
@@ -747,7 +764,10 @@ export function willNoConflictingCheckpointBeJustified(
|
|
|
747
764
|
}
|
|
748
765
|
const targetState = getCurrentEpochState(ctx, store, cache);
|
|
749
766
|
if (!targetState) return false;
|
|
750
|
-
const totalActiveBalance =
|
|
767
|
+
const totalActiveBalance = getTotalActiveBalance(
|
|
768
|
+
{state: targetState, balances: targetState.effectiveBalanceIncrements},
|
|
769
|
+
cache
|
|
770
|
+
);
|
|
751
771
|
const honestSupport = computeHonestFfgSupportForCurrentTarget(ctx, store, cache);
|
|
752
772
|
return 3 * honestSupport > 1 * totalActiveBalance;
|
|
753
773
|
}
|
|
@@ -759,7 +779,10 @@ export function willCurrentTargetBeJustified(
|
|
|
759
779
|
): boolean {
|
|
760
780
|
const targetState = getCurrentEpochState(ctx, store, cache);
|
|
761
781
|
if (!targetState) return false;
|
|
762
|
-
const totalActiveBalance =
|
|
782
|
+
const totalActiveBalance = getTotalActiveBalance(
|
|
783
|
+
{state: targetState, balances: targetState.effectiveBalanceIncrements},
|
|
784
|
+
cache
|
|
785
|
+
);
|
|
763
786
|
const honestSupport = computeHonestFfgSupportForCurrentTarget(ctx, store, cache);
|
|
764
787
|
return 3 * honestSupport >= 2 * totalActiveBalance;
|
|
765
788
|
}
|
|
@@ -589,6 +589,10 @@ export class ForkChoice implements IForkChoice {
|
|
|
589
589
|
return this.protoArray.nodes.filter((node) => node.slot > windowStart).length;
|
|
590
590
|
}
|
|
591
591
|
|
|
592
|
+
getPayloadRevealCounts(fromSlot: Slot, toSlot: Slot): {blocksPresent: number; payloadsRevealed: number} {
|
|
593
|
+
return this.protoArray.getPayloadRevealCounts(fromSlot, toSlot);
|
|
594
|
+
}
|
|
595
|
+
|
|
592
596
|
/** Very expensive function, iterates the entire ProtoArray. Called only in debug API */
|
|
593
597
|
getHeads(): ProtoBlock[] {
|
|
594
598
|
return this.protoArray.nodes.filter((node) => node.bestChild === undefined);
|
|
@@ -632,11 +636,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
632
636
|
blockDelaySec: number,
|
|
633
637
|
currentSlot: Slot,
|
|
634
638
|
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
|
|
639
|
+
dataAvailabilityStatus: DataAvailabilityStatus
|
|
640
640
|
): ProtoBlock {
|
|
641
641
|
const {parentRoot, slot} = block;
|
|
642
642
|
const parentRootHex = toRootHex(parentRoot);
|
|
@@ -711,8 +711,6 @@ export class ForkChoice implements IForkChoice {
|
|
|
711
711
|
isTimely &&
|
|
712
712
|
// only boost the first block we see
|
|
713
713
|
this.proposerBoostRoot === null &&
|
|
714
|
-
expectedProposerIndex !== null &&
|
|
715
|
-
block.proposerIndex === expectedProposerIndex &&
|
|
716
714
|
this.isProposerBoostSameDependentRoot(this.head.blockRoot, parentRootHex);
|
|
717
715
|
// Candidate boost root used for protoArray.onBlock's best-child weighting. Committed to the
|
|
718
716
|
// store only after the insertion succeeds.
|
|
@@ -1505,11 +1503,11 @@ export class ForkChoice implements IForkChoice {
|
|
|
1505
1503
|
}
|
|
1506
1504
|
|
|
1507
1505
|
/**
|
|
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.
|
|
1506
|
+
* Spec: phase0/fork-choice.md#update_proposer_boost_root (`is_same_dependent_root` condition, added in
|
|
1507
|
+
* https://github.com/ethereum/consensus-specs/pull/5306). Proposer boost is only granted when the imported
|
|
1508
|
+
* block shares the same proposer-shuffling dependent root for the current epoch as the canonical head
|
|
1509
|
+
* computed before the block was imported. This withholds the boost from a block built on a different
|
|
1510
|
+
* shuffling branch than the head.
|
|
1513
1511
|
*
|
|
1514
1512
|
* The block is not yet in the proto-array when this runs, so its dependent root is traced from
|
|
1515
1513
|
* its parent
|
|
@@ -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,
|
|
@@ -160,8 +151,7 @@ export interface IForkChoice {
|
|
|
160
151
|
blockDelaySec: number,
|
|
161
152
|
currentSlot: Slot,
|
|
162
153
|
executionStatus: BlockExecutionStatus,
|
|
163
|
-
dataAvailabilityStatus: DataAvailabilityStatus
|
|
164
|
-
expectedProposerIndex: ValidatorIndex | null
|
|
154
|
+
dataAvailabilityStatus: DataAvailabilityStatus
|
|
165
155
|
): ProtoBlock;
|
|
166
156
|
/**
|
|
167
157
|
* Register `attestation` with the fork choice DAG so that it may influence future calls to `getHead`.
|
|
@@ -252,6 +242,11 @@ export interface IForkChoice {
|
|
|
252
242
|
hasPayloadUnsafe(blockRoot: Root): boolean;
|
|
253
243
|
hasPayloadHexUnsafe(blockRoot: RootHex): boolean;
|
|
254
244
|
getSlotsPresent(windowStart: number): number;
|
|
245
|
+
/**
|
|
246
|
+
* Count gloas blocks with fromSlot <= slot <= toSlot and how many of them have a revealed
|
|
247
|
+
* payload (FULL variant exists). Used by the builder circuit breaker.
|
|
248
|
+
*/
|
|
249
|
+
getPayloadRevealCounts(fromSlot: Slot, toSlot: Slot): {blocksPresent: number; payloadsRevealed: number};
|
|
255
250
|
getPTCVotes(blockRootHex: RootHex): (boolean | null)[] | null;
|
|
256
251
|
/** Raw PTC vote tallies for the debug fork choice endpoint; `null` for pre-Gloas roots. */
|
|
257
252
|
getPTCVoteCounts(blockRootHex: RootHex): {
|
|
@@ -672,6 +672,35 @@ export class ProtoArray {
|
|
|
672
672
|
this.maybeUpdateBestChildAndDescendant(pendingIndex, fullIndex, currentSlot, proposerBoostRoot);
|
|
673
673
|
}
|
|
674
674
|
|
|
675
|
+
/**
|
|
676
|
+
* Count gloas blocks with fromSlot <= slot <= toSlot and how many of them have a revealed
|
|
677
|
+
* payload (FULL variant exists). Used by the builder circuit breaker.
|
|
678
|
+
*/
|
|
679
|
+
getPayloadRevealCounts(fromSlot: Slot, toSlot: Slot): {blocksPresent: number; payloadsRevealed: number} {
|
|
680
|
+
let blocksPresent = 0;
|
|
681
|
+
let payloadsRevealed = 0;
|
|
682
|
+
// Iterate backward as recent blocks are at the end of the nodes array. Only break on block
|
|
683
|
+
// nodes below the window since FULL variants are appended late when the envelope is imported
|
|
684
|
+
for (let i = this.nodes.length - 1; i >= 0; i--) {
|
|
685
|
+
const node = this.nodes[i];
|
|
686
|
+
if (node.slot < fromSlot) {
|
|
687
|
+
if (isGloasBlock(node) && node.payloadStatus === PayloadStatus.FULL) {
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
// Count each gloas block once via its PENDING variant, pre-gloas nodes are FULL only
|
|
693
|
+
if (node.slot > toSlot || node.payloadStatus !== PayloadStatus.PENDING) {
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
blocksPresent++;
|
|
697
|
+
if (this.hasPayload(node.blockRoot)) {
|
|
698
|
+
payloadsRevealed++;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return {blocksPresent, payloadsRevealed};
|
|
702
|
+
}
|
|
703
|
+
|
|
675
704
|
/**
|
|
676
705
|
* Update PTC votes for multiple validators attesting to a block
|
|
677
706
|
* Spec: gloas/fork-choice.md#new-notify_ptc_messages
|