From 7b1c83f0222df99f86dea9c0481d9a86f4c6373a Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Sat, 22 Apr 2023 15:52:25 +0700 Subject: [PATCH] Only call getBlockSlotState() if necessary --- .../src/api/impl/validator/index.ts | 16 +++------------- packages/beacon-node/src/chain/chain.ts | 19 +++++++++++++++---- packages/beacon-node/src/chain/interface.ts | 5 +++-- .../beacon-node/src/chain/regen/interface.ts | 1 + .../beacon-node/src/chain/regen/queued.ts | 6 ++++++ .../src/chain/validation/voluntaryExit.ts | 3 ++- .../test/utils/mocks/chain/chain.ts | 5 +++++ 7 files changed, 35 insertions(+), 20 deletions(-) diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 4c73ca20d6a7..600a4bf7a39f 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -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); @@ -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: { @@ -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[] = []; @@ -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`. diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index e32b890ce2e4..4f0eacdebb7b 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -333,11 +333,22 @@ export class BeaconChain implements IBeaconChain { return headState; } - async getHeadStateAtCurrentEpoch(): Promise { - const currentEpochStartSlot = computeStartSlotAtEpoch(this.clock.currentEpoch); + async getHeadStateAtCurrentEpoch(regenCaller: RegenCaller): Promise { + return this.getHeadStateAtEpoch(this.clock.currentEpoch, regenCaller); + } + + async getHeadStateAtEpoch(epoch: Epoch, regenCaller: RegenCaller): Promise { + // 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 { diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index 990cfda1ea9c..a8b1270e2241 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -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 { @@ -105,7 +105,8 @@ export interface IBeaconChain { validatorSeenAtEpoch(index: ValidatorIndex, epoch: Epoch): boolean; getHeadState(): CachedBeaconStateAllForks; - getHeadStateAtCurrentEpoch(): Promise; + getHeadStateAtCurrentEpoch(regenCaller: RegenCaller): Promise; + getHeadStateAtEpoch(epoch: Epoch, regenCaller: RegenCaller): Promise; /** * Since we can have multiple parallel chains, diff --git a/packages/beacon-node/src/chain/regen/interface.ts b/packages/beacon-node/src/chain/regen/interface.ts index 845868074bd2..67d4bbcc52b9 100644 --- a/packages/beacon-node/src/chain/regen/interface.ts +++ b/packages/beacon-node/src/chain/regen/interface.ts @@ -11,6 +11,7 @@ export enum RegenCaller { processBlocksInEpoch = "processBlocksInEpoch", validateGossipAggregateAndProof = "validateGossipAggregateAndProof", validateGossipAttestation = "validateGossipAttestation", + validateGossipVoluntaryExit = "validateGossipVoluntaryExit", onForkChoiceFinalized = "onForkChoiceFinalized", } diff --git a/packages/beacon-node/src/chain/regen/queued.ts b/packages/beacon-node/src/chain/regen/queued.ts index b4ee806cfba4..9c4f8f97bdf2 100644 --- a/packages/beacon-node/src/chain/regen/queued.ts +++ b/packages/beacon-node/src/chain/regen/queued.ts @@ -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, diff --git a/packages/beacon-node/src/chain/validation/voluntaryExit.ts b/packages/beacon-node/src/chain/validation/voluntaryExit.ts index b3b9e2a81c5a..385c370b8de2 100644 --- a/packages/beacon-node/src/chain/validation/voluntaryExit.ts +++ b/packages/beacon-node/src/chain/validation/voluntaryExit.ts @@ -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, @@ -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 diff --git a/packages/beacon-node/test/utils/mocks/chain/chain.ts b/packages/beacon-node/test/utils/mocks/chain/chain.ts index 7fb7629770a2..85d3a4922209 100644 --- a/packages/beacon-node/test/utils/mocks/chain/chain.ts +++ b/packages/beacon-node/test/utils/mocks/chain/chain.ts @@ -165,6 +165,7 @@ export class MockBeaconChain implements IBeaconChain { this.pubkey2index = new PubkeyIndexMap(); this.index2pubkey = []; } + executionBuilder?: IExecutionBuilder | undefined; validatorSeenAtEpoch(): boolean { @@ -181,6 +182,10 @@ export class MockBeaconChain implements IBeaconChain { return this.state; } + async getHeadStateAtEpoch(): Promise { + return this.state; + } + async getCanonicalBlockAtSlot(): Promise { throw Error("Not implemented"); }