diff --git a/yarn-project/archiver/src/archiver-misc.test.ts b/yarn-project/archiver/src/archiver-misc.test.ts index 78de2d98cdef..d021953fa1f3 100644 --- a/yarn-project/archiver/src/archiver-misc.test.ts +++ b/yarn-project/archiver/src/archiver-misc.test.ts @@ -64,7 +64,9 @@ describe('Archiver misc', () => { publicClient, rollupContract, { + rollupAddress: EthAddress.random(), registryAddress: EthAddress.random(), + inboxAddress: EthAddress.random(), governanceProposerAddress: EthAddress.random(), slashingProposerAddress: EthAddress.random(), }, diff --git a/yarn-project/archiver/src/archiver-store.test.ts b/yarn-project/archiver/src/archiver-store.test.ts index c9d705efc4b4..c3bc47e6306e 100644 --- a/yarn-project/archiver/src/archiver-store.test.ts +++ b/yarn-project/archiver/src/archiver-store.test.ts @@ -33,6 +33,7 @@ import { makeChainedCheckpoints } from './test/mock_structs.js'; describe('Archiver Store', () => { const rollupAddress = EthAddress.random(); const registryAddress = EthAddress.random(); + const inboxAddress = EthAddress.random(); const governanceProposerAddress = EthAddress.random(); const slashingProposerAddress = EthAddress.random(); @@ -76,7 +77,9 @@ describe('Archiver Store', () => { }; const contractAddresses = { + rollupAddress, registryAddress, + inboxAddress, governanceProposerAddress, slashingProposerAddress, }; diff --git a/yarn-project/archiver/src/archiver-sync.test.ts b/yarn-project/archiver/src/archiver-sync.test.ts index cecfb3dfe5cf..9501538c6a36 100644 --- a/yarn-project/archiver/src/archiver-sync.test.ts +++ b/yarn-project/archiver/src/archiver-sync.test.ts @@ -100,7 +100,9 @@ describe('Archiver Sync', () => { archiverStore = new KVArchiverDataStore(await openTmpStore('archiver_sync_test'), 1000); const contractAddresses = { + rollupAddress, registryAddress, + inboxAddress, governanceProposerAddress, slashingProposerAddress, }; @@ -114,6 +116,7 @@ describe('Archiver Sync', () => { batchSize: 1000, maxAllowedEthClientDriftSeconds: 300, ethereumAllowNoDebugHosts: true, + skipHistoricalLogsCheck: true, }; // Create event emitter shared by archiver and synchronizer diff --git a/yarn-project/archiver/src/archiver.ts b/yarn-project/archiver/src/archiver.ts index 931395155957..0e440d8c9697 100644 --- a/yarn-project/archiver/src/archiver.ts +++ b/yarn-project/archiver/src/archiver.ts @@ -31,6 +31,7 @@ import { type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@a import { type ArchiverConfig, mapArchiverConfig } from './config.js'; import { BlockAlreadyCheckpointedError, NoBlobBodiesFoundError } from './errors.js'; +import { validateAndLogHistoricalLogsAvailability } from './l1/validate_historical_logs.js'; import { validateAndLogTraceAvailability } from './l1/validate_trace.js'; import { ArchiverDataSourceBase } from './modules/data_source_base.js'; import { ArchiverDataStoreUpdater } from './modules/data_store_updater.js'; @@ -106,7 +107,10 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra private readonly publicClient: ViemPublicClient, private readonly debugClient: ViemPublicDebugClient, private readonly rollup: RollupContract, - private readonly l1Addresses: Pick & { + private readonly l1Addresses: Pick< + L1ContractAddresses, + 'rollupAddress' | 'registryAddress' | 'inboxAddress' | 'governanceProposerAddress' + > & { slashingProposerAddress: EthAddress; }, readonly dataStore: KVArchiverDataStore, @@ -116,6 +120,7 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra skipValidateCheckpointAttestations?: boolean; maxAllowedEthClientDriftSeconds: number; ethereumAllowNoDebugHosts?: boolean; + skipHistoricalLogsCheck?: boolean; }, private readonly blobClient: BlobClientInterface, instrumentation: ArchiverInstrumentation, @@ -172,6 +177,17 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra this.config.ethereumAllowNoDebugHosts ?? false, this.log.getBindings(), ); + await validateAndLogHistoricalLogsAvailability( + this.publicClient, + { + rollupAddress: this.l1Addresses.rollupAddress, + inboxAddress: this.l1Addresses.inboxAddress, + registryAddress: this.l1Addresses.registryAddress, + governanceProposerAddress: this.l1Addresses.governanceProposerAddress, + }, + this.config.skipHistoricalLogsCheck ?? false, + this.log.getBindings(), + ); // Log initial state for the archiver const { l1StartBlock } = this.l1Constants; diff --git a/yarn-project/archiver/src/config.ts b/yarn-project/archiver/src/config.ts index 93f3bdbe83f4..3e9780a9d706 100644 --- a/yarn-project/archiver/src/config.ts +++ b/yarn-project/archiver/src/config.ts @@ -67,6 +67,13 @@ export const archiverConfigMappings: ConfigMappingsType = { description: 'Whether to allow starting the archiver without debug/trace method support on Ethereum hosts', ...booleanConfigHelper(true), }, + archiverSkipHistoricalLogsCheck: { + env: 'ARCHIVER_SKIP_HISTORICAL_LOGS_CHECK', + description: + 'Skip the startup check that probes the L1 RPC for historical Rollup contract logs. ' + + 'Set to true to bypass the check when the connected RPC node is known to prune old logs.', + ...booleanConfigHelper(false), + }, ...chainConfigMappings, ...l1ReaderConfigMappings, viemPollingIntervalMS: { @@ -98,5 +105,6 @@ export function mapArchiverConfig(config: Partial) { skipValidateCheckpointAttestations: config.skipValidateCheckpointAttestations, maxAllowedEthClientDriftSeconds: config.maxAllowedEthClientDriftSeconds, ethereumAllowNoDebugHosts: config.ethereumAllowNoDebugHosts, + skipHistoricalLogsCheck: config.archiverSkipHistoricalLogsCheck, }; } diff --git a/yarn-project/archiver/src/factory.ts b/yarn-project/archiver/src/factory.ts index db7b06b5b1da..481638943414 100644 --- a/yarn-project/archiver/src/factory.ts +++ b/yarn-project/archiver/src/factory.ts @@ -121,6 +121,7 @@ export async function createArchiver( batchSize: 100, maxAllowedEthClientDriftSeconds: 300, ethereumAllowNoDebugHosts: false, + skipHistoricalLogsCheck: false, }, mapArchiverConfig(config), ); diff --git a/yarn-project/archiver/src/l1/validate_historical_logs.test.ts b/yarn-project/archiver/src/l1/validate_historical_logs.test.ts new file mode 100644 index 000000000000..d9275e322048 --- /dev/null +++ b/yarn-project/archiver/src/l1/validate_historical_logs.test.ts @@ -0,0 +1,134 @@ +import { RollupContract } from '@aztec/ethereum/contracts'; +import type { ViemPublicClient } from '@aztec/ethereum/types'; +import { EthAddress } from '@aztec/foundation/eth-address'; + +import { jest } from '@jest/globals'; +import { createPublicClient, fallback, http } from 'viem'; +import { foundry } from 'viem/chains'; + +import { + type HistoricalLogsContractAddresses, + validateAndLogHistoricalLogsAvailability, +} from './validate_historical_logs.js'; + +describe('validateAndLogHistoricalLogsAvailability', () => { + const url1 = 'http://fake-url-1:8545'; + const url2 = 'http://fake-url-2:8545'; + + let client: ViemPublicClient; + let addresses: HistoricalLogsContractAddresses; + let probeSpy: jest.SpiedFunction; + let fetchSpy: jest.Spied; + + beforeEach(() => { + // Build a real fallback-transport client over two fake URLs so the validator can extract them. + client = createPublicClient({ + chain: foundry, + transport: fallback([http(url1, { batch: false }), http(url2, { batch: false })]), + }) as ViemPublicClient; + + addresses = { + rollupAddress: EthAddress.random(), + inboxAddress: EthAddress.random(), + registryAddress: EthAddress.random(), + governanceProposerAddress: EthAddress.random(), + }; + + probeSpy = jest.spyOn(RollupContract.prototype, 'getOwnershipTransferredEventsAtDeploy'); + + // By default, make fetch (used by the per-URL client for web3_clientVersion) reject so we exercise + // the "Could not determine client version" branch without real network traffic. + fetchSpy = jest.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('fetch disabled in tests')); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('resolves when every URL returns the OwnershipTransferred event', async () => { + probeSpy.mockResolvedValue([{ blockNumber: 10n } as any]); + await expect(validateAndLogHistoricalLogsAvailability(client, addresses, false)).resolves.toBeUndefined(); + expect(probeSpy).toHaveBeenCalledTimes(2); + }); + + it('throws on the first URL that returns no events, and includes that URL and all addresses', async () => { + probeSpy.mockResolvedValue([]); + const err = await validateAndLogHistoricalLogsAvailability(client, addresses, false).then( + () => new Error('expected to throw'), + (e: unknown) => e as Error, + ); + expect(err).toBeInstanceOf(Error); + expect(err.message).toMatch(/does not return historical logs/); + expect(err.message).toContain(url1); + expect(err.message).not.toContain(url2); + expect(err.message).toContain(addresses.rollupAddress.toString()); + expect(err.message).toContain(addresses.inboxAddress.toString()); + expect(err.message).toContain(addresses.registryAddress.toString()); + expect(err.message).toContain(addresses.governanceProposerAddress.toString()); + expect(probeSpy).toHaveBeenCalledTimes(1); + }); + + it('throws on the second URL when the first one succeeds', async () => { + probeSpy.mockResolvedValueOnce([{ blockNumber: 10n } as any]); + probeSpy.mockResolvedValueOnce([]); + const err = await validateAndLogHistoricalLogsAvailability(client, addresses, false).then( + () => new Error('expected to throw'), + (e: unknown) => e as Error, + ); + expect(err.message).toContain(url2); + expect(err.message).not.toContain(url1); + expect(probeSpy).toHaveBeenCalledTimes(2); + }); + + it('throws when the RPC event query itself fails', async () => { + probeSpy.mockRejectedValue(new Error('rpc exploded')); + const err = await validateAndLogHistoricalLogsAvailability(client, addresses, false).then( + () => new Error('expected to throw'), + (e: unknown) => e as Error, + ); + expect(err.message).toMatch(/rpc exploded/); + }); + + it('logs a warning and continues when skipCheck is true', async () => { + probeSpy.mockResolvedValue([]); + await expect(validateAndLogHistoricalLogsAvailability(client, addresses, true)).resolves.toBeUndefined(); + // Skip mode still probes every URL. + expect(probeSpy).toHaveBeenCalledTimes(2); + }); + + it('includes reth-specific guidance when the L1 client reports reth', async () => { + probeSpy.mockResolvedValue([]); + fetchSpy.mockImplementation(() => + Promise.resolve( + new Response(JSON.stringify({ jsonrpc: '2.0', id: 1, result: 'reth/v1.0.0-abcdef/linux-x86_64/1.75.0' }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }), + ), + ); + const err = await validateAndLogHistoricalLogsAvailability(client, addresses, false).then( + () => new Error('expected to throw'), + (e: unknown) => e as Error, + ); + expect(err.message).toMatch(/Detected L1 client version for .*: reth/); + expect(err.message).toMatch(/prune\.segments\.receipts_log_filter/); + }); + + it('falls back to generic guidance when the L1 client is not reth', async () => { + probeSpy.mockResolvedValue([]); + fetchSpy.mockImplementation(() => + Promise.resolve( + new Response(JSON.stringify({ jsonrpc: '2.0', id: 1, result: 'Geth/v1.14.0-stable/linux-amd64/go1.22.0' }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }), + ), + ); + const err = await validateAndLogHistoricalLogsAvailability(client, addresses, false).then( + () => new Error('expected to throw'), + (e: unknown) => e as Error, + ); + expect(err.message).not.toMatch(/prune\.segments\.receipts_log_filter/); + expect(err.message).toMatch(/retains full log history/); + }); +}); diff --git a/yarn-project/archiver/src/l1/validate_historical_logs.ts b/yarn-project/archiver/src/l1/validate_historical_logs.ts new file mode 100644 index 000000000000..efd8e40e24b7 --- /dev/null +++ b/yarn-project/archiver/src/l1/validate_historical_logs.ts @@ -0,0 +1,140 @@ +import { getPublicClient, getRpcUrlsFromClient } from '@aztec/ethereum/client'; +import { RollupContract } from '@aztec/ethereum/contracts'; +import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses'; +import type { ViemPublicClient } from '@aztec/ethereum/types'; +import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log'; + +/** Subset of L1 contract addresses whose historical logs the Aztec node relies on. */ +export type HistoricalLogsContractAddresses = Pick< + L1ContractAddresses, + 'rollupAddress' | 'inboxAddress' | 'registryAddress' | 'governanceProposerAddress' +>; + +/** Result of probing a single RPC URL. */ +type ProbeResult = { ok: true } | { ok: false; reason: string; clientVersion: string | undefined }; + +/** + * Validates that every configured L1 RPC URL returns historical logs for the Rollup contract. + * + * Some RPC providers prune old logs, which would cause L1 syncing to silently fail. To detect this, + * we query for the `OwnershipTransferred` event which every Rollup emits in its constructor (via + * Ownable) on the block it was deployed (`l1StartBlock`). The `client` is typically a viem fallback + * transport over several user-configured RPC URLs — checking only the first URL would miss a bad + * secondary, so we probe each URL independently. The first URL that fails aborts startup, unless + * the operator has explicitly opted out. + * + * @param client - The L1 public client built from the user-configured RPC URLs. + * @param addresses - The subset of L1 contract addresses we rely on for historical log retrieval. + * @param skipCheck - If true, log warnings instead of throwing. + * @param bindings - Optional logger bindings for context. + * @throws Error if any URL fails the probe and skipCheck is false. + */ +export async function validateAndLogHistoricalLogsAvailability( + client: ViemPublicClient, + addresses: HistoricalLogsContractAddresses, + skipCheck: boolean, + bindings?: LoggerBindings, +): Promise { + const logger = createLogger('archiver:validate_historical_logs', bindings); + logger.debug('Validating historical log availability on L1 RPCs'); + + const urls = getRpcUrlsFromClient(client); + if (urls.length === 0) { + logger.warn('Could not determine L1 RPC URLs from the public client; skipping historical logs check.'); + return; + } + + const chainId = client.chain?.id; + if (chainId === undefined) { + logger.warn('Could not determine L1 chain ID from the public client; skipping historical logs check.'); + return; + } + + for (const url of urls) { + const probeClient = getPublicClient({ l1RpcUrls: [url], l1ChainId: chainId }); + const rollup = new RollupContract(probeClient, addresses.rollupAddress.toString()); + const result = await probeRpcUrl(rollup, probeClient, logger); + + if (result.ok) { + logger.debug(`L1 RPC ${url} returned historical OwnershipTransferred log for the Rollup contract.`); + continue; + } + + const errorMessage = buildErrorMessage(url, result, addresses); + if (skipCheck) { + logger.warn(`${errorMessage}\nContinuing because ARCHIVER_SKIP_HISTORICAL_LOGS_CHECK is true.`); + continue; + } + + logger.error(errorMessage); + throw new Error(errorMessage); + } +} + +/** Runs the OwnershipTransferred probe against a single RPC and queries its client version. */ +async function probeRpcUrl(rollup: RollupContract, client: ViemPublicClient, logger: Logger): Promise { + let queryError: unknown; + try { + const logs = await rollup.getOwnershipTransferredEventsAtDeploy(); + if (logs.length > 0) { + return { ok: true }; + } + } catch (err) { + queryError = err; + } + + const clientVersion = await getClientVersion(client, logger); + + let reason: string; + if (queryError instanceof Error) { + reason = `Query for historical logs failed: ${queryError.message}`; + } else if (queryError !== undefined) { + reason = 'Query for historical logs failed with a non-Error value.'; + } else { + reason = 'No OwnershipTransferred event was returned by the L1 RPC for the Rollup deploy block.'; + } + return { ok: false, reason, clientVersion }; +} + +/** Builds the operator-facing error message for a failing RPC URL. */ +function buildErrorMessage( + url: string, + result: Extract, + addresses: HistoricalLogsContractAddresses, +): string { + return [ + `L1 RPC at ${url} does not return historical logs for the Rollup contract. ${result.reason}`, + `This likely means this Ethereum RPC node prunes old logs, which would cause the archiver ` + + `to silently miss data during L1 sync.`, + result.clientVersion + ? `Detected L1 client version for ${url}: ${result.clientVersion}.` + : `Could not determine L1 client version for ${url}.`, + `The following L1 contract addresses must have their historical logs retained by the RPC node:`, + ` - Rollup: ${addresses.rollupAddress.toString()}`, + ` - Inbox: ${addresses.inboxAddress.toString()}`, + ` - Registry: ${addresses.registryAddress.toString()}`, + ` - GovernanceProposer: ${addresses.governanceProposerAddress.toString()}`, + isReth(result.clientVersion) + ? `To retain logs for these contracts, configure reth with a ` + + `prune.segments.receipts_log_filter entry for each address above ` + + `so reth does not prune their receipts/logs. See https://reth.rs/run/pruning.html for details.` + : `Point this RPC endpoint at a node that retains full log history for the addresses above.`, + `Set ARCHIVER_SKIP_HISTORICAL_LOGS_CHECK=true to bypass this check at your own risk.`, + ].join('\n'); +} + +/** Queries `web3_clientVersion` on the L1 RPC. Returns undefined if the call fails or returns a non-string. */ +async function getClientVersion(client: ViemPublicClient, logger: Logger): Promise { + try { + const result = await client.request({ method: 'web3_clientVersion' }); + return typeof result === 'string' ? result : undefined; + } catch (err) { + logger.debug(`Failed to query web3_clientVersion: ${err instanceof Error ? err.message : err}`); + return undefined; + } +} + +/** Heuristic check for reth based on the web3_clientVersion string (reth returns e.g. "reth/v1.0.0-..."). */ +function isReth(clientVersion: string | undefined): boolean { + return !!clientVersion && /reth/i.test(clientVersion); +} diff --git a/yarn-project/archiver/src/test/noop_l1_archiver.ts b/yarn-project/archiver/src/test/noop_l1_archiver.ts index e1eccb37b5b6..3f70da3452d1 100644 --- a/yarn-project/archiver/src/test/noop_l1_archiver.ts +++ b/yarn-project/archiver/src/test/noop_l1_archiver.ts @@ -70,7 +70,9 @@ export class NoopL1Archiver extends Archiver { debugClient, rollup, { + rollupAddress: EthAddress.ZERO, registryAddress: EthAddress.ZERO, + inboxAddress: EthAddress.ZERO, governanceProposerAddress: EthAddress.ZERO, slashingProposerAddress: EthAddress.ZERO, }, @@ -81,6 +83,7 @@ export class NoopL1Archiver extends Archiver { skipValidateCheckpointAttestations: true, maxAllowedEthClientDriftSeconds: 300, ethereumAllowNoDebugHosts: true, // Skip trace validation + skipHistoricalLogsCheck: true, // Skip historical logs validation }, blobClient, instrumentation, diff --git a/yarn-project/ethereum/src/client.ts b/yarn-project/ethereum/src/client.ts index 3769b798719c..010097d50894 100644 --- a/yarn-project/ethereum/src/client.ts +++ b/yarn-project/ethereum/src/client.ts @@ -36,6 +36,24 @@ export function makeL1HttpTransport(rpcUrls: string[], opts?: { timeout?: number return fallback(rpcUrls.map(url => http(url, { batch: false, timeout: opts?.timeout }))); } +/** + * Returns the individual RPC URLs underlying a viem public client that was constructed with a + * fallback HTTP transport (see {@link makeL1HttpTransport}). Returns an empty array if the + * transport shape is not recognized (e.g. mock clients in tests, or non-fallback transports). + */ +export function getRpcUrlsFromClient(client: ViemPublicClient): string[] { + const transport = client.transport as unknown as { + transports?: { value?: { url?: string } }[]; + value?: { url?: string }; + url?: string; + }; + if (Array.isArray(transport?.transports)) { + return transport.transports.map(t => t?.value?.url).filter((url): url is string => typeof url === 'string'); + } + const singleUrl = transport?.value?.url ?? transport?.url; + return typeof singleUrl === 'string' ? [singleUrl] : []; +} + // TODO: Use these methods to abstract the creation of viem clients. /** Returns a viem public client given the L1 config. */ diff --git a/yarn-project/ethereum/src/contracts/rollup.test.ts b/yarn-project/ethereum/src/contracts/rollup.test.ts index a9ef1863314d..b2e0f3238e4b 100644 --- a/yarn-project/ethereum/src/contracts/rollup.test.ts +++ b/yarn-project/ethereum/src/contracts/rollup.test.ts @@ -355,6 +355,18 @@ describe('Rollup', () => { }); }); + describe('getOwnershipTransferredEventsAtDeploy', () => { + it('finds OwnershipTransferred event emitted at deploy block', async () => { + const logs = await rollup.getOwnershipTransferredEventsAtDeploy(); + expect(logs.length).toBeGreaterThan(0); + + const l1StartBlock = await rollup.getL1StartBlock(); + for (const log of logs) { + expect(log.blockNumber).toBe(l1StartBlock); + } + }); + }); + describe('compressFeeHeader', () => { it('compressed fee header can be read back by L1 getFeeHeader', async () => { const feeHeader: FeeHeader = { diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index df763e2a73ee..1dd381b926df 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -1266,6 +1266,17 @@ export class RollupContract { ); } + /** + * Fetches OwnershipTransferred events emitted on the L1 block this rollup was deployed on. + * The Rollup inherits from Ownable and emits this event in its constructor, so the event + * is guaranteed to exist on `l1StartBlock` for any correctly deployed rollup. Used as a + * probe to detect RPC nodes that prune historical logs. + */ + async getOwnershipTransferredEventsAtDeploy() { + const l1StartBlock = await this.getL1StartBlock(); + return await this.rollup.getEvents.OwnershipTransferred({}, { fromBlock: l1StartBlock, toBlock: l1StartBlock }); + } + /** Fetches CheckpointProposed events within the given block range. */ async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise { const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock }); diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index ec423746dcab..b4048381ecef 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -7,6 +7,7 @@ export type EnvVar = | 'API_PREFIX' | 'ARCHIVER_MAX_LOGS' | 'ARCHIVER_POLLING_INTERVAL_MS' + | 'ARCHIVER_SKIP_HISTORICAL_LOGS_CHECK' | 'ARCHIVER_URL' | 'ARCHIVER_VIEM_POLLING_INTERVAL_MS' | 'ARCHIVER_BATCH_SIZE' diff --git a/yarn-project/stdlib/src/interfaces/archiver.ts b/yarn-project/stdlib/src/interfaces/archiver.ts index cf5f22ebf026..d722819638f8 100644 --- a/yarn-project/stdlib/src/interfaces/archiver.ts +++ b/yarn-project/stdlib/src/interfaces/archiver.ts @@ -60,6 +60,9 @@ export type ArchiverSpecificConfig = { /** Whether to allow starting the archiver without debug/trace method support on Ethereum hosts */ ethereumAllowNoDebugHosts?: boolean; + /** Skip the startup check that probes the L1 RPC for historical logs on the Rollup contract. */ + archiverSkipHistoricalLogsCheck?: boolean; + /** Skip validating checkpoint attestations (for testing purposes only) */ skipValidateCheckpointAttestations?: boolean; }; @@ -72,6 +75,7 @@ export const ArchiverSpecificConfigSchema = z.object({ archiverStoreMapSizeKb: schemas.Integer.optional(), maxAllowedEthClientDriftSeconds: schemas.Integer.optional(), ethereumAllowNoDebugHosts: z.boolean().optional(), + archiverSkipHistoricalLogsCheck: z.boolean().optional(), skipValidateCheckpointAttestations: z.boolean().optional(), });