@@ -3,8 +3,6 @@ import {intDiv} from "@lodestar/utils";
3
3
import { EPOCHS_PER_SLASHINGS_VECTOR , FAR_FUTURE_EPOCH , ForkSeq , MAX_EFFECTIVE_BALANCE } from "@lodestar/params" ;
4
4
5
5
import {
6
- AttesterStatus ,
7
- createAttesterStatus ,
8
6
hasMarkers ,
9
7
FLAG_UNSLASHED ,
10
8
FLAG_ELIGIBLE_ATTESTER ,
@@ -128,7 +126,12 @@ export interface EpochTransitionCache {
128
126
* - prev attester flag set
129
127
* With a status flag to check this conditions at once we just have to mask with an OR of the conditions.
130
128
*/
131
- statuses : AttesterStatus [ ] ;
129
+
130
+ proposerIndices : number [ ] ;
131
+
132
+ inclusionDelays : number [ ] ;
133
+
134
+ flags : number [ ] ;
132
135
133
136
/**
134
137
* balances array will be populated by processRewardsAndPenalties() and consumed by processEffectiveBalanceUpdates().
@@ -161,13 +164,40 @@ export interface EpochTransitionCache {
161
164
*/
162
165
nextEpochTotalActiveBalanceByIncrement : number ;
163
166
167
+ /**
168
+ * Track by validator index if it's active in the prev epoch.
169
+ * Used in metrics
170
+ */
171
+ isActivePrevEpoch : boolean [ ] ;
172
+
173
+ /**
174
+ * Track by validator index if it's active in the current epoch.
175
+ * Used in metrics
176
+ */
177
+ isActiveCurrEpoch : boolean [ ] ;
178
+
164
179
/**
165
180
* Track by validator index if it's active in the next epoch.
166
181
* Used in `processEffectiveBalanceUpdates` to save one loop over validators after epoch process.
167
182
*/
168
183
isActiveNextEpoch : boolean [ ] ;
169
184
}
170
185
186
+ // reuse arrays to avoid memory reallocation and gc
187
+ // WARNING: this is not async safe
188
+ /** WARNING: reused, never gc'd */
189
+ const isActivePrevEpoch = new Array < boolean > ( ) ;
190
+ /** WARNING: reused, never gc'd */
191
+ const isActiveCurrEpoch = new Array < boolean > ( ) ;
192
+ /** WARNING: reused, never gc'd */
193
+ const isActiveNextEpoch = new Array < boolean > ( ) ;
194
+ /** WARNING: reused, never gc'd */
195
+ const proposerIndices = new Array < number > ( ) ;
196
+ /** WARNING: reused, never gc'd */
197
+ const inclusionDelays = new Array < number > ( ) ;
198
+ /** WARNING: reused, never gc'd */
199
+ const flags = new Array < number > ( ) ;
200
+
171
201
export function beforeProcessEpoch (
172
202
state : CachedBeaconStateAllForks ,
173
203
opts ?: EpochTransitionCacheOpts
@@ -188,9 +218,6 @@ export function beforeProcessEpoch(
188
218
const indicesEligibleForActivation : ValidatorIndex [ ] = [ ] ;
189
219
const indicesToEject : ValidatorIndex [ ] = [ ] ;
190
220
const nextEpochShufflingActiveValidatorIndices : ValidatorIndex [ ] = [ ] ;
191
- const isActivePrevEpoch : boolean [ ] = [ ] ;
192
- const isActiveNextEpoch : boolean [ ] = [ ] ;
193
- const statuses : AttesterStatus [ ] = [ ] ;
194
221
195
222
let totalActiveStakeByIncrement = 0 ;
196
223
@@ -200,21 +227,47 @@ export function beforeProcessEpoch(
200
227
const validators = state . validators . getAllReadonlyValues ( ) ;
201
228
const validatorCount = validators . length ;
202
229
230
+ // pre-fill with true (most validators are active)
231
+ isActivePrevEpoch . length = validatorCount ;
232
+ isActiveCurrEpoch . length = validatorCount ;
233
+ isActiveNextEpoch . length = validatorCount ;
234
+ isActivePrevEpoch . fill ( true ) ;
235
+ isActiveCurrEpoch . fill ( true ) ;
236
+ isActiveNextEpoch . fill ( true ) ;
237
+
238
+ // During the epoch transition, additional data is precomputed to avoid traversing any state a second
239
+ // time. Attestations are a big part of this, and each validator has a "status" to represent its
240
+ // precomputed participation.
241
+ // - proposerIndex: number; // -1 when not included by any proposer
242
+ // - inclusionDelay: number;
243
+ // - flags: number; // bitfield of AttesterFlags
244
+ proposerIndices . length = validatorCount ;
245
+ inclusionDelays . length = validatorCount ;
246
+ flags . length = validatorCount ;
247
+ proposerIndices . fill ( - 1 ) ;
248
+ inclusionDelays . fill ( 0 ) ;
249
+ // flags.fill(0);
250
+ // flags will be zero'd out below
251
+ // In the first loop, set slashed+eligibility
252
+ // In the second loop, set participation flags
253
+ // TODO: optimize by combining the two loops
254
+ // likely will require splitting into phase0 and post-phase0 versions
255
+
203
256
// Clone before being mutated in processEffectiveBalanceUpdates
204
257
epochCtx . beforeEpochTransition ( ) ;
205
258
206
259
const effectiveBalancesByIncrements = epochCtx . effectiveBalanceIncrements ;
207
260
208
261
for ( let i = 0 ; i < validatorCount ; i ++ ) {
209
262
const validator = validators [ i ] ;
210
- const status = createAttesterStatus ( ) ;
263
+ let flag = 0 ;
211
264
212
265
if ( validator . slashed ) {
213
266
if ( slashingsEpoch === validator . withdrawableEpoch ) {
214
267
indicesToSlash . push ( i ) ;
215
268
}
216
269
} else {
217
- status . flags |= FLAG_UNSLASHED ;
270
+ flag |= FLAG_UNSLASHED ;
218
271
}
219
272
220
273
const { activationEpoch, exitEpoch} = validator ;
@@ -223,19 +276,24 @@ export function beforeProcessEpoch(
223
276
const isActiveNext = activationEpoch <= nextEpoch && nextEpoch < exitEpoch ;
224
277
const isActiveNext2 = activationEpoch <= nextEpoch2 && nextEpoch2 < exitEpoch ;
225
278
226
- isActivePrevEpoch . push ( isActivePrev ) ;
279
+ if ( ! isActivePrev ) {
280
+ isActivePrevEpoch [ i ] = false ;
281
+ }
227
282
228
283
// Both active validators and slashed-but-not-yet-withdrawn validators are eligible to receive penalties.
229
284
// This is done to prevent self-slashing from being a way to escape inactivity leaks.
230
285
// TODO: Consider using an array of `eligibleValidatorIndices: number[]`
231
286
if ( isActivePrev || ( validator . slashed && prevEpoch + 1 < validator . withdrawableEpoch ) ) {
232
287
eligibleValidatorIndices . push ( i ) ;
233
- status . flags |= FLAG_ELIGIBLE_ATTESTER ;
288
+ flag |= FLAG_ELIGIBLE_ATTESTER ;
234
289
}
235
290
291
+ flags [ i ] = flag ;
292
+
236
293
if ( isActiveCurr ) {
237
- status . active = true ;
238
294
totalActiveStakeByIncrement += effectiveBalancesByIncrements [ i ] ;
295
+ } else {
296
+ isActiveCurrEpoch [ i ] = false ;
239
297
}
240
298
241
299
// To optimize process_registry_updates():
@@ -278,16 +336,16 @@ export function beforeProcessEpoch(
278
336
//
279
337
// Use `else` since indicesEligibleForActivationQueue + indicesEligibleForActivation + indicesToEject are mutually exclusive
280
338
else if (
281
- status . active &&
339
+ isActiveCurr &&
282
340
validator . exitEpoch === FAR_FUTURE_EPOCH &&
283
341
validator . effectiveBalance <= config . EJECTION_BALANCE
284
342
) {
285
343
indicesToEject . push ( i ) ;
286
344
}
287
345
288
- statuses . push ( status ) ;
289
-
290
- isActiveNextEpoch . push ( isActiveNext ) ;
346
+ if ( ! isActiveNext ) {
347
+ isActiveNextEpoch [ i ] = false ;
348
+ }
291
349
292
350
if ( isActiveNext2 ) {
293
351
nextEpochShufflingActiveValidatorIndices . push ( i ) ;
@@ -312,7 +370,9 @@ export function beforeProcessEpoch(
312
370
if ( forkSeq === ForkSeq . phase0 ) {
313
371
processPendingAttestations (
314
372
state as CachedBeaconStatePhase0 ,
315
- statuses ,
373
+ proposerIndices ,
374
+ inclusionDelays ,
375
+ flags ,
316
376
( state as CachedBeaconStatePhase0 ) . previousEpochAttestations . getAllReadonly ( ) ,
317
377
prevEpoch ,
318
378
FLAG_PREV_SOURCE_ATTESTER ,
@@ -321,7 +381,9 @@ export function beforeProcessEpoch(
321
381
) ;
322
382
processPendingAttestations (
323
383
state as CachedBeaconStatePhase0 ,
324
- statuses ,
384
+ proposerIndices ,
385
+ inclusionDelays ,
386
+ flags ,
325
387
( state as CachedBeaconStatePhase0 ) . currentEpochAttestations . getAllReadonly ( ) ,
326
388
currentEpoch ,
327
389
FLAG_CURR_SOURCE_ATTESTER ,
@@ -330,23 +392,15 @@ export function beforeProcessEpoch(
330
392
) ;
331
393
} else {
332
394
const previousEpochParticipation = ( state as CachedBeaconStateAltair ) . previousEpochParticipation . getAll ( ) ;
333
- for ( let i = 0 ; i < previousEpochParticipation . length ; i ++ ) {
334
- const status = statuses [ i ] ;
335
- // this is required to pass random spec tests in altair
336
- if ( isActivePrevEpoch [ i ] ) {
337
- // FLAG_PREV are indexes [0,1,2]
338
- status . flags |= previousEpochParticipation [ i ] ;
339
- }
340
- }
341
-
342
395
const currentEpochParticipation = ( state as CachedBeaconStateAltair ) . currentEpochParticipation . getAll ( ) ;
343
- for ( let i = 0 ; i < currentEpochParticipation . length ; i ++ ) {
344
- const status = statuses [ i ] ;
345
- // this is required to pass random spec tests in altair
346
- if ( status . active ) {
347
- // FLAG_PREV are indexes [3,4,5], so shift by 3
348
- status . flags |= currentEpochParticipation [ i ] << 3 ;
349
- }
396
+ for ( let i = 0 ; i < validatorCount ; i ++ ) {
397
+ flags [ i ] |=
398
+ // checking active status first is required to pass random spec tests in altair
399
+ // in practice, inactive validators will have 0 participation
400
+ // FLAG_PREV are indexes [0,1,2]
401
+ ( isActivePrevEpoch [ i ] ? previousEpochParticipation [ i ] : 0 ) |
402
+ // FLAG_CURR are indexes [3,4,5], so shift by 3
403
+ ( isActiveCurrEpoch [ i ] ? currentEpochParticipation [ i ] << 3 : 0 ) ;
350
404
}
351
405
}
352
406
@@ -361,19 +415,19 @@ export function beforeProcessEpoch(
361
415
const FLAG_PREV_HEAD_ATTESTER_UNSLASHED = FLAG_PREV_HEAD_ATTESTER | FLAG_UNSLASHED ;
362
416
const FLAG_CURR_TARGET_UNSLASHED = FLAG_CURR_TARGET_ATTESTER | FLAG_UNSLASHED ;
363
417
364
- for ( let i = 0 ; i < statuses . length ; i ++ ) {
365
- const status = statuses [ i ] ;
418
+ for ( let i = 0 ; i < validatorCount ; i ++ ) {
366
419
const effectiveBalanceByIncrement = effectiveBalancesByIncrements [ i ] ;
367
- if ( hasMarkers ( status . flags , FLAG_PREV_SOURCE_ATTESTER_UNSLASHED ) ) {
420
+ const flag = flags [ i ] ;
421
+ if ( hasMarkers ( flag , FLAG_PREV_SOURCE_ATTESTER_UNSLASHED ) ) {
368
422
prevSourceUnslStake += effectiveBalanceByIncrement ;
369
423
}
370
- if ( hasMarkers ( status . flags , FLAG_PREV_TARGET_ATTESTER_UNSLASHED ) ) {
424
+ if ( hasMarkers ( flag , FLAG_PREV_TARGET_ATTESTER_UNSLASHED ) ) {
371
425
prevTargetUnslStake += effectiveBalanceByIncrement ;
372
426
}
373
- if ( hasMarkers ( status . flags , FLAG_PREV_HEAD_ATTESTER_UNSLASHED ) ) {
427
+ if ( hasMarkers ( flag , FLAG_PREV_HEAD_ATTESTER_UNSLASHED ) ) {
374
428
prevHeadUnslStake += effectiveBalanceByIncrement ;
375
429
}
376
- if ( hasMarkers ( status . flags , FLAG_CURR_TARGET_UNSLASHED ) ) {
430
+ if ( hasMarkers ( flag , FLAG_CURR_TARGET_UNSLASHED ) ) {
377
431
currTargetUnslStake += effectiveBalanceByIncrement ;
378
432
}
379
433
}
@@ -421,8 +475,12 @@ export function beforeProcessEpoch(
421
475
nextEpochShufflingActiveValidatorIndices,
422
476
// to be updated in processEffectiveBalanceUpdates
423
477
nextEpochTotalActiveBalanceByIncrement : 0 ,
478
+ isActivePrevEpoch,
479
+ isActiveCurrEpoch,
424
480
isActiveNextEpoch,
425
- statuses,
481
+ proposerIndices,
482
+ inclusionDelays,
483
+ flags,
426
484
427
485
// Will be assigned in processRewardsAndPenalties()
428
486
balances : undefined ,
0 commit comments