@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
170 lines • 8.7 kB
JavaScript
import { callFnWhenAwait } from "@lodestar/utils";
import { isOptimisticBlock } from "../../util/forkChoice.js";
import { JobItemQueue, isQueueErrorAborted } 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";
export { ArchiveStoreTask };
var ArchiveStoreTask;
(function (ArchiveStoreTask) {
ArchiveStoreTask["ArchiveBlocks"] = "archive_blocks";
ArchiveStoreTask["PruneHistory"] = "prune_history";
ArchiveStoreTask["OnFinalizedCheckpoint"] = "on_finalized_checkpoint";
ArchiveStoreTask["MaybeArchiveState"] = "maybe_archive_state";
ArchiveStoreTask["RegenPruneOnFinalized"] = "regen_prune_on_finalized";
ArchiveStoreTask["ForkchoicePrune"] = "forkchoice_prune";
ArchiveStoreTask["UpdateBackfillRange"] = "update_backfill_range";
})(ArchiveStoreTask || (ArchiveStoreTask = {}));
/**
* Used for running tasks that depends on some events or are executed
* periodically.
*/
export class ArchiveStore {
archiveMode;
jobQueue;
archiveDataEpochs;
statesArchiverStrategy;
chain;
db;
logger;
metrics;
opts;
signal;
historicalStateRegen;
constructor(modules, opts, signal) {
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.archiveDataEpochs = opts.archiveDataEpochs;
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,
nativeStateView: this.opts.nativeStateView ?? false,
},
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());
}
//-------------------------------------------------------------------------
// Event handlers
//-------------------------------------------------------------------------
onFinalizedCheckpoint = (finalized) => {
this.jobQueue.push(finalized).catch((e) => {
if (!isQueueErrorAborted(e)) {
this.logger.error("Error queuing finalized checkpoint", { epoch: finalized.epoch }, e);
}
});
};
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);
});
};
processFinalizedCheckpoint = async (finalized) => {
try {
const finalizedEpoch = finalized.epoch;
this.logger.verbose("Start processing finalized checkpoint", { epoch: finalizedEpoch, rootHex: finalized.rootHex });
let timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
await archiveBlocks(this.chain.config, this.db, this.chain.forkChoice, this.chain.lightClientServer, this.logger, finalized, this.chain.clock.currentEpoch, this.archiveDataEpochs, this.chain.opts.persistOrphanedBlocks, this.chain.opts.persistOrphanedBlocksDir);
timer?.({ source: ArchiveStoreTask.ArchiveBlocks });
if (this.opts.pruneHistory) {
timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
await pruneHistory(this.chain.config, this.db, this.logger, this.metrics, finalizedEpoch, this.chain.clock.currentEpoch);
timer?.({ source: ArchiveStoreTask.PruneHistory });
}
timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
await this.statesArchiverStrategy.onFinalizedCheckpoint(finalized, this.metrics);
timer?.({ source: ArchiveStoreTask.OnFinalizedCheckpoint });
// should be after ArchiveBlocksTask to handle restart cleanly
timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
await this.statesArchiverStrategy.maybeArchiveState(finalized, this.metrics);
timer?.({ source: ArchiveStoreTask.MaybeArchiveState });
timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
this.chain.regen.pruneOnFinalized(finalizedEpoch);
timer?.({ source: ArchiveStoreTask.RegenPruneOnFinalized });
// tasks rely on extended fork choice
timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
const prunedBlocks = this.chain.forkChoice.prune(finalized.rootHex);
timer?.({ source: ArchiveStoreTask.ForkchoicePrune });
timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
await updateBackfillRange({ chain: this.chain, db: this.db, logger: this.logger }, finalized);
timer?.({ source: ArchiveStoreTask.UpdateBackfillRange });
this.logger.verbose("Finish processing finalized checkpoint", {
epoch: finalizedEpoch,
rootHex: finalized.rootHex,
prunedBlocks: prunedBlocks.length,
});
}
catch (e) {
if (!this.signal.aborted) {
this.logger.error("Error processing finalized checkpoint", { epoch: finalized.epoch }, e);
}
}
};
}
//# sourceMappingURL=archiveStore.js.map