From 09d8c598ebbbc36f399533937202e86fb9878af4 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 16 Oct 2024 16:13:42 +0200 Subject: [PATCH 1/6] Add strategy support for state-archives --- .../src/chain/archiver/archiver.ts | 163 +++++++++++++++++ .../src/chain/archiver/constants.ts | 11 ++ .../beacon-node/src/chain/archiver/index.ts | 173 +----------------- .../src/chain/archiver/interface.ts | 45 +++++ .../fullStateArchiveStrategy.ts} | 36 ++-- packages/beacon-node/src/chain/chain.ts | 2 +- packages/beacon-node/src/chain/options.ts | 5 +- packages/beacon-node/src/node/options.ts | 4 +- .../src/options/beaconNodeOptions/chain.ts | 18 +- 9 files changed, 258 insertions(+), 199 deletions(-) create mode 100644 packages/beacon-node/src/chain/archiver/archiver.ts create mode 100644 packages/beacon-node/src/chain/archiver/constants.ts create mode 100644 packages/beacon-node/src/chain/archiver/interface.ts rename packages/beacon-node/src/chain/archiver/{archiveStates.ts => strategies/fullStateArchiveStrategy.ts} (83%) 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..0647cee93097 --- /dev/null +++ b/packages/beacon-node/src/chain/archiver/archiver.ts @@ -0,0 +1,163 @@ +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 {FullStateArchiveStrategy} from "./strategies/fullStateArchiveStrategy.js"; +import {archiveBlocks} from "./archiveBlocks.js"; +import {ArchiveMode, ArchiverOpts, StateArchiveStrategy} from "./interface.js"; +import {PROCESS_FINALIZED_CHECKPOINT_QUEUE_LEN} from "./constants.js"; + +/** + * Used for running tasks that depends on some events or are executed + * periodically. + */ +export class Archiver { + private archiveMode: ArchiveMode; + 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.archiveMode === ArchiveMode.Full) { + this.statesArchiverStrategy = new FullStateArchiveStrategy(chain.regen, db, logger, opts, chain.bufferPool); + } else { + throw new Error(`State archive strategy "${opts.archiveMode}" currently not supported.`); + } + + this.archiveMode = opts.archiveMode; + 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 { + await 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", {archiveMode: this.archiveMode}, 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/constants.ts b/packages/beacon-node/src/chain/archiver/constants.ts new file mode 100644 index 000000000000..729858c414ba --- /dev/null +++ b/packages/beacon-node/src/chain/archiver/constants.ts @@ -0,0 +1,11 @@ +import {ArchiveMode} from "./interface.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 const PROCESS_FINALIZED_CHECKPOINT_QUEUE_LEN = 256; + +export const DEFAULT_ARCHIVE_MODE = ArchiveMode.Full; diff --git a/packages/beacon-node/src/chain/archiver/index.ts b/packages/beacon-node/src/chain/archiver/index.ts index 45169b2fa802..fac923cc1106 100644 --- a/packages/beacon-node/src/chain/archiver/index.ts +++ b/packages/beacon-node/src/chain/archiver/index.ts @@ -1,170 +1,3 @@ -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"; +export * from "./constants.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..011ba3244b06 --- /dev/null +++ b/packages/beacon-node/src/chain/archiver/interface.ts @@ -0,0 +1,45 @@ +import {CheckpointWithHex} from "@lodestar/fork-choice"; +import {Metrics} from "../../metrics/metrics.js"; +import {RootHex} from "@lodestar/types"; + +export enum ArchiveMode { + Full = "full", + Differential = "diff", +} + +export interface StatesArchiverOpts { + /** + * Minimum number of epochs between archived states + */ + archiveStateEpochFrequency: number; + /** + * Strategy to store archive states + */ + archiveMode: ArchiveMode; +} + +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/fullStateArchiveStrategy.ts similarity index 83% rename from packages/beacon-node/src/chain/archiver/archiveStates.ts rename to packages/beacon-node/src/chain/archiver/strategies/fullStateArchiveStrategy.ts index 8fd9081ab243..9246875bfcac 100644 --- a/packages/beacon-node/src/chain/archiver/archiveStates.ts +++ b/packages/beacon-node/src/chain/archiver/strategies/fullStateArchiveStrategy.ts @@ -1,34 +1,23 @@ 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"; - -/** - * 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; -} +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"; +import {PERSIST_TEMP_STATE_EVERY_EPOCHS} from "../constants.js"; /** * Archives finalized states from active bucket to archive bucket. * * Only the new finalized state is stored to disk */ -export class StatesArchiver { +export class FullStateArchiveStrategy implements StateArchiveStrategy { constructor( private readonly regen: IStateRegenerator, private readonly db: IBeaconDb, @@ -37,6 +26,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 +79,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..58bf76324849 100644 --- a/packages/beacon-node/src/chain/options.ts +++ b/packages/beacon-node/src/chain/options.ts @@ -1,12 +1,14 @@ 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_ARCHIVE_MODE} from "./archiver/constants.js"; +export {ArchiveMode} from "./archiver/interface.js"; export type IChainOptions = BlockProcessOpts & PoolOpts & @@ -102,6 +104,7 @@ export const defaultChainOptions: IChainOptions = { suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient, assertCorrectProgressiveBalances: false, archiveStateEpochFrequency: 1024, + archiveMode: DEFAULT_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..0c2328c67a49 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, ArchiveMode} 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, ArchiveMode}; export interface IBeaconNodeOptions { api: ApiOptions; diff --git a/packages/cli/src/options/beaconNodeOptions/chain.ts b/packages/cli/src/options/beaconNodeOptions/chain.ts index 78ffd47da8f4..d575211517c4 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 {ArchiveMode, defaultOptions, IBeaconNodeOptions} 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.archiveMode": ArchiveMode; "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"], + archiveMode: args["chain.archiveMode"] ?? defaultOptions.chain.archiveMode, nHistoricalStates: args["chain.nHistoricalStates"] ?? defaultOptions.chain.nHistoricalStates, nHistoricalStatesFileDataStore: args["chain.nHistoricalStatesFileDataStore"] ?? defaultOptions.chain.nHistoricalStatesFileDataStore, @@ -210,6 +212,16 @@ Will double processing times. Use only for debugging purposes.", group: "chain", }, + "chain.archiveMode": { + hidden: false, + choices: Object.values(ArchiveMode), + description: "Strategy to manage archive states", + default: defaultOptions.chain.archiveMode, + defaultDescription: String(defaultOptions.chain.archiveMode), + type: "string", + group: "chain", + }, + broadcastValidationStrictness: { // TODO: hide the option till validations fully implemented hidden: true, From 71f032527c677a1d625a95e412506fa4a27c87a9 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 16 Oct 2024 16:18:34 +0200 Subject: [PATCH 2/6] Comment new strategy temporaily --- packages/beacon-node/src/chain/archiver/interface.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/archiver/interface.ts b/packages/beacon-node/src/chain/archiver/interface.ts index 011ba3244b06..54e874441ae8 100644 --- a/packages/beacon-node/src/chain/archiver/interface.ts +++ b/packages/beacon-node/src/chain/archiver/interface.ts @@ -4,7 +4,8 @@ import {RootHex} from "@lodestar/types"; export enum ArchiveMode { Full = "full", - Differential = "diff", + // Specify only existing strategy + // Differential = "diff", } export interface StatesArchiverOpts { From f8e10b7b4a7424d4274859e6c9a2558c577e932f Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 16 Oct 2024 16:54:29 +0200 Subject: [PATCH 3/6] Fix types --- .../test/perf/chain/produceBlock/produceBlockBody.test.ts | 3 ++- .../beacon-node/test/perf/chain/verifyImportBlocks.test.ts | 3 ++- .../beacon-node/test/unit/chain/archive/stateArchiver.test.ts | 2 +- packages/beacon-node/test/utils/networkWithMockDb.ts | 2 ++ packages/cli/test/unit/options/beaconNodeOptions.test.ts | 3 ++- 5 files changed, 9 insertions(+), 4 deletions(-) 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..4a1a18a719f1 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 {ArchiveMode, 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, + archiveMode: ArchiveMode.Full, }, { 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..303e948ec83c 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 {ArchiveMode, 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, + archiveMode: ArchiveMode.Full, }, { 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..e8908a30b6c2 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/fullStateArchiveStrategy.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..e5e5e21564c4 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 {ArchiveMode} from "../../src/index.js"; export type NetworkForTestOpts = { startSlot?: number; @@ -54,6 +55,7 @@ export async function getNetworkForTest( disableLightClientServerOnImportBlockHead: true, disablePrepareNextSlot: true, minSameMessageSignatureSetsToBatch: 32, + archiveMode: ArchiveMode.Full, }, { config: beaconConfig, diff --git a/packages/cli/test/unit/options/beaconNodeOptions.test.ts b/packages/cli/test/unit/options/beaconNodeOptions.test.ts index 879b5bfa2fc9..1f6375f94bc3 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 {ArchiveMode, 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.archiveMode": ArchiveMode.Full, emitPayloadAttributes: false, eth1: true, From 61b63a42481a68c4667f54267c59becdda7d0752 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 16 Oct 2024 17:17:37 +0200 Subject: [PATCH 4/6] Fix unit tests --- packages/cli/test/unit/options/beaconNodeOptions.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/test/unit/options/beaconNodeOptions.test.ts b/packages/cli/test/unit/options/beaconNodeOptions.test.ts index 1f6375f94bc3..230b358e4f16 100644 --- a/packages/cli/test/unit/options/beaconNodeOptions.test.ts +++ b/packages/cli/test/unit/options/beaconNodeOptions.test.ts @@ -39,6 +39,7 @@ describe("options / beaconNodeOptions", () => { "chain.minSameMessageSignatureSetsToBatch": 32, "chain.maxShufflingCacheEpochs": 100, "chain.archiveBlobEpochs": 10000, + archiveMode: "full", "chain.nHistoricalStates": true, "chain.nHistoricalStatesFileDataStore": true, "chain.maxBlockStates": 100, From fe6e7851029fa8fb579c61773a1c29a3e25420f8 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 17 Oct 2024 13:27:15 +0200 Subject: [PATCH 5/6] Fix unit tests --- packages/cli/test/unit/options/beaconNodeOptions.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/test/unit/options/beaconNodeOptions.test.ts b/packages/cli/test/unit/options/beaconNodeOptions.test.ts index 230b358e4f16..5f4ee5580665 100644 --- a/packages/cli/test/unit/options/beaconNodeOptions.test.ts +++ b/packages/cli/test/unit/options/beaconNodeOptions.test.ts @@ -149,6 +149,7 @@ describe("options / beaconNodeOptions", () => { minSameMessageSignatureSetsToBatch: 32, maxShufflingCacheEpochs: 100, archiveBlobEpochs: 10000, + archiveMode: ArchiveMode.Full, nHistoricalStates: true, nHistoricalStatesFileDataStore: true, maxBlockStates: 100, From 25293b49d42062da8f6985463d0294cb46c8c234 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 17 Oct 2024 14:21:13 +0200 Subject: [PATCH 6/6] Hide new option --- packages/cli/src/options/beaconNodeOptions/chain.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/cli/src/options/beaconNodeOptions/chain.ts b/packages/cli/src/options/beaconNodeOptions/chain.ts index d575211517c4..3c99e5dd74d2 100644 --- a/packages/cli/src/options/beaconNodeOptions/chain.ts +++ b/packages/cli/src/options/beaconNodeOptions/chain.ts @@ -213,11 +213,10 @@ Will double processing times. Use only for debugging purposes.", }, "chain.archiveMode": { - hidden: false, + hidden: true, choices: Object.values(ArchiveMode), description: "Strategy to manage archive states", default: defaultOptions.chain.archiveMode, - defaultDescription: String(defaultOptions.chain.archiveMode), type: "string", group: "chain", },