Skip to content

Commit a1ec88f

Browse files
author
AztecBot
committed
Merge branch 'v5-next' into merge-train/fairies-v5
2 parents 78ad75a + 42eea48 commit a1ec88f

55 files changed

Lines changed: 1697 additions & 295 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -224,23 +224,6 @@ export abstract class ArchiverDataSourceBase
224224
}
225225
}
226226

227-
/**
228-
* Resolves a {@link CheckpointsQuery} to a concrete `{from, limit}` pair used by BlockStore,
229-
* or undefined when the epoch has no checkpoints.
230-
*/
231-
private async resolveCheckpointsQuery(
232-
query: CheckpointsQuery,
233-
): Promise<{ from: CheckpointNumber; limit: number } | undefined> {
234-
if ('from' in query) {
235-
return query;
236-
}
237-
const numbers = await this.getCheckpointNumbersForEpoch(query.epoch);
238-
if (numbers.length === 0) {
239-
return undefined;
240-
}
241-
return { from: numbers[0], limit: numbers.length };
242-
}
243-
244227
public async getCheckpoint(query: CheckpointQuery): Promise<PublishedCheckpoint | undefined> {
245228
const number = await this.resolveCheckpointQuery(query);
246229
if (number === undefined || number === 0) {
@@ -254,11 +237,7 @@ export abstract class ArchiverDataSourceBase
254237
}
255238

256239
public async getCheckpoints(query: CheckpointsQuery): Promise<PublishedCheckpoint[]> {
257-
const resolved = await this.resolveCheckpointsQuery(query);
258-
if (!resolved) {
259-
return [];
260-
}
261-
const checkpoints = await this.stores.blocks.getRangeOfCheckpoints(resolved.from, resolved.limit);
240+
const checkpoints = await this.getCheckpointsData(query);
262241
return Promise.all(checkpoints.map(ch => this.getPublishedCheckpointFromCheckpointData(ch)));
263242
}
264243

@@ -271,11 +250,14 @@ export abstract class ArchiverDataSourceBase
271250
}
272251

273252
public async getCheckpointsData(query: CheckpointsQuery): Promise<CheckpointData[]> {
274-
const resolved = await this.resolveCheckpointsQuery(query);
275-
if (!resolved) {
276-
return [];
253+
if ('fromSlot' in query) {
254+
return this.stores.blocks.getCheckpointsBySlot(query.fromSlot, query.limit, query.reverse ?? false);
277255
}
278-
return this.stores.blocks.getRangeOfCheckpoints(resolved.from, resolved.limit);
256+
if ('from' in query) {
257+
return this.stores.blocks.getRangeOfCheckpoints(query.from, query.limit);
258+
}
259+
const numbers = await this.getCheckpointNumbersForEpoch(query.epoch);
260+
return numbers.length > 0 ? this.stores.blocks.getRangeOfCheckpoints(numbers[0], numbers.length) : [];
279261
}
280262

281263
public getProposedCheckpointData(query?: ProposedCheckpointQuery): Promise<ProposedCheckpointData | undefined> {

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,6 +1686,77 @@ describe('BlockStore', () => {
16861686
});
16871687
});
16881688

1689+
describe('getCheckpointsBySlot', () => {
1690+
// cp1: slot 5, blocks 1-2 | cp2: slot 8, blocks 3-5 | cp3: slot 12, block 6.
1691+
const addCheckpointsAtSlots = async () => {
1692+
const cp1 = makePublishedCheckpoint(
1693+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1, slotNumber: SlotNumber(5) }),
1694+
10,
1695+
);
1696+
const cp2 = makePublishedCheckpoint(
1697+
await Checkpoint.random(CheckpointNumber(2), {
1698+
numBlocks: 3,
1699+
startBlockNumber: 3,
1700+
previousArchive: cp1.checkpoint.blocks.at(-1)!.archive,
1701+
slotNumber: SlotNumber(8),
1702+
}),
1703+
11,
1704+
);
1705+
const cp3 = makePublishedCheckpoint(
1706+
await Checkpoint.random(CheckpointNumber(3), {
1707+
numBlocks: 1,
1708+
startBlockNumber: 6,
1709+
previousArchive: cp2.checkpoint.blocks.at(-1)!.archive,
1710+
slotNumber: SlotNumber(12),
1711+
}),
1712+
12,
1713+
);
1714+
await blockStore.addCheckpoints([cp1, cp2, cp3]);
1715+
};
1716+
1717+
const numbersBySlot = (slot: number, limit: number, reverse: boolean) =>
1718+
blockStore.getCheckpointsBySlot(SlotNumber(slot), limit, reverse).then(cps => cps.map(cp => cp.checkpointNumber));
1719+
1720+
it('returns empty array when no checkpoints exist', async () => {
1721+
expect(await numbersBySlot(10, 1, true)).toEqual([]);
1722+
});
1723+
1724+
it('returns the latest checkpoint at or before the slot when reverse (exact hit)', async () => {
1725+
await addCheckpointsAtSlots();
1726+
const [cp] = await blockStore.getCheckpointsBySlot(SlotNumber(8), 1, true);
1727+
expect(cp.checkpointNumber).toBe(2);
1728+
expect(cp.startBlock).toBe(3);
1729+
expect(cp.blockCount).toBe(3);
1730+
});
1731+
1732+
it('walks back to the nearest earlier checkpoint when the slot falls in a gap', async () => {
1733+
await addCheckpointsAtSlots();
1734+
// Slot 10 has no checkpoint; the nearest at or before is cp2 at slot 8.
1735+
expect(await numbersBySlot(10, 1, true)).toEqual([2]);
1736+
});
1737+
1738+
it('returns empty when reverse and the slot precedes the earliest checkpoint', async () => {
1739+
await addCheckpointsAtSlots();
1740+
expect(await numbersBySlot(4, 1, true)).toEqual([]);
1741+
});
1742+
1743+
it('returns multiple checkpoints nearest-first when reverse', async () => {
1744+
await addCheckpointsAtSlots();
1745+
expect(await numbersBySlot(12, 2, true)).toEqual([3, 2]);
1746+
});
1747+
1748+
it('returns the earliest checkpoint at or after the slot when forward', async () => {
1749+
await addCheckpointsAtSlots();
1750+
// Slot 6 has no checkpoint; the nearest at or after is cp2 at slot 8.
1751+
expect(await numbersBySlot(6, 1, false)).toEqual([2]);
1752+
});
1753+
1754+
it('returns multiple checkpoints ascending when forward', async () => {
1755+
await addCheckpointsAtSlots();
1756+
expect(await numbersBySlot(5, 3, false)).toEqual([1, 2, 3]);
1757+
});
1758+
});
1759+
16891760
describe('getCheckpointedBlock', () => {
16901761
beforeEach(async () => {
16911762
await blockStore.addCheckpoints(publishedCheckpoints);

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,26 @@ export class BlockStore {
627627
return checkpoints;
628628
}
629629

630+
/**
631+
* Returns up to `limit` checkpoints anchored at `fromSlot`, ordered nearest-first, walking the slot index.
632+
* With `reverse`, takes the checkpoints at or before `fromSlot` (descending by slot); otherwise the
633+
* checkpoints at or after it (ascending). `limit: 1, reverse: true` yields the latest checkpoint at or
634+
* before the slot in a single range scan.
635+
*/
636+
async getCheckpointsBySlot(fromSlot: SlotNumber, limit: number, reverse: boolean): Promise<CheckpointData[]> {
637+
// The KV range bounds are direction-dependent: forward is [start, end), reverse is (start, end], so a
638+
// reverse scan uses `end: fromSlot` (inclusive) with no +1 to include the checkpoint at fromSlot itself.
639+
const range = reverse ? { end: fromSlot, reverse: true, limit } : { start: fromSlot, limit };
640+
const result: CheckpointData[] = [];
641+
for await (const [, checkpointNumber] of this.#slotToCheckpoint.entriesAsync(range)) {
642+
const checkpointStorage = await this.#checkpoints.getAsync(checkpointNumber);
643+
if (checkpointStorage) {
644+
result.push(this.checkpointDataFromCheckpointStorage(checkpointStorage));
645+
}
646+
}
647+
return result;
648+
}
649+
630650
/** Returns checkpoint data for all checkpoints whose slot falls within the given range (inclusive). */
631651
async getCheckpointDataForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise<CheckpointData[]> {
632652
const result: CheckpointData[] = [];

yarn-project/archiver/src/test/mock_l2_block_source.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,13 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
323323
if ('from' in query) {
324324
return this.checkpointList.slice(query.from - 1, query.from - 1 + query.limit);
325325
}
326+
if ('fromSlot' in query) {
327+
const matching = this.checkpointList.filter(c =>
328+
query.reverse ? c.header.slotNumber <= query.fromSlot : c.header.slotNumber >= query.fromSlot,
329+
);
330+
const nearestFirst = query.reverse ? matching.reverse() : matching;
331+
return nearestFirst.slice(0, query.limit);
332+
}
326333
return this.getCheckpointsInEpoch(query.epoch);
327334
}
328335

0 commit comments

Comments
 (0)