Skip to content

Commit eb9ac68

Browse files
authored
Merge 21e3728 into 6ab2697
2 parents 6ab2697 + 21e3728 commit eb9ac68

13 files changed

+287
-162
lines changed

packages/beacon-node/src/chain/rewards/attestationsRewards.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -140,35 +140,35 @@ function computeTotalAttestationsRewardsAltair(
140140
validatorIds: (ValidatorIndex | string)[] = []
141141
): TotalAttestationsReward[] {
142142
const rewards = [];
143-
const {statuses} = transitionCache;
143+
const {flags} = transitionCache;
144144
const {epochCtx, config} = state;
145145
const validatorIndices = validatorIds
146146
.map((id) => (typeof id === "number" ? id : epochCtx.pubkey2index.get(id)))
147147
.filter((index) => index !== undefined); // Validator indices to include in the result
148148

149149
const inactivityPenaltyDenominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR;
150150

151-
for (let i = 0; i < statuses.length; i++) {
151+
for (let i = 0; i < flags.length; i++) {
152152
if (validatorIndices.length && !validatorIndices.includes(i)) {
153153
continue;
154154
}
155155

156-
const status = statuses[i];
157-
if (!hasMarkers(status.flags, FLAG_ELIGIBLE_ATTESTER)) {
156+
const flag = flags[i];
157+
if (!hasMarkers(flag, FLAG_ELIGIBLE_ATTESTER)) {
158158
continue;
159159
}
160160

161161
const effectiveBalanceIncrement = epochCtx.effectiveBalanceIncrements[i];
162162

163163
const currentRewards = {...defaultAttestationsReward, validatorIndex: i};
164164

165-
if (hasMarkers(status.flags, FLAG_PREV_SOURCE_ATTESTER_UNSLASHED)) {
165+
if (hasMarkers(flag, FLAG_PREV_SOURCE_ATTESTER_UNSLASHED)) {
166166
currentRewards.source = idealRewards[effectiveBalanceIncrement].source;
167167
} else {
168168
currentRewards.source = penalties[effectiveBalanceIncrement].source * -1; // Negative reward to indicate penalty
169169
}
170170

171-
if (hasMarkers(status.flags, FLAG_PREV_TARGET_ATTESTER_UNSLASHED)) {
171+
if (hasMarkers(flag, FLAG_PREV_TARGET_ATTESTER_UNSLASHED)) {
172172
currentRewards.target = idealRewards[effectiveBalanceIncrement].target;
173173
} else {
174174
currentRewards.target = penalties[effectiveBalanceIncrement].target * -1;
@@ -179,7 +179,7 @@ function computeTotalAttestationsRewardsAltair(
179179
currentRewards.inactivity = Math.floor(inactivityPenaltyNumerator / inactivityPenaltyDenominator) * -1;
180180
}
181181

182-
if (hasMarkers(status.flags, FLAG_PREV_HEAD_ATTESTER_UNSLASHED)) {
182+
if (hasMarkers(flag, FLAG_PREV_HEAD_ATTESTER_UNSLASHED)) {
183183
currentRewards.head = idealRewards[effectiveBalanceIncrement].head;
184184
}
185185

packages/beacon-node/src/metrics/validatorMonitor.ts

+25-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
computeEpochAtSlot,
3-
AttesterStatus,
43
parseAttesterFlags,
54
CachedBeaconStateAllForks,
65
CachedBeaconStateAltair,
@@ -39,7 +38,14 @@ export enum OpSource {
3938
export type ValidatorMonitor = {
4039
registerLocalValidator(index: number): void;
4140
registerLocalValidatorInSyncCommittee(index: number, untilEpoch: Epoch): void;
42-
registerValidatorStatuses(currentEpoch: Epoch, statuses: AttesterStatus[], balances?: number[]): void;
41+
registerValidatorStatuses(
42+
currentEpoch: Epoch,
43+
inclusionDelays: number[],
44+
flags: number[],
45+
isActiveCurrEpoch: boolean[],
46+
isActivePrevEpoch: boolean[],
47+
balances?: number[]
48+
): void;
4349
registerBeaconBlock(src: OpSource, seenTimestampSec: Seconds, block: BeaconBlock): void;
4450
registerBlobSidecar(src: OpSource, seenTimestampSec: Seconds, blob: deneb.BlobSidecar): void;
4551
registerImportedBlock(block: BeaconBlock, data: {proposerBalanceDelta: number}): void;
@@ -115,12 +121,17 @@ type ValidatorStatus = {
115121
inclusionDistance: number;
116122
};
117123

118-
function statusToSummary(status: AttesterStatus): ValidatorStatus {
119-
const flags = parseAttesterFlags(status.flags);
124+
function statusToSummary(
125+
inclusionDelay: number,
126+
flag: number,
127+
isActiveInCurrentEpoch: boolean,
128+
isActiveInPreviousEpoch: boolean
129+
): ValidatorStatus {
130+
const flags = parseAttesterFlags(flag);
120131
return {
121132
isSlashed: flags.unslashed,
122-
isActiveInCurrentEpoch: status.active,
123-
isActiveInPreviousEpoch: status.active,
133+
isActiveInCurrentEpoch,
134+
isActiveInPreviousEpoch,
124135
// TODO: Implement
125136
currentEpochEffectiveBalance: 0,
126137

@@ -130,7 +141,7 @@ function statusToSummary(status: AttesterStatus): ValidatorStatus {
130141
isCurrSourceAttester: flags.currSourceAttester,
131142
isCurrTargetAttester: flags.currTargetAttester,
132143
isCurrHeadAttester: flags.currHeadAttester,
133-
inclusionDistance: status.inclusionDelay,
144+
inclusionDistance: inclusionDelay,
134145
};
135146
}
136147

@@ -287,7 +298,7 @@ export function createValidatorMonitor(
287298
}
288299
},
289300

290-
registerValidatorStatuses(currentEpoch, statuses, balances) {
301+
registerValidatorStatuses(currentEpoch, inclusionDelays, flags, isActiveCurrEpoch, isActiveInPrevEpoch, balances) {
291302
// Prevent registering status for the same epoch twice. processEpoch() may be ran more than once for the same epoch.
292303
if (currentEpoch <= lastRegisteredStatusEpoch) {
293304
return;
@@ -301,12 +312,12 @@ export function createValidatorMonitor(
301312
// - One to account for it being the previous epoch.
302313
// - One to account for the state advancing an epoch whilst generating the validator
303314
// statuses.
304-
const status = statuses[index];
305-
if (status === undefined) {
306-
continue;
307-
}
308-
309-
const summary = statusToSummary(status);
315+
const summary = statusToSummary(
316+
inclusionDelays[index],
317+
flags[index],
318+
isActiveCurrEpoch[index],
319+
isActiveInPrevEpoch[index]
320+
);
310321

311322
if (summary.isPrevSourceAttester) {
312323
metrics.validatorMonitor.prevEpochOnChainSourceAttesterHit.inc();

packages/state-transition/src/cache/epochTransitionCache.ts

+98-40
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import {intDiv} from "@lodestar/utils";
33
import {EPOCHS_PER_SLASHINGS_VECTOR, FAR_FUTURE_EPOCH, ForkSeq, MAX_EFFECTIVE_BALANCE} from "@lodestar/params";
44

55
import {
6-
AttesterStatus,
7-
createAttesterStatus,
86
hasMarkers,
97
FLAG_UNSLASHED,
108
FLAG_ELIGIBLE_ATTESTER,
@@ -128,7 +126,12 @@ export interface EpochTransitionCache {
128126
* - prev attester flag set
129127
* With a status flag to check this conditions at once we just have to mask with an OR of the conditions.
130128
*/
131-
statuses: AttesterStatus[];
129+
130+
proposerIndices: number[];
131+
132+
inclusionDelays: number[];
133+
134+
flags: number[];
132135

133136
/**
134137
* balances array will be populated by processRewardsAndPenalties() and consumed by processEffectiveBalanceUpdates().
@@ -161,13 +164,40 @@ export interface EpochTransitionCache {
161164
*/
162165
nextEpochTotalActiveBalanceByIncrement: number;
163166

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+
164179
/**
165180
* Track by validator index if it's active in the next epoch.
166181
* Used in `processEffectiveBalanceUpdates` to save one loop over validators after epoch process.
167182
*/
168183
isActiveNextEpoch: boolean[];
169184
}
170185

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+
171201
export function beforeProcessEpoch(
172202
state: CachedBeaconStateAllForks,
173203
opts?: EpochTransitionCacheOpts
@@ -188,9 +218,6 @@ export function beforeProcessEpoch(
188218
const indicesEligibleForActivation: ValidatorIndex[] = [];
189219
const indicesToEject: ValidatorIndex[] = [];
190220
const nextEpochShufflingActiveValidatorIndices: ValidatorIndex[] = [];
191-
const isActivePrevEpoch: boolean[] = [];
192-
const isActiveNextEpoch: boolean[] = [];
193-
const statuses: AttesterStatus[] = [];
194221

195222
let totalActiveStakeByIncrement = 0;
196223

@@ -200,21 +227,47 @@ export function beforeProcessEpoch(
200227
const validators = state.validators.getAllReadonlyValues();
201228
const validatorCount = validators.length;
202229

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+
203256
// Clone before being mutated in processEffectiveBalanceUpdates
204257
epochCtx.beforeEpochTransition();
205258

206259
const effectiveBalancesByIncrements = epochCtx.effectiveBalanceIncrements;
207260

208261
for (let i = 0; i < validatorCount; i++) {
209262
const validator = validators[i];
210-
const status = createAttesterStatus();
263+
let flag = 0;
211264

212265
if (validator.slashed) {
213266
if (slashingsEpoch === validator.withdrawableEpoch) {
214267
indicesToSlash.push(i);
215268
}
216269
} else {
217-
status.flags |= FLAG_UNSLASHED;
270+
flag |= FLAG_UNSLASHED;
218271
}
219272

220273
const {activationEpoch, exitEpoch} = validator;
@@ -223,19 +276,24 @@ export function beforeProcessEpoch(
223276
const isActiveNext = activationEpoch <= nextEpoch && nextEpoch < exitEpoch;
224277
const isActiveNext2 = activationEpoch <= nextEpoch2 && nextEpoch2 < exitEpoch;
225278

226-
isActivePrevEpoch.push(isActivePrev);
279+
if (!isActivePrev) {
280+
isActivePrevEpoch[i] = false;
281+
}
227282

228283
// Both active validators and slashed-but-not-yet-withdrawn validators are eligible to receive penalties.
229284
// This is done to prevent self-slashing from being a way to escape inactivity leaks.
230285
// TODO: Consider using an array of `eligibleValidatorIndices: number[]`
231286
if (isActivePrev || (validator.slashed && prevEpoch + 1 < validator.withdrawableEpoch)) {
232287
eligibleValidatorIndices.push(i);
233-
status.flags |= FLAG_ELIGIBLE_ATTESTER;
288+
flag |= FLAG_ELIGIBLE_ATTESTER;
234289
}
235290

291+
flags[i] = flag;
292+
236293
if (isActiveCurr) {
237-
status.active = true;
238294
totalActiveStakeByIncrement += effectiveBalancesByIncrements[i];
295+
} else {
296+
isActiveCurrEpoch[i] = false;
239297
}
240298

241299
// To optimize process_registry_updates():
@@ -278,16 +336,16 @@ export function beforeProcessEpoch(
278336
//
279337
// Use `else` since indicesEligibleForActivationQueue + indicesEligibleForActivation + indicesToEject are mutually exclusive
280338
else if (
281-
status.active &&
339+
isActiveCurr &&
282340
validator.exitEpoch === FAR_FUTURE_EPOCH &&
283341
validator.effectiveBalance <= config.EJECTION_BALANCE
284342
) {
285343
indicesToEject.push(i);
286344
}
287345

288-
statuses.push(status);
289-
290-
isActiveNextEpoch.push(isActiveNext);
346+
if (!isActiveNext) {
347+
isActiveNextEpoch[i] = false;
348+
}
291349

292350
if (isActiveNext2) {
293351
nextEpochShufflingActiveValidatorIndices.push(i);
@@ -312,7 +370,9 @@ export function beforeProcessEpoch(
312370
if (forkSeq === ForkSeq.phase0) {
313371
processPendingAttestations(
314372
state as CachedBeaconStatePhase0,
315-
statuses,
373+
proposerIndices,
374+
inclusionDelays,
375+
flags,
316376
(state as CachedBeaconStatePhase0).previousEpochAttestations.getAllReadonly(),
317377
prevEpoch,
318378
FLAG_PREV_SOURCE_ATTESTER,
@@ -321,7 +381,9 @@ export function beforeProcessEpoch(
321381
);
322382
processPendingAttestations(
323383
state as CachedBeaconStatePhase0,
324-
statuses,
384+
proposerIndices,
385+
inclusionDelays,
386+
flags,
325387
(state as CachedBeaconStatePhase0).currentEpochAttestations.getAllReadonly(),
326388
currentEpoch,
327389
FLAG_CURR_SOURCE_ATTESTER,
@@ -330,23 +392,15 @@ export function beforeProcessEpoch(
330392
);
331393
} else {
332394
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-
342395
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);
350404
}
351405
}
352406

@@ -361,19 +415,19 @@ export function beforeProcessEpoch(
361415
const FLAG_PREV_HEAD_ATTESTER_UNSLASHED = FLAG_PREV_HEAD_ATTESTER | FLAG_UNSLASHED;
362416
const FLAG_CURR_TARGET_UNSLASHED = FLAG_CURR_TARGET_ATTESTER | FLAG_UNSLASHED;
363417

364-
for (let i = 0; i < statuses.length; i++) {
365-
const status = statuses[i];
418+
for (let i = 0; i < validatorCount; i++) {
366419
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)) {
368422
prevSourceUnslStake += effectiveBalanceByIncrement;
369423
}
370-
if (hasMarkers(status.flags, FLAG_PREV_TARGET_ATTESTER_UNSLASHED)) {
424+
if (hasMarkers(flag, FLAG_PREV_TARGET_ATTESTER_UNSLASHED)) {
371425
prevTargetUnslStake += effectiveBalanceByIncrement;
372426
}
373-
if (hasMarkers(status.flags, FLAG_PREV_HEAD_ATTESTER_UNSLASHED)) {
427+
if (hasMarkers(flag, FLAG_PREV_HEAD_ATTESTER_UNSLASHED)) {
374428
prevHeadUnslStake += effectiveBalanceByIncrement;
375429
}
376-
if (hasMarkers(status.flags, FLAG_CURR_TARGET_UNSLASHED)) {
430+
if (hasMarkers(flag, FLAG_CURR_TARGET_UNSLASHED)) {
377431
currTargetUnslStake += effectiveBalanceByIncrement;
378432
}
379433
}
@@ -421,8 +475,12 @@ export function beforeProcessEpoch(
421475
nextEpochShufflingActiveValidatorIndices,
422476
// to be updated in processEffectiveBalanceUpdates
423477
nextEpochTotalActiveBalanceByIncrement: 0,
478+
isActivePrevEpoch,
479+
isActiveCurrEpoch,
424480
isActiveNextEpoch,
425-
statuses,
481+
proposerIndices,
482+
inclusionDelays,
483+
flags,
426484

427485
// Will be assigned in processRewardsAndPenalties()
428486
balances: undefined,

0 commit comments

Comments
 (0)