From 79e2401e1ade77b2a09d0da370da65047a633394 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Wed, 27 Nov 2024 15:23:39 +0700 Subject: [PATCH 01/13] feat: add asyncShufflingCalculation to StateTransitionOpts --- .../src/chain/blocks/verifyBlocksStateTransitionOnly.ts | 2 ++ .../src/chain/historicalState/getHistoricalState.ts | 2 ++ .../beacon-node/src/chain/produceBlock/computeNewStateRoot.ts | 2 ++ packages/beacon-node/src/chain/regen/regen.ts | 3 +++ packages/state-transition/src/stateTransition.ts | 2 ++ 5 files changed, 11 insertions(+) diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts index b2f70d6274b3..7923e27861cf 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts @@ -56,6 +56,8 @@ export async function verifyBlocksStateTransitionOnly( // if block is trusted don't verify proposer or op signature verifyProposer: !useBlsBatchVerify && !validSignatures && !validProposerSignature, verifySignatures: !useBlsBatchVerify && !validSignatures, + // do no queue shuffling calculation, run it sync for epoch transitions + asyncShufflingCalculation: false }, metrics ); diff --git a/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts b/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts index 20a53c40d73d..7446254c3183 100644 --- a/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts +++ b/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts @@ -87,6 +87,8 @@ export async function getHistoricalState( verifyStateRoot: false, executionPayloadStatus: ExecutionPayloadStatus.valid, dataAvailableStatus: DataAvailableStatus.available, + // do no queue shuffling calculation, run it sync for epoch transitions + asyncShufflingCalculation: false, }, metrics ); diff --git a/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts b/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts index bfa30e570e06..9005be2147f1 100644 --- a/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts +++ b/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts @@ -38,6 +38,8 @@ export function computeNewStateRoot( verifySignatures: false, // Preserve cache in source state, since the resulting state is not added to the state cache dontTransferCache: true, + // This is called for produceBlockWrapper. Shuffling should be available so can run sync + asyncShufflingCalculation: false, }, metrics ); diff --git a/packages/beacon-node/src/chain/regen/regen.ts b/packages/beacon-node/src/chain/regen/regen.ts index 073556d8162f..a508192c1c4f 100644 --- a/packages/beacon-node/src/chain/regen/regen.ts +++ b/packages/beacon-node/src/chain/regen/regen.ts @@ -261,6 +261,9 @@ export class StateRegenerator implements IStateRegeneratorInternal { verifyStateRoot: false, verifyProposer: false, verifySignatures: false, + // If regen state is necessary do not run shuffling async. If an epoch boundary is crossed then the + // shuffling will be calculated JIT + asyncShufflingCalculation: false, }, this.modules.metrics ); diff --git a/packages/state-transition/src/stateTransition.ts b/packages/state-transition/src/stateTransition.ts index 929a37468c6e..4c701b0e632c 100644 --- a/packages/state-transition/src/stateTransition.ts +++ b/packages/state-transition/src/stateTransition.ts @@ -36,6 +36,7 @@ export type StateTransitionOpts = BlockExternalData & verifyProposer?: boolean; verifySignatures?: boolean; dontTransferCache?: boolean; + asyncShufflingCalculation: boolean; }; /** @@ -68,6 +69,7 @@ export function stateTransition( // Assume default to be valid and available executionPayloadStatus: ExecutionPayloadStatus.valid, dataAvailableStatus: DataAvailableStatus.available, + asyncShufflingCalculation: false, }, metrics?: BeaconStateTransitionMetrics | null ): CachedBeaconStateAllForks { From 6c18ed610cbb7449a139e1163825abb9ed5db75b Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Thu, 28 Nov 2024 14:18:00 +0700 Subject: [PATCH 02/13] feat: add asyncShufflingCalculation to all regen / processSlots consumers --- .../src/chain/blocks/verifyBlock.ts | 6 +++++- packages/beacon-node/src/chain/chain.ts | 15 ++++++++++----- .../beacon-node/src/chain/prepareNextSlot.ts | 4 ++-- .../beacon-node/src/chain/regen/interface.ts | 4 ++++ .../beacon-node/src/chain/regen/queued.ts | 19 ++++++++++++------- .../stateCache/inMemoryCheckpointsCache.ts | 3 ++- .../stateCache/persistentCheckpointsCache.ts | 6 +++++- .../src/chain/validation/blobSidecar.ts | 9 ++++++++- .../beacon-node/src/chain/validation/block.ts | 4 +++- .../src/cache/epochTransitionCache.ts | 4 ++++ .../state-transition/src/stateTransition.ts | 1 - 11 files changed, 55 insertions(+), 20 deletions(-) diff --git a/packages/beacon-node/src/chain/blocks/verifyBlock.ts b/packages/beacon-node/src/chain/blocks/verifyBlock.ts index 5ead67a720f7..190527acb8ce 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlock.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlock.ts @@ -66,7 +66,11 @@ export async function verifyBlocksInEpoch( // Retrieve preState from cache (regen) const preState0 = await this.regen // transfer cache to process faster, postState will be in block state cache - .getPreState(block0.message, {dontTransferCache: false}, RegenCaller.processBlocksInEpoch) + .getPreState( + block0.message, + {dontTransferCache: false, asyncShufflingCalculation: false}, + RegenCaller.processBlocksInEpoch + ) .catch((e) => { throw new BlockError(block0, {code: BlockErrorCode.PRESTATE_MISSING, error: e as Error}); }); diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 4751770b3bfc..1d3bb6c2af1f 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -435,7 +435,12 @@ export class BeaconChain implements IBeaconChain { // only use regen queue if necessary, it'll cache in checkpointStateCache if regen gets through epoch transition const head = this.forkChoice.getHead(); const startSlot = computeStartSlotAtEpoch(epoch); - return this.regen.getBlockSlotState(head.blockRoot, startSlot, {dontTransferCache: true}, regenCaller); + return this.regen.getBlockSlotState( + head.blockRoot, + startSlot, + {dontTransferCache: true, asyncShufflingCalculation: false}, + regenCaller + ); } async getStateBySlot( @@ -456,7 +461,7 @@ export class BeaconChain implements IBeaconChain { const state = await this.regen.getBlockSlotState( block.blockRoot, slot, - {dontTransferCache: true}, + {dontTransferCache: true, asyncShufflingCalculation: false}, RegenCaller.restApi ); return { @@ -615,7 +620,7 @@ export class BeaconChain implements IBeaconChain { const state = await this.regen.getBlockSlotState( toRootHex(parentBlockRoot), slot, - {dontTransferCache: true}, + {dontTransferCache: true, asyncShufflingCalculation: false}, RegenCaller.produceBlock ); @@ -664,7 +669,7 @@ export class BeaconChain implements IBeaconChain { const state = await this.regen.getBlockSlotState( toRootHex(parentBlockRoot), slot, - {dontTransferCache: true}, + {dontTransferCache: true, asyncShufflingCalculation: false}, RegenCaller.produceBlock ); const proposerIndex = state.epochCtx.getBeaconProposer(slot); @@ -899,7 +904,7 @@ export class BeaconChain implements IBeaconChain { state = await this.regen.getBlockSlotState( attHeadBlock.blockRoot, targetSlot, - {dontTransferCache: true}, + {dontTransferCache: true, asyncShufflingCalculation: false}, regenCaller ); } else if (blockEpoch > attEpoch) { diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index 40cadb3774c7..01f9bc621d0d 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -114,7 +114,7 @@ export class PrepareNextSlotScheduler { // the slot 0 of next epoch will likely use this Previous Root Checkpoint state for state transition so we transfer cache here // the resulting state with cache will be cached in Checkpoint State Cache which is used for the upcoming block processing // for other slots dontTransferCached=true because we don't run state transition on this state - {dontTransferCache: !isEpochTransition}, + {dontTransferCache: !isEpochTransition, asyncShufflingCalculation: isEpochTransition}, RegenCaller.precomputeEpoch ); @@ -140,7 +140,7 @@ export class PrepareNextSlotScheduler { updatedPrepareState = (await this.chain.regen.getBlockSlotState( proposerHeadRoot, prepareSlot, - {dontTransferCache: !isEpochTransition}, + {dontTransferCache: !isEpochTransition, asyncShufflingCalculation: isEpochTransition}, RegenCaller.predictProposerHead )) as CachedBeaconStateExecutions; updatedHeadRoot = proposerHeadRoot; diff --git a/packages/beacon-node/src/chain/regen/interface.ts b/packages/beacon-node/src/chain/regen/interface.ts index b70fbc059875..f4401c0d54f2 100644 --- a/packages/beacon-node/src/chain/regen/interface.ts +++ b/packages/beacon-node/src/chain/regen/interface.ts @@ -30,6 +30,10 @@ export enum RegenFnName { export type StateCloneOpts = { dontTransferCache: boolean; + /** + * Do not queue shuffling calculation async. Forces sync JIT calculation in afterProcessEpoch + */ + asyncShufflingCalculation: boolean; }; export interface IStateRegenerator extends IStateRegeneratorInternal { diff --git a/packages/beacon-node/src/chain/regen/queued.ts b/packages/beacon-node/src/chain/regen/queued.ts index b5084d593356..49f8d0516887 100644 --- a/packages/beacon-node/src/chain/regen/queued.ts +++ b/packages/beacon-node/src/chain/regen/queued.ts @@ -76,17 +76,19 @@ export class QueuedStateRegenerator implements IStateRegenerator { * This is not for block processing so don't transfer cache */ getStateSync(stateRoot: RootHex): CachedBeaconStateAllForks | null { - return this.blockStateCache.get(stateRoot, {dontTransferCache: true}); + return this.blockStateCache.get(stateRoot, {dontTransferCache: true, asyncShufflingCalculation: false}); } /** * Get state for block processing. * By default, do not transfer cache except for the block at clock slot * which is usually the gossip block. + * + * For sync operations also run shuffling calculation sync if more than one epoch is crossed */ getPreStateSync( block: BeaconBlock, - opts: StateCloneOpts = {dontTransferCache: true} + opts: StateCloneOpts = {dontTransferCache: true, asyncShufflingCalculation: false} ): CachedBeaconStateAllForks | null { const parentRoot = toRootHex(block.parentRoot); const parentBlock = this.forkChoice.getBlockHex(parentRoot); @@ -129,14 +131,14 @@ export class QueuedStateRegenerator implements IStateRegenerator { * Get checkpoint state from cache, this function is not for block processing so don't transfer cache */ getCheckpointStateSync(cp: CheckpointHex): CachedBeaconStateAllForks | null { - return this.checkpointStateCache.get(cp, {dontTransferCache: true}); + return this.checkpointStateCache.get(cp, {dontTransferCache: true, asyncShufflingCalculation: false}); } /** * Get state closest to head, this function is not for block processing so don't transfer cache */ getClosestHeadState(head: ProtoBlock): CachedBeaconStateAllForks | null { - const opts = {dontTransferCache: true}; + const opts: StateCloneOpts = {dontTransferCache: true, asyncShufflingCalculation: false}; return ( this.checkpointStateCache.getLatest(head.blockRoot, Infinity, opts) || this.blockStateCache.get(head.stateRoot, opts) @@ -178,7 +180,8 @@ export class QueuedStateRegenerator implements IStateRegenerator { newHeadStateRoot === maybeHeadStateRoot ? maybeHeadState : // maybeHeadState was already in block state cache so we don't transfer the cache - this.blockStateCache.get(newHeadStateRoot, {dontTransferCache: true}); + // run shuffling async if epoch transition is crossed. This will only happen once and will be cached + this.blockStateCache.get(newHeadStateRoot, {dontTransferCache: true, asyncShufflingCalculation: true}); if (headState) { this.blockStateCache.setHeadState(headState); @@ -194,7 +197,8 @@ export class QueuedStateRegenerator implements IStateRegenerator { // for the new FIFOBlockStateCache, it's important to reload state to regen head state here if needed const allowDiskReload = true; // transfer cache here because we want to regen state asap - const cloneOpts = {dontTransferCache: false}; + // run shuffling async as there will likely only be one epoch transition and it will get cached + const cloneOpts: StateCloneOpts = {dontTransferCache: false, asyncShufflingCalculation: true}; this.regen.getState(newHeadStateRoot, RegenCaller.processBlock, cloneOpts, allowDiskReload).then( (headStateRegen) => this.blockStateCache.setHeadState(headStateRegen), (e) => this.logger.error("Error on head state regen", logCtx, e) @@ -268,7 +272,8 @@ export class QueuedStateRegenerator implements IStateRegenerator { async getState( stateRoot: RootHex, rCaller: RegenCaller, - opts: StateCloneOpts = {dontTransferCache: true} + // default to non-async shuffling here. we are currently updating slot/epoch via getBlockSlotState + opts: StateCloneOpts = {dontTransferCache: true, asyncShufflingCalculation: false} ): Promise { this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getState}); diff --git a/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts index 4caa6779f697..cd9bb0d0956f 100644 --- a/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts @@ -48,7 +48,8 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { async getStateOrBytes(cp: CheckpointHex): Promise { // no need to transfer cache for this api - return this.get(cp, {dontTransferCache: true}); + // shuffling not touched here. just need to satisfy interface + return this.get(cp, {dontTransferCache: true, asyncShufflingCalculation: false}); } async getOrReloadLatest( diff --git a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts index 4c9a8b0f1265..526b0b8a6dcc 100644 --- a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts @@ -247,7 +247,11 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { */ async getStateOrBytes(cp: CheckpointHex): Promise { // don't have to transfer cache for this specific api - const stateOrLoadedState = await this.getStateOrLoadDb(cp, {dontTransferCache: true}); + // shuffling not touched here. just need to satisfy interface + const stateOrLoadedState = await this.getStateOrLoadDb(cp, { + dontTransferCache: true, + asyncShufflingCalculation: false, + }); if (stateOrLoadedState === null || isCachedBeaconState(stateOrLoadedState)) { return stateOrLoadedState; } diff --git a/packages/beacon-node/src/chain/validation/blobSidecar.ts b/packages/beacon-node/src/chain/validation/blobSidecar.ts index 3b90972f62b1..6044feaa4bcc 100644 --- a/packages/beacon-node/src/chain/validation/blobSidecar.ts +++ b/packages/beacon-node/src/chain/validation/blobSidecar.ts @@ -98,8 +98,15 @@ export async function validateGossipBlobSidecar( // this is something we should change this in the future to make the code airtight to the spec. // [IGNORE] The block's parent (defined by block.parent_root) has been seen (via both gossip and non-gossip sources) (a client MAY queue blocks for processing once the parent block is retrieved). // [REJECT] The block's parent (defined by block.parent_root) passes validation. + // + // For validation run next shuffling calculation sync. Should not actually be needed unless two epochs are crossed and the next shuffling is not already cached const blockState = await chain.regen - .getBlockSlotState(parentRoot, blobSlot, {dontTransferCache: true}, RegenCaller.validateGossipBlock) + .getBlockSlotState( + parentRoot, + blobSlot, + {dontTransferCache: true, asyncShufflingCalculation: false}, + RegenCaller.validateGossipBlock + ) .catch(() => { throw new BlobSidecarGossipError(GossipAction.IGNORE, {code: BlobSidecarErrorCode.PARENT_UNKNOWN, parentRoot}); }); diff --git a/packages/beacon-node/src/chain/validation/block.ts b/packages/beacon-node/src/chain/validation/block.ts index 1b5251f77809..a7de7ebb64d4 100644 --- a/packages/beacon-node/src/chain/validation/block.ts +++ b/packages/beacon-node/src/chain/validation/block.ts @@ -115,8 +115,10 @@ export async function validateGossipBlock( // this is something we should change this in the future to make the code airtight to the spec. // [IGNORE] The block's parent (defined by block.parent_root) has been seen (via both gossip and non-gossip sources) (a client MAY queue blocks for processing once the parent block is retrieved). // [REJECT] The block's parent (defined by block.parent_root) passes validation. + // + // For validation run next shuffling calculation sync. Should not actually be needed unless two epochs are crossed and the next shuffling is not already cached const blockState = await chain.regen - .getPreState(block, {dontTransferCache: true}, RegenCaller.validateGossipBlock) + .getPreState(block, {dontTransferCache: true, asyncShufflingCalculation: false}, RegenCaller.validateGossipBlock) .catch(() => { throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot}); }); diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index d86431c72eee..df49e7aeea9c 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -28,6 +28,10 @@ export type EpochTransitionCacheOpts = { * Assert progressive balances the same to EpochTransitionCache */ assertCorrectProgressiveBalances?: boolean; + /** + * Do not queue shuffling calculation async. Forces sync JIT calculation in afterProcessEpoch + */ + asyncShufflingCalculation: boolean; }; /** diff --git a/packages/state-transition/src/stateTransition.ts b/packages/state-transition/src/stateTransition.ts index 4c701b0e632c..c224280469b9 100644 --- a/packages/state-transition/src/stateTransition.ts +++ b/packages/state-transition/src/stateTransition.ts @@ -36,7 +36,6 @@ export type StateTransitionOpts = BlockExternalData & verifyProposer?: boolean; verifySignatures?: boolean; dontTransferCache?: boolean; - asyncShufflingCalculation: boolean; }; /** From 0493df269aeee0302df15de8b5f9f2b04002a8c9 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Thu, 28 Nov 2024 14:25:05 +0700 Subject: [PATCH 03/13] fix: default to false for async shuffling and remove unnecessary props --- .../src/chain/blocks/verifyBlock.ts | 6 +----- packages/beacon-node/src/chain/chain.ts | 15 +++++---------- .../beacon-node/src/chain/prepareNextSlot.ts | 2 +- .../beacon-node/src/chain/regen/interface.ts | 2 +- .../beacon-node/src/chain/regen/queued.ts | 19 +++++++------------ .../stateCache/inMemoryCheckpointsCache.ts | 3 +-- .../stateCache/persistentCheckpointsCache.ts | 6 +----- .../src/chain/validation/blobSidecar.ts | 9 +-------- .../beacon-node/src/chain/validation/block.ts | 4 +--- .../src/cache/epochTransitionCache.ts | 2 +- .../state-transition/src/stateTransition.ts | 1 + 11 files changed, 21 insertions(+), 48 deletions(-) diff --git a/packages/beacon-node/src/chain/blocks/verifyBlock.ts b/packages/beacon-node/src/chain/blocks/verifyBlock.ts index 190527acb8ce..5ead67a720f7 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlock.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlock.ts @@ -66,11 +66,7 @@ export async function verifyBlocksInEpoch( // Retrieve preState from cache (regen) const preState0 = await this.regen // transfer cache to process faster, postState will be in block state cache - .getPreState( - block0.message, - {dontTransferCache: false, asyncShufflingCalculation: false}, - RegenCaller.processBlocksInEpoch - ) + .getPreState(block0.message, {dontTransferCache: false}, RegenCaller.processBlocksInEpoch) .catch((e) => { throw new BlockError(block0, {code: BlockErrorCode.PRESTATE_MISSING, error: e as Error}); }); diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 1d3bb6c2af1f..4751770b3bfc 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -435,12 +435,7 @@ export class BeaconChain implements IBeaconChain { // only use regen queue if necessary, it'll cache in checkpointStateCache if regen gets through epoch transition const head = this.forkChoice.getHead(); const startSlot = computeStartSlotAtEpoch(epoch); - return this.regen.getBlockSlotState( - head.blockRoot, - startSlot, - {dontTransferCache: true, asyncShufflingCalculation: false}, - regenCaller - ); + return this.regen.getBlockSlotState(head.blockRoot, startSlot, {dontTransferCache: true}, regenCaller); } async getStateBySlot( @@ -461,7 +456,7 @@ export class BeaconChain implements IBeaconChain { const state = await this.regen.getBlockSlotState( block.blockRoot, slot, - {dontTransferCache: true, asyncShufflingCalculation: false}, + {dontTransferCache: true}, RegenCaller.restApi ); return { @@ -620,7 +615,7 @@ export class BeaconChain implements IBeaconChain { const state = await this.regen.getBlockSlotState( toRootHex(parentBlockRoot), slot, - {dontTransferCache: true, asyncShufflingCalculation: false}, + {dontTransferCache: true}, RegenCaller.produceBlock ); @@ -669,7 +664,7 @@ export class BeaconChain implements IBeaconChain { const state = await this.regen.getBlockSlotState( toRootHex(parentBlockRoot), slot, - {dontTransferCache: true, asyncShufflingCalculation: false}, + {dontTransferCache: true}, RegenCaller.produceBlock ); const proposerIndex = state.epochCtx.getBeaconProposer(slot); @@ -904,7 +899,7 @@ export class BeaconChain implements IBeaconChain { state = await this.regen.getBlockSlotState( attHeadBlock.blockRoot, targetSlot, - {dontTransferCache: true, asyncShufflingCalculation: false}, + {dontTransferCache: true}, regenCaller ); } else if (blockEpoch > attEpoch) { diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index 01f9bc621d0d..cc9eb877a1a3 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -140,7 +140,7 @@ export class PrepareNextSlotScheduler { updatedPrepareState = (await this.chain.regen.getBlockSlotState( proposerHeadRoot, prepareSlot, - {dontTransferCache: !isEpochTransition, asyncShufflingCalculation: isEpochTransition}, + {dontTransferCache: !isEpochTransition}, RegenCaller.predictProposerHead )) as CachedBeaconStateExecutions; updatedHeadRoot = proposerHeadRoot; diff --git a/packages/beacon-node/src/chain/regen/interface.ts b/packages/beacon-node/src/chain/regen/interface.ts index f4401c0d54f2..5c0c00a85cbf 100644 --- a/packages/beacon-node/src/chain/regen/interface.ts +++ b/packages/beacon-node/src/chain/regen/interface.ts @@ -33,7 +33,7 @@ export type StateCloneOpts = { /** * Do not queue shuffling calculation async. Forces sync JIT calculation in afterProcessEpoch */ - asyncShufflingCalculation: boolean; + asyncShufflingCalculation?: boolean; }; export interface IStateRegenerator extends IStateRegeneratorInternal { diff --git a/packages/beacon-node/src/chain/regen/queued.ts b/packages/beacon-node/src/chain/regen/queued.ts index 49f8d0516887..b5084d593356 100644 --- a/packages/beacon-node/src/chain/regen/queued.ts +++ b/packages/beacon-node/src/chain/regen/queued.ts @@ -76,19 +76,17 @@ export class QueuedStateRegenerator implements IStateRegenerator { * This is not for block processing so don't transfer cache */ getStateSync(stateRoot: RootHex): CachedBeaconStateAllForks | null { - return this.blockStateCache.get(stateRoot, {dontTransferCache: true, asyncShufflingCalculation: false}); + return this.blockStateCache.get(stateRoot, {dontTransferCache: true}); } /** * Get state for block processing. * By default, do not transfer cache except for the block at clock slot * which is usually the gossip block. - * - * For sync operations also run shuffling calculation sync if more than one epoch is crossed */ getPreStateSync( block: BeaconBlock, - opts: StateCloneOpts = {dontTransferCache: true, asyncShufflingCalculation: false} + opts: StateCloneOpts = {dontTransferCache: true} ): CachedBeaconStateAllForks | null { const parentRoot = toRootHex(block.parentRoot); const parentBlock = this.forkChoice.getBlockHex(parentRoot); @@ -131,14 +129,14 @@ export class QueuedStateRegenerator implements IStateRegenerator { * Get checkpoint state from cache, this function is not for block processing so don't transfer cache */ getCheckpointStateSync(cp: CheckpointHex): CachedBeaconStateAllForks | null { - return this.checkpointStateCache.get(cp, {dontTransferCache: true, asyncShufflingCalculation: false}); + return this.checkpointStateCache.get(cp, {dontTransferCache: true}); } /** * Get state closest to head, this function is not for block processing so don't transfer cache */ getClosestHeadState(head: ProtoBlock): CachedBeaconStateAllForks | null { - const opts: StateCloneOpts = {dontTransferCache: true, asyncShufflingCalculation: false}; + const opts = {dontTransferCache: true}; return ( this.checkpointStateCache.getLatest(head.blockRoot, Infinity, opts) || this.blockStateCache.get(head.stateRoot, opts) @@ -180,8 +178,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { newHeadStateRoot === maybeHeadStateRoot ? maybeHeadState : // maybeHeadState was already in block state cache so we don't transfer the cache - // run shuffling async if epoch transition is crossed. This will only happen once and will be cached - this.blockStateCache.get(newHeadStateRoot, {dontTransferCache: true, asyncShufflingCalculation: true}); + this.blockStateCache.get(newHeadStateRoot, {dontTransferCache: true}); if (headState) { this.blockStateCache.setHeadState(headState); @@ -197,8 +194,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { // for the new FIFOBlockStateCache, it's important to reload state to regen head state here if needed const allowDiskReload = true; // transfer cache here because we want to regen state asap - // run shuffling async as there will likely only be one epoch transition and it will get cached - const cloneOpts: StateCloneOpts = {dontTransferCache: false, asyncShufflingCalculation: true}; + const cloneOpts = {dontTransferCache: false}; this.regen.getState(newHeadStateRoot, RegenCaller.processBlock, cloneOpts, allowDiskReload).then( (headStateRegen) => this.blockStateCache.setHeadState(headStateRegen), (e) => this.logger.error("Error on head state regen", logCtx, e) @@ -272,8 +268,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { async getState( stateRoot: RootHex, rCaller: RegenCaller, - // default to non-async shuffling here. we are currently updating slot/epoch via getBlockSlotState - opts: StateCloneOpts = {dontTransferCache: true, asyncShufflingCalculation: false} + opts: StateCloneOpts = {dontTransferCache: true} ): Promise { this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getState}); diff --git a/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts index cd9bb0d0956f..4caa6779f697 100644 --- a/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts @@ -48,8 +48,7 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { async getStateOrBytes(cp: CheckpointHex): Promise { // no need to transfer cache for this api - // shuffling not touched here. just need to satisfy interface - return this.get(cp, {dontTransferCache: true, asyncShufflingCalculation: false}); + return this.get(cp, {dontTransferCache: true}); } async getOrReloadLatest( diff --git a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts index 526b0b8a6dcc..4c9a8b0f1265 100644 --- a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts @@ -247,11 +247,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { */ async getStateOrBytes(cp: CheckpointHex): Promise { // don't have to transfer cache for this specific api - // shuffling not touched here. just need to satisfy interface - const stateOrLoadedState = await this.getStateOrLoadDb(cp, { - dontTransferCache: true, - asyncShufflingCalculation: false, - }); + const stateOrLoadedState = await this.getStateOrLoadDb(cp, {dontTransferCache: true}); if (stateOrLoadedState === null || isCachedBeaconState(stateOrLoadedState)) { return stateOrLoadedState; } diff --git a/packages/beacon-node/src/chain/validation/blobSidecar.ts b/packages/beacon-node/src/chain/validation/blobSidecar.ts index 6044feaa4bcc..3b90972f62b1 100644 --- a/packages/beacon-node/src/chain/validation/blobSidecar.ts +++ b/packages/beacon-node/src/chain/validation/blobSidecar.ts @@ -98,15 +98,8 @@ export async function validateGossipBlobSidecar( // this is something we should change this in the future to make the code airtight to the spec. // [IGNORE] The block's parent (defined by block.parent_root) has been seen (via both gossip and non-gossip sources) (a client MAY queue blocks for processing once the parent block is retrieved). // [REJECT] The block's parent (defined by block.parent_root) passes validation. - // - // For validation run next shuffling calculation sync. Should not actually be needed unless two epochs are crossed and the next shuffling is not already cached const blockState = await chain.regen - .getBlockSlotState( - parentRoot, - blobSlot, - {dontTransferCache: true, asyncShufflingCalculation: false}, - RegenCaller.validateGossipBlock - ) + .getBlockSlotState(parentRoot, blobSlot, {dontTransferCache: true}, RegenCaller.validateGossipBlock) .catch(() => { throw new BlobSidecarGossipError(GossipAction.IGNORE, {code: BlobSidecarErrorCode.PARENT_UNKNOWN, parentRoot}); }); diff --git a/packages/beacon-node/src/chain/validation/block.ts b/packages/beacon-node/src/chain/validation/block.ts index a7de7ebb64d4..1b5251f77809 100644 --- a/packages/beacon-node/src/chain/validation/block.ts +++ b/packages/beacon-node/src/chain/validation/block.ts @@ -115,10 +115,8 @@ export async function validateGossipBlock( // this is something we should change this in the future to make the code airtight to the spec. // [IGNORE] The block's parent (defined by block.parent_root) has been seen (via both gossip and non-gossip sources) (a client MAY queue blocks for processing once the parent block is retrieved). // [REJECT] The block's parent (defined by block.parent_root) passes validation. - // - // For validation run next shuffling calculation sync. Should not actually be needed unless two epochs are crossed and the next shuffling is not already cached const blockState = await chain.regen - .getPreState(block, {dontTransferCache: true, asyncShufflingCalculation: false}, RegenCaller.validateGossipBlock) + .getPreState(block, {dontTransferCache: true}, RegenCaller.validateGossipBlock) .catch(() => { throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot}); }); diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index df49e7aeea9c..871b8a4ca75e 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -31,7 +31,7 @@ export type EpochTransitionCacheOpts = { /** * Do not queue shuffling calculation async. Forces sync JIT calculation in afterProcessEpoch */ - asyncShufflingCalculation: boolean; + asyncShufflingCalculation?: boolean; }; /** diff --git a/packages/state-transition/src/stateTransition.ts b/packages/state-transition/src/stateTransition.ts index c224280469b9..033cd5582f4a 100644 --- a/packages/state-transition/src/stateTransition.ts +++ b/packages/state-transition/src/stateTransition.ts @@ -36,6 +36,7 @@ export type StateTransitionOpts = BlockExternalData & verifyProposer?: boolean; verifySignatures?: boolean; dontTransferCache?: boolean; + asyncShufflingCalculation?: boolean; }; /** From 4ffb36474ec91568661212901a98f450e15032f2 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Thu, 28 Nov 2024 14:39:37 +0700 Subject: [PATCH 04/13] fix: remove unnecessary flags from stateTransition --- .../src/chain/blocks/verifyBlocksStateTransitionOnly.ts | 2 -- .../src/chain/historicalState/getHistoricalState.ts | 2 -- .../beacon-node/src/chain/produceBlock/computeNewStateRoot.ts | 2 -- packages/beacon-node/src/chain/regen/regen.ts | 3 --- packages/state-transition/src/stateTransition.ts | 2 -- 5 files changed, 11 deletions(-) diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts index 7923e27861cf..b2f70d6274b3 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts @@ -56,8 +56,6 @@ export async function verifyBlocksStateTransitionOnly( // if block is trusted don't verify proposer or op signature verifyProposer: !useBlsBatchVerify && !validSignatures && !validProposerSignature, verifySignatures: !useBlsBatchVerify && !validSignatures, - // do no queue shuffling calculation, run it sync for epoch transitions - asyncShufflingCalculation: false }, metrics ); diff --git a/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts b/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts index 7446254c3183..20a53c40d73d 100644 --- a/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts +++ b/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts @@ -87,8 +87,6 @@ export async function getHistoricalState( verifyStateRoot: false, executionPayloadStatus: ExecutionPayloadStatus.valid, dataAvailableStatus: DataAvailableStatus.available, - // do no queue shuffling calculation, run it sync for epoch transitions - asyncShufflingCalculation: false, }, metrics ); diff --git a/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts b/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts index 9005be2147f1..bfa30e570e06 100644 --- a/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts +++ b/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts @@ -38,8 +38,6 @@ export function computeNewStateRoot( verifySignatures: false, // Preserve cache in source state, since the resulting state is not added to the state cache dontTransferCache: true, - // This is called for produceBlockWrapper. Shuffling should be available so can run sync - asyncShufflingCalculation: false, }, metrics ); diff --git a/packages/beacon-node/src/chain/regen/regen.ts b/packages/beacon-node/src/chain/regen/regen.ts index a508192c1c4f..073556d8162f 100644 --- a/packages/beacon-node/src/chain/regen/regen.ts +++ b/packages/beacon-node/src/chain/regen/regen.ts @@ -261,9 +261,6 @@ export class StateRegenerator implements IStateRegeneratorInternal { verifyStateRoot: false, verifyProposer: false, verifySignatures: false, - // If regen state is necessary do not run shuffling async. If an epoch boundary is crossed then the - // shuffling will be calculated JIT - asyncShufflingCalculation: false, }, this.modules.metrics ); diff --git a/packages/state-transition/src/stateTransition.ts b/packages/state-transition/src/stateTransition.ts index 033cd5582f4a..929a37468c6e 100644 --- a/packages/state-transition/src/stateTransition.ts +++ b/packages/state-transition/src/stateTransition.ts @@ -36,7 +36,6 @@ export type StateTransitionOpts = BlockExternalData & verifyProposer?: boolean; verifySignatures?: boolean; dontTransferCache?: boolean; - asyncShufflingCalculation?: boolean; }; /** @@ -69,7 +68,6 @@ export function stateTransition( // Assume default to be valid and available executionPayloadStatus: ExecutionPayloadStatus.valid, dataAvailableStatus: DataAvailableStatus.available, - asyncShufflingCalculation: false, }, metrics?: BeaconStateTransitionMetrics | null ): CachedBeaconStateAllForks { From e3ea7c3174b95e9f73f015eff96dad574d0e9112 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Thu, 28 Nov 2024 14:42:44 +0700 Subject: [PATCH 05/13] feat: implement conditional build of shuffling for prepareNextSlot --- packages/beacon-node/src/chain/prepareNextSlot.ts | 2 +- packages/state-transition/src/cache/epochTransitionCache.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index cc9eb877a1a3..2445e010216f 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -114,7 +114,7 @@ export class PrepareNextSlotScheduler { // the slot 0 of next epoch will likely use this Previous Root Checkpoint state for state transition so we transfer cache here // the resulting state with cache will be cached in Checkpoint State Cache which is used for the upcoming block processing // for other slots dontTransferCached=true because we don't run state transition on this state - {dontTransferCache: !isEpochTransition, asyncShufflingCalculation: isEpochTransition}, + {dontTransferCache: !isEpochTransition, asyncShufflingCalculation: true}, RegenCaller.precomputeEpoch ); diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index 871b8a4ca75e..c36cde27f77e 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -380,7 +380,10 @@ export function beforeProcessEpoch( for (let i = 0; i < nextEpochShufflingActiveIndicesLength; i++) { nextShufflingActiveIndices[i] = nextEpochShufflingActiveValidatorIndices[i]; } - state.epochCtx.shufflingCache?.build(epochAfterNext, nextShufflingDecisionRoot, state, nextShufflingActiveIndices); + + if (opts?.asyncShufflingCalculation) { + state.epochCtx.shufflingCache?.build(epochAfterNext, nextShufflingDecisionRoot, state, nextShufflingActiveIndices); + } if (totalActiveStakeByIncrement < 1) { totalActiveStakeByIncrement = 1; From 4d22fd8e6bfba1c683296c4c293e6c37a64d07f9 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Thu, 28 Nov 2024 18:34:08 +0700 Subject: [PATCH 06/13] fix: spec test bug where shufflingCache is present from BeaconChain constructor --- packages/state-transition/src/cache/epochCache.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index 86e63c672024..e162030fcabd 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -667,10 +667,10 @@ export class EpochCache { this.shufflingCache .get(epochAfterUpcoming, this.nextDecisionRoot) .then((shuffling) => { - if (!shuffling) { - throw new Error("EpochShuffling not returned from get in afterProcessEpoch"); - } - this.nextShuffling = shuffling; + this.nextShuffling = shuffling + ? shuffling + : // in some spec tests the BeaconChain class is used and it creates a ShufflingCache + computeEpochShuffling(state, this.nextActiveIndices, epochAfterUpcoming); }) .catch((err) => { this.shufflingCache?.logger?.error( From 1da2eb6cad61b7511a174c9d4e19ac51e106974a Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Fri, 29 Nov 2024 13:42:54 +0700 Subject: [PATCH 07/13] feat: sync build next shuffling if not queued async --- packages/state-transition/src/cache/epochCache.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index e162030fcabd..1fecdae1d644 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -669,8 +669,7 @@ export class EpochCache { .then((shuffling) => { this.nextShuffling = shuffling ? shuffling - : // in some spec tests the BeaconChain class is used and it creates a ShufflingCache - computeEpochShuffling(state, this.nextActiveIndices, epochAfterUpcoming); + : this.shufflingCache.build(epochAfterUpcoming, this.nextDecisionRoot, state, this.nextActiveIndices); }) .catch((err) => { this.shufflingCache?.logger?.error( From db9eecce704678c0282477248b40315de82e5d46 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Fri, 29 Nov 2024 13:55:57 +0700 Subject: [PATCH 08/13] fix: use getSync to pull next shuffling correctly --- packages/state-transition/src/cache/epochCache.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index 1fecdae1d644..a87b19396a01 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -669,7 +669,11 @@ export class EpochCache { .then((shuffling) => { this.nextShuffling = shuffling ? shuffling - : this.shufflingCache.build(epochAfterUpcoming, this.nextDecisionRoot, state, this.nextActiveIndices); + : // biome-ignore lint/style/noNonNullAssertion: object must be defined to be in this branch of the conditional + this.shufflingCache!.getSync(epochAfterUpcoming, this.nextDecisionRoot, { + state, + activeIndices: this.nextActiveIndices, + }); }) .catch((err) => { this.shufflingCache?.logger?.error( From 969da02cd045ebcd982f9a3d47a409b81542bbbe Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Tue, 3 Dec 2024 16:58:42 +0700 Subject: [PATCH 09/13] docs: add comment to prepareNextSlot --- packages/beacon-node/src/chain/prepareNextSlot.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index 2445e010216f..f09a53d2677b 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -114,6 +114,11 @@ export class PrepareNextSlotScheduler { // the slot 0 of next epoch will likely use this Previous Root Checkpoint state for state transition so we transfer cache here // the resulting state with cache will be cached in Checkpoint State Cache which is used for the upcoming block processing // for other slots dontTransferCached=true because we don't run state transition on this state + // + // Shuffling calculation will be done asynchronously when passing asyncShufflingCalculation=true. Shuffling will be queued in + // beforeProcessEpoch and should theoretically be ready immediately after the synchronous epoch transition finished and the + // event loop is free. In long periods of non-finality too many forks will cause the shufflingCache to throw an error for + // too many queued shufflings so only run async during normal epoch transition {dontTransferCache: !isEpochTransition, asyncShufflingCalculation: true}, RegenCaller.precomputeEpoch ); From a006e689f9b266b40d1fda3b2f0445469e5d2e21 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Tue, 3 Dec 2024 17:02:05 +0700 Subject: [PATCH 10/13] refactor: rename StateCloneOpts to StateRegenerationOpts --- .../beacon-node/src/chain/regen/interface.ts | 16 ++++++++++------ packages/beacon-node/src/chain/regen/queued.ts | 18 ++++++++++++------ packages/beacon-node/src/chain/regen/regen.ts | 14 +++++++------- .../chain/stateCache/blockStateCacheImpl.ts | 4 ++-- .../chain/stateCache/fifoBlockStateCache.ts | 4 ++-- .../stateCache/inMemoryCheckpointsCache.ts | 10 +++++----- .../stateCache/persistentCheckpointsCache.ts | 12 ++++++------ .../beacon-node/src/chain/stateCache/types.ts | 12 ++++++------ 8 files changed, 50 insertions(+), 40 deletions(-) diff --git a/packages/beacon-node/src/chain/regen/interface.ts b/packages/beacon-node/src/chain/regen/interface.ts index 5c0c00a85cbf..b9a4e38b5b68 100644 --- a/packages/beacon-node/src/chain/regen/interface.ts +++ b/packages/beacon-node/src/chain/regen/interface.ts @@ -28,10 +28,10 @@ export enum RegenFnName { getCheckpointState = "getCheckpointState", } -export type StateCloneOpts = { +export type StateRegenerationOpts = { dontTransferCache: boolean; /** - * Do not queue shuffling calculation async. Forces sync JIT calculation in afterProcessEpoch + * Do not queue shuffling calculation async. Forces sync JIT calculation in afterProcessEpoch if not passed as `true` */ asyncShufflingCalculation?: boolean; }; @@ -60,7 +60,11 @@ export interface IStateRegeneratorInternal { * Return a valid pre-state for a beacon block * This will always return a state in the latest viable epoch */ - getPreState(block: BeaconBlock, opts: StateCloneOpts, rCaller: RegenCaller): Promise; + getPreState( + block: BeaconBlock, + opts: StateRegenerationOpts, + rCaller: RegenCaller + ): Promise; /** * Return a valid checkpoint state @@ -68,7 +72,7 @@ export interface IStateRegeneratorInternal { */ getCheckpointState( cp: phase0.Checkpoint, - opts: StateCloneOpts, + opts: StateRegenerationOpts, rCaller: RegenCaller ): Promise; @@ -78,12 +82,12 @@ export interface IStateRegeneratorInternal { getBlockSlotState( blockRoot: RootHex, slot: Slot, - opts: StateCloneOpts, + opts: StateRegenerationOpts, rCaller: RegenCaller ): Promise; /** * Return the exact state with `stateRoot` */ - getState(stateRoot: RootHex, rCaller: RegenCaller, opts?: StateCloneOpts): Promise; + getState(stateRoot: RootHex, rCaller: RegenCaller, opts?: StateRegenerationOpts): Promise; } diff --git a/packages/beacon-node/src/chain/regen/queued.ts b/packages/beacon-node/src/chain/regen/queued.ts index b5084d593356..9069b384fd58 100644 --- a/packages/beacon-node/src/chain/regen/queued.ts +++ b/packages/beacon-node/src/chain/regen/queued.ts @@ -8,7 +8,13 @@ import {JobItemQueue} from "../../util/queue/index.js"; import {CheckpointHex, toCheckpointHex} from "../stateCache/index.js"; import {BlockStateCache, CheckpointStateCache} from "../stateCache/types.js"; import {RegenError, RegenErrorCode} from "./errors.js"; -import {IStateRegenerator, IStateRegeneratorInternal, RegenCaller, RegenFnName, StateCloneOpts} from "./interface.js"; +import { + IStateRegenerator, + IStateRegeneratorInternal, + RegenCaller, + RegenFnName, + StateRegenerationOpts, +} from "./interface.js"; import {RegenModules, StateRegenerator} from "./regen.js"; const REGEN_QUEUE_MAX_LEN = 256; @@ -86,7 +92,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { */ getPreStateSync( block: BeaconBlock, - opts: StateCloneOpts = {dontTransferCache: true} + opts: StateRegenerationOpts = {dontTransferCache: true} ): CachedBeaconStateAllForks | null { const parentRoot = toRootHex(block.parentRoot); const parentBlock = this.forkChoice.getBlockHex(parentRoot); @@ -212,7 +218,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { */ async getPreState( block: BeaconBlock, - opts: StateCloneOpts, + opts: StateRegenerationOpts, rCaller: RegenCaller ): Promise { this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getPreState}); @@ -231,7 +237,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { async getCheckpointState( cp: phase0.Checkpoint, - opts: StateCloneOpts, + opts: StateRegenerationOpts, rCaller: RegenCaller ): Promise { this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getCheckpointState}); @@ -256,7 +262,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { async getBlockSlotState( blockRoot: RootHex, slot: Slot, - opts: StateCloneOpts, + opts: StateRegenerationOpts, rCaller: RegenCaller ): Promise { this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getBlockSlotState}); @@ -268,7 +274,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { async getState( stateRoot: RootHex, rCaller: RegenCaller, - opts: StateCloneOpts = {dontTransferCache: true} + opts: StateRegenerationOpts = {dontTransferCache: true} ): Promise { this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getState}); diff --git a/packages/beacon-node/src/chain/regen/regen.ts b/packages/beacon-node/src/chain/regen/regen.ts index 073556d8162f..06d1cee71332 100644 --- a/packages/beacon-node/src/chain/regen/regen.ts +++ b/packages/beacon-node/src/chain/regen/regen.ts @@ -20,7 +20,7 @@ import {getCheckpointFromState} from "../blocks/utils/checkpoint.js"; import {ChainEvent, ChainEventEmitter} from "../emitter.js"; import {BlockStateCache, CheckpointStateCache} from "../stateCache/types.js"; import {RegenError, RegenErrorCode} from "./errors.js"; -import {IStateRegeneratorInternal, RegenCaller, StateCloneOpts} from "./interface.js"; +import {IStateRegeneratorInternal, RegenCaller, StateRegenerationOpts} from "./interface.js"; export type RegenModules = { db: IBeaconDb; @@ -51,7 +51,7 @@ export class StateRegenerator implements IStateRegeneratorInternal { */ async getPreState( block: BeaconBlock, - opts: StateCloneOpts, + opts: StateRegenerationOpts, regenCaller: RegenCaller ): Promise { const parentBlock = this.modules.forkChoice.getBlock(block.parentRoot); @@ -84,7 +84,7 @@ export class StateRegenerator implements IStateRegeneratorInternal { */ async getCheckpointState( cp: phase0.Checkpoint, - opts: StateCloneOpts, + opts: StateRegenerationOpts, regenCaller: RegenCaller, allowDiskReload = false ): Promise { @@ -99,7 +99,7 @@ export class StateRegenerator implements IStateRegeneratorInternal { async getBlockSlotState( blockRoot: RootHex, slot: Slot, - opts: StateCloneOpts, + opts: StateRegenerationOpts, regenCaller: RegenCaller, allowDiskReload = false ): Promise { @@ -146,7 +146,7 @@ export class StateRegenerator implements IStateRegeneratorInternal { async getState( stateRoot: RootHex, caller: RegenCaller, - opts?: StateCloneOpts, + opts?: StateRegenerationOpts, // internal option, don't want to expose to external caller allowDiskReload = false ): Promise { @@ -322,7 +322,7 @@ async function processSlotsByCheckpoint( preState: CachedBeaconStateAllForks, slot: Slot, regenCaller: RegenCaller, - opts: StateCloneOpts + opts: StateRegenerationOpts ): Promise { let postState = await processSlotsToNearestCheckpoint(modules, preState, slot, regenCaller, opts); if (postState.slot < slot) { @@ -343,7 +343,7 @@ async function processSlotsToNearestCheckpoint( preState: CachedBeaconStateAllForks, slot: Slot, regenCaller: RegenCaller, - opts: StateCloneOpts + opts: StateRegenerationOpts ): Promise { const preSlot = preState.slot; const postSlot = slot; diff --git a/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts b/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts index f57c9a411923..7d87675b7bbc 100644 --- a/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts +++ b/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts @@ -3,7 +3,7 @@ import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {Epoch, RootHex} from "@lodestar/types"; import {toRootHex} from "@lodestar/utils"; import {Metrics} from "../../metrics/index.js"; -import {StateCloneOpts} from "../regen/interface.js"; +import {StateRegenerationOpts} from "../regen/interface.js"; import {MapTracker} from "./mapMetrics.js"; import {BlockStateCache} from "./types.js"; @@ -39,7 +39,7 @@ export class BlockStateCacheImpl implements BlockStateCache { } } - get(rootHex: RootHex, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { + get(rootHex: RootHex, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null { this.metrics?.lookups.inc(); const item = this.head?.stateRoot === rootHex ? this.head.state : this.cache.get(rootHex); if (!item) { diff --git a/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts b/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts index eec1fce5d6c2..a119efe66887 100644 --- a/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts +++ b/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts @@ -4,7 +4,7 @@ import {RootHex} from "@lodestar/types"; import {toRootHex} from "@lodestar/utils"; import {Metrics} from "../../metrics/index.js"; import {LinkedList} from "../../util/array.js"; -import {StateCloneOpts} from "../regen/interface.js"; +import {StateRegenerationOpts} from "../regen/interface.js"; import {MapTracker} from "./mapMetrics.js"; import {BlockStateCache} from "./types.js"; @@ -93,7 +93,7 @@ export class FIFOBlockStateCache implements BlockStateCache { /** * Get a state from this cache given a state root hex. */ - get(rootHex: RootHex, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { + get(rootHex: RootHex, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null { this.metrics?.lookups.inc(); const item = this.cache.get(rootHex); if (!item) { diff --git a/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts index 4caa6779f697..81562d669365 100644 --- a/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts @@ -3,7 +3,7 @@ import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {Epoch, RootHex, phase0} from "@lodestar/types"; import {MapDef, toRootHex} from "@lodestar/utils"; import {Metrics} from "../../metrics/index.js"; -import {StateCloneOpts} from "../regen/interface.js"; +import {StateRegenerationOpts} from "../regen/interface.js"; import {MapTracker} from "./mapMetrics.js"; import {CacheItemType, CheckpointStateCache} from "./types.js"; @@ -42,7 +42,7 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { this.maxEpochs = maxEpochs; } - async getOrReload(cp: CheckpointHex, opts?: StateCloneOpts): Promise { + async getOrReload(cp: CheckpointHex, opts?: StateRegenerationOpts): Promise { return this.get(cp, opts); } @@ -54,7 +54,7 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { async getOrReloadLatest( rootHex: string, maxEpoch: number, - opts?: StateCloneOpts + opts?: StateRegenerationOpts ): Promise { return this.getLatest(rootHex, maxEpoch, opts); } @@ -64,7 +64,7 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { return 0; } - get(cp: CheckpointHex, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { + get(cp: CheckpointHex, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null { this.metrics?.lookups.inc(); const cpKey = toCheckpointKey(cp); const item = this.cache.get(cpKey); @@ -98,7 +98,7 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { /** * Searches for the latest cached state with a `root`, starting with `epoch` and descending */ - getLatest(rootHex: RootHex, maxEpoch: Epoch, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { + getLatest(rootHex: RootHex, maxEpoch: Epoch, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null { // sort epochs in descending order, only consider epochs lte `epoch` const epochs = Array.from(this.epochIndex.keys()) .sort((a, b) => b - a) diff --git a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts index 4c9a8b0f1265..cacb24be4495 100644 --- a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts @@ -7,7 +7,7 @@ import {Logger, MapDef, fromHex, sleep, toHex, toRootHex} from "@lodestar/utils" import {Metrics} from "../../metrics/index.js"; import {AllocSource, BufferPool, BufferWithKey} from "../../util/bufferPool.js"; import {IClock} from "../../util/clock.js"; -import {StateCloneOpts} from "../regen/interface.js"; +import {StateRegenerationOpts} from "../regen/interface.js"; import {serializeState} from "../serializeState.js"; import {ShufflingCache} from "../shufflingCache.js"; import {CPStateDatastore, DatastoreKey, datastoreKeyToCheckpoint} from "./datastore/index.js"; @@ -187,7 +187,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { * - Get block for processing * - Regen head state */ - async getOrReload(cp: CheckpointHex, opts?: StateCloneOpts): Promise { + async getOrReload(cp: CheckpointHex, opts?: StateRegenerationOpts): Promise { const stateOrStateBytesData = await this.getStateOrLoadDb(cp, opts); if (stateOrStateBytesData === null || isCachedBeaconState(stateOrStateBytesData)) { return stateOrStateBytesData?.clone(opts?.dontTransferCache) ?? null; @@ -259,7 +259,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { */ async getStateOrLoadDb( cp: CheckpointHex, - opts?: StateCloneOpts + opts?: StateRegenerationOpts ): Promise { const cpKey = toCacheKey(cp); const inMemoryState = this.get(cpKey, opts); @@ -291,7 +291,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { /** * Similar to get() api without reloading from disk */ - get(cpOrKey: CheckpointHex | string, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { + get(cpOrKey: CheckpointHex | string, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null { this.metrics?.cpStateCache.lookups.inc(); const cpKey = typeof cpOrKey === "string" ? cpOrKey : toCacheKey(cpOrKey); const cacheItem = this.cache.get(cpKey); @@ -342,7 +342,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { /** * Searches in-memory state for the latest cached state with a `root` without reload, starting with `epoch` and descending */ - getLatest(rootHex: RootHex, maxEpoch: Epoch, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { + getLatest(rootHex: RootHex, maxEpoch: Epoch, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null { // sort epochs in descending order, only consider epochs lte `epoch` const epochs = Array.from(this.epochIndex.keys()) .sort((a, b) => b - a) @@ -368,7 +368,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { async getOrReloadLatest( rootHex: RootHex, maxEpoch: Epoch, - opts?: StateCloneOpts + opts?: StateRegenerationOpts ): Promise { // sort epochs in descending order, only consider epochs lte `epoch` const epochs = Array.from(this.epochIndex.keys()) diff --git a/packages/beacon-node/src/chain/stateCache/types.ts b/packages/beacon-node/src/chain/stateCache/types.ts index 403b469dd352..19f05c23ee35 100644 --- a/packages/beacon-node/src/chain/stateCache/types.ts +++ b/packages/beacon-node/src/chain/stateCache/types.ts @@ -1,7 +1,7 @@ import {routes} from "@lodestar/api"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {Epoch, RootHex, phase0} from "@lodestar/types"; -import {StateCloneOpts} from "../regen/interface.js"; +import {StateRegenerationOpts} from "../regen/interface.js"; export type CheckpointHex = {epoch: Epoch; rootHex: RootHex}; @@ -21,7 +21,7 @@ export type CheckpointHex = {epoch: Epoch; rootHex: RootHex}; * The cache key is state root */ export interface BlockStateCache { - get(rootHex: RootHex, opts?: StateCloneOpts): CachedBeaconStateAllForks | null; + get(rootHex: RootHex, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null; add(item: CachedBeaconStateAllForks): void; setHeadState(item: CachedBeaconStateAllForks | null): void; /** @@ -60,15 +60,15 @@ export interface BlockStateCache { */ export interface CheckpointStateCache { init?: () => Promise; - getOrReload(cp: CheckpointHex, opts?: StateCloneOpts): Promise; + getOrReload(cp: CheckpointHex, opts?: StateRegenerationOpts): Promise; getStateOrBytes(cp: CheckpointHex): Promise; - get(cpOrKey: CheckpointHex | string, opts?: StateCloneOpts): CachedBeaconStateAllForks | null; + get(cpOrKey: CheckpointHex | string, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null; add(cp: phase0.Checkpoint, state: CachedBeaconStateAllForks): void; - getLatest(rootHex: RootHex, maxEpoch: Epoch, opts?: StateCloneOpts): CachedBeaconStateAllForks | null; + getLatest(rootHex: RootHex, maxEpoch: Epoch, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null; getOrReloadLatest( rootHex: RootHex, maxEpoch: Epoch, - opts?: StateCloneOpts + opts?: StateRegenerationOpts ): Promise; updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null; prune(finalizedEpoch: Epoch, justifiedEpoch: Epoch): void; From f456906d19967e0256048cedc2c79272d86c5dc9 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Tue, 3 Dec 2024 17:19:32 +0700 Subject: [PATCH 11/13] feat: pass asyncShufflingCalculation through to afterProcessEpoch and refactor conditional to run purely sync --- .../state-transition/src/cache/epochCache.ts | 62 +++++++++---------- .../src/cache/epochTransitionCache.ts | 10 ++- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index a87b19396a01..c8d877c99fd9 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -58,6 +58,7 @@ import { getSyncCommitteeCache, } from "./syncCommitteeCache.js"; import {BeaconStateAllForks, BeaconStateAltair} from "./types.js"; +import {EpochTransitionCache} from "./epochTransitionCache.js"; /** `= PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)` */ export const PROPOSER_WEIGHT_FACTOR = PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT); @@ -605,14 +606,7 @@ export class EpochCache { * Steps for afterProcessEpoch * 1) update previous/current/next values of cached items */ - afterProcessEpoch( - state: CachedBeaconStateAllForks, - epochTransitionCache: { - nextShufflingDecisionRoot: RootHex; - nextShufflingActiveIndices: Uint32Array; - nextEpochTotalActiveBalanceByIncrement: number; - } - ): void { + afterProcessEpoch(state: CachedBeaconStateAllForks, epochTransitionCache: EpochTransitionCache): void { // Because the slot was incremented before entering this function the "next epoch" is actually the "current epoch" // in this context but that is not actually true because the state transition happens in the last 4 seconds of the // epoch. For the context of this function "upcoming epoch" is used to denote the epoch that will begin after this @@ -657,31 +651,35 @@ export class EpochCache { this.nextDecisionRoot = epochTransitionCache.nextShufflingDecisionRoot; this.nextActiveIndices = epochTransitionCache.nextShufflingActiveIndices; if (this.shufflingCache) { - this.nextShuffling = null; - // This promise will resolve immediately after the synchronous code of the state-transition runs. Until - // the build is done on a worker thread it will be calculated immediately after the epoch transition - // completes. Once the work is done concurrently it should be ready by time this get runs so the promise - // will resolve directly on the next spin of the event loop because the epoch transition and shuffling take - // about the same time to calculate so theoretically its ready now. Do not await here though in case it - // is not ready yet as the transition must not be asynchronous. - this.shufflingCache - .get(epochAfterUpcoming, this.nextDecisionRoot) - .then((shuffling) => { - this.nextShuffling = shuffling - ? shuffling - : // biome-ignore lint/style/noNonNullAssertion: object must be defined to be in this branch of the conditional - this.shufflingCache!.getSync(epochAfterUpcoming, this.nextDecisionRoot, { - state, - activeIndices: this.nextActiveIndices, - }); - }) - .catch((err) => { - this.shufflingCache?.logger?.error( - "EPOCH_CONTEXT_SHUFFLING_BUILD_ERROR", - {epoch: epochAfterUpcoming, decisionRoot: epochTransitionCache.nextShufflingDecisionRoot}, - err - ); + if (!epochTransitionCache.asyncShufflingCalculation) { + this.nextShuffling = this.shufflingCache.getSync(epochAfterUpcoming, this.nextDecisionRoot, { + state, + activeIndices: this.nextActiveIndices, }); + } else { + this.nextShuffling = null; + // This promise will resolve immediately after the synchronous code of the state-transition runs. Until + // the build is done on a worker thread it will be calculated immediately after the epoch transition + // completes. Once the work is done concurrently it should be ready by time this get runs so the promise + // will resolve directly on the next spin of the event loop because the epoch transition and shuffling take + // about the same time to calculate so theoretically its ready now. Do not await here though in case it + // is not ready yet as the transition must not be asynchronous. + this.shufflingCache + .get(epochAfterUpcoming, this.nextDecisionRoot) + .then((shuffling) => { + if (!shuffling) { + throw new Error("EpochShuffling not returned from get in afterProcessEpoch"); + } + this.nextShuffling = shuffling; + }) + .catch((err) => { + this.shufflingCache?.logger?.error( + "EPOCH_CONTEXT_SHUFFLING_BUILD_ERROR", + {epoch: epochAfterUpcoming, decisionRoot: epochTransitionCache.nextShufflingDecisionRoot}, + err + ); + }); + } } else { // Only for testing. shufflingCache should always be available in prod this.nextShuffling = computeEpochShuffling(state, this.nextActiveIndices, epochAfterUpcoming); diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index c36cde27f77e..b40ecfa09fa2 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -179,6 +179,12 @@ export interface EpochTransitionCache { */ nextEpochTotalActiveBalanceByIncrement: number; + /** + * Compute the shuffling sync or async. Defaults to synchronous. Need to pass `true` with the + * `EpochTransitionCacheOpts` + */ + asyncShufflingCalculation: boolean; + /** * Track by validator index if it's active in the prev epoch. * Used in metrics @@ -381,7 +387,8 @@ export function beforeProcessEpoch( nextShufflingActiveIndices[i] = nextEpochShufflingActiveValidatorIndices[i]; } - if (opts?.asyncShufflingCalculation) { + const asyncShufflingCalculation = opts?.asyncShufflingCalculation ?? false; + if (asyncShufflingCalculation) { state.epochCtx.shufflingCache?.build(epochAfterNext, nextShufflingDecisionRoot, state, nextShufflingActiveIndices); } @@ -510,6 +517,7 @@ export function beforeProcessEpoch( indicesToEject, nextShufflingDecisionRoot, nextShufflingActiveIndices, + asyncShufflingCalculation, // to be updated in processEffectiveBalanceUpdates nextEpochTotalActiveBalanceByIncrement: 0, isActivePrevEpoch, From 1b5fb43d93afb49cee4556aacef693eabac7ed69 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Tue, 3 Dec 2024 17:21:08 +0700 Subject: [PATCH 12/13] docs: add issue number to comment --- packages/beacon-node/src/chain/prepareNextSlot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index f09a53d2677b..f78c1842bd78 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -118,7 +118,7 @@ export class PrepareNextSlotScheduler { // Shuffling calculation will be done asynchronously when passing asyncShufflingCalculation=true. Shuffling will be queued in // beforeProcessEpoch and should theoretically be ready immediately after the synchronous epoch transition finished and the // event loop is free. In long periods of non-finality too many forks will cause the shufflingCache to throw an error for - // too many queued shufflings so only run async during normal epoch transition + // too many queued shufflings so only run async during normal epoch transition. See issue ChainSafe/lodestar#7244 {dontTransferCache: !isEpochTransition, asyncShufflingCalculation: true}, RegenCaller.precomputeEpoch ); From d9c103f6260932ef8f9d4063a15252a42f63518a Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Tue, 3 Dec 2024 17:46:23 +0700 Subject: [PATCH 13/13] chore: lint --- packages/state-transition/src/cache/epochCache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index c8d877c99fd9..af9e79bc831c 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -49,6 +49,7 @@ import { import {computeBaseRewardPerIncrement, computeSyncParticipantReward} from "../util/syncCommittee.js"; import {sumTargetUnslashedBalanceIncrements} from "../util/targetUnslashedBalance.js"; import {EffectiveBalanceIncrements, getEffectiveBalanceIncrementsWithLen} from "./effectiveBalanceIncrements.js"; +import {EpochTransitionCache} from "./epochTransitionCache.js"; import {Index2PubkeyCache, syncPubkeys} from "./pubkeyCache.js"; import {CachedBeaconStateAllForks} from "./stateCache.js"; import { @@ -58,7 +59,6 @@ import { getSyncCommitteeCache, } from "./syncCommitteeCache.js"; import {BeaconStateAllForks, BeaconStateAltair} from "./types.js"; -import {EpochTransitionCache} from "./epochTransitionCache.js"; /** `= PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)` */ export const PROPOSER_WEIGHT_FACTOR = PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT);