Skip to content

Commit 7c2c34a

Browse files
committed
feat(pipeline): allow syncing blocks ontop of the proposed chain
1 parent c60ca91 commit 7c2c34a

33 files changed

Lines changed: 1010 additions & 165 deletions

yarn-project/archiver/src/archiver.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@ import { ArchiverDataSourceBase } from './modules/data_source_base.js';
3636
import { ArchiverDataStoreUpdater } from './modules/data_store_updater.js';
3737
import type { ArchiverInstrumentation } from './modules/instrumentation.js';
3838
import type { ArchiverL1Synchronizer } from './modules/l1_synchronizer.js';
39+
import type { PendingCheckpointStorage } from './store/block_store.js';
3940
import type { KVArchiverDataStore } from './store/kv_archiver_store.js';
4041
import { L2TipsCache } from './store/l2_tips_cache.js';
4142

43+
export type { PendingCheckpointStorage };
44+
4245
/** Export ArchiverEmitter for use in factory and tests. */
4346
export type { ArchiverEmitter };
4447

@@ -209,6 +212,10 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
209212
});
210213
}
211214

215+
public async setPendingCheckpoint(pending: PendingCheckpointStorage): Promise<void> {
216+
await this.dataStore.blockStore.setPendingCheckpoint(pending);
217+
}
218+
212219
/**
213220
* Processes all queued blocks, adding them to the store.
214221
* Called at the beginning of each sync iteration.

yarn-project/archiver/src/errors.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@ export class InitialCheckpointNumberNotSequentialError extends Error {
2626
}
2727

2828
export class CheckpointNumberNotSequentialError extends Error {
29-
constructor(newCheckpointNumber: number, previous: number | undefined) {
29+
constructor(
30+
newCheckpointNumber: number,
31+
previous: number | undefined,
32+
source: 'confirmed' | 'pending' = 'confirmed',
33+
) {
3034
super(
31-
`Cannot insert new checkpoint ${newCheckpointNumber} given previous checkpoint number in batch is ${previous ?? 'undefined'}`,
35+
`Cannot insert new checkpoint ${newCheckpointNumber} given previous ${source} checkpoint number is ${previous ?? 'undefined'}`,
3236
);
3337
}
3438
}

yarn-project/archiver/src/modules/data_source_base.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type { BlockHeader, IndexedTxEffect, TxHash, TxReceipt } from '@aztec/std
1818
import type { UInt64 } from '@aztec/stdlib/types';
1919

2020
import type { ArchiverDataSource } from '../interfaces.js';
21+
import type { PendingCheckpointStorage } from '../store/block_store.js';
2122
import type { KVArchiverDataStore } from '../store/kv_archiver_store.js';
2223
import type { ValidateCheckpointResult } from './validation.js';
2324

@@ -173,6 +174,10 @@ export abstract class ArchiverDataSourceBase
173174
return this.store.getPrivateLogsByTags(tags, page, upToBlockNumber);
174175
}
175176

177+
public getPendingCheckpoint(): Promise<PendingCheckpointStorage | undefined> {
178+
return this.store.blockStore.getPendingCheckpoint();
179+
}
180+
176181
public getPublicLogsByTagsFromContract(
177182
contractAddress: AztecAddress,
178183
tags: Tag[],

yarn-project/archiver/src/modules/validation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
getAttestationInfoFromPayload,
1010
} from '@aztec/stdlib/block';
1111
import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
12-
import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
12+
import { type L1RollupConstants, computeQuorum, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
1313
import { ConsensusPayload } from '@aztec/stdlib/p2p';
1414

1515
export type { ValidateCheckpointResult };
@@ -66,7 +66,7 @@ export async function validateCheckpointAttestations(
6666
return { valid: true };
6767
}
6868

69-
const requiredAttestationCount = Math.floor((committee.length * 2) / 3) + 1;
69+
const requiredAttestationCount = computeQuorum(committee.length);
7070

7171
const failedValidationResult = <TReason extends ValidateCheckpointNegativeResult['reason']>(reason: TReason) => ({
7272
valid: false as const,

yarn-project/archiver/src/store/block_store.ts

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ type CheckpointStorage = {
6969
attestations: Buffer[];
7070
};
7171

72+
/** Quorum-attested checkpoint awaiting L1 confirmation. Stored as a single value for atomic reads/writes. */
73+
export type PendingCheckpointStorage = {
74+
checkpointNumber: CheckpointNumber;
75+
archive: Fr;
76+
};
77+
7278
export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
7379

7480
/**
@@ -111,6 +117,8 @@ export class BlockStore {
111117
/** Index mapping block archive to block number */
112118
#blockArchiveIndex: AztecAsyncMap<string, number>;
113119

120+
#pendingCheckpoint: AztecAsyncSingleton<PendingCheckpointStorage>;
121+
114122
#log = createLogger('archiver:block_store');
115123

116124
constructor(private db: AztecAsyncKVStore) {
@@ -126,6 +134,7 @@ export class BlockStore {
126134
this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
127135
this.#checkpoints = db.openMap('archiver_checkpoints');
128136
this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
137+
this.#pendingCheckpoint = db.openSingleton('pending_checkpoint');
129138
}
130139

131140
/**
@@ -161,6 +170,7 @@ export class BlockStore {
161170

162171
// Extract the latest block and checkpoint numbers
163172
const previousBlockNumber = await this.getLatestBlockNumber();
173+
const pendingCheckpointNumber = await this.getPendingCheckpointNumber();
164174
const previousCheckpointNumber = await this.getLatestCheckpointNumber();
165175

166176
// Verify we're not overwriting checkpointed blocks
@@ -179,9 +189,19 @@ export class BlockStore {
179189
throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
180190
}
181191

182-
// The same check as above but for checkpoints
183-
if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) {
184-
throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber);
192+
// The same check as above but for checkpoints. Accept the block if either the confirmed
193+
// checkpoint or the pending (locally validated but not yet confirmed) checkpoint matches.
194+
const expectedCheckpointNumber = blockCheckpointNumber - 1;
195+
if (
196+
!opts.force &&
197+
previousCheckpointNumber !== expectedCheckpointNumber &&
198+
pendingCheckpointNumber !== expectedCheckpointNumber
199+
) {
200+
const [reported, source]: [CheckpointNumber, 'confirmed' | 'pending'] =
201+
pendingCheckpointNumber > previousCheckpointNumber
202+
? [pendingCheckpointNumber, 'pending']
203+
: [previousCheckpointNumber, 'confirmed'];
204+
throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, reported, source);
185205
}
186206

187207
// Extract the previous block if there is one and see if it is for the same checkpoint or not
@@ -326,6 +346,13 @@ export class BlockStore {
326346
await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
327347
}
328348

349+
// Clear the pending checkpoint if any of the confirmed checkpoints match or supersede it
350+
const pendingCheckpointNumber = await this.getPendingCheckpointNumber();
351+
const lastConfirmedCheckpointNumber = checkpoints[checkpoints.length - 1].checkpoint.number;
352+
if (pendingCheckpointNumber <= lastConfirmedCheckpointNumber) {
353+
await this.#pendingCheckpoint.delete();
354+
}
355+
329356
await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
330357
return true;
331358
});
@@ -423,6 +450,12 @@ export class BlockStore {
423450
this.#log.debug(`Removed checkpoint ${c}`);
424451
}
425452

453+
// Clear any pending checkpoint that was removed
454+
const pendingCheckpointNumber = await this.getPendingCheckpointNumber();
455+
if (pendingCheckpointNumber > checkpointNumber) {
456+
await this.#pendingCheckpoint.delete();
457+
}
458+
426459
return { blocksRemoved };
427460
});
428461
}
@@ -576,6 +609,15 @@ export class BlockStore {
576609
return CheckpointNumber(latestCheckpointNumber);
577610
}
578611

612+
async getPendingCheckpoint(): Promise<PendingCheckpointStorage | undefined> {
613+
return await this.#pendingCheckpoint.getAsync();
614+
}
615+
616+
async getPendingCheckpointNumber(): Promise<CheckpointNumber> {
617+
const pending = await this.getPendingCheckpoint();
618+
return pending?.checkpointNumber ?? CheckpointNumber(INITIAL_CHECKPOINT_NUMBER - 1);
619+
}
620+
579621
async getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined> {
580622
const blockStorage = await this.#blocks.getAsync(number);
581623
if (!blockStorage) {
@@ -950,6 +992,16 @@ export class BlockStore {
950992
return this.#lastSynchedL1Block.set(l1BlockNumber);
951993
}
952994

995+
/** Sets the pending checkpoint (quorum-attested but not yet L1-confirmed). Only updates if the new value is greater than the current one. */
996+
async setPendingCheckpoint(pending: PendingCheckpointStorage) {
997+
const current = await this.getPendingCheckpointNumber();
998+
if (pending.checkpointNumber <= current) {
999+
this.#log.warn(`Ignoring stale pending checkpoint number ${pending.checkpointNumber} (current: ${current})`);
1000+
return;
1001+
}
1002+
await this.#pendingCheckpoint.set(pending);
1003+
}
1004+
9531005
async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
9541006
const [latestCheckpointNumber, provenCheckpointNumber] = await Promise.all([
9551007
this.getLatestCheckpointNumber(),

0 commit comments

Comments
 (0)