UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

146 lines • 7.41 kB
import { SLOTS_PER_EPOCH } from "@lodestar/params"; import { computeEpochAtSlot, computeStartSlotAtEpoch } from "@lodestar/state-transition"; import { AllocSource } from "../../../util/bufferPool.js"; import { getStateSlotFromBytes } from "../../../util/multifork.js"; import { serializeState } from "../../serializeState.js"; /** * Minimum number of epochs between single temp archived states * These states will be pruned once a new state is persisted */ export const PERSIST_TEMP_STATE_EVERY_EPOCHS = 32; export { FrequencyStateArchiveStep }; var FrequencyStateArchiveStep; (function (FrequencyStateArchiveStep) { FrequencyStateArchiveStep["LoadLastStoredSlot"] = "load_last_stored_slot"; FrequencyStateArchiveStep["GetFinalizedState"] = "get_finalized_state"; // SerializeState is tracked via stateSerializeDuration metric FrequencyStateArchiveStep["PersistState"] = "persist_state"; FrequencyStateArchiveStep["LoadStoredSlotsToDelete"] = "load_stored_slots_to_delete"; FrequencyStateArchiveStep["DeleteOldStates"] = "delete_old_states"; })(FrequencyStateArchiveStep || (FrequencyStateArchiveStep = {})); /** * Archives finalized states from active bucket to archive bucket. * * Only the new finalized state is stored to disk */ export class FrequencyStateArchiveStrategy { regen; db; logger; opts; bufferPool; constructor(regen, db, logger, opts, bufferPool) { this.regen = regen; this.db = db; this.logger = logger; this.opts = opts; this.bufferPool = bufferPool; } async onFinalizedCheckpoint(_finalized, _metrics) { } async onCheckpoint(_stateRoot, _metrics) { } /** * Persist states every some epochs to * - Minimize disk space, storing the least states possible * - Minimize the sync progress lost on unexpected crash, storing temp state every few epochs * * At epoch `e` there will be states peristed at intervals of `PERSIST_STATE_EVERY_EPOCHS` = 32 * and one at `PERSIST_TEMP_STATE_EVERY_EPOCHS` = 1024 * ``` * | | | . * epoch - 1024*2 epoch - 1024 epoch - 32 epoch * ``` */ async maybeArchiveState(finalized, metrics) { let timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer(); const lastStoredSlot = await this.db.stateArchive.lastKey(); timer?.({ step: FrequencyStateArchiveStep.LoadLastStoredSlot }); const lastStoredEpoch = computeEpochAtSlot(lastStoredSlot ?? 0); const { archiveStateEpochFrequency } = this.opts; const logCtx = { finalizedEpoch: finalized.epoch, lastStoredEpoch, archiveStateEpochFrequency }; if (finalized.epoch - lastStoredEpoch >= Math.min(PERSIST_TEMP_STATE_EVERY_EPOCHS, archiveStateEpochFrequency)) { this.logger.verbose("Start archiving state", logCtx); await this.archiveState(finalized, metrics); // Only check the current and previous intervals const minEpoch = Math.max(0, (Math.floor(finalized.epoch / archiveStateEpochFrequency) - 1) * archiveStateEpochFrequency); timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer(); const storedStateSlots = await this.db.stateArchive.keys({ lt: computeStartSlotAtEpoch(finalized.epoch), gte: computeStartSlotAtEpoch(minEpoch), }); timer?.({ step: FrequencyStateArchiveStep.LoadStoredSlotsToDelete }); const statesSlotsToDelete = computeStateSlotsToDelete(storedStateSlots, archiveStateEpochFrequency); timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer(); if (statesSlotsToDelete.length > 0) { await this.db.stateArchive.batchDelete(statesSlotsToDelete); } timer?.({ step: FrequencyStateArchiveStep.DeleteOldStates }); // More logs to investigate the rss spike issue https://github.com/ChainSafe/lodestar/issues/5591 this.logger.verbose("Archived state completed", { ...logCtx, minEpoch, storedStateSlots: storedStateSlots.join(","), statesSlotsToDelete: statesSlotsToDelete.join(","), }); } else { this.logger.verbose("Skip archiving state", logCtx); } } /** * Archives finalized states from active bucket to archive bucket. * Only the new finalized state is stored to disk */ async archiveState(finalized, metrics) { // starting from Mar 2024, the finalized state could be from disk or in memory let timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer(); const finalizedHex = { epoch: finalized.epoch, rootHex: finalized.rootHex }; const finalizedStateOrBytes = await this.regen.getCheckpointStateOrBytes(finalizedHex); timer?.({ step: FrequencyStateArchiveStep.GetFinalizedState }); const { rootHex } = finalized; if (!finalizedStateOrBytes) { throw Error(`No state in cache for finalized checkpoint state epoch #${finalized.epoch} root ${rootHex}`); } if (finalizedStateOrBytes instanceof Uint8Array) { const slot = getStateSlotFromBytes(finalizedStateOrBytes); timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer(); await this.db.stateArchive.putBinary(slot, finalizedStateOrBytes); timer?.({ step: FrequencyStateArchiveStep.PersistState }); this.logger.verbose("Archived finalized state bytes", { epoch: finalized.epoch, slot, root: rootHex }); } else { // serialize state using BufferPool if provided const sszTimer = metrics?.stateSerializeDuration.startTimer({ source: AllocSource.ARCHIVE_STATE }); await serializeState(finalizedStateOrBytes, AllocSource.ARCHIVE_STATE, async (stateBytes) => { sszTimer?.(); timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer(); await this.db.stateArchive.putBinary(finalizedStateOrBytes.slot, stateBytes); timer?.({ step: FrequencyStateArchiveStep.PersistState }); }, this.bufferPool); // don't delete states before the finalized state, auto-prune will take care of it this.logger.verbose("Archived finalized state", { epoch: finalized.epoch, slot: finalizedStateOrBytes.slot, root: rootHex, }); } } } /** * Keeps first epoch per interval of persistEveryEpochs, deletes the rest */ export function computeStateSlotsToDelete(storedStateSlots, persistEveryEpochs) { const persistEverySlots = persistEveryEpochs * SLOTS_PER_EPOCH; const intervalsWithStates = new Set(); const stateSlotsToDelete = new Set(); for (const slot of storedStateSlots) { const interval = Math.floor(slot / persistEverySlots); if (intervalsWithStates.has(interval)) { stateSlotsToDelete.add(slot); } else { intervalsWithStates.add(interval); } } return Array.from(stateSlotsToDelete.values()); } //# sourceMappingURL=frequencyStateArchiveStrategy.js.map