From 3c5ecef2707857984480c5eeab1bcd71cd24da16 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 23 Oct 2024 14:04:41 +0200 Subject: [PATCH] Update the state archive to use a defined structure --- .../chain/historicalState/historicalState.ts | 223 +++++++++++------- .../historicalState/historicalStateRegen.ts | 2 +- .../src/chain/historicalState/metrics.ts | 4 +- .../src/chain/historicalState/types.ts | 12 +- .../src/chain/historicalState/utils/diff.ts | 76 +++--- .../utils/hierarchicalLayers.ts | 14 +- .../chain/historicalState/utils/snapshot.ts | 31 +-- .../historicalState/utils/stateArchive.ts | 114 +++++++++ .../chain/historicalState/utils/xdelta3.ts | 14 +- .../src/chain/historicalState/worker.ts | 29 ++- packages/beacon-node/src/chain/index.ts | 2 +- packages/beacon-node/src/index.ts | 1 + .../historicalState/utils/xdelta3.test.ts | 5 +- .../historialState/historicalState.test.ts | 18 +- .../chain/historialState/utils/diff.test.ts | 28 +-- .../historialState/utils/xdelta3.test.ts | 5 +- .../cli/src/cmds/beacon/initBeaconState.ts | 6 + 17 files changed, 382 insertions(+), 202 deletions(-) create mode 100644 packages/beacon-node/src/chain/historicalState/utils/stateArchive.ts diff --git a/packages/beacon-node/src/chain/historicalState/historicalState.ts b/packages/beacon-node/src/chain/historicalState/historicalState.ts index f9d508532597..1df7efbe4bc1 100644 --- a/packages/beacon-node/src/chain/historicalState/historicalState.ts +++ b/packages/beacon-node/src/chain/historicalState/historicalState.ts @@ -1,84 +1,103 @@ import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {Slot} from "@lodestar/types"; import {Logger} from "@lodestar/logger"; -import {BeaconConfig} from "@lodestar/config"; +import {BeaconConfig, ChainForkConfig} from "@lodestar/config"; import {computeEpochAtSlot} from "@lodestar/state-transition"; import {formatBytes} from "@lodestar/utils"; import {IBeaconDb} from "../../db/interface.js"; -import {HistoricalStateRegenMetrics, IBinaryDiffCodec, HistoricalStateSlotType} from "./types.js"; +import {HistoricalStateRegenMetrics, IStateDiffCodec, HistoricalStateStorageType} from "./types.js"; import {replayBlocks} from "./utils/blockReplay.js"; import {HierarchicalLayers} from "./utils/hierarchicalLayers.js"; import {XDelta3Codec} from "./utils/xdelta3.js"; -import {getDiffState} from "./utils/diff.js"; +import {getDiffStateArchive} from "./utils/diff.js"; import {StateArchiveMode} from "../archiver/interface.js"; +import { + computeDiffArchive, + getLastStoredStateArchive, + StateArchiveSSZType, + stateArchiveToStateBytes, + stateBytesToStateArchive, +} from "./utils/stateArchive.js"; -export const codec: IBinaryDiffCodec = new XDelta3Codec(); +export const codec: IStateDiffCodec = new XDelta3Codec(); + +type HStateOperationOptions = { + db: IBeaconDb; + config: BeaconConfig; + logger: Logger; + hierarchicalLayers: HierarchicalLayers; + metrics?: HistoricalStateRegenMetrics; + stateArchiveMode: StateArchiveMode; +}; export async function getHistoricalState( - {slot, archiveMode}: {slot: Slot; archiveMode: StateArchiveMode}, + slot: Slot, { + stateArchiveMode, db, logger, config, metrics, hierarchicalLayers, pubkey2index, - }: { - config: BeaconConfig; - db: IBeaconDb; - pubkey2index: PubkeyIndexMap; - logger: Logger; - hierarchicalLayers: HierarchicalLayers; - metrics?: HistoricalStateRegenMetrics; - } + }: HStateOperationOptions & {pubkey2index: PubkeyIndexMap} ): Promise { const regenTimer = metrics?.regenTime.startTimer(); const epoch = computeEpochAtSlot(slot); - const strategy = hierarchicalLayers.getSlotType(slot, archiveMode); - logger.verbose("Fetching state archive", {strategy, slot, epoch}); + const slotType = hierarchicalLayers.getStorageType(slot, stateArchiveMode); + logger.verbose("Fetching state archive", {slotType, slot, epoch}); - switch (strategy) { - case HistoricalStateSlotType.Full: { + switch (slotType) { + case HistoricalStateStorageType.Full: { const loadStateTimer = metrics?.loadSnapshotStateTime.startTimer(); const state = await db.stateArchive.getBinary(slot); loadStateTimer?.(); - regenTimer?.({strategy: HistoricalStateSlotType.Full}); + regenTimer?.({strategy: HistoricalStateStorageType.Full}); return state; } - case HistoricalStateSlotType.Snapshot: { + case HistoricalStateStorageType.Snapshot: { const loadStateTimer = metrics?.loadSnapshotStateTime.startTimer(); - const state = await db.stateArchive.getBinary(slot); + const stateArchive = await db.stateArchive.getBinary(slot); + + const state = stateArchive + ? stateArchiveToStateBytes(StateArchiveSSZType.deserialize(stateArchive), config) + : null; + loadStateTimer?.(); - regenTimer?.({strategy: HistoricalStateSlotType.Snapshot}); + regenTimer?.({strategy: HistoricalStateStorageType.Snapshot}); return state; } - case HistoricalStateSlotType.Diff: { - const {diffStateBytes: diffState} = await getDiffState( + case HistoricalStateStorageType.Diff: { + const {stateArchive} = await getDiffStateArchive( {slot, skipSlotDiff: false}, {db, metrics, logger, hierarchicalLayers: hierarchicalLayers, codec} ); - regenTimer?.({strategy: HistoricalStateSlotType.Diff}); + regenTimer?.({strategy: HistoricalStateStorageType.Diff}); - return diffState; + return stateArchive ? stateArchiveToStateBytes(stateArchive, config) : null; } - case HistoricalStateSlotType.BlockReplay: { - const {diffStateBytes, diffSlots} = await getDiffState( + case HistoricalStateStorageType.BlockReplay: { + const {stateArchive, diffSlots} = await getDiffStateArchive( {slot, skipSlotDiff: false}, {db, metrics, logger, hierarchicalLayers: hierarchicalLayers, codec} ); - if (!diffStateBytes) { - regenTimer?.({strategy: HistoricalStateSlotType.BlockReplay}); + if (!stateArchive) { + regenTimer?.({strategy: HistoricalStateStorageType.BlockReplay}); return null; } const state = replayBlocks( - {toSlot: slot, lastFullSlot: diffSlots[diffSlots.length - 1], lastFullStateBytes: diffStateBytes}, + { + toSlot: slot, + lastFullSlot: diffSlots[diffSlots.length - 1], + lastFullStateBytes: stateArchiveToStateBytes(stateArchive, config), + }, {config, db, metrics, pubkey2index} ); - regenTimer?.({strategy: HistoricalStateSlotType.BlockReplay}); + regenTimer?.({strategy: HistoricalStateStorageType.BlockReplay}); return state; } @@ -86,25 +105,16 @@ export async function getHistoricalState( } export async function putHistoricalState( - {slot, archiveMode, stateBytes}: {slot: Slot; archiveMode: StateArchiveMode; stateBytes: Uint8Array}, - { - db, - logger, - metrics, - hierarchicalLayers, - }: { - db: IBeaconDb; - logger: Logger; - metrics?: HistoricalStateRegenMetrics; - hierarchicalLayers: HierarchicalLayers; - } + slot: Slot, + stateBytes: Uint8Array, + {db, logger, metrics, hierarchicalLayers, stateArchiveMode, config}: HStateOperationOptions ): Promise { const epoch = computeEpochAtSlot(slot); - const strategy = hierarchicalLayers.getSlotType(slot, archiveMode); - logger.info("Archiving historical state", {epoch, slot, strategy}); + const storageType = hierarchicalLayers.getStorageType(slot, stateArchiveMode); + logger.info("Archiving historical state", {epoch, slot, slotType: storageType}); - switch (strategy) { - case HistoricalStateSlotType.Full: { + switch (storageType) { + case HistoricalStateStorageType.Full: { metrics?.stateSnapshotSize.set(stateBytes.byteLength); await db.stateArchive.putBinary(slot, stateBytes); logger.verbose("State stored as full", { @@ -115,38 +125,40 @@ export async function putHistoricalState( break; } - case HistoricalStateSlotType.Snapshot: { + case HistoricalStateStorageType.Snapshot: { + const stateArchiveBytes = StateArchiveSSZType.serialize(stateBytesToStateArchive(stateBytes, config)); + await db.stateArchive.putBinary(slot, stateArchiveBytes); + metrics?.stateSnapshotSize.set(stateBytes.byteLength); - await db.stateArchive.putBinary(slot, stateBytes); logger.verbose("State stored as snapshot", { epoch, slot, - snapshotSize: formatBytes(stateBytes.byteLength), + snapshotSize: formatBytes(stateArchiveBytes.byteLength), }); break; } - case HistoricalStateSlotType.Diff: { - const {diffStateBytes: diffState} = await getDiffState( + case HistoricalStateStorageType.Diff: { + const {stateArchive: diffStateArchive} = await getDiffStateArchive( {slot, skipSlotDiff: true}, - {db, metrics, logger, hierarchicalLayers: hierarchicalLayers, codec} + {db, metrics, logger, hierarchicalLayers, codec} ); - if (!diffState) return; + if (!diffStateArchive) return; - const diff = codec.compute(diffState, stateBytes); - await db.stateArchive.putBinary(slot, diff); - - metrics?.stateDiffSize.set(diff.byteLength); + const diffArchiveBytes = StateArchiveSSZType.serialize( + computeDiffArchive(diffStateArchive, stateBytesToStateArchive(stateBytes, config), codec) + ); + await db.stateArchive.putBinary(slot, diffArchiveBytes); + metrics?.stateDiffSize.set(diffArchiveBytes.byteLength); logger.verbose("State stored as diff", { epoch, slot, - baseSize: formatBytes(diffState.byteLength), - diffSize: formatBytes(diff.byteLength), + diffSize: formatBytes(diffArchiveBytes.byteLength), }); break; } - case HistoricalStateSlotType.BlockReplay: { + case HistoricalStateStorageType.BlockReplay: { logger.verbose("Skipping storage of historical state for block replay", { epoch, slot, @@ -163,53 +175,106 @@ export async function getLastStoredState({ metrics, logger, archiveMode, + forkConfig, }: { db: IBeaconDb; hierarchicalLayers: HierarchicalLayers; archiveMode: StateArchiveMode; + forkConfig: ChainForkConfig; metrics?: HistoricalStateRegenMetrics; logger?: Logger; }): Promise<{stateBytes: Uint8Array | null; slot: Slot | null}> { - const lastStoredDiffSlot = await db.stateArchive.lastKey(); - const lastStoredSnapshotSlot = await db.stateArchive.lastKey(); + if (archiveMode === StateArchiveMode.Frequency) { + const lastStoredSlot = await db.stateArchive.lastKey(); + return {stateBytes: lastStoredSlot ? await db.stateArchive.getBinary(lastStoredSlot) : null, slot: lastStoredSlot}; + } - logger?.info("Last archived state slots", {snapshot: lastStoredSnapshotSlot, diff: lastStoredDiffSlot}); + const lastStoredDiffArchive = await getLastStoredStateArchive({db, snapshot: false}); + const lastStoredSnapshotArchive = await getLastStoredStateArchive({db, snapshot: true}); - if (lastStoredDiffSlot === null && lastStoredSnapshotSlot === null) { + if (!lastStoredDiffArchive && !lastStoredSnapshotArchive) { logger?.verbose("State archive db is empty"); return {stateBytes: null, slot: null}; } - const lastStoredSlot = Math.max(lastStoredDiffSlot ?? 0, lastStoredSnapshotSlot ?? 0); - const strategy = hierarchicalLayers.getSlotType(lastStoredSlot, archiveMode); - logger?.verbose("Loading the last archived state", {strategy, slot: lastStoredSlot}); + if (!lastStoredSnapshotArchive) { + logger?.verbose("State archive db does not contain any snapshot state"); + // TODO: Need to clean the stateArchive db + return {stateBytes: null, slot: null}; + } + + logger?.info("Last archived state slots", { + snapshot: lastStoredSnapshotArchive?.slot, + diff: lastStoredDiffArchive?.slot, + }); - switch (strategy) { - case HistoricalStateSlotType.Full: - return {stateBytes: await db.stateArchive.getBinary(lastStoredSlot), slot: lastStoredSlot}; - case HistoricalStateSlotType.Snapshot: + const lastStoredSlot = Math.max(lastStoredDiffArchive?.slot ?? 0, lastStoredSnapshotArchive.slot ?? 0); + const storageType = hierarchicalLayers.getStorageType(lastStoredSlot, archiveMode); + logger?.verbose("Loading the last archived state", {storageType, slot: lastStoredSlot}); + + switch (storageType) { + case HistoricalStateStorageType.Full: { return {stateBytes: await db.stateArchive.getBinary(lastStoredSlot), slot: lastStoredSlot}; - case HistoricalStateSlotType.Diff: { - if (lastStoredSlot === lastStoredSnapshotSlot) { + } + case HistoricalStateStorageType.Snapshot: { + const stateArchive = await db.stateArchive.getBinary(lastStoredSlot); + + return { + stateBytes: stateArchive + ? stateArchiveToStateBytes(StateArchiveSSZType.deserialize(stateArchive), forkConfig) + : null, + slot: lastStoredSlot, + }; + } + case HistoricalStateStorageType.Diff: { + if (lastStoredSlot === lastStoredSnapshotArchive.slot) { logger?.warn("Last archived snapshot is not at expected epoch boundary, possibly because of checkpoint sync."); return {stateBytes: await db.stateArchive.getBinary(lastStoredSlot), slot: lastStoredSlot}; } - const {diffStateBytes} = await getDiffState( + const diffStateArchive = await getDiffStateArchive( {slot: lastStoredSlot, skipSlotDiff: false}, {db, metrics, logger, hierarchicalLayers: hierarchicalLayers, codec} ); + if (!diffStateArchive.stateArchive) throw new Error("Can not compute the last stored state"); + return { - stateBytes: diffStateBytes, + stateBytes: stateArchiveToStateBytes(diffStateArchive.stateArchive, forkConfig), slot: lastStoredSlot, }; } - case HistoricalStateSlotType.BlockReplay: - if (lastStoredSlot === lastStoredSnapshotSlot) { + case HistoricalStateStorageType.BlockReplay: + if (lastStoredSlot === lastStoredSnapshotArchive.slot) { logger?.warn("Last archived snapshot is not at expected epoch boundary, possibly because of checkpoint sync."); - return {stateBytes: await db.stateArchive.getBinary(lastStoredSlot), slot: lastStoredSlot}; + return {stateBytes: stateArchiveToStateBytes(lastStoredSnapshotArchive, forkConfig), slot: lastStoredSlot}; } + throw new Error(`Unexpected stored slot for a non epoch slot=${lastStoredSlot}`); } } + +export async function migrateStateArchive({ + db, + archiveMode, + logger, +}: {db: IBeaconDb; archiveMode: StateArchiveMode; logger?: Logger}): Promise { + if (archiveMode === StateArchiveMode.Differential) { + const lastStoredSlot = await db.stateArchive.lastKey(); + if (!lastStoredSlot) return; + + const archiveBytes = await db.stateArchive.getBinary(lastStoredSlot); + if (!archiveBytes) return; + + try { + StateArchiveSSZType.deserialize(archiveBytes); + } catch { + logger?.info("Found that stateArchiveMode was switch recently. Cleaning up state archives to store new format."); + for await (const slot of db.stateArchive.keysStream()) { + await db.stateArchive.delete(slot); + } + } + } + + return; +} diff --git a/packages/beacon-node/src/chain/historicalState/historicalStateRegen.ts b/packages/beacon-node/src/chain/historicalState/historicalStateRegen.ts index 9f88106746e9..1956ef42567d 100644 --- a/packages/beacon-node/src/chain/historicalState/historicalStateRegen.ts +++ b/packages/beacon-node/src/chain/historicalState/historicalStateRegen.ts @@ -74,6 +74,6 @@ export class HistoricalStateRegen implements IHistoricalStateRegen { } async storeHistoricalState(slot: number, stateBytes: Uint8Array): Promise { - return this.api.storeHistoricalState(slot, this.stateArchiveMode, stateBytes); + return this.api.storeHistoricalState(slot, stateBytes, this.stateArchiveMode); } } diff --git a/packages/beacon-node/src/chain/historicalState/metrics.ts b/packages/beacon-node/src/chain/historicalState/metrics.ts index 2c46a62b9603..2eaf4f04f6b4 100644 --- a/packages/beacon-node/src/chain/historicalState/metrics.ts +++ b/packages/beacon-node/src/chain/historicalState/metrics.ts @@ -1,6 +1,6 @@ import {EpochTransitionStep, StateCloneSource, StateHashTreeRootSource} from "@lodestar/state-transition"; import {RegistryMetricCreator} from "../../metrics/index.js"; -import {HistoricalStateRegenMetrics, RegenErrorType, HistoricalStateSlotType} from "./types.js"; +import {HistoricalStateRegenMetrics, RegenErrorType, HistoricalStateStorageType} from "./types.js"; export function getMetrics(metricsRegister: RegistryMetricCreator): HistoricalStateRegenMetrics { return { @@ -88,7 +88,7 @@ export function getMetrics(metricsRegister: RegistryMetricCreator): HistoricalSt registerValidatorStatuses: () => {}, // historical state regen metrics - regenTime: metricsRegister.histogram<{strategy: HistoricalStateSlotType}>({ + regenTime: metricsRegister.histogram<{strategy: HistoricalStateStorageType}>({ name: "lodestar_historical_state_regen_time_seconds", help: "Time to regenerate a historical state in seconds", // Historical state regen can take up to 3h as of Aug 2024 diff --git a/packages/beacon-node/src/chain/historicalState/types.ts b/packages/beacon-node/src/chain/historicalState/types.ts index b83150e361a6..5ebe046123a8 100644 --- a/packages/beacon-node/src/chain/historicalState/types.ts +++ b/packages/beacon-node/src/chain/historicalState/types.ts @@ -27,8 +27,8 @@ export interface IHistoricalStateRegen { export type HistoricalStateWorkerApi = { close(): Promise; scrapeMetrics(): Promise; - getHistoricalState(slot: number, archiveMode: StateArchiveMode): Promise; - storeHistoricalState(slot: number, archiveMode: StateArchiveMode, stateBytes: Uint8Array): Promise; + getHistoricalState(slot: number, stateArchiveMode: StateArchiveMode): Promise; + storeHistoricalState(slot: number, stateBytes: Uint8Array, stateArchiveMode: StateArchiveMode): Promise; }; export type HistoricalStateRegenModules = HistoricalStateRegenInitModules & { @@ -54,7 +54,7 @@ export enum RegenErrorType { } export type HistoricalStateRegenMetrics = BeaconStateTransitionMetrics & { - regenTime: Histogram<{strategy: HistoricalStateSlotType}>; + regenTime: Histogram<{strategy: HistoricalStateStorageType}>; loadSnapshotStateTime: Histogram; loadDiffStateTime: Histogram; stateTransitionTime: Histogram; @@ -67,14 +67,12 @@ export type HistoricalStateRegenMetrics = BeaconStateTransitionMetrics & { stateSnapshotSize: Gauge; }; -export interface IBinaryDiffCodec { - init(): Promise; - initialized: boolean; +export interface IStateDiffCodec { compute(base: Uint8Array, changed: Uint8Array): Uint8Array; apply(base: Uint8Array, delta: Uint8Array): Uint8Array; } -export enum HistoricalStateSlotType { +export enum HistoricalStateStorageType { // Used to refer to full archive in `StateArchiveMode.Frequency` Full = "full", // Refer to the snapshot for differential backup diff --git a/packages/beacon-node/src/chain/historicalState/utils/diff.ts b/packages/beacon-node/src/chain/historicalState/utils/diff.ts index a05a1a836a0c..42616ae92631 100644 --- a/packages/beacon-node/src/chain/historicalState/utils/diff.ts +++ b/packages/beacon-node/src/chain/historicalState/utils/diff.ts @@ -2,34 +2,31 @@ import {Slot} from "@lodestar/types"; import {Logger} from "@lodestar/logger"; import {computeEpochAtSlot} from "@lodestar/state-transition"; import {formatBytes} from "@lodestar/utils"; -import {HistoricalStateRegenMetrics, IBinaryDiffCodec, RegenErrorType} from "../types.js"; +import {HistoricalStateRegenMetrics, IStateDiffCodec, RegenErrorType} from "../types.js"; import {IBeaconDb} from "../../../db/interface.js"; import {HierarchicalLayers} from "./hierarchicalLayers.js"; -import {getSnapshotStateWithFallback} from "./snapshot.js"; +import {getSnapshotStateArchiveWithFallback} from "./snapshot.js"; +import {applyDiffArchive, StateArchive, StateArchiveSSZType} from "./stateArchive.js"; export async function replayStateDiffs( - {diffs, snapshotStateBytes}: {diffs: {slot: Slot; diff: Uint8Array}[]; snapshotStateBytes: Uint8Array}, - {codec, logger}: {codec: IBinaryDiffCodec; logger?: Logger} -): Promise { - if (!codec.initialized) { - logger?.verbose("Initializing the binary diff codec."); - await codec.init(); - } + {diffArchives, snapshotArchive}: {diffArchives: StateArchive[]; snapshotArchive: StateArchive}, + {codec, logger}: {codec: IStateDiffCodec; logger?: Logger} +): Promise { + let activeStateArchive: StateArchive = snapshotArchive; - let activeStateBytes: Uint8Array = snapshotStateBytes; - for (const intermediateStateDiff of diffs) { + for (const intermediateStateArchive of diffArchives) { logger?.verbose("Applying state diff", { - slot: intermediateStateDiff.slot, - activeStateSize: formatBytes(activeStateBytes.byteLength), - diffSize: formatBytes(intermediateStateDiff.diff.byteLength), + slot: intermediateStateArchive.slot, + activeStateSize: formatBytes(StateArchiveSSZType.serialize(activeStateArchive).byteLength), + diffSize: formatBytes(StateArchiveSSZType.serialize(intermediateStateArchive).byteLength), }); - activeStateBytes = codec.apply(activeStateBytes, intermediateStateDiff.diff); + activeStateArchive = applyDiffArchive(activeStateArchive, intermediateStateArchive, codec); } - return activeStateBytes; + return activeStateArchive; } -export async function getDiffState( +export async function getDiffStateArchive( {slot, skipSlotDiff}: {slot: Slot; skipSlotDiff: boolean}, { db, @@ -42,9 +39,9 @@ export async function getDiffState( metrics?: HistoricalStateRegenMetrics; logger?: Logger; hierarchicalLayers: HierarchicalLayers; - codec: IBinaryDiffCodec; + codec: IStateDiffCodec; } -): Promise<{diffStateBytes: Uint8Array | null; diffSlots: Slot[]}> { +): Promise<{stateArchive: StateArchive | null; diffSlots: Slot[]}> { const epoch = computeEpochAtSlot(slot); const diffSlots = hierarchicalLayers.getArchiveLayers(slot); const processableDiffs = [...diffSlots]; @@ -59,39 +56,42 @@ export async function getDiffState( if (snapshotSlot === undefined) { logger?.error("Missing the snapshot state", {snapshotSlot}); metrics?.regenErrorCount.inc({reason: RegenErrorType.loadState}); - return {diffSlots, diffStateBytes: null}; + return {diffSlots, stateArchive: null}; } - const snapshot = await getSnapshotStateWithFallback(snapshotSlot, db); - if (!snapshot.stateBytes) { + const snapshotArchive = await getSnapshotStateArchiveWithFallback({ + slot: snapshotSlot, + db, + fallbackTillSlot: hierarchicalLayers.getPreviousSlotForLayer(snapshotSlot, 0), + }); + + if (!snapshotArchive) { logger?.error("Missing the snapshot state", {snapshotSlot}); metrics?.regenErrorCount.inc({reason: RegenErrorType.loadState}); - return {diffStateBytes: null, diffSlots}; + return {diffSlots, stateArchive: null}; } - if (snapshot.slot !== snapshotSlot) { + if (snapshotArchive.slot !== snapshotSlot) { // Possibly because of checkpoint sync logger?.warn("Last archived snapshot is not at expected slot", { expectedSnapshotSlot: snapshotSlot, - availableSnapshotSlot: snapshot.slot, + availableSnapshotSlot: snapshotArchive.slot, }); - snapshotSlot = snapshot.slot; + snapshotSlot = snapshotArchive.slot; } // Get all diffs except the first one which was a snapshot layer - const diffs = await Promise.all( + const diffArchives = await Promise.all( processableDiffs.map((s) => { const loadStateTimer = metrics?.loadDiffStateTime.startTimer(); return db.stateArchive.getBinary(s).then((diff) => { loadStateTimer?.(); - return {slot: s, diff}; + return diff ? StateArchiveSSZType.deserialize(diff) : null; }); }) ); - const nonEmptyDiffs = diffs.filter((d) => d.diff !== undefined && d.diff !== null) as { - slot: number; - diff: Uint8Array; - }[]; + + const nonEmptyDiffs = diffArchives.filter(Boolean) as StateArchive[]; if (nonEmptyDiffs.length < processableDiffs.length) { logger?.warn("Missing some diff states", { @@ -112,16 +112,14 @@ export async function getDiffState( diffPath: diffSlots.join(","), availableDiffs: nonEmptyDiffs.map((d) => d.slot).join(","), }); - const diffState = await replayStateDiffs( - {diffs: nonEmptyDiffs, snapshotStateBytes: snapshot.stateBytes}, - {codec, logger} - ); - if (diffState.byteLength === 0) { + const diffState = await replayStateDiffs({diffArchives: nonEmptyDiffs, snapshotArchive}, {codec, logger}); + + if (diffState.partialState.byteLength === 0 || diffState.balances.byteLength === 0) { throw new Error("Some error during applying diffs"); } - return {diffSlots, diffStateBytes: diffState}; + return {diffSlots, stateArchive: diffState}; } catch (err) { logger?.error( "Can not compute the diff state", @@ -129,6 +127,6 @@ export async function getDiffState( err as Error ); metrics?.regenErrorCount.inc({reason: RegenErrorType.loadState}); - return {diffSlots, diffStateBytes: null}; + return {diffSlots, stateArchive: null}; } } diff --git a/packages/beacon-node/src/chain/historicalState/utils/hierarchicalLayers.ts b/packages/beacon-node/src/chain/historicalState/utils/hierarchicalLayers.ts index 855f30263b7b..9acd4e42ae8b 100644 --- a/packages/beacon-node/src/chain/historicalState/utils/hierarchicalLayers.ts +++ b/packages/beacon-node/src/chain/historicalState/utils/hierarchicalLayers.ts @@ -1,6 +1,6 @@ import {Epoch, Slot} from "@lodestar/types"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; -import {HistoricalStateSlotType} from "../types.js"; +import {HistoricalStateStorageType} from "../types.js"; import {StateArchiveMode} from "../../archiver/interface.js"; /* @@ -64,17 +64,17 @@ export class HierarchicalLayers { return this.diffEverySlot.length + 1; } - getSlotType(slot: Slot, archiveMode: StateArchiveMode): HistoricalStateSlotType { - if (archiveMode === StateArchiveMode.Frequency) return HistoricalStateSlotType.Full; + getStorageType(slot: Slot, stateArchiveMode: StateArchiveMode): HistoricalStateStorageType { + if (stateArchiveMode === StateArchiveMode.Frequency) return HistoricalStateStorageType.Full; if (slot === 0) { - return HistoricalStateSlotType.Snapshot; + return HistoricalStateStorageType.Snapshot; } - if (slot % this.snapshotEverySlot === 0) return HistoricalStateSlotType.Full; - if (this.diffEverySlot.some((s) => slot % s === 0)) return HistoricalStateSlotType.Diff; + if (slot % this.snapshotEverySlot === 0) return HistoricalStateStorageType.Snapshot; + if (this.diffEverySlot.some((s) => slot % s === 0)) return HistoricalStateStorageType.Diff; - return HistoricalStateSlotType.BlockReplay; + return HistoricalStateStorageType.BlockReplay; } getArchiveLayers(slot: Slot): Slot[] { diff --git a/packages/beacon-node/src/chain/historicalState/utils/snapshot.ts b/packages/beacon-node/src/chain/historicalState/utils/snapshot.ts index 2d109f733019..eb780655a602 100644 --- a/packages/beacon-node/src/chain/historicalState/utils/snapshot.ts +++ b/packages/beacon-node/src/chain/historicalState/utils/snapshot.ts @@ -1,21 +1,22 @@ import {Slot} from "@lodestar/types"; import {IBeaconDb} from "../../../db/index.js"; +import {StateArchive, StateArchiveSSZType} from "./stateArchive.js"; -export async function getSnapshotStateWithFallback( - slot: Slot, - db: IBeaconDb -): Promise<{stateBytes: Uint8Array | null; slot: Slot}> { - const state = await db.stateArchive.getBinary(slot); - if (state) return {slot, stateBytes: state}; +export async function getSnapshotStateArchiveWithFallback({ + slot, + fallbackTillSlot, + db, +}: {slot: Slot; fallbackTillSlot: Slot; db: IBeaconDb}): Promise { + const stateArchiveBytes = await db.stateArchive.getBinary(slot); + if (stateArchiveBytes) { + const stateArchive = StateArchiveSSZType.deserialize(stateArchiveBytes); + if (stateArchive.snapshot) return stateArchive; + } - // There is a possibility that node is started with checkpoint and initial snapshot - // is not persisted on expected slot - const lastSnapshotSlot = await db.stateArchive.lastKey(); - if (lastSnapshotSlot !== null) - return { - slot: lastSnapshotSlot, - stateBytes: await db.stateArchive.getBinary(lastSnapshotSlot), - }; + for await (const archiveBytes of db.stateArchive.valuesStream({lt: slot, gte: fallbackTillSlot})) { + const stateArchive = StateArchiveSSZType.deserialize(archiveBytes); + if (stateArchive.snapshot) return stateArchive; + } - return {stateBytes: null, slot}; + return null; } diff --git a/packages/beacon-node/src/chain/historicalState/utils/stateArchive.ts b/packages/beacon-node/src/chain/historicalState/utils/stateArchive.ts new file mode 100644 index 000000000000..d07173b36ddb --- /dev/null +++ b/packages/beacon-node/src/chain/historicalState/utils/stateArchive.ts @@ -0,0 +1,114 @@ +import {ByteListType, ContainerType, ListUintNum64Type, BooleanType, ValueOf, UintNumberType} from "@chainsafe/ssz"; +import {ChainForkConfig} from "@lodestar/config"; +import {VALIDATOR_REGISTRY_LIMIT} from "@lodestar/params"; +import {BeaconState, Slot} from "@lodestar/types"; +import {IStateDiffCodec} from "../types.js"; +import {getStateSlotFromBytes} from "../../../util/multifork.js"; +import {IBeaconDb} from "../../../db/interface.js"; + +export const BalancesSSZType = new ListUintNum64Type(VALIDATOR_REGISTRY_LIMIT); + +export type Balances = ValueOf; + +export const StateArchiveSSZType = new ContainerType({ + snapshot: new BooleanType(), + slot: new UintNumberType(8), + partialState: new ByteListType(99_999_9000), + balances: new ByteListType(99_999_9000), +}); + +export type StateArchive = ValueOf; + +export function stateToStateArchive(state: BeaconState, forkConfig: ChainForkConfig): StateArchive { + const partialState = forkConfig.getForkTypes(state.slot).BeaconState.clone(state); + + const balances = BalancesSSZType.clone(partialState.balances); + partialState.balances = []; + + return { + snapshot: true, + slot: state.slot, + partialState: forkConfig.getForkTypes(state.slot).BeaconState.serialize(partialState), + balances: BalancesSSZType.serialize(balances), + }; +} + +export function stateBytesToStateArchive(stateBytes: Uint8Array, forkConfig: ChainForkConfig): StateArchive { + const slot = getStateSlotFromBytes(stateBytes); + return stateToStateArchive(forkConfig.getForkTypes(slot).BeaconState.deserialize(stateBytes), forkConfig); +} + +export function stateArchiveToState(stateArchive: StateArchive, forkConfig: ChainForkConfig): BeaconState { + if (!stateArchive.snapshot) throw new Error("Can not convert a diff state archive to full state"); + + const partialState = forkConfig.getForkTypes(stateArchive.slot).BeaconState.deserialize(stateArchive.partialState); + const balances = BalancesSSZType.deserialize(stateArchive.balances); + partialState.balances = [...balances]; + + return partialState; +} + +export function stateArchiveToStateBytes(stateArchive: StateArchive, forkConfig: ChainForkConfig): Uint8Array { + const state = stateArchiveToState(stateArchive, forkConfig); + return forkConfig.getForkTypes(state.slot).BeaconState.serialize(state); +} + +export function computeDiffArchive(base: StateArchive, updated: StateArchive, codec: IStateDiffCodec): StateArchive { + if (!base.snapshot) { + throw new Error("A snapshot state is required to compute binary diff"); + } + + const stateDiff = codec.compute(base.partialState, updated.partialState); + const balancesDiff = codec.compute(base.balances, updated.balances); + + return { + snapshot: false, + slot: updated.slot, + partialState: stateDiff, + balances: balancesDiff, + }; +} + +export function applyDiffArchive(base: StateArchive, updated: StateArchive, codec: IStateDiffCodec): StateArchive { + if (!base.snapshot) { + throw new Error("A snapshot state is required to compute binary diff"); + } + + if (updated.snapshot) { + throw new Error("A diff state is required to apply binary difference"); + } + + const partialState = codec.apply(base.partialState, updated.partialState); + const balances = codec.compute(base.balances, updated.balances); + + return { + snapshot: true, + slot: updated.slot, + partialState, + balances, + }; +} + +const DEFAULT_MAX_SEARCH_FALLBACK = 10; + +export async function getLastStoredStateArchive({ + db, + maxFallback, + snapshot, +}: {db: IBeaconDb; maxFallback?: number; snapshot: boolean}): Promise { + const lastStoredSlot = await db.stateArchive.lastKey(); + const maxFallbackCount = maxFallback ?? DEFAULT_MAX_SEARCH_FALLBACK; + let tries = 0; + + if (!lastStoredSlot) return null; + + for await (const archiveBytes of db.stateArchive.valuesStream({lte: lastStoredSlot})) { + const stateArchive = StateArchiveSSZType.deserialize(archiveBytes); + if (stateArchive.snapshot === snapshot) return stateArchive; + + if (tries === maxFallbackCount) return null; + tries += 1; + } + + return null; +} diff --git a/packages/beacon-node/src/chain/historicalState/utils/xdelta3.ts b/packages/beacon-node/src/chain/historicalState/utils/xdelta3.ts index d95d56c2266c..525afa2c3a2f 100644 --- a/packages/beacon-node/src/chain/historicalState/utils/xdelta3.ts +++ b/packages/beacon-node/src/chain/historicalState/utils/xdelta3.ts @@ -1,17 +1,7 @@ import {encodeSync, decodeSync} from "@chainsafe/xdelta3-node"; -import {IBinaryDiffCodec} from "../types.js"; - -export class XDelta3Codec implements IBinaryDiffCodec { - private isInitialized: boolean = false; - - async init(): Promise { - this.isInitialized = true; - } - - get initialized(): boolean { - return this.isInitialized; - } +import {IStateDiffCodec} from "../types.js"; +export class XDelta3Codec implements IStateDiffCodec { compute(base: Uint8Array, changed: Uint8Array): Uint8Array { try { return encodeSync(base, changed); diff --git a/packages/beacon-node/src/chain/historicalState/worker.ts b/packages/beacon-node/src/chain/historicalState/worker.ts index 373fede1fdab..44dfb2a88758 100644 --- a/packages/beacon-node/src/chain/historicalState/worker.ts +++ b/packages/beacon-node/src/chain/historicalState/worker.ts @@ -83,14 +83,19 @@ const api: HistoricalStateWorkerApi = { async scrapeMetrics() { return metricsRegister?.metrics() ?? ""; }, - async getHistoricalState(slot, archiveMode) { + async getHistoricalState(slot, stateArchiveMode) { historicalStateRegenMetrics?.regenRequestCount.inc(); const stateBytes = await queue.push(() => - getHistoricalState( - {slot, archiveMode}, - {config, db, pubkey2index, logger, hierarchicalLayers: hierarchicalLayers, metrics: historicalStateRegenMetrics} - ) + getHistoricalState(slot, { + config, + stateArchiveMode, + db, + pubkey2index, + logger, + hierarchicalLayers, + metrics: historicalStateRegenMetrics, + }) ); if (stateBytes) { @@ -102,11 +107,15 @@ const api: HistoricalStateWorkerApi = { return null; }, - async storeHistoricalState(slot, archiveMode, stateBytes) { - return putHistoricalState( - {slot, archiveMode, stateBytes}, - {db, logger, hierarchicalLayers: hierarchicalLayers, metrics: historicalStateRegenMetrics} - ); + async storeHistoricalState(slot, stateBytes, stateArchiveMode) { + return putHistoricalState(slot, stateBytes, { + db, + config, + logger, + stateArchiveMode, + hierarchicalLayers, + metrics: historicalStateRegenMetrics, + }); }, }; diff --git a/packages/beacon-node/src/chain/index.ts b/packages/beacon-node/src/chain/index.ts index 6fdc476d41df..2cfce5c26e75 100644 --- a/packages/beacon-node/src/chain/index.ts +++ b/packages/beacon-node/src/chain/index.ts @@ -6,5 +6,5 @@ export * from "./initState.js"; export * from "./stateCache/index.js"; // To initialize the state from outside beacon-node package -export {getLastStoredState} from "./historicalState/historicalState.js"; +export {getLastStoredState, migrateStateArchive} from "./historicalState/historicalState.js"; export {HierarchicalLayers} from "./historicalState/utils/hierarchicalLayers.js"; diff --git a/packages/beacon-node/src/index.ts b/packages/beacon-node/src/index.ts index 6bc1a1e5978b..7045bfaca0d2 100644 --- a/packages/beacon-node/src/index.ts +++ b/packages/beacon-node/src/index.ts @@ -3,6 +3,7 @@ export { initStateFromEth1, HierarchicalLayers, getLastStoredState, + migrateStateArchive, } from "./chain/index.js"; export {BeaconDb, type IBeaconDb} from "./db/index.js"; export {Eth1Provider, type IEth1Provider} from "./eth1/index.js"; diff --git a/packages/beacon-node/test/perf/historicalState/utils/xdelta3.test.ts b/packages/beacon-node/test/perf/historicalState/utils/xdelta3.test.ts index be7f4ab7e0fd..192cd0b1503e 100644 --- a/packages/beacon-node/test/perf/historicalState/utils/xdelta3.test.ts +++ b/packages/beacon-node/test/perf/historicalState/utils/xdelta3.test.ts @@ -2,19 +2,18 @@ import fs from "node:fs"; import path from "node:path"; import {itBench} from "@dapplion/benchmark"; import {XDelta3Codec} from "../../../../src/chain/historicalState/utils/xdelta3.js"; -import {IBinaryDiffCodec} from "../../../../src/chain/historicalState/index.js"; +import {IStateDiffCodec} from "../../../../src/chain/historicalState/index.js"; describe("BinaryDiffVCDiffCodec", () => { let originalState: Uint8Array; let changedState: Uint8Array; - let codec: IBinaryDiffCodec; + let codec: IStateDiffCodec; let diff: Uint8Array; before(async function () { this.timeout(2 * 60 * 1000); // Generating the states for the first time is very slow codec = new XDelta3Codec(); - await codec.init(); originalState = Buffer.from( fs.readFileSync(path.join(import.meta.dirname, "../../../../fixtures/binaryDiff/source.txt"), "utf8"), diff --git a/packages/beacon-node/test/unit/chain/historialState/historicalState.test.ts b/packages/beacon-node/test/unit/chain/historialState/historicalState.test.ts index 02138583d350..d08a02a89afb 100644 --- a/packages/beacon-node/test/unit/chain/historialState/historicalState.test.ts +++ b/packages/beacon-node/test/unit/chain/historialState/historicalState.test.ts @@ -1,7 +1,7 @@ import {describe, it, expect, beforeEach} from "vitest"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {HierarchicalLayers} from "../../../../src/chain/historicalState/utils/hierarchicalLayers.js"; -import {HistoricalStateSlotType} from "../../../../src/chain/historicalState/types.js"; +import {HistoricalStateStorageType} from "../../../../src/chain/historicalState/types.js"; import {StateArchiveMode} from "../../../../src/chain/archiver/index.js"; const layer0 = 5; @@ -23,16 +23,16 @@ describe("HierarchicalLayers", () => { describe("getArchiveStrategy", () => { it("should return snapshot strategy for slot 0", () => { - expect(hierarchicalLayers.getSlotType(0, StateArchiveMode.Differential)).toEqual( - HistoricalStateSlotType.Snapshot + expect(hierarchicalLayers.getStorageType(0, StateArchiveMode.Differential)).toEqual( + HistoricalStateStorageType.Snapshot ); }); it.each([0, layer0 * SLOTS_PER_EPOCH, layer0 * SLOTS_PER_EPOCH * 2, layer0 * SLOTS_PER_EPOCH * 3])( "should return snapshot strategy for slot %i", (slot) => { - expect(hierarchicalLayers.getSlotType(slot, StateArchiveMode.Frequency)).toEqual( - HistoricalStateSlotType.Snapshot + expect(hierarchicalLayers.getStorageType(slot, StateArchiveMode.Frequency)).toEqual( + HistoricalStateStorageType.Snapshot ); } ); @@ -47,7 +47,9 @@ describe("HierarchicalLayers", () => { [layer3 * SLOTS_PER_EPOCH, layer3 * SLOTS_PER_EPOCH * 3], ].flat() )("should return diff strategy for slot %i", (slot) => { - expect(hierarchicalLayers.getSlotType(slot, StateArchiveMode.Differential)).toEqual(HistoricalStateSlotType.Diff); + expect(hierarchicalLayers.getStorageType(slot, StateArchiveMode.Differential)).toEqual( + HistoricalStateStorageType.Diff + ); }); it.each( @@ -60,8 +62,8 @@ describe("HierarchicalLayers", () => { [layer3 * SLOTS_PER_EPOCH + 1, layer3 * SLOTS_PER_EPOCH * 3 + 3], ].flat() )("should return block replay strategy for slot %i", (slot) => { - expect(hierarchicalLayers.getSlotType(slot, StateArchiveMode.Differential)).toEqual( - HistoricalStateSlotType.BlockReplay + expect(hierarchicalLayers.getStorageType(slot, StateArchiveMode.Differential)).toEqual( + HistoricalStateStorageType.BlockReplay ); }); }); diff --git a/packages/beacon-node/test/unit/chain/historialState/utils/diff.test.ts b/packages/beacon-node/test/unit/chain/historialState/utils/diff.test.ts index 5b354b3accaf..a6cafc028487 100644 --- a/packages/beacon-node/test/unit/chain/historialState/utils/diff.test.ts +++ b/packages/beacon-node/test/unit/chain/historialState/utils/diff.test.ts @@ -4,8 +4,8 @@ import {Logger} from "@lodestar/logger"; import {IBeaconDb} from "../../../../../src/index.js"; import {getMockedBeaconDb} from "../../../../mocks/mockedBeaconDb.js"; import {getMockedLogger} from "../../../../mocks/loggerMock.js"; -import {getDiffState} from "../../../../../src/chain/historicalState/utils/diff.js"; -import {IBinaryDiffCodec} from "../../../../../src/chain/historicalState/types.js"; +import {getDiffStateArchive} from "../../../../../src/chain/historicalState/utils/diff.js"; +import {IStateDiffCodec} from "../../../../../src/chain/historicalState/types.js"; import {HierarchicalLayers} from "../../../../../src/chain/historicalState/utils/hierarchicalLayers.js"; import {XDelta3Codec} from "../../../../../src/chain/historicalState/utils/xdelta3.js"; @@ -13,7 +13,7 @@ describe("historicalState/util", () => { let db: IBeaconDb; let logger: Logger; let hierarchicalLayers: HierarchicalLayers; - let codec: IBinaryDiffCodec; + let codec: IStateDiffCodec; beforeEach(async () => { db = getMockedBeaconDb(); @@ -23,8 +23,6 @@ describe("historicalState/util", () => { vi.spyOn(codec, "apply"); vi.spyOn(codec, "compute"); - - await codec.init(); }); afterEach(() => { @@ -37,7 +35,7 @@ describe("historicalState/util", () => { const skipSlotDiff = false; await expect( - getDiffState({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}) + getDiffStateArchive({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}) ).resolves.toEqual({ diffStateBytes: null, diffSlots: [0], @@ -48,7 +46,7 @@ describe("historicalState/util", () => { const slot = 0; const skipSlotDiff = false; - await getDiffState({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); + await getDiffStateArchive({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); expect(codec.compute).not.toBeCalled(); }); @@ -61,7 +59,7 @@ describe("historicalState/util", () => { when(db.stateArchive.getBinary).calledWith(0).thenResolve(null); await expect( - getDiffState({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}) + getDiffStateArchive({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}) ).resolves.toEqual({ diffStateBytes: null, diffSlots: [0, 10, 20, 30, 40], @@ -75,7 +73,7 @@ describe("historicalState/util", () => { vi.spyOn(hierarchicalLayers, "getArchiveLayers").mockReturnValue([0, 10, 20, 30, 40]); when(db.stateArchive.getBinary).calledWith(0).thenResolve(null); - await getDiffState({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); + await getDiffStateArchive({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); expect(db.stateArchive.lastKey).toBeCalledTimes(1); }); @@ -92,7 +90,7 @@ describe("historicalState/util", () => { when(db.stateArchive.getBinary).calledWith(30).thenResolve(null); when(db.stateArchive.getBinary).calledWith(40).thenResolve(null); - await getDiffState({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); + await getDiffStateArchive({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); expect(db.stateArchive.lastKey).not.toBeCalled(); }); @@ -106,7 +104,7 @@ describe("historicalState/util", () => { when(db.stateArchive.getBinary).calledWith(0).thenResolve(snapshotState); vi.mocked(db.stateArchive.getBinary).mockResolvedValue(null); - await getDiffState({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); + await getDiffStateArchive({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); expect(db.stateArchive.getBinary).toHaveBeenCalledTimes(4); expect(db.stateArchive.getBinary).toHaveBeenNthCalledWith(1, 10); @@ -124,7 +122,7 @@ describe("historicalState/util", () => { when(db.stateArchive.getBinary).calledWith(0).thenResolve(snapshotState); vi.mocked(db.stateArchive.getBinary).mockResolvedValue(null); - await getDiffState({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); + await getDiffStateArchive({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); expect(db.stateArchive.getBinary).toHaveBeenCalledTimes(3); expect(db.stateArchive.getBinary).toHaveBeenNthCalledWith(1, 10); @@ -141,7 +139,7 @@ describe("historicalState/util", () => { when(db.stateArchive.getBinary).calledWith(0).thenResolve(snapshotState); vi.mocked(db.stateArchive.getBinary).mockResolvedValue(null); - await getDiffState({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); + await getDiffStateArchive({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); expect(db.stateArchive.getBinary).toHaveBeenCalledTimes(4); expect(db.stateArchive.getBinary).toHaveBeenNthCalledWith(1, 10); @@ -159,7 +157,7 @@ describe("historicalState/util", () => { when(db.stateArchive.getBinary).calledWith(0).thenResolve(snapshotState); vi.mocked(db.stateArchive.getBinary).mockResolvedValue(null); - await getDiffState({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); + await getDiffStateArchive({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); expect(codec.apply).not.toBeCalled(); }); @@ -184,7 +182,7 @@ describe("historicalState/util", () => { when(db.stateArchive.getBinary).calledWith(30).thenResolve(diff3); when(db.stateArchive.getBinary).calledWith(40).thenResolve(diff4); - await getDiffState({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); + await getDiffStateArchive({slot, skipSlotDiff}, {db, logger, hierarchicalLayers: hierarchicalLayers, codec}); expect(codec.apply).toBeCalledTimes(4); expect(codec.apply).toHaveBeenNthCalledWith(1, snapshotState, diff1); diff --git a/packages/beacon-node/test/unit/chain/historialState/utils/xdelta3.test.ts b/packages/beacon-node/test/unit/chain/historialState/utils/xdelta3.test.ts index 97d804cd56a7..b600966dcc79 100644 --- a/packages/beacon-node/test/unit/chain/historialState/utils/xdelta3.test.ts +++ b/packages/beacon-node/test/unit/chain/historialState/utils/xdelta3.test.ts @@ -6,7 +6,7 @@ import {fromHex} from "@lodestar/utils"; import {ForkName} from "@lodestar/params"; import {XDelta3Codec} from "../../../../../src/chain/historicalState/utils/xdelta3.js"; import {generateState} from "../../../../utils/state.js"; -import {IBinaryDiffCodec} from "../../../../../src/chain/historicalState/types.js"; +import {IStateDiffCodec} from "../../../../../src/chain/historicalState/types.js"; const testsCases: {title: string; base: () => Uint8Array; changed: () => Uint8Array}[] = [ { @@ -65,12 +65,11 @@ const testsCases: {title: string; base: () => Uint8Array; changed: () => Uint8Ar const binaryValue = (s: string): Uint8Array => Uint8Array.from(Buffer.from(s, "utf8")); describe("BinaryDiffCodec", () => { - let codec: IBinaryDiffCodec; + let codec: IStateDiffCodec; let multiDiffData: Record; beforeAll(async () => { codec = new XDelta3Codec(); - await codec.init(); multiDiffData = { snapshot: { diff --git a/packages/cli/src/cmds/beacon/initBeaconState.ts b/packages/cli/src/cmds/beacon/initBeaconState.ts index f163c6669ed9..bfd2b7c47733 100644 --- a/packages/cli/src/cmds/beacon/initBeaconState.ts +++ b/packages/cli/src/cmds/beacon/initBeaconState.ts @@ -16,6 +16,7 @@ import { getStateTypeFromBytes, HierarchicalLayers, getLastStoredState, + migrateStateArchive, } from "@lodestar/beacon-node"; import {Checkpoint} from "@lodestar/types/phase0"; @@ -102,11 +103,16 @@ export async function initBeaconState( if (args.forceCheckpointSync && !(args.checkpointState || args.checkpointSyncUrl)) { throw new Error("Forced checkpoint sync without specifying a checkpointState or checkpointSyncUrl"); } + + // Migrate state archive structure if necessary + await migrateStateArchive({db, archiveMode: options.chain.stateArchiveMode, logger}); + // fetch the latest state stored in the db which will be used in all cases, if it exists, either // i) used directly as the anchor state // ii) used to load and verify a weak subjectivity state, // ii) used during verification of a weak subjectivity state, const {stateBytes, slot: lastDbSlot} = await getLastStoredState({ + forkConfig: chainForkConfig, db, hierarchicalLayers: HierarchicalLayers.fromString(), logger,