From c9cf3ea85c638d490a76646e8c120a583253cca1 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 21 Oct 2024 11:10:23 +0200 Subject: [PATCH] feat: add strategy support for state archives (#7170) * Add strategy support for state-archives * Comment new strategy temporaily * Fix types * Fix unit tests * Fix unit tests * Hide new option * Rename ArchiveMode.Full to ArchiveMode.Frequency * Rename ArchiveMode to StateArchiveMode * Update the code as per feedback * Fix cli package import --- .../src/chain/archiver/archiver.ts | 166 +++++++++++++++++ .../beacon-node/src/chain/archiver/index.ts | 172 +----------------- .../src/chain/archiver/interface.ts | 47 +++++ .../frequencyStateArchiveStrategy.ts} | 31 ++-- packages/beacon-node/src/chain/chain.ts | 2 +- packages/beacon-node/src/chain/options.ts | 6 +- packages/beacon-node/src/node/options.ts | 4 +- .../produceBlock/produceBlockBody.test.ts | 3 +- .../perf/chain/verifyImportBlocks.test.ts | 3 +- .../unit/chain/archive/stateArchiver.test.ts | 2 +- .../test/utils/networkWithMockDb.ts | 2 + .../src/options/beaconNodeOptions/chain.ts | 17 +- .../unit/options/beaconNodeOptions.test.ts | 4 +- 13 files changed, 261 insertions(+), 198 deletions(-) create mode 100644 packages/beacon-node/src/chain/archiver/archiver.ts create mode 100644 packages/beacon-node/src/chain/archiver/interface.ts rename packages/beacon-node/src/chain/archiver/{archiveStates.ts => strategies/frequencyStateArchiveStrategy.ts} (84%) diff --git a/packages/beacon-node/src/chain/archiver/archiver.ts b/packages/beacon-node/src/chain/archiver/archiver.ts new file mode 100644 index 000000000000..2d79f584ea79 --- /dev/null +++ b/packages/beacon-node/src/chain/archiver/archiver.ts @@ -0,0 +1,166 @@ +import {Logger} from "@lodestar/utils"; +import {CheckpointWithHex} from "@lodestar/fork-choice"; +import {IBeaconDb} from "../../db/index.js"; +import {JobItemQueue} from "../../util/queue/index.js"; +import {IBeaconChain} from "../interface.js"; +import {ChainEvent} from "../emitter.js"; +import {Metrics} from "../../metrics/metrics.js"; +import {FrequencyStateArchiveStrategy} from "./strategies/frequencyStateArchiveStrategy.js"; +import {archiveBlocks} from "./archiveBlocks.js"; +import {StateArchiveMode, ArchiverOpts, StateArchiveStrategy} from "./interface.js"; + +export const DEFAULT_STATE_ARCHIVE_MODE = StateArchiveMode.Frequency; + +export const PROCESS_FINALIZED_CHECKPOINT_QUEUE_LEN = 256; + +/** + * Used for running tasks that depends on some events or are executed + * periodically. + */ +export class Archiver { + private stateArchiveMode: StateArchiveMode; + private jobQueue: JobItemQueue<[CheckpointWithHex], void>; + + private prevFinalized: CheckpointWithHex; + private readonly statesArchiverStrategy: StateArchiveStrategy; + private archiveBlobEpochs?: number; + + constructor( + private readonly db: IBeaconDb, + private readonly chain: IBeaconChain, + private readonly logger: Logger, + signal: AbortSignal, + opts: ArchiverOpts, + private readonly metrics?: Metrics | null + ) { + if (opts.stateArchiveMode === StateArchiveMode.Frequency) { + this.statesArchiverStrategy = new FrequencyStateArchiveStrategy(chain.regen, db, logger, opts, chain.bufferPool); + } else { + throw new Error(`State archive strategy "${opts.stateArchiveMode}" currently not supported.`); + } + + this.stateArchiveMode = opts.stateArchiveMode; + this.archiveBlobEpochs = opts.archiveBlobEpochs; + this.prevFinalized = chain.forkChoice.getFinalizedCheckpoint(); + this.jobQueue = new JobItemQueue<[CheckpointWithHex], void>(this.processFinalizedCheckpoint, { + maxLength: PROCESS_FINALIZED_CHECKPOINT_QUEUE_LEN, + signal, + }); + + if (!opts.disableArchiveOnCheckpoint) { + this.chain.emitter.on(ChainEvent.forkChoiceFinalized, this.onFinalizedCheckpoint); + this.chain.emitter.on(ChainEvent.checkpoint, this.onCheckpoint); + + signal.addEventListener( + "abort", + () => { + this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.onFinalizedCheckpoint); + this.chain.emitter.off(ChainEvent.checkpoint, this.onCheckpoint); + }, + {once: true} + ); + } + } + + /** Archive latest finalized state */ + async persistToDisk(): Promise { + return this.statesArchiverStrategy.maybeArchiveState(this.chain.forkChoice.getFinalizedCheckpoint()); + } + + private onFinalizedCheckpoint = async (finalized: CheckpointWithHex): Promise => { + return this.jobQueue.push(finalized); + }; + + private onCheckpoint = (): void => { + 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", {stateArchiveMode: this.stateArchiveMode}, err); + }); + }; + + private processFinalizedCheckpoint = async (finalized: CheckpointWithHex): Promise => { + 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.prevFinalized = finalized; + + 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 this.updateBackfillRange(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 as Error); + } + }; + + /** + * Backfill sync relies on verified connected ranges (which are represented as key,value + * with a verified jump from a key back to value). Since the node could have progressed + * ahead from, we need to save the forward progress of this node as another backfill + * range entry, that backfill sync will use to jump back if this node is restarted + * for any reason. + * The current backfill has its own backfill entry from anchor slot to last backfilled + * slot. And this would create the entry from the current finalized slot to the anchor + * slot. + */ + private updateBackfillRange = async (finalized: CheckpointWithHex): Promise => { + try { + // Mark the sequence in backfill db from finalized block's slot till anchor slot as + // filled. + const finalizedBlockFC = this.chain.forkChoice.getBlockHex(finalized.rootHex); + if (finalizedBlockFC && finalizedBlockFC.slot > this.chain.anchorStateLatestBlockSlot) { + await this.db.backfilledRanges.put(finalizedBlockFC.slot, this.chain.anchorStateLatestBlockSlot); + + // Clear previously marked sequence till anchorStateLatestBlockSlot, without + // touching backfill sync process sequence which are at + // <=anchorStateLatestBlockSlot i.e. clear >anchorStateLatestBlockSlot + // and < currentSlot + const filteredSeqs = await this.db.backfilledRanges.entries({ + gt: this.chain.anchorStateLatestBlockSlot, + lt: finalizedBlockFC.slot, + }); + this.logger.debug("updated backfilledRanges", { + key: finalizedBlockFC.slot, + value: this.chain.anchorStateLatestBlockSlot, + }); + if (filteredSeqs.length > 0) { + await this.db.backfilledRanges.batchDelete(filteredSeqs.map((entry) => entry.key)); + this.logger.debug( + `Forward Sync - cleaned up backfilledRanges between ${finalizedBlockFC.slot},${this.chain.anchorStateLatestBlockSlot}`, + {seqs: JSON.stringify(filteredSeqs)} + ); + } + } + } catch (e) { + this.logger.error("Error updating backfilledRanges on finalization", {epoch: finalized.epoch}, e as Error); + } + }; +} diff --git a/packages/beacon-node/src/chain/archiver/index.ts b/packages/beacon-node/src/chain/archiver/index.ts index 45169b2fa802..dbcafbe458a6 100644 --- a/packages/beacon-node/src/chain/archiver/index.ts +++ b/packages/beacon-node/src/chain/archiver/index.ts @@ -1,170 +1,2 @@ -import {Logger} from "@lodestar/utils"; -import {CheckpointWithHex} from "@lodestar/fork-choice"; -import {IBeaconDb} from "../../db/index.js"; -import {JobItemQueue} from "../../util/queue/index.js"; -import {IBeaconChain} from "../interface.js"; -import {ChainEvent} from "../emitter.js"; -import {Metrics} from "../../metrics/metrics.js"; -import {StatesArchiver, StatesArchiverOpts} from "./archiveStates.js"; -import {archiveBlocks} from "./archiveBlocks.js"; - -const PROCESS_FINALIZED_CHECKPOINT_QUEUE_LEN = 256; - -export type ArchiverOpts = StatesArchiverOpts & { - disableArchiveOnCheckpoint?: boolean; - archiveBlobEpochs?: number; -}; - -type ProposalStats = { - total: number; - finalized: number; - orphaned: number; - missed: number; -}; - -export type FinalizedStats = { - allValidators: ProposalStats; - attachedValidators: ProposalStats; - finalizedCanonicalCheckpointsCount: number; - finalizedFoundCheckpointsInStateCache: number; - finalizedAttachedValidatorsCount: number; -}; - -/** - * Used for running tasks that depends on some events or are executed - * periodically. - */ -export class Archiver { - private jobQueue: JobItemQueue<[CheckpointWithHex], void>; - - private prevFinalized: CheckpointWithHex; - private readonly statesArchiver: StatesArchiver; - private archiveBlobEpochs?: number; - - constructor( - private readonly db: IBeaconDb, - private readonly chain: IBeaconChain, - private readonly logger: Logger, - signal: AbortSignal, - opts: ArchiverOpts, - private readonly metrics?: Metrics | null - ) { - this.archiveBlobEpochs = opts.archiveBlobEpochs; - this.statesArchiver = new StatesArchiver(chain.regen, db, logger, opts, chain.bufferPool); - this.prevFinalized = chain.forkChoice.getFinalizedCheckpoint(); - this.jobQueue = new JobItemQueue<[CheckpointWithHex], void>(this.processFinalizedCheckpoint, { - maxLength: PROCESS_FINALIZED_CHECKPOINT_QUEUE_LEN, - signal, - }); - - if (!opts.disableArchiveOnCheckpoint) { - this.chain.emitter.on(ChainEvent.forkChoiceFinalized, this.onFinalizedCheckpoint); - this.chain.emitter.on(ChainEvent.checkpoint, this.onCheckpoint); - - signal.addEventListener( - "abort", - () => { - this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.onFinalizedCheckpoint); - this.chain.emitter.off(ChainEvent.checkpoint, this.onCheckpoint); - }, - {once: true} - ); - } - } - - /** Archive latest finalized state */ - async persistToDisk(): Promise { - await this.statesArchiver.archiveState(this.chain.forkChoice.getFinalizedCheckpoint()); - } - - private onFinalizedCheckpoint = async (finalized: CheckpointWithHex): Promise => { - return this.jobQueue.push(finalized); - }; - - private onCheckpoint = (): void => { - const headStateRoot = this.chain.forkChoice.getHead().stateRoot; - this.chain.regen.pruneOnCheckpoint( - this.chain.forkChoice.getFinalizedCheckpoint().epoch, - this.chain.forkChoice.getJustifiedCheckpoint().epoch, - headStateRoot - ); - }; - - private processFinalizedCheckpoint = async (finalized: CheckpointWithHex): Promise => { - 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.prevFinalized = finalized; - - // should be after ArchiveBlocksTask to handle restart cleanly - await this.statesArchiver.maybeArchiveState(finalized, this.metrics); - - this.chain.regen.pruneOnFinalized(finalizedEpoch); - - // tasks rely on extended fork choice - const prunedBlocks = this.chain.forkChoice.prune(finalized.rootHex); - await this.updateBackfillRange(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 as Error); - } - }; - - /** - * Backfill sync relies on verified connected ranges (which are represented as key,value - * with a verified jump from a key back to value). Since the node could have progressed - * ahead from, we need to save the forward progress of this node as another backfill - * range entry, that backfill sync will use to jump back if this node is restarted - * for any reason. - * The current backfill has its own backfill entry from anchor slot to last backfilled - * slot. And this would create the entry from the current finalized slot to the anchor - * slot. - */ - private updateBackfillRange = async (finalized: CheckpointWithHex): Promise => { - try { - // Mark the sequence in backfill db from finalized block's slot till anchor slot as - // filled. - const finalizedBlockFC = this.chain.forkChoice.getBlockHex(finalized.rootHex); - if (finalizedBlockFC && finalizedBlockFC.slot > this.chain.anchorStateLatestBlockSlot) { - await this.db.backfilledRanges.put(finalizedBlockFC.slot, this.chain.anchorStateLatestBlockSlot); - - // Clear previously marked sequence till anchorStateLatestBlockSlot, without - // touching backfill sync process sequence which are at - // <=anchorStateLatestBlockSlot i.e. clear >anchorStateLatestBlockSlot - // and < currentSlot - const filteredSeqs = await this.db.backfilledRanges.entries({ - gt: this.chain.anchorStateLatestBlockSlot, - lt: finalizedBlockFC.slot, - }); - this.logger.debug("updated backfilledRanges", { - key: finalizedBlockFC.slot, - value: this.chain.anchorStateLatestBlockSlot, - }); - if (filteredSeqs.length > 0) { - await this.db.backfilledRanges.batchDelete(filteredSeqs.map((entry) => entry.key)); - this.logger.debug( - `Forward Sync - cleaned up backfilledRanges between ${finalizedBlockFC.slot},${this.chain.anchorStateLatestBlockSlot}`, - {seqs: JSON.stringify(filteredSeqs)} - ); - } - } - } catch (e) { - this.logger.error("Error updating backfilledRanges on finalization", {epoch: finalized.epoch}, e as Error); - } - }; -} +export * from "./archiver.js"; +export * from "./interface.js"; diff --git a/packages/beacon-node/src/chain/archiver/interface.ts b/packages/beacon-node/src/chain/archiver/interface.ts new file mode 100644 index 000000000000..48c930c78cd3 --- /dev/null +++ b/packages/beacon-node/src/chain/archiver/interface.ts @@ -0,0 +1,47 @@ +import {CheckpointWithHex} from "@lodestar/fork-choice"; +import {Metrics} from "../../metrics/metrics.js"; +import {RootHex} from "@lodestar/types"; + +export enum StateArchiveMode { + Frequency = "frequency", + // New strategy to be implemented + // WIP: https://github.com/ChainSafe/lodestar/pull/7005 + // Differential = "diff", +} + +export interface StatesArchiverOpts { + /** + * Minimum number of epochs between archived states + */ + archiveStateEpochFrequency: number; + /** + * Strategy to store archive states + */ + stateArchiveMode: StateArchiveMode; +} + +export type ArchiverOpts = StatesArchiverOpts & { + disableArchiveOnCheckpoint?: boolean; + archiveBlobEpochs?: number; +}; + +export type ProposalStats = { + total: number; + finalized: number; + orphaned: number; + missed: number; +}; + +export type FinalizedStats = { + allValidators: ProposalStats; + attachedValidators: ProposalStats; + finalizedCanonicalCheckpointsCount: number; + finalizedFoundCheckpointsInStateCache: number; + finalizedAttachedValidatorsCount: number; +}; + +export interface StateArchiveStrategy { + onCheckpoint(stateRoot: RootHex, metrics?: Metrics | null): Promise; + onFinalizedCheckpoint(finalized: CheckpointWithHex, metrics?: Metrics | null): Promise; + maybeArchiveState(finalized: CheckpointWithHex, metrics?: Metrics | null): Promise; +} diff --git a/packages/beacon-node/src/chain/archiver/archiveStates.ts b/packages/beacon-node/src/chain/archiver/strategies/frequencyStateArchiveStrategy.ts similarity index 84% rename from packages/beacon-node/src/chain/archiver/archiveStates.ts rename to packages/beacon-node/src/chain/archiver/strategies/frequencyStateArchiveStrategy.ts index 8fd9081ab243..a701da5b22ec 100644 --- a/packages/beacon-node/src/chain/archiver/archiveStates.ts +++ b/packages/beacon-node/src/chain/archiver/strategies/frequencyStateArchiveStrategy.ts @@ -1,34 +1,28 @@ import {Logger} from "@lodestar/utils"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; -import {Slot, Epoch} from "@lodestar/types"; +import {Slot, Epoch, RootHex} from "@lodestar/types"; import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {CheckpointWithHex} from "@lodestar/fork-choice"; -import {IBeaconDb} from "../../db/index.js"; -import {IStateRegenerator} from "../regen/interface.js"; -import {getStateSlotFromBytes} from "../../util/multifork.js"; -import {serializeState} from "../serializeState.js"; -import {AllocSource, BufferPool} from "../../util/bufferPool.js"; -import {Metrics} from "../../metrics/metrics.js"; +import {IBeaconDb} from "../../../db/index.js"; +import {IStateRegenerator} from "../../regen/interface.js"; +import {getStateSlotFromBytes} from "../../../util/multifork.js"; +import {serializeState} from "../../serializeState.js"; +import {AllocSource, BufferPool} from "../../../util/bufferPool.js"; +import {Metrics} from "../../../metrics/metrics.js"; +import {StateArchiveStrategy, StatesArchiverOpts} from "../interface.js"; /** * Minimum number of epochs between single temp archived states * These states will be pruned once a new state is persisted */ -const PERSIST_TEMP_STATE_EVERY_EPOCHS = 32; - -export interface StatesArchiverOpts { - /** - * Minimum number of epochs between archived states - */ - archiveStateEpochFrequency: number; -} +export const PERSIST_TEMP_STATE_EVERY_EPOCHS = 32; /** * Archives finalized states from active bucket to archive bucket. * * Only the new finalized state is stored to disk */ -export class StatesArchiver { +export class FrequencyStateArchiveStrategy implements StateArchiveStrategy { constructor( private readonly regen: IStateRegenerator, private readonly db: IBeaconDb, @@ -37,6 +31,9 @@ export class StatesArchiver { private readonly bufferPool?: BufferPool | null ) {} + async onFinalizedCheckpoint(_finalized: CheckpointWithHex, _metrics?: Metrics | null): Promise {} + async onCheckpoint(_stateRoot: RootHex, _metrics?: Metrics | null): Promise {} + /** * Persist states every some epochs to * - Minimize disk space, storing the least states possible @@ -87,7 +84,7 @@ export class StatesArchiver { * Archives finalized states from active bucket to archive bucket. * Only the new finalized state is stored to disk */ - async archiveState(finalized: CheckpointWithHex, metrics?: Metrics | null): Promise { + private async archiveState(finalized: CheckpointWithHex, metrics?: Metrics | null): Promise { // starting from Mar 2024, the finalized state could be from disk or in memory const finalizedStateOrBytes = await this.regen.getCheckpointStateOrBytes(finalized); const {rootHex} = finalized; diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 195b8736b2c3..144b73f0c01d 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -77,7 +77,7 @@ import { OpPool, } from "./opPools/index.js"; import {LightClientServer} from "./lightClient/index.js"; -import {Archiver} from "./archiver/index.js"; +import {Archiver} from "./archiver/archiver.js"; import {PrepareNextSlotScheduler} from "./prepareNextSlot.js"; import {ReprocessController} from "./reprocess.js"; import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js"; diff --git a/packages/beacon-node/src/chain/options.ts b/packages/beacon-node/src/chain/options.ts index bc2b73256272..cf83c4432984 100644 --- a/packages/beacon-node/src/chain/options.ts +++ b/packages/beacon-node/src/chain/options.ts @@ -1,12 +1,15 @@ import {SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY} from "@lodestar/params"; import {defaultOptions as defaultValidatorOptions} from "@lodestar/validator"; -import {ArchiverOpts} from "./archiver/index.js"; +import {ArchiverOpts} from "./archiver/interface.js"; import {ForkChoiceOpts} from "./forkChoice/index.js"; import {LightClientServerOpts} from "./lightClient/index.js"; import {ShufflingCacheOpts} from "./shufflingCache.js"; import {DEFAULT_MAX_BLOCK_STATES, FIFOBlockStateCacheOpts} from "./stateCache/fifoBlockStateCache.js"; import {PersistentCheckpointStateCacheOpts} from "./stateCache/persistentCheckpointsCache.js"; import {DEFAULT_MAX_CP_STATE_EPOCHS_IN_MEMORY} from "./stateCache/persistentCheckpointsCache.js"; +import {DEFAULT_STATE_ARCHIVE_MODE} from "./archiver/archiver.js"; +export {StateArchiveMode} from "./archiver/interface.js"; +export {DEFAULT_STATE_ARCHIVE_MODE} from "./archiver/archiver.js"; export type IChainOptions = BlockProcessOpts & PoolOpts & @@ -102,6 +105,7 @@ export const defaultChainOptions: IChainOptions = { suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient, assertCorrectProgressiveBalances: false, archiveStateEpochFrequency: 1024, + stateArchiveMode: DEFAULT_STATE_ARCHIVE_MODE, emitPayloadAttributes: false, // for gossip block validation, it's unlikely we see a reorg with 32 slots // for attestation validation, having this value ensures we don't have to regen states most of the time diff --git a/packages/beacon-node/src/node/options.ts b/packages/beacon-node/src/node/options.ts index 475a4debee63..e587e58ec127 100644 --- a/packages/beacon-node/src/node/options.ts +++ b/packages/beacon-node/src/node/options.ts @@ -1,5 +1,5 @@ import {defaultApiOptions, ApiOptions} from "../api/options.js"; -import {defaultChainOptions, IChainOptions} from "../chain/options.js"; +import {defaultChainOptions, IChainOptions, StateArchiveMode, DEFAULT_STATE_ARCHIVE_MODE} from "../chain/options.js"; import {defaultDbOptions, DatabaseOptions} from "../db/options.js"; import {defaultEth1Options, Eth1Options} from "../eth1/options.js"; import {defaultMetricsOptions, MetricsOptions} from "../metrics/options.js"; @@ -18,7 +18,7 @@ import { export {allNamespaces} from "../api/rest/index.js"; // Re-export to use as default values in CLI args -export {defaultExecutionEngineHttpOpts, defaultExecutionBuilderHttpOpts}; +export {defaultExecutionEngineHttpOpts, defaultExecutionBuilderHttpOpts, StateArchiveMode, DEFAULT_STATE_ARCHIVE_MODE}; export interface IBeaconNodeOptions { api: ApiOptions; diff --git a/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts b/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts index fdd6f60f5a47..c08cf64fc974 100644 --- a/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts +++ b/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts @@ -10,7 +10,7 @@ import {BeaconChain} from "../../../../src/chain/index.js"; import {BlockType, produceBlockBody} from "../../../../src/chain/produceBlock/produceBlockBody.js"; import {Eth1ForBlockProductionDisabled} from "../../../../src/eth1/index.js"; import {ExecutionEngineDisabled} from "../../../../src/execution/engine/index.js"; -import {BeaconDb} from "../../../../src/index.js"; +import {StateArchiveMode, BeaconDb} from "../../../../src/index.js"; import {testLogger} from "../../../utils/logger.js"; const logger = testLogger(); @@ -36,6 +36,7 @@ describe("produceBlockBody", () => { skipCreateStateCacheIfAvailable: true, archiveStateEpochFrequency: 1024, minSameMessageSignatureSetsToBatch: 32, + stateArchiveMode: StateArchiveMode.Frequency, }, { config: state.config, diff --git a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts index cd4d61b173c7..6f1cf2ef3da6 100644 --- a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts +++ b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts @@ -12,7 +12,7 @@ import {ExecutionEngineDisabled} from "../../../src/execution/engine/index.js"; import {Eth1ForBlockProductionDisabled} from "../../../src/eth1/index.js"; import {testLogger} from "../../utils/logger.js"; import {linspace} from "../../../src/util/numpy.js"; -import {BeaconDb} from "../../../src/index.js"; +import {StateArchiveMode, BeaconDb} from "../../../src/index.js"; import {getBlockInput, AttestationImportOpt, BlockSource} from "../../../src/chain/blocks/types.js"; // Define this params in `packages/state-transition/test/perf/params.ts` @@ -85,6 +85,7 @@ describe.skip("verify+import blocks - range sync perf test", () => { skipCreateStateCacheIfAvailable: true, archiveStateEpochFrequency: 1024, minSameMessageSignatureSetsToBatch: 32, + stateArchiveMode: StateArchiveMode.Frequency, }, { config: state.config, diff --git a/packages/beacon-node/test/unit/chain/archive/stateArchiver.test.ts b/packages/beacon-node/test/unit/chain/archive/stateArchiver.test.ts index fe21fd64af96..cbfba0a362df 100644 --- a/packages/beacon-node/test/unit/chain/archive/stateArchiver.test.ts +++ b/packages/beacon-node/test/unit/chain/archive/stateArchiver.test.ts @@ -1,6 +1,6 @@ import {describe, it, expect} from "vitest"; import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; -import {computeStateSlotsToDelete} from "../../../../src/chain/archiver/archiveStates.js"; +import {computeStateSlotsToDelete} from "../../../../src/chain/archiver/strategies/frequencyStateArchiveStrategy.js"; describe("state archiver task", () => { describe("computeStateSlotsToDelete", () => { diff --git a/packages/beacon-node/test/utils/networkWithMockDb.ts b/packages/beacon-node/test/utils/networkWithMockDb.ts index b1c4293588d2..84c3821bb22c 100644 --- a/packages/beacon-node/test/utils/networkWithMockDb.ts +++ b/packages/beacon-node/test/utils/networkWithMockDb.ts @@ -12,6 +12,7 @@ import {createCachedBeaconStateTest} from "./cachedBeaconState.js"; import {ClockStatic} from "./clock.js"; import {testLogger} from "./logger.js"; import {generateState} from "./state.js"; +import {StateArchiveMode} from "../../src/index.js"; export type NetworkForTestOpts = { startSlot?: number; @@ -54,6 +55,7 @@ export async function getNetworkForTest( disableLightClientServerOnImportBlockHead: true, disablePrepareNextSlot: true, minSameMessageSignatureSetsToBatch: 32, + stateArchiveMode: StateArchiveMode.Frequency, }, { config: beaconConfig, diff --git a/packages/cli/src/options/beaconNodeOptions/chain.ts b/packages/cli/src/options/beaconNodeOptions/chain.ts index 78ffd47da8f4..c5f907804a83 100644 --- a/packages/cli/src/options/beaconNodeOptions/chain.ts +++ b/packages/cli/src/options/beaconNodeOptions/chain.ts @@ -1,5 +1,5 @@ import * as path from "node:path"; -import {defaultOptions, IBeaconNodeOptions} from "@lodestar/beacon-node"; +import {StateArchiveMode, defaultOptions, IBeaconNodeOptions, DEFAULT_STATE_ARCHIVE_MODE} from "@lodestar/beacon-node"; import {CliCommandOptions} from "@lodestar/utils"; export type ChainArgs = { @@ -22,12 +22,13 @@ export type ChainArgs = { "chain.maxSkipSlots"?: number; "chain.trustedSetup"?: string; "safe-slots-to-import-optimistically": number; - "chain.archiveStateEpochFrequency": number; emitPayloadAttributes?: boolean; broadcastValidationStrictness?: string; "chain.minSameMessageSignatureSetsToBatch"?: number; "chain.maxShufflingCacheEpochs"?: number; + "chain.archiveStateEpochFrequency": number; "chain.archiveBlobEpochs"?: number; + "chain.stateArchiveMode": StateArchiveMode; "chain.nHistoricalStates"?: boolean; "chain.nHistoricalStatesFileDataStore"?: boolean; "chain.maxBlockStates"?: number; @@ -54,13 +55,14 @@ export function parseArgs(args: ChainArgs): IBeaconNodeOptions["chain"] { maxSkipSlots: args["chain.maxSkipSlots"], trustedSetup: args["chain.trustedSetup"], safeSlotsToImportOptimistically: args["safe-slots-to-import-optimistically"], - archiveStateEpochFrequency: args["chain.archiveStateEpochFrequency"], emitPayloadAttributes: args.emitPayloadAttributes, broadcastValidationStrictness: args.broadcastValidationStrictness, minSameMessageSignatureSetsToBatch: args["chain.minSameMessageSignatureSetsToBatch"] ?? defaultOptions.chain.minSameMessageSignatureSetsToBatch, maxShufflingCacheEpochs: args["chain.maxShufflingCacheEpochs"] ?? defaultOptions.chain.maxShufflingCacheEpochs, + archiveStateEpochFrequency: args["chain.archiveStateEpochFrequency"], archiveBlobEpochs: args["chain.archiveBlobEpochs"], + stateArchiveMode: args["chain.stateArchiveMode"] ?? defaultOptions.chain.stateArchiveMode, nHistoricalStates: args["chain.nHistoricalStates"] ?? defaultOptions.chain.nHistoricalStates, nHistoricalStatesFileDataStore: args["chain.nHistoricalStatesFileDataStore"] ?? defaultOptions.chain.nHistoricalStatesFileDataStore, @@ -210,6 +212,15 @@ Will double processing times. Use only for debugging purposes.", group: "chain", }, + "chain.stateArchiveMode": { + hidden: true, + choices: Object.values(StateArchiveMode), + description: `Strategy to manage archive states, only support ${DEFAULT_STATE_ARCHIVE_MODE} at this time`, + default: defaultOptions.chain.stateArchiveMode, + type: "string", + group: "chain", + }, + broadcastValidationStrictness: { // TODO: hide the option till validations fully implemented hidden: true, diff --git a/packages/cli/test/unit/options/beaconNodeOptions.test.ts b/packages/cli/test/unit/options/beaconNodeOptions.test.ts index 879b5bfa2fc9..8d295197b541 100644 --- a/packages/cli/test/unit/options/beaconNodeOptions.test.ts +++ b/packages/cli/test/unit/options/beaconNodeOptions.test.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import {describe, it, expect} from "vitest"; -import {IBeaconNodeOptions} from "@lodestar/beacon-node"; +import {StateArchiveMode, IBeaconNodeOptions} from "@lodestar/beacon-node"; import {RecursivePartial} from "@lodestar/utils"; import {parseBeaconNodeArgs, BeaconNodeArgs} from "../../../src/options/beaconNodeOptions/index.js"; import {getTestdirPath} from "../../utils.js"; @@ -43,6 +43,7 @@ describe("options / beaconNodeOptions", () => { "chain.nHistoricalStatesFileDataStore": true, "chain.maxBlockStates": 100, "chain.maxCPStateEpochsInMemory": 100, + "chain.stateArchiveMode": StateArchiveMode.Frequency, emitPayloadAttributes: false, eth1: true, @@ -147,6 +148,7 @@ describe("options / beaconNodeOptions", () => { minSameMessageSignatureSetsToBatch: 32, maxShufflingCacheEpochs: 100, archiveBlobEpochs: 10000, + stateArchiveMode: StateArchiveMode.Frequency, nHistoricalStates: true, nHistoricalStatesFileDataStore: true, maxBlockStates: 100,