Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions yarn-project/archiver/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
description: 'Skip validating checkpoint attestations (for testing purposes only)',
...booleanConfigHelper(false),
},
skipPromoteProposedCheckpointDuringL1Sync: {
description: 'Skip promoting proposed checkpoints during L1 sync (for testing purposes only)',
...booleanConfigHelper(false),
},
maxAllowedEthClientDriftSeconds: {
env: 'MAX_ALLOWED_ETH_CLIENT_DRIFT_SECONDS',
description: 'Maximum allowed drift in seconds between the Ethereum client and current time.',
Expand Down Expand Up @@ -103,6 +107,7 @@ export function mapArchiverConfig(config: Partial<ArchiverConfig>) {
pollingIntervalMs: config.archiverPollingIntervalMS,
batchSize: config.archiverBatchSize,
skipValidateCheckpointAttestations: config.skipValidateCheckpointAttestations,
skipPromoteProposedCheckpointDuringL1Sync: config.skipPromoteProposedCheckpointDuringL1Sync,
maxAllowedEthClientDriftSeconds: config.maxAllowedEthClientDriftSeconds,
ethereumAllowNoDebugHosts: config.ethereumAllowNoDebugHosts,
skipHistoricalLogsCheck: config.archiverSkipHistoricalLogsCheck,
Expand Down
34 changes: 34 additions & 0 deletions yarn-project/archiver/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,40 @@ export class ProposedCheckpointNotSequentialError extends Error {
}
}

/** Thrown when attempting to promote a proposed checkpoint but no proposed checkpoint exists in the store. */
export class NoProposedCheckpointToPromoteError extends Error {
constructor() {
super('Cannot promote proposed checkpoint: no proposed checkpoint exists');
this.name = 'NoProposedCheckpointToPromoteError';
}
}

/** Thrown when the archive root of the proposed checkpoint does not match the expected one. */
export class ProposedCheckpointArchiveRootMismatchError extends Error {
constructor(
public readonly expectedArchiveRoot: Fr,
public readonly actualArchiveRoot: Fr,
) {
super(
`Cannot promote proposed checkpoint: archive root mismatch (expected ${expectedArchiveRoot}, got ${actualArchiveRoot})`,
);
this.name = 'ProposedCheckpointArchiveRootMismatchError';
}
}

/** Thrown when the proposed checkpoint does not directly follow the latest confirmed checkpoint. */
export class ProposedCheckpointPromotionNotSequentialError extends Error {
constructor(
public readonly proposedCheckpointNumber: number,
public readonly latestCheckpointNumber: number,
) {
super(
`Cannot promote proposed checkpoint: not sequential (latest ${latestCheckpointNumber}, proposed ${proposedCheckpointNumber})`,
);
this.name = 'ProposedCheckpointPromotionNotSequentialError';
}
}

/** Thrown when a proposed block conflicts with an already checkpointed block (different content). */
export class CannotOverwriteCheckpointedBlockError extends Error {
constructor(
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/archiver/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ export { KVArchiverDataStore, ARCHIVER_DB_VERSION } from './store/kv_archiver_st
export { ContractInstanceStore } from './store/contract_instance_store.js';
export { L2TipsCache } from './store/l2_tips_cache.js';

export { retrieveCheckpointsFromRollup, retrieveL2ProofVerifiedEvents } from './l1/data_retrieval.js';
export { retrieveL2ProofVerifiedEvents } from './l1/data_retrieval.js';
export { CalldataRetriever } from './l1/calldata_retriever.js';
63 changes: 29 additions & 34 deletions yarn-project/archiver/src/l1/data_retrieval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,28 @@ import type { DataRetrieval } from '../structs/data_retrieval.js';
import type { InboxMessage } from '../structs/inbox_message.js';
import { CalldataRetriever } from './calldata_retriever.js';

export type RetrievedCheckpoint = {
type RetrievedCheckpointBase = {
checkpointNumber: CheckpointNumber;
archiveRoot: Fr;
feeAssetPriceModifier: bigint;
header: CheckpointHeader;
checkpointBlobData: CheckpointBlobData;
l1: L1PublishedData;
chainId: Fr;
version: Fr;
attestations: CommitteeAttestation[];
};

/** Checkpoint data as retrieved from L1 calldata and blob data. */
export type RetrievedCheckpoint = RetrievedCheckpointBase & { checkpointBlobData: CheckpointBlobData };

/** Checkpoint data retrieved from L1 calldata only, without blob data. */
export type RetrievedCheckpointFromCalldata = RetrievedCheckpointBase & {
/** Versioned blob hashes from the checkpoint proposed event. */
blobHashes: Buffer[];
/** Parent beacon block root from the L1 block, used for blob fetching. */
parentBeaconBlockRoot: string | undefined;
};

export async function retrievedToPublishedCheckpoint({
checkpointNumber,
archiveRoot,
Expand Down Expand Up @@ -137,31 +147,27 @@ export async function retrievedToPublishedCheckpoint({
}

/**
* Fetches new checkpoints.
* Fetches checkpoint calldata from the rollup contract without fetching blob data.
* Returns RetrievedCheckpointFromCalldata objects that preserve the information needed for deferred blob fetching.
* @param rollup - The rollup contract wrapper.
* @param publicClient - The viem public client to use for transaction retrieval.
* @param debugClient - The viem debug client to use for trace/debug RPC methods (optional).
* @param blobClient - The blob client client for fetching blob data.
* @param searchStartBlock - The block number to use for starting the search.
* @param searchEndBlock - The highest block number that we should search up to.
* @param contractAddresses - The contract addresses (governanceProposerAddress, slashingProposerAddress).
* @param instrumentation - The archiver instrumentation instance.
* @param logger - The logger instance.
* @param isHistoricalSync - Whether this is a historical sync.
* @returns An array of retrieved checkpoints.
* @returns An array of calldata-only checkpoints.
*/
export async function retrieveCheckpointsFromRollup(
export async function retrieveCheckpointCalldataFromRollup(
rollup: RollupContract,
publicClient: ViemPublicClient,
debugClient: ViemPublicDebugClient,
blobClient: BlobClientInterface,
searchStartBlock: bigint,
searchEndBlock: bigint,
instrumentation: ArchiverInstrumentation,
logger: Logger = createLogger('archiver'),
isHistoricalSync: boolean = false,
): Promise<RetrievedCheckpoint[]> {
const retrievedCheckpoints: RetrievedCheckpoint[] = [];
): Promise<RetrievedCheckpointFromCalldata[]> {
const retrievedCheckpoints: RetrievedCheckpointFromCalldata[] = [];

let rollupConstants: { chainId: Fr; version: Fr; targetCommitteeSize: number } | undefined;

Expand Down Expand Up @@ -197,46 +203,39 @@ export async function retrieveCheckpointsFromRollup(
rollup,
publicClient,
debugClient,
blobClient,
checkpointProposedLogs,
rollupConstants,
instrumentation,
logger,
isHistoricalSync,
);
retrievedCheckpoints.push(...newCheckpoints);
searchStartBlock = lastLog.l1BlockNumber + 1n;
} while (searchStartBlock <= searchEndBlock);

// The asyncPool from processCheckpointProposedLogs will not necessarily return the checkpoints in order, so we sort them before returning.
return retrievedCheckpoints.sort((a, b) => Number(a.l1.blockNumber - b.l1.blockNumber));
}

/**
* Processes newly received CheckpointProposed logs.
* Processes CheckpointProposed logs, fetching only calldata (no blobs).
* @param rollup - The rollup contract wrapper.
* @param publicClient - The viem public client to use for transaction retrieval.
* @param debugClient - The viem debug client to use for trace/debug RPC methods (optional).
* @param blobClient - The blob client client for fetching blob data.
* @param logs - CheckpointProposed logs.
* @param rollupConstants - The rollup constants (chainId, version, targetCommitteeSize).
* @param instrumentation - The archiver instrumentation instance.
* @param logger - The logger instance.
* @param isHistoricalSync - Whether this is a historical sync.
* @returns An array of retrieved checkpoints.
* @returns An array of calldata-only checkpoints.
*/
async function processCheckpointProposedLogs(
rollup: RollupContract,
publicClient: ViemPublicClient,
debugClient: ViemPublicDebugClient,
blobClient: BlobClientInterface,
logs: CheckpointProposedLog[],
{ chainId, version, targetCommitteeSize }: { chainId: Fr; version: Fr; targetCommitteeSize: number },
instrumentation: ArchiverInstrumentation,
logger: Logger,
isHistoricalSync: boolean,
): Promise<RetrievedCheckpoint[]> {
const retrievedCheckpoints: RetrievedCheckpoint[] = [];
): Promise<RetrievedCheckpointFromCalldata[]> {
const retrievedCheckpoints: RetrievedCheckpointFromCalldata[] = [];
const calldataRetriever = new CalldataRetriever(
publicClient,
debugClient,
Expand All @@ -252,7 +251,6 @@ async function processCheckpointProposedLogs(
const archiveFromChain = await rollup.archiveAt(checkpointNumber);
const blobHashes = log.args.versionedBlobHashes;

// The value from the event and contract will match only if the checkpoint is in the chain.
if (archive.equals(archiveFromChain)) {
const expectedHashes = {
attestationsHash: log.args.attestationsHash.toString() as Hex,
Expand All @@ -268,19 +266,16 @@ async function processCheckpointProposedLogs(
const { timestamp, parentBeaconBlockRoot } = await getL1Block(publicClient, log.l1BlockNumber);
const l1 = new L1PublishedData(log.l1BlockNumber, timestamp, log.l1BlockHash.toString());

const checkpointBlobData = await getCheckpointBlobDataFromBlobs(
blobClient,
checkpoint.blockHash,
retrievedCheckpoints.push({
...checkpoint,
l1,
chainId,
version,
blobHashes,
checkpointNumber,
logger,
isHistoricalSync,
parentBeaconBlockRoot,
timestamp,
);
});

retrievedCheckpoints.push({ ...checkpoint, checkpointBlobData, l1, chainId, version });
logger.trace(`Retrieved checkpoint ${checkpointNumber} from L1 tx ${log.l1TransactionHash}`, {
logger.trace(`Retrieved checkpoint calldata ${checkpointNumber} from L1 tx ${log.l1TransactionHash}`, {
l1BlockNumber: log.l1BlockNumber,
checkpointNumber,
archive: archive.toString(),
Expand Down
30 changes: 27 additions & 3 deletions yarn-project/archiver/src/modules/data_store_updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import {
ContractInstancePublishedEvent,
ContractInstanceUpdatedEvent,
} from '@aztec/protocol-contracts/instance-registry';
import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block';
import { type ProposedCheckpointInput, type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
import type { CommitteeAttestation, L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block';
import {
type L1PublishedData,
type ProposedCheckpointInput,
type PublishedCheckpoint,
validateCheckpoint,
} from '@aztec/stdlib/checkpoint';
import {
type ContractClassPublicWithCommitment,
computeContractAddressFromInstance,
Expand Down Expand Up @@ -79,18 +84,29 @@ export class ArchiverDataStoreUpdater {
* Adds new checkpoints to the store with contract class/instance extraction from logs.
* Prunes any local blocks that conflict with checkpoint data (by comparing archive roots).
* Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the checkpoint block logs.
* If `promoteProposed` is supplied, the proposed-checkpoint promotion runs inside the same transaction
* as the added checkpoints so both updates are applied atomically.
*
* @param checkpoints - The published checkpoints to add.
* @param checkpoints - The published checkpoints to add (excluding any being promoted from proposed).
* @param pendingChainValidationStatus - Optional validation status to set.
* @param promoteProposed - Optional promotion of the current proposed checkpoint (fast path when blocks are already local).
* @returns Result with information about any pruned blocks.
*/
public async addCheckpoints(
checkpoints: PublishedCheckpoint[],
pendingChainValidationStatus?: ValidateCheckpointResult,
promoteProposed?: {
l1: L1PublishedData;
attestations: CommitteeAttestation[];
checkpoint: PublishedCheckpoint;
},
): Promise<ReconcileCheckpointsResult> {
for (const checkpoint of checkpoints) {
validateCheckpoint(checkpoint.checkpoint, { rollupManaLimit: this.opts?.rollupManaLimit });
}
if (promoteProposed) {
validateCheckpoint(promoteProposed.checkpoint.checkpoint, { rollupManaLimit: this.opts?.rollupManaLimit });
}

const result = await this.store.transactionAsync(async () => {
// Before adding checkpoints, check for conflicts with local blocks if any
Expand All @@ -110,6 +126,14 @@ export class ArchiverDataStoreUpdater {
this.store.addLogs(newBlocks),
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
...newBlocks.map(block => this.addContractDataToDb(block)),
// Promote the proposed checkpoint if requested
promoteProposed
? this.store.promoteProposedToCheckpointed(
promoteProposed.l1,
promoteProposed.attestations,
promoteProposed.checkpoint.checkpoint.archive.root,
)
: undefined,
]);

await this.l2TipsCache?.refresh();
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/archiver/src/modules/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export class ArchiverInstrumentation {
private blockProposalTxTargetCount: UpDownCounter;

private checkpointL1InclusionDelay: Histogram;
private checkpointPromotedCount: UpDownCounter;

private log = createLogger('archiver:instrumentation');

Expand Down Expand Up @@ -95,6 +96,8 @@ export class ArchiverInstrumentation {

this.checkpointL1InclusionDelay = meter.createHistogram(Metrics.ARCHIVER_CHECKPOINT_L1_INCLUSION_DELAY);

this.checkpointPromotedCount = createUpDownCounterWithDefault(meter, Metrics.ARCHIVER_CHECKPOINT_PROMOTED_COUNT);

this.dbMetrics = new LmdbMetrics(
meter,
{
Expand Down Expand Up @@ -181,6 +184,11 @@ export class ArchiverInstrumentation {
});
}

/** Records a checkpoint promoted from proposed (blob fetch skipped). */
public processCheckpointPromoted() {
this.checkpointPromotedCount.add(1);
}

/**
* Records L1 inclusion timing for a checkpoint observed on L1 (seconds into the L2 slot).
*/
Expand Down
Loading
Loading