@lodestar/state-transition 1.23.0-dev.bb40ef7eb7 → 1.23.0-dev.d0ba6bc3cc
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/cache/epochCache.d.ts +52 -21
- package/lib/cache/epochCache.js +177 -68
- package/lib/cache/epochCache.js.map +1 -1
- package/lib/cache/epochTransitionCache.d.ts +4 -4
- package/lib/cache/epochTransitionCache.js +19 -4
- package/lib/cache/epochTransitionCache.js.map +1 -1
- package/lib/cache/stateCache.js +2 -1
- package/lib/cache/stateCache.js.map +1 -1
- package/lib/epoch/processSyncCommitteeUpdates.js +1 -1
- package/lib/epoch/processSyncCommitteeUpdates.js.map +1 -1
- package/lib/slot/upgradeStateToAltair.js +1 -1
- package/lib/slot/upgradeStateToAltair.js.map +1 -1
- package/lib/stateTransition.js +19 -0
- package/lib/stateTransition.js.map +1 -1
- package/lib/util/calculateCommitteeAssignments.d.ts +12 -0
- package/lib/util/calculateCommitteeAssignments.js +26 -0
- package/lib/util/calculateCommitteeAssignments.js.map +1 -0
- package/lib/util/computeAnchorCheckpoint.d.ts +8 -0
- package/lib/util/computeAnchorCheckpoint.js +32 -0
- package/lib/util/computeAnchorCheckpoint.js.map +1 -0
- package/lib/util/epochShuffling.d.ts +42 -2
- package/lib/util/epochShuffling.js +18 -13
- package/lib/util/epochShuffling.js.map +1 -1
- package/lib/util/index.d.ts +2 -0
- package/lib/util/index.js +2 -0
- package/lib/util/index.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
import { PublicKey } from "@chainsafe/blst";
|
|
2
2
|
import * as immutable from "immutable";
|
|
3
|
-
import { BLSSignature, CommitteeIndex, Epoch, Slot, ValidatorIndex, phase0, SyncPeriod, Attestation, IndexedAttestation } from "@lodestar/types";
|
|
3
|
+
import { BLSSignature, CommitteeIndex, Epoch, Slot, ValidatorIndex, phase0, RootHex, SyncPeriod, Attestation, IndexedAttestation } from "@lodestar/types";
|
|
4
4
|
import { BeaconConfig, ChainConfig } from "@lodestar/config";
|
|
5
5
|
import { ForkSeq } from "@lodestar/params";
|
|
6
6
|
import { LodestarError } from "@lodestar/utils";
|
|
7
|
-
import { EpochShuffling } from "../util/epochShuffling.js";
|
|
7
|
+
import { EpochShuffling, IShufflingCache } from "../util/epochShuffling.js";
|
|
8
|
+
import { AttesterDuty } from "../util/calculateCommitteeAssignments.js";
|
|
8
9
|
import { EpochCacheMetrics } from "../metrics.js";
|
|
9
10
|
import { EffectiveBalanceIncrements } from "./effectiveBalanceIncrements.js";
|
|
11
|
+
import { BeaconStateAllForks } from "./types.js";
|
|
10
12
|
import { Index2PubkeyCache, PubkeyIndexMap, UnfinalizedPubkeyIndexMap, PubkeyHex } from "./pubkeyCache.js";
|
|
11
|
-
import { BeaconStateAllForks, ShufflingGetter } from "./types.js";
|
|
12
13
|
import { SyncCommitteeCache } from "./syncCommitteeCache.js";
|
|
14
|
+
import { CachedBeaconStateAllForks } from "./stateCache.js";
|
|
13
15
|
/** `= PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)` */
|
|
14
16
|
export declare const PROPOSER_WEIGHT_FACTOR: number;
|
|
15
17
|
export type EpochCacheImmutableData = {
|
|
16
18
|
config: BeaconConfig;
|
|
17
19
|
pubkey2index: PubkeyIndexMap;
|
|
18
20
|
index2pubkey: Index2PubkeyCache;
|
|
21
|
+
shufflingCache?: IShufflingCache;
|
|
19
22
|
};
|
|
20
23
|
export type EpochCacheOpts = {
|
|
21
24
|
skipSyncCommitteeCache?: boolean;
|
|
22
25
|
skipSyncPubkeys?: boolean;
|
|
23
|
-
shufflingGetter?: ShufflingGetter;
|
|
24
26
|
};
|
|
25
27
|
/** Defers computing proposers by persisting only the seed, and dropping it once indexes are computed */
|
|
26
28
|
type ProposersDeferred = {
|
|
@@ -79,6 +81,11 @@ export declare class EpochCache {
|
|
|
79
81
|
* Unique pubkey registry shared in the same fork. There should only exist one for the fork.
|
|
80
82
|
*/
|
|
81
83
|
unfinalizedPubkey2index: UnfinalizedPubkeyIndexMap;
|
|
84
|
+
/**
|
|
85
|
+
* ShufflingCache is passed in from `beacon-node` so should be available at runtime but may not be
|
|
86
|
+
* present during testing.
|
|
87
|
+
*/
|
|
88
|
+
shufflingCache?: IShufflingCache;
|
|
82
89
|
/**
|
|
83
90
|
* Indexes of the block proposers for the current epoch.
|
|
84
91
|
*
|
|
@@ -93,6 +100,12 @@ export declare class EpochCache {
|
|
|
93
100
|
* should be in the epoch context.
|
|
94
101
|
*/
|
|
95
102
|
proposersNextEpoch: ProposersDeferred;
|
|
103
|
+
/**
|
|
104
|
+
* Epoch decision roots to look up correct shuffling from the Shuffling Cache
|
|
105
|
+
*/
|
|
106
|
+
previousDecisionRoot: RootHex;
|
|
107
|
+
currentDecisionRoot: RootHex;
|
|
108
|
+
nextDecisionRoot: RootHex;
|
|
96
109
|
/**
|
|
97
110
|
* Shuffling of validator indexes. Immutable through the epoch, then it's replaced entirely.
|
|
98
111
|
* Note: Per spec definition, shuffling will always be defined. They are never called before loadState()
|
|
@@ -103,7 +116,12 @@ export declare class EpochCache {
|
|
|
103
116
|
/** Same as previousShuffling */
|
|
104
117
|
currentShuffling: EpochShuffling;
|
|
105
118
|
/** Same as previousShuffling */
|
|
106
|
-
nextShuffling: EpochShuffling;
|
|
119
|
+
nextShuffling: EpochShuffling | null;
|
|
120
|
+
/**
|
|
121
|
+
* Cache nextActiveIndices so that in afterProcessEpoch the next shuffling can be build synchronously
|
|
122
|
+
* in case it is not built or the ShufflingCache is not available
|
|
123
|
+
*/
|
|
124
|
+
nextActiveIndices: Uint32Array;
|
|
107
125
|
/**
|
|
108
126
|
* Effective balances, for altair processAttestations()
|
|
109
127
|
*/
|
|
@@ -169,7 +187,6 @@ export declare class EpochCache {
|
|
|
169
187
|
currentSyncCommitteeIndexed: SyncCommitteeCache;
|
|
170
188
|
/** TODO: Indexed SyncCommitteeCache */
|
|
171
189
|
nextSyncCommitteeIndexed: SyncCommitteeCache;
|
|
172
|
-
epoch: Epoch;
|
|
173
190
|
syncPeriod: SyncPeriod;
|
|
174
191
|
/**
|
|
175
192
|
* state.validators.length of every state at epoch boundary
|
|
@@ -180,17 +197,24 @@ export declare class EpochCache {
|
|
|
180
197
|
* then the list will be (in terms of epoch) [103, 104, 105]
|
|
181
198
|
*/
|
|
182
199
|
historicalValidatorLengths: immutable.List<number>;
|
|
200
|
+
epoch: Epoch;
|
|
201
|
+
get nextEpoch(): Epoch;
|
|
183
202
|
constructor(data: {
|
|
184
203
|
config: BeaconConfig;
|
|
185
204
|
pubkey2index: PubkeyIndexMap;
|
|
186
205
|
index2pubkey: Index2PubkeyCache;
|
|
187
206
|
unfinalizedPubkey2index: UnfinalizedPubkeyIndexMap;
|
|
207
|
+
shufflingCache?: IShufflingCache;
|
|
188
208
|
proposers: number[];
|
|
189
209
|
proposersPrevEpoch: number[] | null;
|
|
190
210
|
proposersNextEpoch: ProposersDeferred;
|
|
211
|
+
previousDecisionRoot: RootHex;
|
|
212
|
+
currentDecisionRoot: RootHex;
|
|
213
|
+
nextDecisionRoot: RootHex;
|
|
191
214
|
previousShuffling: EpochShuffling;
|
|
192
215
|
currentShuffling: EpochShuffling;
|
|
193
|
-
nextShuffling: EpochShuffling;
|
|
216
|
+
nextShuffling: EpochShuffling | null;
|
|
217
|
+
nextActiveIndices: Uint32Array;
|
|
194
218
|
effectiveBalanceIncrements: EffectiveBalanceIncrements;
|
|
195
219
|
totalSlashingsByIncrement: number;
|
|
196
220
|
syncParticipantReward: number;
|
|
@@ -215,19 +239,23 @@ export declare class EpochCache {
|
|
|
215
239
|
*
|
|
216
240
|
* SLOW CODE - 🐢
|
|
217
241
|
*/
|
|
218
|
-
static createFromState(state: BeaconStateAllForks, { config, pubkey2index, index2pubkey }: EpochCacheImmutableData, opts?: EpochCacheOpts): EpochCache;
|
|
242
|
+
static createFromState(state: BeaconStateAllForks, { config, pubkey2index, index2pubkey, shufflingCache }: EpochCacheImmutableData, opts?: EpochCacheOpts): EpochCache;
|
|
219
243
|
/**
|
|
220
244
|
* Copies a given EpochCache while avoiding copying its immutable parts.
|
|
221
245
|
*/
|
|
222
246
|
clone(): EpochCache;
|
|
223
247
|
/**
|
|
224
248
|
* Called to re-use information, such as the shuffling of the next epoch, after transitioning into a
|
|
225
|
-
* new epoch.
|
|
249
|
+
* new epoch. Also handles pre-computation of values that may change during the upcoming epoch and
|
|
250
|
+
* that get used in the following epoch transition. Often those pre-computations are not used by the
|
|
251
|
+
* chain but are courtesy values that are served via the API for epoch look ahead of duties.
|
|
252
|
+
*
|
|
253
|
+
* Steps for afterProcessEpoch
|
|
254
|
+
* 1) update previous/current/next values of cached items
|
|
226
255
|
*/
|
|
227
|
-
afterProcessEpoch(state:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
nextEpochShufflingActiveIndicesLength: number;
|
|
256
|
+
afterProcessEpoch(state: CachedBeaconStateAllForks, epochTransitionCache: {
|
|
257
|
+
nextShufflingDecisionRoot: RootHex;
|
|
258
|
+
nextShufflingActiveIndices: Uint32Array;
|
|
231
259
|
nextEpochTotalActiveBalanceByIncrement: number;
|
|
232
260
|
}): void;
|
|
233
261
|
beforeEpochTransition(): void;
|
|
@@ -330,6 +358,7 @@ export declare class EpochCache {
|
|
|
330
358
|
getShufflingAtSlot(slot: Slot): EpochShuffling;
|
|
331
359
|
getShufflingAtSlotOrNull(slot: Slot): EpochShuffling | null;
|
|
332
360
|
getShufflingAtEpoch(epoch: Epoch): EpochShuffling;
|
|
361
|
+
getShufflingDecisionRoot(epoch: Epoch): RootHex;
|
|
333
362
|
getShufflingAtEpochOrNull(epoch: Epoch): EpochShuffling | null;
|
|
334
363
|
/**
|
|
335
364
|
* Note: The range of slots a validator has to perform duties is off by one.
|
|
@@ -351,17 +380,11 @@ export declare class EpochCache {
|
|
|
351
380
|
isPostElectra(): boolean;
|
|
352
381
|
getValidatorCountAtEpoch(targetEpoch: Epoch): number | undefined;
|
|
353
382
|
}
|
|
354
|
-
type AttesterDuty = {
|
|
355
|
-
validatorIndex: ValidatorIndex;
|
|
356
|
-
committeeIndex: CommitteeIndex;
|
|
357
|
-
committeeLength: number;
|
|
358
|
-
committeesAtSlot: number;
|
|
359
|
-
validatorCommitteeIndex: number;
|
|
360
|
-
slot: Slot;
|
|
361
|
-
};
|
|
362
383
|
export declare enum EpochCacheErrorCode {
|
|
363
384
|
COMMITTEE_INDEX_OUT_OF_RANGE = "EPOCH_CONTEXT_ERROR_COMMITTEE_INDEX_OUT_OF_RANGE",
|
|
364
385
|
COMMITTEE_EPOCH_OUT_OF_RANGE = "EPOCH_CONTEXT_ERROR_COMMITTEE_EPOCH_OUT_OF_RANGE",
|
|
386
|
+
DECISION_ROOT_EPOCH_OUT_OF_RANGE = "EPOCH_CONTEXT_ERROR_DECISION_ROOT_EPOCH_OUT_OF_RANGE",
|
|
387
|
+
NEXT_SHUFFLING_NOT_AVAILABLE = "EPOCH_CONTEXT_ERROR_NEXT_SHUFFLING_NOT_AVAILABLE",
|
|
365
388
|
NO_SYNC_COMMITTEE = "EPOCH_CONTEXT_ERROR_NO_SYNC_COMMITTEE",
|
|
366
389
|
PROPOSER_EPOCH_MISMATCH = "EPOCH_CONTEXT_ERROR_PROPOSER_EPOCH_MISMATCH"
|
|
367
390
|
}
|
|
@@ -373,6 +396,14 @@ type EpochCacheErrorType = {
|
|
|
373
396
|
code: EpochCacheErrorCode.COMMITTEE_EPOCH_OUT_OF_RANGE;
|
|
374
397
|
requestedEpoch: Epoch;
|
|
375
398
|
currentEpoch: Epoch;
|
|
399
|
+
} | {
|
|
400
|
+
code: EpochCacheErrorCode.DECISION_ROOT_EPOCH_OUT_OF_RANGE;
|
|
401
|
+
requestedEpoch: Epoch;
|
|
402
|
+
currentEpoch: Epoch;
|
|
403
|
+
} | {
|
|
404
|
+
code: EpochCacheErrorCode.NEXT_SHUFFLING_NOT_AVAILABLE;
|
|
405
|
+
epoch: Epoch;
|
|
406
|
+
decisionRoot: RootHex;
|
|
376
407
|
} | {
|
|
377
408
|
code: EpochCacheErrorCode.NO_SYNC_COMMITTEE;
|
|
378
409
|
epoch: Epoch;
|
package/lib/cache/epochCache.js
CHANGED
|
@@ -4,10 +4,11 @@ import { createBeaconConfig } from "@lodestar/config";
|
|
|
4
4
|
import { ATTESTATION_SUBNET_COUNT, DOMAIN_BEACON_PROPOSER, EFFECTIVE_BALANCE_INCREMENT, FAR_FUTURE_EPOCH, ForkSeq, GENESIS_EPOCH, PROPOSER_WEIGHT, SLOTS_PER_EPOCH, WEIGHT_DENOMINATOR, } from "@lodestar/params";
|
|
5
5
|
import { LodestarError, fromHex } from "@lodestar/utils";
|
|
6
6
|
import { computeActivationExitEpoch, computeEpochAtSlot, computeStartSlotAtEpoch, getChurnLimit, isActiveValidator, isAggregatorFromCommitteeLength, computeSyncPeriodAtEpoch, getSeed, computeProposers, getActivationChurnLimit, } from "../util/index.js";
|
|
7
|
-
import { computeEpochShuffling,
|
|
7
|
+
import { computeEpochShuffling, calculateShufflingDecisionRoot, } from "../util/epochShuffling.js";
|
|
8
8
|
import { computeBaseRewardPerIncrement, computeSyncParticipantReward } from "../util/syncCommittee.js";
|
|
9
9
|
import { sumTargetUnslashedBalanceIncrements } from "../util/targetUnslashedBalance.js";
|
|
10
10
|
import { getTotalSlashingsByIncrement } from "../epoch/processSlashings.js";
|
|
11
|
+
import { calculateCommitteeAssignments } from "../util/calculateCommitteeAssignments.js";
|
|
11
12
|
import { getEffectiveBalanceIncrementsWithLen } from "./effectiveBalanceIncrements.js";
|
|
12
13
|
import { PubkeyIndexMap, syncPubkeys, toMemoryEfficientHexStr, newUnfinalizedPubkeyIndexMap, } from "./pubkeyCache.js";
|
|
13
14
|
import { computeSyncCommitteeCache, getSyncCommitteeCache, SyncCommitteeCacheEmpty, } from "./syncCommitteeCache.js";
|
|
@@ -36,17 +37,25 @@ export const PROPOSER_WEIGHT_FACTOR = PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PR
|
|
|
36
37
|
* - syncPeriod
|
|
37
38
|
**/
|
|
38
39
|
export class EpochCache {
|
|
40
|
+
get nextEpoch() {
|
|
41
|
+
return this.epoch + 1;
|
|
42
|
+
}
|
|
39
43
|
constructor(data) {
|
|
40
44
|
this.config = data.config;
|
|
41
45
|
this.pubkey2index = data.pubkey2index;
|
|
42
46
|
this.index2pubkey = data.index2pubkey;
|
|
43
47
|
this.unfinalizedPubkey2index = data.unfinalizedPubkey2index;
|
|
48
|
+
this.shufflingCache = data.shufflingCache;
|
|
44
49
|
this.proposers = data.proposers;
|
|
45
50
|
this.proposersPrevEpoch = data.proposersPrevEpoch;
|
|
46
51
|
this.proposersNextEpoch = data.proposersNextEpoch;
|
|
52
|
+
this.previousDecisionRoot = data.previousDecisionRoot;
|
|
53
|
+
this.currentDecisionRoot = data.currentDecisionRoot;
|
|
54
|
+
this.nextDecisionRoot = data.nextDecisionRoot;
|
|
47
55
|
this.previousShuffling = data.previousShuffling;
|
|
48
56
|
this.currentShuffling = data.currentShuffling;
|
|
49
57
|
this.nextShuffling = data.nextShuffling;
|
|
58
|
+
this.nextActiveIndices = data.nextActiveIndices;
|
|
50
59
|
this.effectiveBalanceIncrements = data.effectiveBalanceIncrements;
|
|
51
60
|
this.totalSlashingsByIncrement = data.totalSlashingsByIncrement;
|
|
52
61
|
this.syncParticipantReward = data.syncParticipantReward;
|
|
@@ -71,7 +80,7 @@ export class EpochCache {
|
|
|
71
80
|
*
|
|
72
81
|
* SLOW CODE - 🐢
|
|
73
82
|
*/
|
|
74
|
-
static createFromState(state, { config, pubkey2index, index2pubkey }, opts) {
|
|
83
|
+
static createFromState(state, { config, pubkey2index, index2pubkey, shufflingCache }, opts) {
|
|
75
84
|
const currentEpoch = computeEpochAtSlot(state.slot);
|
|
76
85
|
const isGenesis = currentEpoch === GENESIS_EPOCH;
|
|
77
86
|
const previousEpoch = isGenesis ? GENESIS_EPOCH : currentEpoch - 1;
|
|
@@ -88,17 +97,17 @@ export class EpochCache {
|
|
|
88
97
|
}
|
|
89
98
|
const effectiveBalanceIncrements = getEffectiveBalanceIncrementsWithLen(validatorCount);
|
|
90
99
|
const totalSlashingsByIncrement = getTotalSlashingsByIncrement(state);
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
100
|
+
const previousActiveIndicesAsNumberArray = [];
|
|
101
|
+
const currentActiveIndicesAsNumberArray = [];
|
|
102
|
+
const nextActiveIndicesAsNumberArray = [];
|
|
94
103
|
// BeaconChain could provide a shuffling cache to avoid re-computing shuffling every epoch
|
|
95
104
|
// in that case, we don't need to compute shufflings again
|
|
96
|
-
const
|
|
97
|
-
const cachedPreviousShuffling =
|
|
98
|
-
const
|
|
99
|
-
const cachedCurrentShuffling =
|
|
100
|
-
const
|
|
101
|
-
const cachedNextShuffling =
|
|
105
|
+
const previousDecisionRoot = calculateShufflingDecisionRoot(config, state, previousEpoch);
|
|
106
|
+
const cachedPreviousShuffling = shufflingCache?.getSync(previousEpoch, previousDecisionRoot);
|
|
107
|
+
const currentDecisionRoot = calculateShufflingDecisionRoot(config, state, currentEpoch);
|
|
108
|
+
const cachedCurrentShuffling = shufflingCache?.getSync(currentEpoch, currentDecisionRoot);
|
|
109
|
+
const nextDecisionRoot = calculateShufflingDecisionRoot(config, state, nextEpoch);
|
|
110
|
+
const cachedNextShuffling = shufflingCache?.getSync(nextEpoch, nextDecisionRoot);
|
|
102
111
|
for (let i = 0; i < validatorCount; i++) {
|
|
103
112
|
const validator = validators[i];
|
|
104
113
|
// Note: Not usable for fork-choice balances since in-active validators are not zero'ed
|
|
@@ -106,17 +115,17 @@ export class EpochCache {
|
|
|
106
115
|
// we only need to track active indices for previous, current and next epoch if we have to compute shufflings
|
|
107
116
|
// skip doing that if we already have cached shufflings
|
|
108
117
|
if (cachedPreviousShuffling == null && isActiveValidator(validator, previousEpoch)) {
|
|
109
|
-
|
|
118
|
+
previousActiveIndicesAsNumberArray.push(i);
|
|
110
119
|
}
|
|
111
120
|
if (isActiveValidator(validator, currentEpoch)) {
|
|
112
121
|
if (cachedCurrentShuffling == null) {
|
|
113
|
-
|
|
122
|
+
currentActiveIndicesAsNumberArray.push(i);
|
|
114
123
|
}
|
|
115
124
|
// We track totalActiveBalanceIncrements as ETH to fit total network balance in a JS number (53 bits)
|
|
116
125
|
totalActiveBalanceIncrements += effectiveBalanceIncrements[i];
|
|
117
126
|
}
|
|
118
127
|
if (cachedNextShuffling == null && isActiveValidator(validator, nextEpoch)) {
|
|
119
|
-
|
|
128
|
+
nextActiveIndicesAsNumberArray.push(i);
|
|
120
129
|
}
|
|
121
130
|
const { exitEpoch } = validator;
|
|
122
131
|
if (exitEpoch !== FAR_FUTURE_EPOCH) {
|
|
@@ -137,13 +146,40 @@ export class EpochCache {
|
|
|
137
146
|
else if (totalActiveBalanceIncrements >= Number.MAX_SAFE_INTEGER) {
|
|
138
147
|
throw Error("totalActiveBalanceIncrements >= Number.MAX_SAFE_INTEGER. MAX_EFFECTIVE_BALANCE is too low.");
|
|
139
148
|
}
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
const nextActiveIndices = new Uint32Array(nextActiveIndicesAsNumberArray);
|
|
150
|
+
let previousShuffling;
|
|
151
|
+
let currentShuffling;
|
|
152
|
+
let nextShuffling;
|
|
153
|
+
if (!shufflingCache) {
|
|
154
|
+
// Only for testing. shufflingCache should always be available in prod
|
|
155
|
+
previousShuffling = computeEpochShuffling(state, new Uint32Array(previousActiveIndicesAsNumberArray), previousEpoch);
|
|
156
|
+
currentShuffling = isGenesis
|
|
157
|
+
? previousShuffling
|
|
158
|
+
: computeEpochShuffling(state, new Uint32Array(currentActiveIndicesAsNumberArray), currentEpoch);
|
|
159
|
+
nextShuffling = computeEpochShuffling(state, nextActiveIndices, nextEpoch);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
currentShuffling = cachedCurrentShuffling
|
|
163
|
+
? cachedCurrentShuffling
|
|
164
|
+
: shufflingCache.getSync(currentEpoch, currentDecisionRoot, {
|
|
165
|
+
state,
|
|
166
|
+
activeIndices: new Uint32Array(currentActiveIndicesAsNumberArray),
|
|
167
|
+
});
|
|
168
|
+
previousShuffling = cachedPreviousShuffling
|
|
169
|
+
? cachedPreviousShuffling
|
|
170
|
+
: isGenesis
|
|
171
|
+
? currentShuffling
|
|
172
|
+
: shufflingCache.getSync(previousEpoch, previousDecisionRoot, {
|
|
173
|
+
state,
|
|
174
|
+
activeIndices: new Uint32Array(previousActiveIndicesAsNumberArray),
|
|
175
|
+
});
|
|
176
|
+
nextShuffling = cachedNextShuffling
|
|
177
|
+
? cachedNextShuffling
|
|
178
|
+
: shufflingCache.getSync(nextEpoch, nextDecisionRoot, {
|
|
179
|
+
state,
|
|
180
|
+
activeIndices: nextActiveIndices,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
147
183
|
const currentProposerSeed = getSeed(state, currentEpoch, DOMAIN_BEACON_PROPOSER);
|
|
148
184
|
// Allow to create CachedBeaconState for empty states, or no active validators
|
|
149
185
|
const proposers = currentShuffling.activeIndices.length > 0
|
|
@@ -207,13 +243,18 @@ export class EpochCache {
|
|
|
207
243
|
index2pubkey,
|
|
208
244
|
// `createFromFinalizedState()` creates cache with empty unfinalizedPubkey2index. Be cautious to only pass in finalized state
|
|
209
245
|
unfinalizedPubkey2index: newUnfinalizedPubkeyIndexMap(),
|
|
246
|
+
shufflingCache,
|
|
210
247
|
proposers,
|
|
211
248
|
// On first epoch, set to null to prevent unnecessary work since this is only used for metrics
|
|
212
249
|
proposersPrevEpoch: null,
|
|
213
250
|
proposersNextEpoch,
|
|
251
|
+
previousDecisionRoot,
|
|
252
|
+
currentDecisionRoot,
|
|
253
|
+
nextDecisionRoot,
|
|
214
254
|
previousShuffling,
|
|
215
255
|
currentShuffling,
|
|
216
256
|
nextShuffling,
|
|
257
|
+
nextActiveIndices,
|
|
217
258
|
effectiveBalanceIncrements,
|
|
218
259
|
totalSlashingsByIncrement,
|
|
219
260
|
syncParticipantReward,
|
|
@@ -247,13 +288,18 @@ export class EpochCache {
|
|
|
247
288
|
index2pubkey: this.index2pubkey,
|
|
248
289
|
// No need to clone this reference. On each mutation the `unfinalizedPubkey2index` reference is replaced, @see `addPubkey`
|
|
249
290
|
unfinalizedPubkey2index: this.unfinalizedPubkey2index,
|
|
291
|
+
shufflingCache: this.shufflingCache,
|
|
250
292
|
// Immutable data
|
|
251
293
|
proposers: this.proposers,
|
|
252
294
|
proposersPrevEpoch: this.proposersPrevEpoch,
|
|
253
295
|
proposersNextEpoch: this.proposersNextEpoch,
|
|
296
|
+
previousDecisionRoot: this.previousDecisionRoot,
|
|
297
|
+
currentDecisionRoot: this.currentDecisionRoot,
|
|
298
|
+
nextDecisionRoot: this.nextDecisionRoot,
|
|
254
299
|
previousShuffling: this.previousShuffling,
|
|
255
300
|
currentShuffling: this.currentShuffling,
|
|
256
301
|
nextShuffling: this.nextShuffling,
|
|
302
|
+
nextActiveIndices: this.nextActiveIndices,
|
|
257
303
|
// Uint8Array, requires cloning, but it is cloned only when necessary before an epoch transition
|
|
258
304
|
// See EpochCache.beforeEpochTransition()
|
|
259
305
|
effectiveBalanceIncrements: this.effectiveBalanceIncrements,
|
|
@@ -278,23 +324,79 @@ export class EpochCache {
|
|
|
278
324
|
}
|
|
279
325
|
/**
|
|
280
326
|
* Called to re-use information, such as the shuffling of the next epoch, after transitioning into a
|
|
281
|
-
* new epoch.
|
|
327
|
+
* new epoch. Also handles pre-computation of values that may change during the upcoming epoch and
|
|
328
|
+
* that get used in the following epoch transition. Often those pre-computations are not used by the
|
|
329
|
+
* chain but are courtesy values that are served via the API for epoch look ahead of duties.
|
|
330
|
+
*
|
|
331
|
+
* Steps for afterProcessEpoch
|
|
332
|
+
* 1) update previous/current/next values of cached items
|
|
282
333
|
*/
|
|
283
334
|
afterProcessEpoch(state, epochTransitionCache) {
|
|
335
|
+
// Because the slot was incremented before entering this function the "next epoch" is actually the "current epoch"
|
|
336
|
+
// in this context but that is not actually true because the state transition happens in the last 4 seconds of the
|
|
337
|
+
// epoch. For the context of this function "upcoming epoch" is used to denote the epoch that will begin after this
|
|
338
|
+
// function returns. The epoch that is "next" once the state transition is complete is referred to as the
|
|
339
|
+
// epochAfterUpcoming for the same reason to help minimize confusion.
|
|
340
|
+
const upcomingEpoch = this.nextEpoch;
|
|
341
|
+
const epochAfterUpcoming = upcomingEpoch + 1;
|
|
342
|
+
// move current to previous
|
|
284
343
|
this.previousShuffling = this.currentShuffling;
|
|
285
|
-
this.
|
|
286
|
-
const currEpoch = this.currentShuffling.epoch;
|
|
287
|
-
const nextEpoch = currEpoch + 1;
|
|
288
|
-
this.nextShuffling = computeEpochShuffling(state, epochTransitionCache.nextEpochShufflingActiveValidatorIndices, epochTransitionCache.nextEpochShufflingActiveIndicesLength, nextEpoch);
|
|
289
|
-
// Roll current proposers into previous proposers for metrics
|
|
344
|
+
this.previousDecisionRoot = this.currentDecisionRoot;
|
|
290
345
|
this.proposersPrevEpoch = this.proposers;
|
|
291
|
-
|
|
292
|
-
this.
|
|
346
|
+
// move next to current or calculate upcoming
|
|
347
|
+
this.currentDecisionRoot = this.nextDecisionRoot;
|
|
348
|
+
if (this.nextShuffling) {
|
|
349
|
+
// was already pulled from the ShufflingCache to the EpochCache (should be in most cases)
|
|
350
|
+
this.currentShuffling = this.nextShuffling;
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
this.shufflingCache?.metrics?.shufflingCache.nextShufflingNotOnEpochCache.inc();
|
|
354
|
+
this.currentShuffling =
|
|
355
|
+
this.shufflingCache?.getSync(upcomingEpoch, this.currentDecisionRoot, {
|
|
356
|
+
state,
|
|
357
|
+
// have to use the "nextActiveIndices" that were saved in the last transition here to calculate
|
|
358
|
+
// the upcoming shuffling if it is not already built (similar condition to the below computation)
|
|
359
|
+
activeIndices: this.nextActiveIndices,
|
|
360
|
+
}) ??
|
|
361
|
+
// allow for this case during testing where the ShufflingCache is not present, may affect perf testing
|
|
362
|
+
// so should be taken into account when structuring tests. Should not affect unit or other tests though
|
|
363
|
+
computeEpochShuffling(state, this.nextActiveIndices, upcomingEpoch);
|
|
364
|
+
}
|
|
365
|
+
const upcomingProposerSeed = getSeed(state, upcomingEpoch, DOMAIN_BEACON_PROPOSER);
|
|
366
|
+
// next epoch was moved to current epoch so use current here
|
|
367
|
+
this.proposers = computeProposers(this.config.getForkSeqAtEpoch(upcomingEpoch), upcomingProposerSeed, this.currentShuffling, this.effectiveBalanceIncrements);
|
|
368
|
+
// handle next values
|
|
369
|
+
this.nextDecisionRoot = epochTransitionCache.nextShufflingDecisionRoot;
|
|
370
|
+
this.nextActiveIndices = epochTransitionCache.nextShufflingActiveIndices;
|
|
371
|
+
if (this.shufflingCache) {
|
|
372
|
+
this.nextShuffling = null;
|
|
373
|
+
// This promise will resolve immediately after the synchronous code of the state-transition runs. Until
|
|
374
|
+
// the build is done on a worker thread it will be calculated immediately after the epoch transition
|
|
375
|
+
// completes. Once the work is done concurrently it should be ready by time this get runs so the promise
|
|
376
|
+
// will resolve directly on the next spin of the event loop because the epoch transition and shuffling take
|
|
377
|
+
// about the same time to calculate so theoretically its ready now. Do not await here though in case it
|
|
378
|
+
// is not ready yet as the transition must not be asynchronous.
|
|
379
|
+
this.shufflingCache
|
|
380
|
+
.get(epochAfterUpcoming, this.nextDecisionRoot)
|
|
381
|
+
.then((shuffling) => {
|
|
382
|
+
if (!shuffling) {
|
|
383
|
+
throw new Error("EpochShuffling not returned from get in afterProcessEpoch");
|
|
384
|
+
}
|
|
385
|
+
this.nextShuffling = shuffling;
|
|
386
|
+
})
|
|
387
|
+
.catch((err) => {
|
|
388
|
+
this.shufflingCache?.logger?.error("EPOCH_CONTEXT_SHUFFLING_BUILD_ERROR", { epoch: epochAfterUpcoming, decisionRoot: epochTransitionCache.nextShufflingDecisionRoot }, err);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
// Only for testing. shufflingCache should always be available in prod
|
|
393
|
+
this.nextShuffling = computeEpochShuffling(state, this.nextActiveIndices, epochAfterUpcoming);
|
|
394
|
+
}
|
|
293
395
|
// Only pre-compute the seed since it's very cheap. Do the expensive computeProposers() call only on demand.
|
|
294
|
-
this.proposersNextEpoch = { computed: false, seed: getSeed(state,
|
|
396
|
+
this.proposersNextEpoch = { computed: false, seed: getSeed(state, epochAfterUpcoming, DOMAIN_BEACON_PROPOSER) };
|
|
295
397
|
// TODO: DEDUPLICATE from createEpochCache
|
|
296
398
|
//
|
|
297
|
-
// Precompute churnLimit for efficient initiateValidatorExit() during block proposing MUST be recompute
|
|
399
|
+
// Precompute churnLimit for efficient initiateValidatorExit() during block proposing MUST be recompute every time the
|
|
298
400
|
// active validator indices set changes in size. Validators change active status only when:
|
|
299
401
|
// - validator.activation_epoch is set. Only changes in process_registry_updates() if validator can be activated. If
|
|
300
402
|
// the value changes it will be set to `epoch + 1 + MAX_SEED_LOOKAHEAD`.
|
|
@@ -311,13 +413,13 @@ export class EpochCache {
|
|
|
311
413
|
this.churnLimit = getChurnLimit(this.config, this.currentShuffling.activeIndices.length);
|
|
312
414
|
this.activationChurnLimit = getActivationChurnLimit(this.config, this.config.getForkSeq(state.slot), this.currentShuffling.activeIndices.length);
|
|
313
415
|
// Maybe advance exitQueueEpoch at the end of the epoch if there haven't been any exists for a while
|
|
314
|
-
const exitQueueEpoch = computeActivationExitEpoch(
|
|
416
|
+
const exitQueueEpoch = computeActivationExitEpoch(upcomingEpoch);
|
|
315
417
|
if (exitQueueEpoch > this.exitQueueEpoch) {
|
|
316
418
|
this.exitQueueEpoch = exitQueueEpoch;
|
|
317
419
|
this.exitQueueChurn = 0;
|
|
318
420
|
}
|
|
319
421
|
this.totalActiveBalanceIncrements = epochTransitionCache.nextEpochTotalActiveBalanceByIncrement;
|
|
320
|
-
if (
|
|
422
|
+
if (upcomingEpoch >= this.config.ALTAIR_FORK_EPOCH) {
|
|
321
423
|
this.syncParticipantReward = computeSyncParticipantReward(this.totalActiveBalanceIncrements);
|
|
322
424
|
this.syncProposerReward = Math.floor(this.syncParticipantReward * PROPOSER_WEIGHT_FACTOR);
|
|
323
425
|
this.baseRewardPerIncrement = computeBaseRewardPerIncrement(this.totalActiveBalanceIncrements);
|
|
@@ -336,7 +438,7 @@ export class EpochCache {
|
|
|
336
438
|
// Only keep validatorLength for epochs after finalized cpState.epoch
|
|
337
439
|
// eg. [100(epoch 1), 102(epoch 2)].push(104(epoch 3)), this.epoch = 3, finalized cp epoch = 1
|
|
338
440
|
// We keep the last (3 - 1) items = [102, 104]
|
|
339
|
-
if (
|
|
441
|
+
if (upcomingEpoch >= this.config.ELECTRA_FORK_EPOCH) {
|
|
340
442
|
this.historicalValidatorLengths = this.historicalValidatorLengths.push(state.validators.length);
|
|
341
443
|
// If number of validatorLengths we want to keep exceeds the current list size, it implies
|
|
342
444
|
// finalized checkpoint hasn't advanced, and no need to slice
|
|
@@ -456,7 +558,7 @@ export class EpochCache {
|
|
|
456
558
|
*/
|
|
457
559
|
getBeaconProposersNextEpoch() {
|
|
458
560
|
if (!this.proposersNextEpoch.computed) {
|
|
459
|
-
const indexes = computeProposers(this.config.getForkSeqAtEpoch(this.epoch + 1), this.proposersNextEpoch.seed, this.
|
|
561
|
+
const indexes = computeProposers(this.config.getForkSeqAtEpoch(this.epoch + 1), this.proposersNextEpoch.seed, this.getShufflingAtEpoch(this.nextEpoch), this.effectiveBalanceIncrements);
|
|
460
562
|
this.proposersNextEpoch = { computed: true, indexes };
|
|
461
563
|
}
|
|
462
564
|
return this.proposersNextEpoch.indexes;
|
|
@@ -497,28 +599,8 @@ export class EpochCache {
|
|
|
497
599
|
}
|
|
498
600
|
}
|
|
499
601
|
getCommitteeAssignments(epoch, requestedValidatorIndices) {
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
const epochCommittees = this.getShufflingAtEpoch(epoch).committees;
|
|
503
|
-
for (let epochSlot = 0; epochSlot < SLOTS_PER_EPOCH; epochSlot++) {
|
|
504
|
-
const slotCommittees = epochCommittees[epochSlot];
|
|
505
|
-
for (let i = 0, committeesAtSlot = slotCommittees.length; i < committeesAtSlot; i++) {
|
|
506
|
-
for (let j = 0, committeeLength = slotCommittees[i].length; j < committeeLength; j++) {
|
|
507
|
-
const validatorIndex = slotCommittees[i][j];
|
|
508
|
-
if (requestedValidatorIndicesSet.has(validatorIndex)) {
|
|
509
|
-
duties.set(validatorIndex, {
|
|
510
|
-
validatorIndex,
|
|
511
|
-
committeeLength,
|
|
512
|
-
committeesAtSlot,
|
|
513
|
-
validatorCommitteeIndex: j,
|
|
514
|
-
committeeIndex: i,
|
|
515
|
-
slot: epoch * SLOTS_PER_EPOCH + epochSlot,
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
return duties;
|
|
602
|
+
const shuffling = this.getShufflingAtEpoch(epoch);
|
|
603
|
+
return calculateCommitteeAssignments(shuffling, requestedValidatorIndices);
|
|
522
604
|
}
|
|
523
605
|
/**
|
|
524
606
|
* Return the committee assignment in the ``epoch`` for ``validator_index``.
|
|
@@ -628,6 +710,13 @@ export class EpochCache {
|
|
|
628
710
|
getShufflingAtEpoch(epoch) {
|
|
629
711
|
const shuffling = this.getShufflingAtEpochOrNull(epoch);
|
|
630
712
|
if (shuffling === null) {
|
|
713
|
+
if (epoch === this.nextEpoch) {
|
|
714
|
+
throw new EpochCacheError({
|
|
715
|
+
code: EpochCacheErrorCode.NEXT_SHUFFLING_NOT_AVAILABLE,
|
|
716
|
+
epoch: epoch,
|
|
717
|
+
decisionRoot: this.getShufflingDecisionRoot(this.nextEpoch),
|
|
718
|
+
});
|
|
719
|
+
}
|
|
631
720
|
throw new EpochCacheError({
|
|
632
721
|
code: EpochCacheErrorCode.COMMITTEE_EPOCH_OUT_OF_RANGE,
|
|
633
722
|
currentEpoch: this.currentShuffling.epoch,
|
|
@@ -636,18 +725,36 @@ export class EpochCache {
|
|
|
636
725
|
}
|
|
637
726
|
return shuffling;
|
|
638
727
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
728
|
+
getShufflingDecisionRoot(epoch) {
|
|
729
|
+
switch (epoch) {
|
|
730
|
+
case this.epoch - 1:
|
|
731
|
+
return this.previousDecisionRoot;
|
|
732
|
+
case this.epoch:
|
|
733
|
+
return this.currentDecisionRoot;
|
|
734
|
+
case this.nextEpoch:
|
|
735
|
+
return this.nextDecisionRoot;
|
|
736
|
+
default:
|
|
737
|
+
throw new EpochCacheError({
|
|
738
|
+
code: EpochCacheErrorCode.DECISION_ROOT_EPOCH_OUT_OF_RANGE,
|
|
739
|
+
currentEpoch: this.epoch,
|
|
740
|
+
requestedEpoch: epoch,
|
|
741
|
+
});
|
|
648
742
|
}
|
|
649
|
-
|
|
650
|
-
|
|
743
|
+
}
|
|
744
|
+
getShufflingAtEpochOrNull(epoch) {
|
|
745
|
+
switch (epoch) {
|
|
746
|
+
case this.epoch - 1:
|
|
747
|
+
return this.previousShuffling;
|
|
748
|
+
case this.epoch:
|
|
749
|
+
return this.currentShuffling;
|
|
750
|
+
case this.nextEpoch:
|
|
751
|
+
if (!this.nextShuffling) {
|
|
752
|
+
this.nextShuffling =
|
|
753
|
+
this.shufflingCache?.getSync(this.nextEpoch, this.getShufflingDecisionRoot(this.nextEpoch)) ?? null;
|
|
754
|
+
}
|
|
755
|
+
return this.nextShuffling;
|
|
756
|
+
default:
|
|
757
|
+
return null;
|
|
651
758
|
}
|
|
652
759
|
}
|
|
653
760
|
/**
|
|
@@ -735,6 +842,8 @@ export var EpochCacheErrorCode;
|
|
|
735
842
|
(function (EpochCacheErrorCode) {
|
|
736
843
|
EpochCacheErrorCode["COMMITTEE_INDEX_OUT_OF_RANGE"] = "EPOCH_CONTEXT_ERROR_COMMITTEE_INDEX_OUT_OF_RANGE";
|
|
737
844
|
EpochCacheErrorCode["COMMITTEE_EPOCH_OUT_OF_RANGE"] = "EPOCH_CONTEXT_ERROR_COMMITTEE_EPOCH_OUT_OF_RANGE";
|
|
845
|
+
EpochCacheErrorCode["DECISION_ROOT_EPOCH_OUT_OF_RANGE"] = "EPOCH_CONTEXT_ERROR_DECISION_ROOT_EPOCH_OUT_OF_RANGE";
|
|
846
|
+
EpochCacheErrorCode["NEXT_SHUFFLING_NOT_AVAILABLE"] = "EPOCH_CONTEXT_ERROR_NEXT_SHUFFLING_NOT_AVAILABLE";
|
|
738
847
|
EpochCacheErrorCode["NO_SYNC_COMMITTEE"] = "EPOCH_CONTEXT_ERROR_NO_SYNC_COMMITTEE";
|
|
739
848
|
EpochCacheErrorCode["PROPOSER_EPOCH_MISMATCH"] = "EPOCH_CONTEXT_ERROR_PROPOSER_EPOCH_MISMATCH";
|
|
740
849
|
})(EpochCacheErrorCode || (EpochCacheErrorCode = {}));
|