@lodestar/state-transition 1.39.0-dev.95389042f8 → 1.39.0-dev.ad129ced66
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/block/isValidIndexedAttestation.d.ts +4 -5
- package/lib/block/isValidIndexedAttestation.d.ts.map +1 -1
- package/lib/block/isValidIndexedAttestation.js +9 -10
- package/lib/block/isValidIndexedAttestation.js.map +1 -1
- package/lib/block/processAttestationPhase0.d.ts.map +1 -1
- package/lib/block/processAttestationPhase0.js +1 -1
- package/lib/block/processAttestationPhase0.js.map +1 -1
- package/lib/block/processAttesterSlashing.d.ts +3 -2
- package/lib/block/processAttesterSlashing.d.ts.map +1 -1
- package/lib/block/processAttesterSlashing.js +3 -3
- package/lib/block/processAttesterSlashing.js.map +1 -1
- package/lib/block/processBlsToExecutionChange.d.ts +3 -1
- package/lib/block/processBlsToExecutionChange.d.ts.map +1 -1
- package/lib/block/processBlsToExecutionChange.js +7 -11
- package/lib/block/processBlsToExecutionChange.js.map +1 -1
- package/lib/block/processProposerSlashing.d.ts +5 -2
- package/lib/block/processProposerSlashing.d.ts.map +1 -1
- package/lib/block/processProposerSlashing.js +7 -5
- package/lib/block/processProposerSlashing.js.map +1 -1
- package/lib/cache/epochCache.d.ts +6 -12
- package/lib/cache/epochCache.d.ts.map +1 -1
- package/lib/cache/epochCache.js +33 -105
- package/lib/cache/epochCache.js.map +1 -1
- package/lib/cache/epochTransitionCache.d.ts +5 -12
- package/lib/cache/epochTransitionCache.d.ts.map +1 -1
- package/lib/cache/epochTransitionCache.js +4 -14
- package/lib/cache/epochTransitionCache.js.map +1 -1
- package/lib/cache/stateCache.d.ts.map +1 -1
- package/lib/cache/stateCache.js +1 -2
- package/lib/cache/stateCache.js.map +1 -1
- package/lib/epoch/processProposerLookahead.d.ts.map +1 -1
- package/lib/epoch/processProposerLookahead.js +3 -6
- package/lib/epoch/processProposerLookahead.js.map +1 -1
- package/lib/rewards/blockRewards.d.ts +2 -1
- package/lib/rewards/blockRewards.d.ts.map +1 -1
- package/lib/rewards/blockRewards.js +3 -2
- package/lib/rewards/blockRewards.js.map +1 -1
- package/lib/rewards/syncCommitteeRewards.d.ts.map +1 -1
- package/lib/rewards/syncCommitteeRewards.js +10 -11
- package/lib/rewards/syncCommitteeRewards.js.map +1 -1
- package/lib/signatureSets/blsToExecutionChange.d.ts +1 -2
- package/lib/signatureSets/blsToExecutionChange.d.ts.map +1 -1
- package/lib/signatureSets/blsToExecutionChange.js +2 -2
- package/lib/signatureSets/blsToExecutionChange.js.map +1 -1
- package/lib/types.d.ts +1 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/util/epochShuffling.d.ts +1 -34
- package/lib/util/epochShuffling.d.ts.map +1 -1
- package/lib/util/epochShuffling.js +1 -1
- package/lib/util/epochShuffling.js.map +1 -1
- package/package.json +7 -7
- package/src/block/isValidIndexedAttestation.ts +17 -12
- package/src/block/processAttestationPhase0.ts +2 -1
- package/src/block/processAttesterSlashing.ts +16 -4
- package/src/block/processBlsToExecutionChange.ts +13 -14
- package/src/block/processProposerSlashing.ts +21 -11
- package/src/cache/epochCache.ts +40 -118
- package/src/cache/epochTransitionCache.ts +9 -34
- package/src/cache/stateCache.ts +1 -2
- package/src/epoch/processProposerLookahead.ts +3 -7
- package/src/rewards/blockRewards.ts +6 -3
- package/src/rewards/syncCommitteeRewards.ts +10 -13
- package/src/signatureSets/blsToExecutionChange.ts +2 -3
- package/src/types.ts +1 -0
- package/src/util/epochShuffling.ts +2 -43
package/src/cache/epochCache.ts
CHANGED
|
@@ -32,7 +32,7 @@ import {getTotalSlashingsByIncrement} from "../epoch/processSlashings.js";
|
|
|
32
32
|
import {AttesterDuty, calculateCommitteeAssignments} from "../util/calculateCommitteeAssignments.js";
|
|
33
33
|
import {
|
|
34
34
|
EpochShuffling,
|
|
35
|
-
|
|
35
|
+
calculateDecisionRoot,
|
|
36
36
|
calculateShufflingDecisionRoot,
|
|
37
37
|
computeEpochShuffling,
|
|
38
38
|
} from "../util/epochShuffling.js";
|
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
computeSyncCommitteeCache,
|
|
62
62
|
getSyncCommitteeCache,
|
|
63
63
|
} from "./syncCommitteeCache.js";
|
|
64
|
-
import {BeaconStateAllForks, BeaconStateAltair, BeaconStateGloas} from "./types.js";
|
|
64
|
+
import {BeaconStateAllForks, BeaconStateAltair, BeaconStateGloas, ShufflingGetter} from "./types.js";
|
|
65
65
|
|
|
66
66
|
/** `= PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)` */
|
|
67
67
|
export const PROPOSER_WEIGHT_FACTOR = PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT);
|
|
@@ -70,12 +70,12 @@ export type EpochCacheImmutableData = {
|
|
|
70
70
|
config: BeaconConfig;
|
|
71
71
|
pubkey2index: PubkeyIndexMap;
|
|
72
72
|
index2pubkey: Index2PubkeyCache;
|
|
73
|
-
shufflingCache?: IShufflingCache;
|
|
74
73
|
};
|
|
75
74
|
|
|
76
75
|
export type EpochCacheOpts = {
|
|
77
76
|
skipSyncCommitteeCache?: boolean;
|
|
78
77
|
skipSyncPubkeys?: boolean;
|
|
78
|
+
shufflingGetter?: ShufflingGetter;
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
/** Defers computing proposers by persisting only the seed, and dropping it once indexes are computed */
|
|
@@ -117,12 +117,6 @@ export class EpochCache {
|
|
|
117
117
|
* $VALIDATOR_COUNT x BLST deserialized pubkey (Jacobian coordinates)
|
|
118
118
|
*/
|
|
119
119
|
index2pubkey: Index2PubkeyCache;
|
|
120
|
-
/**
|
|
121
|
-
* ShufflingCache is passed in from `beacon-node` so should be available at runtime but may not be
|
|
122
|
-
* present during testing.
|
|
123
|
-
*/
|
|
124
|
-
shufflingCache?: IShufflingCache;
|
|
125
|
-
|
|
126
120
|
/**
|
|
127
121
|
* Indexes of the block proposers for the current epoch.
|
|
128
122
|
* For pre-fulu, this is computed and cached from the current shuffling.
|
|
@@ -161,7 +155,7 @@ export class EpochCache {
|
|
|
161
155
|
/** Same as previousShuffling */
|
|
162
156
|
currentShuffling: EpochShuffling;
|
|
163
157
|
/** Same as previousShuffling */
|
|
164
|
-
nextShuffling: EpochShuffling
|
|
158
|
+
nextShuffling: EpochShuffling;
|
|
165
159
|
/**
|
|
166
160
|
* Cache nextActiveIndices so that in afterProcessEpoch the next shuffling can be build synchronously
|
|
167
161
|
* in case it is not built or the ShufflingCache is not available
|
|
@@ -254,7 +248,6 @@ export class EpochCache {
|
|
|
254
248
|
config: BeaconConfig;
|
|
255
249
|
pubkey2index: PubkeyIndexMap;
|
|
256
250
|
index2pubkey: Index2PubkeyCache;
|
|
257
|
-
shufflingCache?: IShufflingCache;
|
|
258
251
|
proposers: number[];
|
|
259
252
|
proposersPrevEpoch: number[] | null;
|
|
260
253
|
proposersNextEpoch: ProposersDeferred;
|
|
@@ -263,7 +256,7 @@ export class EpochCache {
|
|
|
263
256
|
nextDecisionRoot: RootHex;
|
|
264
257
|
previousShuffling: EpochShuffling;
|
|
265
258
|
currentShuffling: EpochShuffling;
|
|
266
|
-
nextShuffling: EpochShuffling
|
|
259
|
+
nextShuffling: EpochShuffling;
|
|
267
260
|
nextActiveIndices: Uint32Array;
|
|
268
261
|
effectiveBalanceIncrements: EffectiveBalanceIncrements;
|
|
269
262
|
totalSlashingsByIncrement: number;
|
|
@@ -286,7 +279,6 @@ export class EpochCache {
|
|
|
286
279
|
this.config = data.config;
|
|
287
280
|
this.pubkey2index = data.pubkey2index;
|
|
288
281
|
this.index2pubkey = data.index2pubkey;
|
|
289
|
-
this.shufflingCache = data.shufflingCache;
|
|
290
282
|
this.proposers = data.proposers;
|
|
291
283
|
this.proposersPrevEpoch = data.proposersPrevEpoch;
|
|
292
284
|
this.proposersNextEpoch = data.proposersNextEpoch;
|
|
@@ -324,7 +316,7 @@ export class EpochCache {
|
|
|
324
316
|
*/
|
|
325
317
|
static createFromState(
|
|
326
318
|
state: BeaconStateAllForks,
|
|
327
|
-
{config, pubkey2index, index2pubkey
|
|
319
|
+
{config, pubkey2index, index2pubkey}: EpochCacheImmutableData,
|
|
328
320
|
opts?: EpochCacheOpts
|
|
329
321
|
): EpochCache {
|
|
330
322
|
const currentEpoch = computeEpochAtSlot(state.slot);
|
|
@@ -351,14 +343,15 @@ export class EpochCache {
|
|
|
351
343
|
const currentActiveIndicesAsNumberArray: ValidatorIndex[] = [];
|
|
352
344
|
const nextActiveIndicesAsNumberArray: ValidatorIndex[] = [];
|
|
353
345
|
|
|
354
|
-
// BeaconChain could provide a shuffling
|
|
346
|
+
// BeaconChain could provide a shuffling getter to avoid re-computing shuffling every epoch
|
|
355
347
|
// in that case, we don't need to compute shufflings again
|
|
348
|
+
const shufflingGetter = opts?.shufflingGetter;
|
|
356
349
|
const previousDecisionRoot = calculateShufflingDecisionRoot(config, state, previousEpoch);
|
|
357
|
-
const cachedPreviousShuffling =
|
|
350
|
+
const cachedPreviousShuffling = shufflingGetter?.(previousEpoch, previousDecisionRoot);
|
|
358
351
|
const currentDecisionRoot = calculateShufflingDecisionRoot(config, state, currentEpoch);
|
|
359
|
-
const cachedCurrentShuffling =
|
|
352
|
+
const cachedCurrentShuffling = shufflingGetter?.(currentEpoch, currentDecisionRoot);
|
|
360
353
|
const nextDecisionRoot = calculateShufflingDecisionRoot(config, state, nextEpoch);
|
|
361
|
-
const cachedNextShuffling =
|
|
354
|
+
const cachedNextShuffling = shufflingGetter?.(nextEpoch, nextDecisionRoot);
|
|
362
355
|
|
|
363
356
|
for (let i = 0; i < validatorCount; i++) {
|
|
364
357
|
const validator = validators[i];
|
|
@@ -366,8 +359,7 @@ export class EpochCache {
|
|
|
366
359
|
// Note: Not usable for fork-choice balances since in-active validators are not zero'ed
|
|
367
360
|
effectiveBalanceIncrements[i] = Math.floor(validator.effectiveBalance / EFFECTIVE_BALANCE_INCREMENT);
|
|
368
361
|
|
|
369
|
-
//
|
|
370
|
-
// skip doing that if we already have cached shufflings
|
|
362
|
+
// Collect active indices for each epoch to compute shufflings
|
|
371
363
|
if (cachedPreviousShuffling == null && isActiveValidator(validator, previousEpoch)) {
|
|
372
364
|
previousActiveIndicesAsNumberArray.push(i);
|
|
373
365
|
}
|
|
@@ -402,47 +394,19 @@ export class EpochCache {
|
|
|
402
394
|
}
|
|
403
395
|
|
|
404
396
|
const nextActiveIndices = new Uint32Array(nextActiveIndicesAsNumberArray);
|
|
405
|
-
let previousShuffling: EpochShuffling;
|
|
406
|
-
let currentShuffling: EpochShuffling;
|
|
407
|
-
let nextShuffling: EpochShuffling;
|
|
408
|
-
|
|
409
|
-
if (!shufflingCache) {
|
|
410
|
-
// Only for testing. shufflingCache should always be available in prod
|
|
411
|
-
previousShuffling = computeEpochShuffling(
|
|
412
|
-
state,
|
|
413
|
-
new Uint32Array(previousActiveIndicesAsNumberArray),
|
|
414
|
-
previousEpoch
|
|
415
|
-
);
|
|
416
397
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
398
|
+
// Use cached shufflings if available, otherwise compute
|
|
399
|
+
const currentShuffling =
|
|
400
|
+
cachedCurrentShuffling ??
|
|
401
|
+
computeEpochShuffling(state, new Uint32Array(currentActiveIndicesAsNumberArray), currentEpoch);
|
|
420
402
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
?
|
|
425
|
-
:
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
previousShuffling = cachedPreviousShuffling
|
|
431
|
-
? cachedPreviousShuffling
|
|
432
|
-
: isGenesis
|
|
433
|
-
? currentShuffling
|
|
434
|
-
: shufflingCache.getSync(previousEpoch, previousDecisionRoot, {
|
|
435
|
-
state,
|
|
436
|
-
activeIndices: new Uint32Array(previousActiveIndicesAsNumberArray),
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
nextShuffling = cachedNextShuffling
|
|
440
|
-
? cachedNextShuffling
|
|
441
|
-
: shufflingCache.getSync(nextEpoch, nextDecisionRoot, {
|
|
442
|
-
state,
|
|
443
|
-
activeIndices: nextActiveIndices,
|
|
444
|
-
});
|
|
445
|
-
}
|
|
403
|
+
const previousShuffling =
|
|
404
|
+
cachedPreviousShuffling ??
|
|
405
|
+
(isGenesis
|
|
406
|
+
? currentShuffling
|
|
407
|
+
: computeEpochShuffling(state, new Uint32Array(previousActiveIndicesAsNumberArray), previousEpoch));
|
|
408
|
+
|
|
409
|
+
const nextShuffling = cachedNextShuffling ?? computeEpochShuffling(state, nextActiveIndices, nextEpoch);
|
|
446
410
|
|
|
447
411
|
const currentProposerSeed = getSeed(state, currentEpoch, DOMAIN_BEACON_PROPOSER);
|
|
448
412
|
|
|
@@ -549,7 +513,6 @@ export class EpochCache {
|
|
|
549
513
|
config,
|
|
550
514
|
pubkey2index,
|
|
551
515
|
index2pubkey,
|
|
552
|
-
shufflingCache,
|
|
553
516
|
proposers,
|
|
554
517
|
// On first epoch, set to null to prevent unnecessary work since this is only used for metrics
|
|
555
518
|
proposersPrevEpoch: null,
|
|
@@ -593,7 +556,6 @@ export class EpochCache {
|
|
|
593
556
|
// Common append-only structures shared with all states, no need to clone
|
|
594
557
|
pubkey2index: this.pubkey2index,
|
|
595
558
|
index2pubkey: this.index2pubkey,
|
|
596
|
-
shufflingCache: this.shufflingCache,
|
|
597
559
|
// Immutable data
|
|
598
560
|
proposers: this.proposers,
|
|
599
561
|
proposersPrevEpoch: this.proposersPrevEpoch,
|
|
@@ -652,62 +614,26 @@ export class EpochCache {
|
|
|
652
614
|
this.previousShuffling = this.currentShuffling;
|
|
653
615
|
this.previousDecisionRoot = this.currentDecisionRoot;
|
|
654
616
|
|
|
655
|
-
// move next to current
|
|
617
|
+
// move next to current
|
|
656
618
|
this.currentDecisionRoot = this.nextDecisionRoot;
|
|
657
|
-
|
|
658
|
-
// was already pulled from the ShufflingCache to the EpochCache (should be in most cases)
|
|
659
|
-
this.currentShuffling = this.nextShuffling;
|
|
660
|
-
} else {
|
|
661
|
-
this.shufflingCache?.metrics?.shufflingCache.nextShufflingNotOnEpochCache.inc();
|
|
662
|
-
this.currentShuffling =
|
|
663
|
-
this.shufflingCache?.getSync(upcomingEpoch, this.currentDecisionRoot, {
|
|
664
|
-
state,
|
|
665
|
-
// have to use the "nextActiveIndices" that were saved in the last transition here to calculate
|
|
666
|
-
// the upcoming shuffling if it is not already built (similar condition to the below computation)
|
|
667
|
-
activeIndices: this.nextActiveIndices,
|
|
668
|
-
}) ??
|
|
669
|
-
// allow for this case during testing where the ShufflingCache is not present, may affect perf testing
|
|
670
|
-
// so should be taken into account when structuring tests. Should not affect unit or other tests though
|
|
671
|
-
computeEpochShuffling(state, this.nextActiveIndices, upcomingEpoch);
|
|
672
|
-
}
|
|
619
|
+
this.currentShuffling = this.nextShuffling;
|
|
673
620
|
|
|
674
|
-
//
|
|
675
|
-
|
|
621
|
+
// Compute shuffling for epoch n+2
|
|
622
|
+
//
|
|
623
|
+
// Post-Fulu (EIP-7917), the beacon state includes a `proposer_lookahead` field that stores
|
|
624
|
+
// proposer indices for MIN_SEED_LOOKAHEAD + 1 epochs ahead (2 epochs with MIN_SEED_LOOKAHEAD=1).
|
|
625
|
+
// At each epoch boundary, processProposerLookahead() shifts out the current epoch's proposers
|
|
626
|
+
// and appends new proposers for epoch n + MIN_SEED_LOOKAHEAD + 1 (i.e., epoch n+2).
|
|
627
|
+
//
|
|
628
|
+
// processProposerLookahead() already computes the n+2 shuffling and stores it in
|
|
629
|
+
// epochTransitionCache.nextShuffling. Reuse it here to avoid duplicate computation.
|
|
630
|
+
// Pre-Fulu, we need to compute it here since processProposerLookahead doesn't run.
|
|
631
|
+
//
|
|
632
|
+
// See: https://eips.ethereum.org/EIPS/eip-7917
|
|
633
|
+
this.nextDecisionRoot = calculateDecisionRoot(state, epochAfterUpcoming);
|
|
676
634
|
this.nextActiveIndices = epochTransitionCache.nextShufflingActiveIndices;
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
this.nextShuffling = this.shufflingCache.getSync(epochAfterUpcoming, this.nextDecisionRoot, {
|
|
680
|
-
state,
|
|
681
|
-
activeIndices: this.nextActiveIndices,
|
|
682
|
-
});
|
|
683
|
-
} else {
|
|
684
|
-
this.nextShuffling = null;
|
|
685
|
-
// This promise will resolve immediately after the synchronous code of the state-transition runs. Until
|
|
686
|
-
// the build is done on a worker thread it will be calculated immediately after the epoch transition
|
|
687
|
-
// completes. Once the work is done concurrently it should be ready by time this get runs so the promise
|
|
688
|
-
// will resolve directly on the next spin of the event loop because the epoch transition and shuffling take
|
|
689
|
-
// about the same time to calculate so theoretically its ready now. Do not await here though in case it
|
|
690
|
-
// is not ready yet as the transition must not be asynchronous.
|
|
691
|
-
this.shufflingCache
|
|
692
|
-
.get(epochAfterUpcoming, this.nextDecisionRoot)
|
|
693
|
-
.then((shuffling) => {
|
|
694
|
-
if (!shuffling) {
|
|
695
|
-
throw new Error("EpochShuffling not returned from get in afterProcessEpoch");
|
|
696
|
-
}
|
|
697
|
-
this.nextShuffling = shuffling;
|
|
698
|
-
})
|
|
699
|
-
.catch((err) => {
|
|
700
|
-
this.shufflingCache?.logger?.error(
|
|
701
|
-
"EPOCH_CONTEXT_SHUFFLING_BUILD_ERROR",
|
|
702
|
-
{epoch: epochAfterUpcoming, decisionRoot: epochTransitionCache.nextShufflingDecisionRoot},
|
|
703
|
-
err
|
|
704
|
-
);
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
} else {
|
|
708
|
-
// Only for testing. shufflingCache should always be available in prod
|
|
709
|
-
this.nextShuffling = computeEpochShuffling(state, this.nextActiveIndices, epochAfterUpcoming);
|
|
710
|
-
}
|
|
635
|
+
this.nextShuffling =
|
|
636
|
+
epochTransitionCache.nextShuffling ?? computeEpochShuffling(state, this.nextActiveIndices, epochAfterUpcoming);
|
|
711
637
|
|
|
712
638
|
// TODO: DEDUPLICATE from createEpochCache
|
|
713
639
|
//
|
|
@@ -1100,10 +1026,6 @@ export class EpochCache {
|
|
|
1100
1026
|
case this.epoch:
|
|
1101
1027
|
return this.currentShuffling;
|
|
1102
1028
|
case this.nextEpoch:
|
|
1103
|
-
if (!this.nextShuffling) {
|
|
1104
|
-
this.nextShuffling =
|
|
1105
|
-
this.shufflingCache?.getSync(this.nextEpoch, this.getShufflingDecisionRoot(this.nextEpoch)) ?? null;
|
|
1106
|
-
}
|
|
1107
1029
|
return this.nextShuffling;
|
|
1108
1030
|
default:
|
|
1109
1031
|
return null;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
ForkSeq,
|
|
5
|
-
MIN_ACTIVATION_BALANCE,
|
|
6
|
-
SLOTS_PER_HISTORICAL_ROOT,
|
|
7
|
-
} from "@lodestar/params";
|
|
8
|
-
import {Epoch, RootHex, ValidatorIndex} from "@lodestar/types";
|
|
9
|
-
import {intDiv, toRootHex} from "@lodestar/utils";
|
|
1
|
+
import {EPOCHS_PER_SLASHINGS_VECTOR, FAR_FUTURE_EPOCH, ForkSeq, MIN_ACTIVATION_BALANCE} from "@lodestar/params";
|
|
2
|
+
import {Epoch, ValidatorIndex} from "@lodestar/types";
|
|
3
|
+
import {intDiv} from "@lodestar/utils";
|
|
10
4
|
import {processPendingAttestations} from "../epoch/processPendingAttestations.js";
|
|
11
5
|
import {
|
|
12
6
|
CachedBeaconStateAllForks,
|
|
@@ -26,16 +20,13 @@ import {
|
|
|
26
20
|
FLAG_UNSLASHED,
|
|
27
21
|
hasMarkers,
|
|
28
22
|
} from "../util/attesterStatus.js";
|
|
23
|
+
import {EpochShuffling} from "../util/epochShuffling.js";
|
|
29
24
|
|
|
30
25
|
export type EpochTransitionCacheOpts = {
|
|
31
26
|
/**
|
|
32
27
|
* Assert progressive balances the same to EpochTransitionCache
|
|
33
28
|
*/
|
|
34
29
|
assertCorrectProgressiveBalances?: boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Do not queue shuffling calculation async. Forces sync JIT calculation in afterProcessEpoch
|
|
37
|
-
*/
|
|
38
|
-
asyncShufflingCalculation?: boolean;
|
|
39
30
|
};
|
|
40
31
|
|
|
41
32
|
/**
|
|
@@ -162,9 +153,10 @@ export interface EpochTransitionCache {
|
|
|
162
153
|
nextShufflingActiveIndices: Uint32Array;
|
|
163
154
|
|
|
164
155
|
/**
|
|
165
|
-
*
|
|
156
|
+
* Pre-computed shuffling for epoch N+2, populated by processProposerLookahead (Fulu+).
|
|
157
|
+
* Used by afterProcessEpoch to avoid recomputing the same shuffling.
|
|
166
158
|
*/
|
|
167
|
-
|
|
159
|
+
nextShuffling: EpochShuffling | null;
|
|
168
160
|
|
|
169
161
|
/**
|
|
170
162
|
* Altair specific, this is total active balances for the next epoch.
|
|
@@ -179,12 +171,6 @@ export interface EpochTransitionCache {
|
|
|
179
171
|
*/
|
|
180
172
|
nextEpochTotalActiveBalanceByIncrement: number;
|
|
181
173
|
|
|
182
|
-
/**
|
|
183
|
-
* Compute the shuffling sync or async. Defaults to synchronous. Need to pass `true` with the
|
|
184
|
-
* `EpochTransitionCacheOpts`
|
|
185
|
-
*/
|
|
186
|
-
asyncShufflingCalculation: boolean;
|
|
187
|
-
|
|
188
174
|
/**
|
|
189
175
|
* Track by validator index if it's active in the prev epoch.
|
|
190
176
|
* Used in metrics
|
|
@@ -379,12 +365,7 @@ export function beforeProcessEpoch(
|
|
|
379
365
|
}
|
|
380
366
|
});
|
|
381
367
|
|
|
382
|
-
//
|
|
383
|
-
const epochAfterNext = state.epochCtx.nextEpoch + 1;
|
|
384
|
-
// cannot call calculateShufflingDecisionRoot here because spec prevent getting current slot
|
|
385
|
-
// as a decision block. we are part way through the transition though and this was added in
|
|
386
|
-
// process slot beforeProcessEpoch happens so it available and valid
|
|
387
|
-
const nextShufflingDecisionRoot = toRootHex(state.blockRoots.get(state.slot % SLOTS_PER_HISTORICAL_ROOT));
|
|
368
|
+
// Prepare shuffling data for epoch after next (nextShuffling post epoch transition)
|
|
388
369
|
const nextShufflingActiveIndices = new Uint32Array(nextEpochShufflingActiveIndicesLength);
|
|
389
370
|
if (nextEpochShufflingActiveIndicesLength > nextEpochShufflingActiveValidatorIndices.length) {
|
|
390
371
|
throw new Error(
|
|
@@ -396,11 +377,6 @@ export function beforeProcessEpoch(
|
|
|
396
377
|
nextShufflingActiveIndices[i] = nextEpochShufflingActiveValidatorIndices[i];
|
|
397
378
|
}
|
|
398
379
|
|
|
399
|
-
const asyncShufflingCalculation = opts?.asyncShufflingCalculation ?? false;
|
|
400
|
-
if (asyncShufflingCalculation) {
|
|
401
|
-
state.epochCtx.shufflingCache?.build(epochAfterNext, nextShufflingDecisionRoot, state, nextShufflingActiveIndices);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
380
|
if (totalActiveStakeByIncrement < 1) {
|
|
405
381
|
totalActiveStakeByIncrement = 1;
|
|
406
382
|
} else if (totalActiveStakeByIncrement >= Number.MAX_SAFE_INTEGER) {
|
|
@@ -524,9 +500,8 @@ export function beforeProcessEpoch(
|
|
|
524
500
|
indicesEligibleForActivationQueue,
|
|
525
501
|
indicesEligibleForActivation: indicesEligibleForActivation.map(({validatorIndex}) => validatorIndex),
|
|
526
502
|
indicesToEject,
|
|
527
|
-
nextShufflingDecisionRoot,
|
|
528
503
|
nextShufflingActiveIndices,
|
|
529
|
-
|
|
504
|
+
nextShuffling: null,
|
|
530
505
|
// to be updated in processEffectiveBalanceUpdates
|
|
531
506
|
nextEpochTotalActiveBalanceByIncrement: 0,
|
|
532
507
|
isActivePrevEpoch,
|
package/src/cache/stateCache.ts
CHANGED
|
@@ -180,7 +180,7 @@ export function loadCachedBeaconState<T extends BeaconStateAllForks & BeaconStat
|
|
|
180
180
|
stateBytes,
|
|
181
181
|
seedValidatorsBytes
|
|
182
182
|
);
|
|
183
|
-
const {pubkey2index, index2pubkey
|
|
183
|
+
const {pubkey2index, index2pubkey} = cachedSeedState.epochCtx;
|
|
184
184
|
// Get the validators sub tree once for all the loop
|
|
185
185
|
const validators = migratedState.validators;
|
|
186
186
|
for (const validatorIndex of modifiedValidators) {
|
|
@@ -196,7 +196,6 @@ export function loadCachedBeaconState<T extends BeaconStateAllForks & BeaconStat
|
|
|
196
196
|
config: cachedSeedState.config,
|
|
197
197
|
pubkey2index,
|
|
198
198
|
index2pubkey,
|
|
199
|
-
shufflingCache,
|
|
200
199
|
},
|
|
201
200
|
{...(opts ?? {}), ...{skipSyncPubkeys: true}}
|
|
202
201
|
) as T;
|
|
@@ -22,13 +22,9 @@ export function processProposerLookahead(
|
|
|
22
22
|
// Fill in the last epoch with new proposer indices
|
|
23
23
|
const epoch = state.epochCtx.epoch + MIN_SEED_LOOKAHEAD + 1;
|
|
24
24
|
|
|
25
|
-
const shuffling =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
activeIndices: cache.nextShufflingActiveIndices,
|
|
29
|
-
}) ??
|
|
30
|
-
// Only for testing. shufflingCache should always be available in prod
|
|
31
|
-
computeEpochShuffling(state, cache.nextShufflingActiveIndices, epoch);
|
|
25
|
+
const shuffling = computeEpochShuffling(state, cache.nextShufflingActiveIndices, epoch);
|
|
26
|
+
// Save shuffling to cache so afterProcessEpoch can reuse it instead of recomputing
|
|
27
|
+
cache.nextShuffling = shuffling;
|
|
32
28
|
|
|
33
29
|
const lastEpochProposerLookahead = computeProposerIndices(fork, state, shuffling, epoch);
|
|
34
30
|
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "@lodestar/params";
|
|
8
8
|
import {BeaconBlock, altair, phase0, rewards} from "@lodestar/types";
|
|
9
9
|
import {processAttestationsAltair} from "../block/processAttestationsAltair.js";
|
|
10
|
+
import {RewardCache} from "../cache/rewardCache.js";
|
|
10
11
|
import {CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStatePhase0} from "../cache/stateCache.js";
|
|
11
12
|
import {getAttesterSlashableIndices} from "../util/attestation.js";
|
|
12
13
|
|
|
@@ -23,12 +24,14 @@ type SubRewardValue = number; // All reward values should be integer
|
|
|
23
24
|
export async function computeBlockRewards(
|
|
24
25
|
config: BeaconConfig,
|
|
25
26
|
block: BeaconBlock,
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
preStateIn: CachedBeaconStateAllForks,
|
|
28
|
+
proposerRewards?: RewardCache
|
|
28
29
|
): Promise<rewards.BlockRewards> {
|
|
30
|
+
const preState = preStateIn.clone();
|
|
31
|
+
|
|
29
32
|
const fork = config.getForkName(block.slot);
|
|
30
33
|
const {attestations: cachedAttestationsReward = 0, syncAggregate: cachedSyncAggregateReward = 0} =
|
|
31
|
-
|
|
34
|
+
proposerRewards ?? {};
|
|
32
35
|
let blockAttestationReward = cachedAttestationsReward;
|
|
33
36
|
let syncAggregateReward = cachedSyncAggregateReward;
|
|
34
37
|
|
|
@@ -4,8 +4,6 @@ import {BeaconBlock, ValidatorIndex, altair, rewards} from "@lodestar/types";
|
|
|
4
4
|
import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
|
|
5
5
|
import {CachedBeaconStateAllForks, CachedBeaconStateAltair} from "../cache/stateCache.js";
|
|
6
6
|
|
|
7
|
-
type BalanceRecord = {val: number}; // Use val for convenient way to increment/decrement balance
|
|
8
|
-
|
|
9
7
|
export async function computeSyncCommitteeRewards(
|
|
10
8
|
config: BeaconConfig,
|
|
11
9
|
index2pubkey: Index2PubkeyCache,
|
|
@@ -19,7 +17,7 @@ export async function computeSyncCommitteeRewards(
|
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
const altairBlock = block as altair.BeaconBlock;
|
|
22
|
-
const preStateAltair = preState as CachedBeaconStateAltair;
|
|
20
|
+
const preStateAltair = preState.clone() as CachedBeaconStateAltair;
|
|
23
21
|
|
|
24
22
|
// Bound syncCommitteeValidatorIndices in case it goes beyond SYNC_COMMITTEE_SIZE just to be safe
|
|
25
23
|
const syncCommitteeValidatorIndices = preStateAltair.epochCtx.currentSyncCommitteeIndexed.validatorIndices.slice(
|
|
@@ -29,24 +27,23 @@ export async function computeSyncCommitteeRewards(
|
|
|
29
27
|
const {syncParticipantReward} = preStateAltair.epochCtx;
|
|
30
28
|
const {syncCommitteeBits} = altairBlock.body.syncAggregate;
|
|
31
29
|
|
|
32
|
-
//
|
|
33
|
-
const
|
|
34
|
-
for (const i of syncCommitteeValidatorIndices) {
|
|
35
|
-
balances.set(i, {val: preStateAltair.balances.get(i)});
|
|
36
|
-
}
|
|
30
|
+
// Track reward deltas per validator (can appear multiple times in sync committee)
|
|
31
|
+
const rewardDeltas: Map<ValidatorIndex, number> = new Map();
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
// Iterate by position index to correctly access syncCommitteeBits
|
|
34
|
+
for (let i = 0; i < syncCommitteeValidatorIndices.length; i++) {
|
|
35
|
+
const validatorIndex = syncCommitteeValidatorIndices[i];
|
|
36
|
+
const currentDelta = rewardDeltas.get(validatorIndex) ?? 0;
|
|
40
37
|
if (syncCommitteeBits.get(i)) {
|
|
41
38
|
// Positive rewards for participants
|
|
42
|
-
|
|
39
|
+
rewardDeltas.set(validatorIndex, currentDelta + syncParticipantReward);
|
|
43
40
|
} else {
|
|
44
41
|
// Negative rewards for non participants
|
|
45
|
-
|
|
42
|
+
rewardDeltas.set(validatorIndex, currentDelta - syncParticipantReward);
|
|
46
43
|
}
|
|
47
44
|
}
|
|
48
45
|
|
|
49
|
-
const rewards = Array.from(
|
|
46
|
+
const rewards = Array.from(rewardDeltas, ([validatorIndex, reward]) => ({validatorIndex, reward}));
|
|
50
47
|
|
|
51
48
|
if (validatorIds.length) {
|
|
52
49
|
const filtersSet = new Set(validatorIds);
|
|
@@ -2,14 +2,13 @@ import {PublicKey} from "@chainsafe/blst";
|
|
|
2
2
|
import {BeaconConfig} from "@lodestar/config";
|
|
3
3
|
import {DOMAIN_BLS_TO_EXECUTION_CHANGE, ForkName} from "@lodestar/params";
|
|
4
4
|
import {capella, ssz} from "@lodestar/types";
|
|
5
|
-
import {CachedBeaconStateAllForks} from "../types.js";
|
|
6
5
|
import {ISignatureSet, SignatureSetType, computeSigningRoot, verifySignatureSet} from "../util/index.js";
|
|
7
6
|
|
|
8
7
|
export function verifyBlsToExecutionChangeSignature(
|
|
9
|
-
|
|
8
|
+
config: BeaconConfig,
|
|
10
9
|
signedBLSToExecutionChange: capella.SignedBLSToExecutionChange
|
|
11
10
|
): boolean {
|
|
12
|
-
return verifySignatureSet(getBlsToExecutionChangeSignatureSet(
|
|
11
|
+
return verifySignatureSet(getBlsToExecutionChangeSignatureSet(config, signedBLSToExecutionChange));
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
/**
|
package/src/types.ts
CHANGED
|
@@ -9,54 +9,13 @@ import {
|
|
|
9
9
|
TARGET_COMMITTEE_SIZE,
|
|
10
10
|
} from "@lodestar/params";
|
|
11
11
|
import {Epoch, RootHex, ValidatorIndex, ssz} from "@lodestar/types";
|
|
12
|
-
import {
|
|
12
|
+
import {intDiv, toRootHex} from "@lodestar/utils";
|
|
13
13
|
import {BeaconStateAllForks} from "../types.js";
|
|
14
14
|
import {getBlockRootAtSlot} from "./blockRoot.js";
|
|
15
15
|
import {computeAnchorCheckpoint} from "./computeAnchorCheckpoint.js";
|
|
16
16
|
import {computeStartSlotAtEpoch} from "./epoch.js";
|
|
17
17
|
import {getSeed} from "./seed.js";
|
|
18
18
|
|
|
19
|
-
export interface ShufflingBuildProps {
|
|
20
|
-
state: BeaconStateAllForks;
|
|
21
|
-
activeIndices: Uint32Array;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface PublicShufflingCacheMetrics {
|
|
25
|
-
shufflingCache: {
|
|
26
|
-
nextShufflingNotOnEpochCache: GaugeExtra<NoLabels>;
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
export interface IShufflingCache {
|
|
30
|
-
metrics: PublicShufflingCacheMetrics | null;
|
|
31
|
-
logger: Logger | null;
|
|
32
|
-
/**
|
|
33
|
-
* Gets a cached shuffling via the epoch and decision root. If the state and
|
|
34
|
-
* activeIndices are passed and a shuffling is not available it will be built
|
|
35
|
-
* synchronously. If the state is not passed and the shuffling is not available
|
|
36
|
-
* nothing will be returned.
|
|
37
|
-
*
|
|
38
|
-
* NOTE: If a shuffling is already queued and not calculated it will build and resolve
|
|
39
|
-
* the promise but the already queued build will happen at some later time
|
|
40
|
-
*/
|
|
41
|
-
getSync<T extends ShufflingBuildProps | undefined>(
|
|
42
|
-
epoch: Epoch,
|
|
43
|
-
decisionRoot: RootHex,
|
|
44
|
-
buildProps?: T
|
|
45
|
-
): T extends ShufflingBuildProps ? EpochShuffling : EpochShuffling | null;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Gets a cached shuffling via the epoch and decision root. Returns a promise
|
|
49
|
-
* for the shuffling if it hs not calculated yet. Returns null if a build has
|
|
50
|
-
* not been queued nor a shuffling was calculated.
|
|
51
|
-
*/
|
|
52
|
-
get(epoch: Epoch, decisionRoot: RootHex): Promise<EpochShuffling | null>;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Queue asynchronous build for an EpochShuffling
|
|
56
|
-
*/
|
|
57
|
-
build(epoch: Epoch, decisionRoot: RootHex, state: BeaconStateAllForks, activeIndices: Uint32Array): void;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
19
|
/**
|
|
61
20
|
* Readonly interface for EpochShuffling.
|
|
62
21
|
*/
|
|
@@ -164,7 +123,7 @@ export async function computeEpochShufflingAsync(
|
|
|
164
123
|
};
|
|
165
124
|
}
|
|
166
125
|
|
|
167
|
-
function calculateDecisionRoot(state: BeaconStateAllForks, epoch: Epoch): RootHex {
|
|
126
|
+
export function calculateDecisionRoot(state: BeaconStateAllForks, epoch: Epoch): RootHex {
|
|
168
127
|
const pivotSlot = computeStartSlotAtEpoch(epoch - 1) - 1;
|
|
169
128
|
return toRootHex(getBlockRootAtSlot(state, pivotSlot));
|
|
170
129
|
}
|