@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
127 lines • 6.65 kB
JavaScript
import { callFnWhenAwait } from "@lodestar/utils";
import { isOptimisticBlock } from "../../util/forkChoice.js";
import { JobItemQueue } from "../../util/queue/index.js";
import { ChainEvent } from "../emitter.js";
import { PROCESS_FINALIZED_CHECKPOINT_QUEUE_LENGTH } from "./constants.js";
import { HistoricalStateRegen } from "./historicalState/historicalStateRegen.js";
import { ArchiveMode } from "./interface.js";
import { FrequencyStateArchiveStrategy } from "./strategies/frequencyStateArchiveStrategy.js";
import { archiveBlocks } from "./utils/archiveBlocks.js";
import { pruneHistory } from "./utils/pruneHistory.js";
import { updateBackfillRange } from "./utils/updateBackfillRange.js";
/**
* Used for running tasks that depends on some events or are executed
* periodically.
*/
export class ArchiveStore {
constructor(modules, opts, signal) {
//-------------------------------------------------------------------------
// Event handlers
//-------------------------------------------------------------------------
this.onFinalizedCheckpoint = async (finalized) => {
return this.jobQueue.push(finalized);
};
this.onCheckpoint = () => {
const headStateRoot = this.chain.forkChoice.getHead().stateRoot;
this.chain.regen.pruneOnCheckpoint(this.chain.forkChoice.getFinalizedCheckpoint().epoch, this.chain.forkChoice.getJustifiedCheckpoint().epoch, headStateRoot);
this.statesArchiverStrategy.onCheckpoint(headStateRoot, this.metrics).catch((err) => {
this.logger.error("Error during state archive", { archiveMode: this.archiveMode }, err);
});
};
this.processFinalizedCheckpoint = async (finalized) => {
try {
const finalizedEpoch = finalized.epoch;
this.logger.verbose("Start processing finalized checkpoint", { epoch: finalizedEpoch, rootHex: finalized.rootHex });
await archiveBlocks(this.chain.config, this.db, this.chain.forkChoice, this.chain.lightClientServer, this.logger, finalized, this.chain.clock.currentEpoch, this.archiveBlobEpochs, this.chain.opts.persistOrphanedBlocks, this.chain.opts.persistOrphanedBlocksDir);
if (this.opts.pruneHistory) {
await pruneHistory(this.chain.config, this.db, this.logger, this.metrics, finalizedEpoch, this.chain.clock.currentEpoch);
}
await this.statesArchiverStrategy.onFinalizedCheckpoint(finalized, this.metrics);
// should be after ArchiveBlocksTask to handle restart cleanly
await this.statesArchiverStrategy.maybeArchiveState(finalized, this.metrics);
this.chain.regen.pruneOnFinalized(finalizedEpoch);
// tasks rely on extended fork choice
const prunedBlocks = this.chain.forkChoice.prune(finalized.rootHex);
await updateBackfillRange({ chain: this.chain, db: this.db, logger: this.logger }, finalized);
this.logger.verbose("Finish processing finalized checkpoint", {
epoch: finalizedEpoch,
rootHex: finalized.rootHex,
prunedBlocks: prunedBlocks.length,
});
}
catch (e) {
this.logger.error("Error processing finalized checkpoint", { epoch: finalized.epoch }, e);
}
};
this.chain = modules.chain;
this.db = modules.db;
this.logger = modules.logger;
this.metrics = modules.metrics;
this.opts = opts;
this.signal = signal;
this.archiveMode = opts.archiveMode;
this.archiveBlobEpochs = opts.archiveBlobEpochs;
this.jobQueue = new JobItemQueue(this.processFinalizedCheckpoint, {
maxLength: PROCESS_FINALIZED_CHECKPOINT_QUEUE_LENGTH,
signal,
});
if (opts.archiveMode === ArchiveMode.Frequency) {
this.statesArchiverStrategy = new FrequencyStateArchiveStrategy(this.chain.regen, this.db, this.logger, opts, this.chain.bufferPool);
}
else {
throw new Error(`State archive strategy "${opts.archiveMode}" currently not supported.`);
}
if (!opts.disableArchiveOnCheckpoint) {
this.chain.emitter.on(ChainEvent.forkChoiceFinalized, this.onFinalizedCheckpoint);
this.chain.emitter.on(ChainEvent.checkpoint, this.onCheckpoint);
this.signal.addEventListener("abort", () => {
this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.onFinalizedCheckpoint);
this.chain.emitter.off(ChainEvent.checkpoint, this.onCheckpoint);
}, { once: true });
}
}
async init() {
if (this.opts.pruneHistory) {
// prune ALL stale data before starting
this.logger.info("Pruning historical data");
await callFnWhenAwait(pruneHistory(this.chain.config, this.db, this.logger, this.metrics, this.opts.anchorState.finalizedCheckpoint.epoch, this.chain.clock.currentEpoch), () => this.logger.info("Still pruning historical data, please wait..."), 30_000, this.signal);
}
if (this.opts.serveHistoricalState) {
this.historicalStateRegen = await HistoricalStateRegen.init({
opts: {
genesisTime: this.chain.clock.genesisTime,
dbLocation: this.opts.dbName,
},
config: this.chain.config,
metrics: this.metrics,
logger: this.logger,
signal: this.signal,
});
}
}
async close() {
await this.historicalStateRegen?.close();
}
async scrapeMetrics() {
return this.historicalStateRegen?.scrapeMetrics() ?? "";
}
async getHistoricalStateBySlot(slot) {
const finalizedBlock = this.chain.forkChoice.getFinalizedBlock();
if (slot >= finalizedBlock.slot) {
return null;
}
// request for finalized state using historical state regen
const stateSerialized = await this.historicalStateRegen?.getHistoricalState(slot);
if (!stateSerialized) {
return null;
}
return { state: stateSerialized, executionOptimistic: isOptimisticBlock(finalizedBlock), finalized: true };
}
/**
* Archive latest finalized state
* */
async persistToDisk() {
return this.statesArchiverStrategy.archiveState(this.chain.forkChoice.getFinalizedCheckpoint());
}
}
//# sourceMappingURL=archiveStore.js.map