Skip to content

Commit fb26214

Browse files
authored
fix: query shuffling from cache to compute proposer lookahead (#7988)
**Motivation** Right now we always compute shuffling during epoch transition when processing proposer lookahead (`processProposerLookahead()`) post fulu but it should already be in cache most of the time. **Description** Query shuffling from cache to compute proposer lookahead
1 parent ea1977d commit fb26214

File tree

5 files changed

+47
-24
lines changed

5 files changed

+47
-24
lines changed

packages/beacon-node/test/spec/presets/epoch_processing.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ const epochTransitionFns: Record<string, EpochTransitionFn> = {
4747
historical_summaries_update: epochFns.processHistoricalSummariesUpdate as EpochTransitionFn,
4848
pending_deposits: epochFns.processPendingDeposits as EpochTransitionFn,
4949
pending_consolidations: epochFns.processPendingConsolidations as EpochTransitionFn,
50-
proposer_lookahead: (state, _) => {
50+
proposer_lookahead: (state, epochTransitionCache) => {
5151
const fork = state.config.getForkSeq(state.slot);
52-
epochFns.processProposerLookahead(fork, state as CachedBeaconStateFulu);
52+
epochFns.processProposerLookahead(fork, state as CachedBeaconStateFulu, epochTransitionCache);
5353
},
5454
};
5555

packages/state-transition/src/epoch/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export function processEpoch(
194194
const timer = metrics?.epochTransitionStepTime.startTimer({
195195
step: EpochTransitionStep.processProposerLookahead,
196196
});
197-
processProposerLookahead(fork, state as CachedBeaconStateFulu);
197+
processProposerLookahead(fork, state as CachedBeaconStateFulu, cache);
198198
timer?.();
199199
}
200200
}

packages/state-transition/src/epoch/processProposerLookahead.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {ForkSeq, MIN_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
22
import {ssz} from "@lodestar/types";
3-
import {CachedBeaconStateFulu} from "../types.js";
3+
import {CachedBeaconStateFulu, EpochTransitionCache} from "../types.js";
4+
import {computeEpochShuffling} from "../util/epochShuffling.js";
45
import {computeProposerIndices} from "../util/seed.js";
56

67
/**
@@ -10,12 +11,26 @@ import {computeProposerIndices} from "../util/seed.js";
1011
* this means that at the start of epoch `N`, the proposer lookahead for epoch
1112
* `N+1` will be computed and included in the beacon state's lookahead.
1213
*/
13-
export function processProposerLookahead(fork: ForkSeq, state: CachedBeaconStateFulu): void {
14+
export function processProposerLookahead(
15+
fork: ForkSeq,
16+
state: CachedBeaconStateFulu,
17+
cache: EpochTransitionCache
18+
): void {
1419
// Shift out proposers in the first epoch
1520
const remainingProposerLookahead = state.proposerLookahead.getAll().slice(SLOTS_PER_EPOCH);
1621

1722
// Fill in the last epoch with new proposer indices
18-
const lastEpochProposerLookahead = computeProposerIndices(fork, state, state.epochCtx.epoch + MIN_SEED_LOOKAHEAD + 1);
23+
const epoch = state.epochCtx.epoch + MIN_SEED_LOOKAHEAD + 1;
24+
25+
const shuffling =
26+
state.epochCtx.shufflingCache?.getSync(epoch, cache.nextShufflingDecisionRoot, {
27+
state,
28+
activeIndices: cache.nextShufflingActiveIndices,
29+
}) ??
30+
// Only for testing. shufflingCache should always be available in prod
31+
computeEpochShuffling(state, cache.nextShufflingActiveIndices, epoch);
32+
33+
const lastEpochProposerLookahead = computeProposerIndices(fork, state, shuffling, epoch);
1934

2035
state.proposerLookahead = ssz.fulu.ProposerLookahead.toViewDU([
2136
...remainingProposerLookahead,

packages/state-transition/src/util/fulu.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import {ForkSeq, MIN_SEED_LOOKAHEAD} from "@lodestar/params";
22
import {ValidatorIndex} from "@lodestar/types";
33
import {CachedBeaconStateElectra} from "../types.js";
4+
import {computeEpochShuffling} from "./epochShuffling.js";
45
import {computeProposerIndices} from "./seed.js";
6+
import {getActiveValidatorIndices} from "./validator.js";
57

68
/**
79
* Return the proposer indices for the full available lookahead starting from current epoch.
@@ -12,8 +14,29 @@ export function initializeProposerLookahead(state: CachedBeaconStateElectra): Va
1214

1315
const lookahead: ValidatorIndex[] = [];
1416

15-
for (let i = 0; i < MIN_SEED_LOOKAHEAD + 1; i++) {
16-
lookahead.push(...computeProposerIndices(ForkSeq.fulu, state, currentEpoch + i));
17+
for (let i = 0; i <= MIN_SEED_LOOKAHEAD; i++) {
18+
const epoch = currentEpoch + i;
19+
20+
// Try to pull cached shuffling first
21+
let shuffling = state.epochCtx.getShufflingAtEpochOrNull(epoch);
22+
23+
if (!shuffling) {
24+
// Only compute epoch shuffling if cache is not yet populated
25+
let activeIndices: Uint32Array;
26+
if (epoch === currentEpoch) {
27+
// This should never happen as current shuffling will always be cached
28+
activeIndices = state.epochCtx.currentShuffling.activeIndices;
29+
} else if (epoch === currentEpoch + 1) {
30+
activeIndices = state.epochCtx.nextActiveIndices;
31+
} else {
32+
// This will never be reached with current spec as `MIN_SEED_LOOKAHEAD == 1`
33+
activeIndices = getActiveValidatorIndices(state, epoch);
34+
}
35+
36+
shuffling = computeEpochShuffling(state, activeIndices, epoch);
37+
}
38+
39+
lookahead.push(...computeProposerIndices(ForkSeq.fulu, state, shuffling, epoch));
1740
}
1841

1942
return lookahead;

packages/state-transition/src/util/seed.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ import {EffectiveBalanceIncrements} from "../cache/effectiveBalanceIncrements.js
2222
import {BeaconStateAllForks, CachedBeaconStateAllForks} from "../types.js";
2323
import {computeStartSlotAtEpoch} from "./epoch.js";
2424
import {computeEpochAtSlot} from "./epoch.js";
25-
import {EpochShuffling, computeEpochShuffling} from "./epochShuffling.js";
26-
import {getActiveValidatorIndices} from "./validator.js";
2725

2826
/**
2927
* Compute proposer indices for an epoch
@@ -145,25 +143,12 @@ export function computeProposerIndex(
145143
export function computeProposerIndices(
146144
fork: ForkSeq,
147145
state: CachedBeaconStateAllForks,
146+
shuffling: {activeIndices: Uint32Array},
148147
epoch: Epoch
149148
): ValidatorIndex[] {
150149
const startSlot = computeStartSlotAtEpoch(epoch);
151150
const proposers = [];
152151
const epochSeed = getSeed(state, epoch, DOMAIN_BEACON_PROPOSER);
153-
// TODO FULU: Compute shuffling if shuffling cache miss is a temporary workaround.
154-
// EpochCache needs to cache shuffling for nextEpoch + 1 too.
155-
let shuffling: EpochShuffling;
156-
try {
157-
shuffling = state.epochCtx.getShufflingAtEpoch(epoch);
158-
} catch (e) {
159-
state.epochCtx.shufflingCache?.logger?.verbose(
160-
`Shuffling cache miss for epoch ${epoch}. Current epoch ${state.epochCtx.epoch}, computing shuffling...`,
161-
{},
162-
e as Error
163-
);
164-
state.commit();
165-
shuffling = computeEpochShuffling(state, getActiveValidatorIndices(state, epoch), epoch);
166-
}
167152

168153
for (let slot = startSlot; slot < startSlot + SLOTS_PER_EPOCH; slot++) {
169154
proposers.push(

0 commit comments

Comments
 (0)