UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

151 lines 5.42 kB
import { MapDef, toRootHex } from "@lodestar/utils"; import { MapTracker } from "./mapMetrics.js"; import { CacheItemType } from "./types.js"; const MAX_EPOCHS = 10; /** * In memory cache of CachedBeaconState * belonging to checkpoint * * Similar API to Repository */ export class InMemoryCheckpointStateCache { constructor({ metrics = null }, { maxEpochs = MAX_EPOCHS } = {}) { /** Epoch -> Set<blockRoot> */ this.epochIndex = new MapDef(() => new Set()); this.preComputedCheckpoint = null; this.preComputedCheckpointHits = null; this.cache = new MapTracker(metrics?.cpStateCache); if (metrics) { this.metrics = metrics.cpStateCache; metrics.cpStateCache.size.addCollect(() => metrics.cpStateCache.size.set({ type: CacheItemType.inMemory }, this.cache.size)); metrics.cpStateCache.epochSize.addCollect(() => metrics.cpStateCache.epochSize.set({ type: CacheItemType.inMemory }, this.epochIndex.size)); } this.maxEpochs = maxEpochs; } async getOrReload(cp, opts) { return this.get(cp, opts); } async getStateOrBytes(cp) { // no need to transfer cache for this api return this.get(cp, { dontTransferCache: true }); } async getOrReloadLatest(rootHex, maxEpoch, opts) { return this.getLatest(rootHex, maxEpoch, opts); } async processState() { // do nothing, this class does not support prunning return 0; } get(cp, opts) { this.metrics?.lookups.inc(); const cpKey = toCheckpointKey(cp); const item = this.cache.get(cpKey); if (!item) { return null; } this.metrics?.hits.inc(); if (cpKey === this.preComputedCheckpoint) { this.preComputedCheckpointHits = (this.preComputedCheckpointHits ?? 0) + 1; } this.metrics?.stateClonedCount.observe(item.clonedCount); return item.clone(opts?.dontTransferCache); } add(cp, item) { const cpHex = toCheckpointHex(cp); const key = toCheckpointKey(cpHex); if (this.cache.has(key)) { return; } this.metrics?.adds.inc(); this.cache.set(key, item); this.epochIndex.getOrDefault(cp.epoch).add(cpHex.rootHex); } /** * Searches for the latest cached state with a `root`, starting with `epoch` and descending */ getLatest(rootHex, maxEpoch, opts) { // sort epochs in descending order, only consider epochs lte `epoch` const epochs = Array.from(this.epochIndex.keys()) .sort((a, b) => b - a) .filter((e) => e <= maxEpoch); for (const epoch of epochs) { if (this.epochIndex.get(epoch)?.has(rootHex)) { return this.get({ rootHex, epoch }, opts); } } return null; } /** * Update the precomputed checkpoint and return the number of his for the * previous one (if any). */ updatePreComputedCheckpoint(rootHex, epoch) { const previousHits = this.preComputedCheckpointHits; this.preComputedCheckpoint = toCheckpointKey({ rootHex, epoch }); this.preComputedCheckpointHits = 0; return previousHits; } pruneFinalized(finalizedEpoch) { for (const epoch of this.epochIndex.keys()) { if (epoch < finalizedEpoch) { this.deleteAllEpochItems(epoch); } } } prune(finalizedEpoch, justifiedEpoch) { const epochs = Array.from(this.epochIndex.keys()).filter((epoch) => epoch !== finalizedEpoch && epoch !== justifiedEpoch); if (epochs.length > this.maxEpochs) { for (const epoch of epochs.slice(0, epochs.length - this.maxEpochs)) { this.deleteAllEpochItems(epoch); } } } delete(cp) { this.cache.delete(toCheckpointKey(toCheckpointHex(cp))); const epochKey = toRootHex(cp.root); const value = this.epochIndex.get(cp.epoch); if (value) { value.delete(epochKey); if (value.size === 0) { this.epochIndex.delete(cp.epoch); } } } deleteAllEpochItems(epoch) { for (const rootHex of this.epochIndex.get(epoch) || []) { this.cache.delete(toCheckpointKey({ rootHex, epoch })); } this.epochIndex.delete(epoch); } clear() { this.cache.clear(); this.epochIndex.clear(); } /** 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: true, })); } getStates() { return this.cache.values(); } /** ONLY FOR DEBUGGING PURPOSES. For spec tests on error */ dumpCheckpointKeys() { return Array.from(this.cache.keys()); } } export function toCheckpointHex(checkpoint) { return { epoch: checkpoint.epoch, rootHex: toRootHex(checkpoint.root), }; } export function toCheckpointKey(cp) { return `${cp.rootHex}:${cp.epoch}`; } //# sourceMappingURL=inMemoryCheckpointsCache.js.map