Skip to content

Commit

Permalink
Only call getBlockSlotState() if necessary
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed Apr 22, 2023
1 parent 99d4944 commit 7b1c83f
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 20 deletions.
16 changes: 3 additions & 13 deletions packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@ export function getValidatorApi({
const headState = chain.getHeadState();
const headSlot = headState.slot;
const attEpoch = computeEpochAtSlot(slot);
const headEpoch = computeEpochAtSlot(headSlot);
const headBlockRootHex = chain.forkChoice.getHead().blockRoot;
const headBlockRoot = fromHexString(headBlockRootHex);

Expand All @@ -304,16 +303,7 @@ export function getValidatorApi({

// To get the correct source we must get a state in the same epoch as the attestation's epoch.
// An epoch transition may change state.currentJustifiedCheckpoint
const attEpochState =
attEpoch <= headEpoch
? headState
: // Will advance the state to the correct next epoch if necessary
await chain.regen.getBlockSlotState(
headBlockRootHex,
slot,
{dontTransferCache: true},
RegenCaller.produceAttestationData
);
const attEpochState = await chain.getHeadStateAtEpoch(attEpoch, RegenCaller.produceAttestationData);

return {
data: {
Expand Down Expand Up @@ -367,7 +357,7 @@ export function getValidatorApi({
await waitForNextClosestEpoch();

const head = chain.forkChoice.getHead();
const state = await chain.getHeadStateAtCurrentEpoch();
const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties);

const stateEpoch = state.epochCtx.epoch;
let indexes: ValidatorIndex[] = [];
Expand Down Expand Up @@ -423,7 +413,7 @@ export function getValidatorApi({
}

const head = chain.forkChoice.getHead();
const state = await chain.getHeadStateAtCurrentEpoch();
const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties);

// TODO: Determine what the current epoch would be if we fast-forward our system clock by
// `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
Expand Down
19 changes: 15 additions & 4 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,11 +333,22 @@ export class BeaconChain implements IBeaconChain {
return headState;
}

async getHeadStateAtCurrentEpoch(): Promise<CachedBeaconStateAllForks> {
const currentEpochStartSlot = computeStartSlotAtEpoch(this.clock.currentEpoch);
async getHeadStateAtCurrentEpoch(regenCaller: RegenCaller): Promise<CachedBeaconStateAllForks> {
return this.getHeadStateAtEpoch(this.clock.currentEpoch, regenCaller);
}

async getHeadStateAtEpoch(epoch: Epoch, regenCaller: RegenCaller): Promise<CachedBeaconStateAllForks> {
// using getHeadState() means we'll use checkpointStateCache if it's available
const headState = this.getHeadState();
// head state is in the same epoch, or we pulled up head state already from past epoch
if (epoch <= computeEpochAtSlot(headState.slot)) {
// should go to this most of the time
return headState;
}
// only use regen queue if necessary, it'll cache in checkpointStateCache if regen gets through epoch transition
const head = this.forkChoice.getHead();
const bestSlot = currentEpochStartSlot > head.slot ? currentEpochStartSlot : head.slot;
return this.regen.getBlockSlotState(head.blockRoot, bestSlot, {dontTransferCache: true}, RegenCaller.getDuties);
const startSlot = computeStartSlotAtEpoch(epoch);
return this.regen.getBlockSlotState(head.blockRoot, startSlot, {dontTransferCache: true}, regenCaller);
}

async getCanonicalBlockAtSlot(slot: Slot): Promise<allForks.SignedBeaconBlock | null> {
Expand Down
5 changes: 3 additions & 2 deletions packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {IExecutionEngine, IExecutionBuilder} from "../execution/index.js";
import {Metrics} from "../metrics/metrics.js";
import {BeaconClock} from "./clock/interface.js";
import {ChainEventEmitter} from "./emitter.js";
import {IStateRegenerator} from "./regen/index.js";
import {IStateRegenerator, RegenCaller} from "./regen/index.js";
import {StateContextCache, CheckpointStateCache} from "./stateCache/index.js";
import {IBlsVerifier} from "./bls/index.js";
import {
Expand Down Expand Up @@ -105,7 +105,8 @@ export interface IBeaconChain {
validatorSeenAtEpoch(index: ValidatorIndex, epoch: Epoch): boolean;

getHeadState(): CachedBeaconStateAllForks;
getHeadStateAtCurrentEpoch(): Promise<CachedBeaconStateAllForks>;
getHeadStateAtCurrentEpoch(regenCaller: RegenCaller): Promise<CachedBeaconStateAllForks>;
getHeadStateAtEpoch(epoch: Epoch, regenCaller: RegenCaller): Promise<CachedBeaconStateAllForks>;

/**
* Since we can have multiple parallel chains,
Expand Down
1 change: 1 addition & 0 deletions packages/beacon-node/src/chain/regen/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum RegenCaller {
processBlocksInEpoch = "processBlocksInEpoch",
validateGossipAggregateAndProof = "validateGossipAggregateAndProof",
validateGossipAttestation = "validateGossipAttestation",
validateGossipVoluntaryExit = "validateGossipVoluntaryExit",
onForkChoiceFinalized = "onForkChoiceFinalized",
}

Expand Down
6 changes: 6 additions & 0 deletions packages/beacon-node/src/chain/regen/queued.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ export class QueuedStateRegenerator implements IStateRegenerator {
return this.jobQueue.push({key: "getCheckpointState", args: [cp, opts, rCaller]});
}

/**
* Get state of provided `blockRoot` and dial forward to `slot`
* Use this api with care because we don't want the queue to be busy
* For the context, gossip block validation uses this api so we want it to be as fast as possible
* @returns
*/
async getBlockSlotState(
blockRoot: RootHex,
slot: Slot,
Expand Down
3 changes: 2 additions & 1 deletion packages/beacon-node/src/chain/validation/voluntaryExit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {phase0} from "@lodestar/types";
import {isValidVoluntaryExit, getVoluntaryExitSignatureSet} from "@lodestar/state-transition";
import {IBeaconChain} from "..";
import {VoluntaryExitError, VoluntaryExitErrorCode, GossipAction} from "../errors/index.js";
import {RegenCaller} from "../regen/index.js";

export async function validateGossipVoluntaryExit(
chain: IBeaconChain,
Expand All @@ -22,7 +23,7 @@ export async function validateGossipVoluntaryExit(
// The voluntaryExit.epoch must be in the past but the validator's status may change in recent epochs.
// We dial the head state to the current epoch to get the current status of the validator. This is
// relevant on periods of many skipped slots.
const state = await chain.getHeadStateAtCurrentEpoch();
const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.validateGossipVoluntaryExit);

// [REJECT] All of the conditions within process_voluntary_exit pass validation.
// verifySignature = false, verified in batch below
Expand Down
5 changes: 5 additions & 0 deletions packages/beacon-node/test/utils/mocks/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export class MockBeaconChain implements IBeaconChain {
this.pubkey2index = new PubkeyIndexMap();
this.index2pubkey = [];
}

executionBuilder?: IExecutionBuilder | undefined;

validatorSeenAtEpoch(): boolean {
Expand All @@ -181,6 +182,10 @@ export class MockBeaconChain implements IBeaconChain {
return this.state;
}

async getHeadStateAtEpoch(): Promise<CachedBeaconStateAllForks> {
return this.state;
}

async getCanonicalBlockAtSlot(): Promise<allForks.SignedBeaconBlock> {
throw Error("Not implemented");
}
Expand Down

0 comments on commit 7b1c83f

Please sign in to comment.