-
Notifications
You must be signed in to change notification settings - Fork 599
feat(pipeline): allow syncing blocks ontop of the proposed chain #21025
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,7 +19,12 @@ import { | |
| deserializeValidateCheckpointResult, | ||
| serializeValidateCheckpointResult, | ||
| } from '@aztec/stdlib/block'; | ||
| import { type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint'; | ||
| import { | ||
| type CheckpointData, | ||
| L1PublishedData, | ||
| type PendingCheckpointData, | ||
| PublishedCheckpoint, | ||
| } from '@aztec/stdlib/checkpoint'; | ||
| import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers'; | ||
| import { CheckpointHeader } from '@aztec/stdlib/rollup'; | ||
| import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees'; | ||
|
|
@@ -69,6 +74,16 @@ type CheckpointStorage = { | |
| attestations: Buffer[]; | ||
| }; | ||
|
|
||
| /** Storage format for a pending checkpoint (attested but not yet L1-confirmed). */ | ||
| type PendingCheckpointStore = { | ||
| header: Buffer; | ||
| checkpointNumber: number; | ||
| startBlock: number; | ||
| blockCount: number; | ||
| totalManaUsed: string; | ||
| feeAssetPriceModifier: string; | ||
| }; | ||
|
Comment on lines
+77
to
+85
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why can't we capture archive, outhash, or all data that's not L1 or attestations? Also, nit: rename to
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it can, I just kept the minimum; will add |
||
|
|
||
| export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined }; | ||
|
|
||
| /** | ||
|
|
@@ -111,6 +126,8 @@ export class BlockStore { | |
| /** Index mapping block archive to block number */ | ||
| #blockArchiveIndex: AztecAsyncMap<string, number>; | ||
|
|
||
| #pendingCheckpoint: AztecAsyncSingleton<PendingCheckpointStore>; | ||
|
|
||
| #log = createLogger('archiver:block_store'); | ||
|
|
||
| constructor(private db: AztecAsyncKVStore) { | ||
|
|
@@ -126,6 +143,7 @@ export class BlockStore { | |
| this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status'); | ||
| this.#checkpoints = db.openMap('archiver_checkpoints'); | ||
| this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint'); | ||
| this.#pendingCheckpoint = db.openSingleton('pending_checkpoint_data'); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -161,6 +179,7 @@ export class BlockStore { | |
|
|
||
| // Extract the latest block and checkpoint numbers | ||
| const previousBlockNumber = await this.getLatestBlockNumber(); | ||
| const pendingCheckpointNumber = await this.getPendingCheckpointNumber(); | ||
| const previousCheckpointNumber = await this.getLatestCheckpointNumber(); | ||
|
|
||
| // Verify we're not overwriting checkpointed blocks | ||
|
|
@@ -179,9 +198,19 @@ export class BlockStore { | |
| throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber); | ||
| } | ||
|
|
||
| // The same check as above but for checkpoints | ||
| if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) { | ||
| throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber); | ||
| // The same check as above but for checkpoints. Accept the block if either the confirmed | ||
| // checkpoint or the pending (locally validated but not yet confirmed) checkpoint matches. | ||
| const expectedCheckpointNumber = blockCheckpointNumber - 1; | ||
| if ( | ||
| !opts.force && | ||
| previousCheckpointNumber !== expectedCheckpointNumber && | ||
| pendingCheckpointNumber !== expectedCheckpointNumber | ||
| ) { | ||
| const [reported, source]: [CheckpointNumber, 'confirmed' | 'pending'] = | ||
| pendingCheckpointNumber > previousCheckpointNumber | ||
| ? [pendingCheckpointNumber, 'pending'] | ||
| : [previousCheckpointNumber, 'confirmed']; | ||
| throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, reported, source); | ||
|
Comment on lines
+201
to
+213
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any situation where
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, adding a block to a pending checkpoint breaks the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, we should not end up adding directly to the pending checkpoint, only above it. This case is to allow building ontop of the pending checkpoint - not for. But it looks like it may allow what you have mentioned, I'll make it more strict |
||
| } | ||
|
|
||
| // Extract the previous block if there is one and see if it is for the same checkpoint or not | ||
|
|
@@ -326,6 +355,13 @@ export class BlockStore { | |
| await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number); | ||
| } | ||
|
|
||
| // Clear the pending checkpoint if any of the confirmed checkpoints match or supersede it | ||
| const pendingCheckpointNumber = await this.getPendingCheckpointNumber(); | ||
| const lastConfirmedCheckpointNumber = checkpoints[checkpoints.length - 1].checkpoint.number; | ||
| if (pendingCheckpointNumber <= lastConfirmedCheckpointNumber) { | ||
| await this.#pendingCheckpoint.delete(); | ||
| } | ||
|
|
||
| await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber); | ||
| return true; | ||
| }); | ||
|
|
@@ -423,6 +459,12 @@ export class BlockStore { | |
| this.#log.debug(`Removed checkpoint ${c}`); | ||
| } | ||
|
|
||
| // Clear any pending checkpoint that was removed | ||
| const pendingCheckpointNumber = await this.getPendingCheckpointNumber(); | ||
| if (pendingCheckpointNumber > checkpointNumber) { | ||
| await this.#pendingCheckpoint.delete(); | ||
| } | ||
|
|
||
| return { blocksRemoved }; | ||
| }); | ||
| } | ||
|
|
@@ -576,6 +618,34 @@ export class BlockStore { | |
| return CheckpointNumber(latestCheckpointNumber); | ||
| } | ||
|
|
||
| async getPendingCheckpoint(): Promise<PendingCheckpointData | undefined> { | ||
| const stored = await this.#pendingCheckpoint.getAsync(); | ||
| if (!stored) { | ||
| return undefined; | ||
| } | ||
| return { | ||
| checkpointNumber: CheckpointNumber(stored.checkpointNumber), | ||
| header: CheckpointHeader.fromBuffer(stored.header), | ||
| startBlock: BlockNumber(stored.startBlock), | ||
| blockCount: stored.blockCount, | ||
| totalManaUsed: BigInt(stored.totalManaUsed ?? '0'), | ||
| feeAssetPriceModifier: BigInt(stored.feeAssetPriceModifier ?? '0'), | ||
| }; | ||
| } | ||
|
|
||
| async getPendingCheckpointNumber(): Promise<CheckpointNumber> { | ||
| const pending = await this.getPendingCheckpoint(); | ||
| return CheckpointNumber(pending?.checkpointNumber ?? INITIAL_CHECKPOINT_NUMBER - 1); | ||
| } | ||
|
|
||
| async getPendingCheckpointL2BlockNumber(): Promise<BlockNumber> { | ||
| const pending = await this.getPendingCheckpoint(); | ||
| if (!pending) { | ||
| return BlockNumber(INITIAL_L2_BLOCK_NUM - 1); | ||
| } | ||
| return BlockNumber(pending.startBlock + pending.blockCount - 1); | ||
| } | ||
|
|
||
| async getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined> { | ||
| const blockStorage = await this.#blocks.getAsync(number); | ||
| if (!blockStorage) { | ||
|
|
@@ -950,6 +1020,30 @@ export class BlockStore { | |
| return this.#lastSynchedL1Block.set(l1BlockNumber); | ||
| } | ||
|
|
||
| /** Sets the pending checkpoint (quorum-attested but not yet L1-confirmed). Only accepts confirmed + 1. */ | ||
| async setPendingCheckpoint(pending: PendingCheckpointData) { | ||
| const current = await this.getPendingCheckpointNumber(); | ||
| if (pending.checkpointNumber <= current) { | ||
| this.#log.warn(`Ignoring stale pending checkpoint number ${pending.checkpointNumber} (current: ${current})`); | ||
| return; | ||
| } | ||
| const confirmed = await this.getLatestCheckpointNumber(); | ||
| if (pending.checkpointNumber !== confirmed + 1) { | ||
| this.#log.warn( | ||
| `Ignoring pending checkpoint ${pending.checkpointNumber}: expected ${confirmed + 1} (confirmed + 1)`, | ||
| ); | ||
| return; | ||
| } | ||
|
Comment on lines
+1025
to
+1036
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless we can think of legitimate situations for this, I'd throw instead of warning. It will help us catch inconsistencies easier. |
||
| await this.#pendingCheckpoint.set({ | ||
| header: pending.header.toBuffer(), | ||
| checkpointNumber: pending.checkpointNumber, | ||
| startBlock: pending.startBlock, | ||
| blockCount: pending.blockCount, | ||
| totalManaUsed: pending.totalManaUsed.toString(), | ||
| feeAssetPriceModifier: pending.feeAssetPriceModifier.toString(), | ||
| }); | ||
| } | ||
|
|
||
| async getProvenCheckpointNumber(): Promise<CheckpointNumber> { | ||
| const [latestCheckpointNumber, provenCheckpointNumber] = await Promise.all([ | ||
| this.getLatestCheckpointNumber(), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slightly confused by this. What happens if a checkpoint fails to land on L1? Surely the blocks covered by that (pending) checkpoint are removed? As well as all blocks built afterwards?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, md/pipeline-recovery-2 im dealing with this currently in this branch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking forward to it!