@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
124 lines • 4.22 kB
JavaScript
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