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