UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

124 lines 4.22 kB
import { toRootHex } from "@lodestar/utils"; import { MapTracker } from "./mapMetrics.js"; const MAX_STATES = 3 * 32; /** * Old implementation of StateCache (used to call `StateContextCache`) * - Prune per checkpoint so number of states ranges from 96 to 128 * - Keep a separate head state to make sure it is always available */ export class BlockStateCacheImpl { constructor({ maxStates = MAX_STATES, metrics }) { /** Epoch -> Set<blockRoot> */ this.epochIndex = new Map(); /** * Strong reference to prevent head state from being pruned. * null if head state is being regen and not available at the moment. */ this.head = null; this.maxStates = maxStates; this.cache = new MapTracker(metrics?.stateCache); if (metrics) { this.metrics = metrics.stateCache; metrics.stateCache.size.addCollect(() => metrics.stateCache.size.set(this.cache.size)); } } get(rootHex, opts) { this.metrics?.lookups.inc(); const item = this.head?.stateRoot === rootHex ? this.head.state : this.cache.get(rootHex); if (!item) { return null; } this.metrics?.hits.inc(); this.metrics?.stateClonedCount.observe(item.clonedCount); return item.clone(opts?.dontTransferCache); } add(item) { const key = toRootHex(item.hashTreeRoot()); if (this.cache.get(key)) { return; } this.metrics?.adds.inc(); this.cache.set(key, item); const epoch = item.epochCtx.epoch; const blockRoots = this.epochIndex.get(epoch); if (blockRoots) { blockRoots.add(key); } else { this.epochIndex.set(epoch, new Set([key])); } } setHeadState(item) { if (item) { const key = toRootHex(item.hashTreeRoot()); this.head = { state: item, stateRoot: key }; } else { this.head = null; } } /** * Get a seed state for state reload. * This is to conform to the api only as this cache is not used in n-historical state. * See ./fifoBlockStateCache.ts for implementation */ getSeedState() { throw Error("Not implemented for BlockStateCacheImpl"); } clear() { this.cache.clear(); this.epochIndex.clear(); } get size() { return this.cache.size; } /** * TODO make this more robust. * Without more thought, this currently breaks our assumptions about recent state availablity */ prune(headStateRootHex) { const keys = Array.from(this.cache.keys()); if (keys.length > this.maxStates) { // object keys are stored in insertion order, delete keys starting from the front for (const key of keys.slice(0, keys.length - this.maxStates)) { if (key !== headStateRootHex) { const item = this.cache.get(key); if (item) { this.epochIndex.get(item.epochCtx.epoch)?.delete(key); this.cache.delete(key); } } } } } /** * Prune per finalized epoch. */ deleteAllBeforeEpoch(finalizedEpoch) { for (const epoch of this.epochIndex.keys()) { if (epoch < finalizedEpoch) { this.deleteAllEpochItems(epoch); } } } /** ONLY FOR DEBUGGING PURPOSES. For lodestar debug API */ dumpSummary() { return Array.from(this.cache.entries()).map(([key, state]) => ({ slot: state.slot, root: toRootHex(state.hashTreeRoot()), reads: this.cache.readCount.get(key) ?? 0, lastRead: this.cache.lastRead.get(key) ?? 0, checkpointState: false, })); } getStates() { return this.cache.values(); } deleteAllEpochItems(epoch) { for (const rootHex of this.epochIndex.get(epoch) || []) { this.cache.delete(rootHex); } this.epochIndex.delete(epoch); } } //# sourceMappingURL=blockStateCacheImpl.js.map