Skip to content

Commit ec34442

Browse files
authored
fix: Allow unwinding multiple empty blocks (#10084)
The archiver kv store used the block *body* hash as identifier to store the block bodies. This means that two empty blocks would have the same identifier. So, when unwinding (ie deleting) two or more empty blocks, the first one would remove the body that corresponds to an empty block, and the second one would fail with "Body could not be retrieved". This fixes the issue by using the block hash, guaranteed to be unique, as a key to store the block bodies.
1 parent 375482f commit ec34442

File tree

5 files changed

+39
-14
lines changed

5 files changed

+39
-14
lines changed

yarn-project/archiver/src/archiver/archiver_store_test_suite.ts

+25-4
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,18 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
3838
[5, 2, () => blocks.slice(4, 6)],
3939
];
4040

41+
const makeL1Published = (block: L2Block, l1BlockNumber: number): L1Published<L2Block> => ({
42+
data: block,
43+
l1: {
44+
blockNumber: BigInt(l1BlockNumber),
45+
blockHash: `0x${l1BlockNumber}`,
46+
timestamp: BigInt(l1BlockNumber * 1000),
47+
},
48+
});
49+
4150
beforeEach(() => {
4251
store = getStore();
43-
blocks = times(10, i => ({
44-
data: L2Block.random(i + 1),
45-
l1: { blockNumber: BigInt(i + 10), blockHash: `0x${i}`, timestamp: BigInt(i * 1000) },
46-
}));
52+
blocks = times(10, i => makeL1Published(L2Block.random(i + 1), i + 10));
4753
});
4854

4955
describe('addBlocks', () => {
@@ -69,6 +75,21 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
6975
expect(await store.getSynchedL2BlockNumber()).toBe(blockNumber - 1);
7076
expect(await store.getBlocks(blockNumber, 1)).toEqual([]);
7177
});
78+
79+
it('can unwind multiple empty blocks', async () => {
80+
const emptyBlocks = times(10, i => makeL1Published(L2Block.random(i + 1, 0), i + 10));
81+
await store.addBlocks(emptyBlocks);
82+
expect(await store.getSynchedL2BlockNumber()).toBe(10);
83+
84+
await store.unwindBlocks(10, 3);
85+
expect(await store.getSynchedL2BlockNumber()).toBe(7);
86+
expect((await store.getBlocks(1, 10)).map(b => b.data.number)).toEqual([1, 2, 3, 4, 5, 6, 7]);
87+
});
88+
89+
it('refuses to unwind blocks if the tip is not the last block', async () => {
90+
await store.addBlocks(blocks);
91+
await expect(store.unwindBlocks(5, 1)).rejects.toThrow(/can only unwind blocks from the tip/i);
92+
});
7293
});
7394

7495
describe('getBlocks', () => {

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

+11-7
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class BlockStore {
2020
/** Map block number to block data */
2121
#blocks: AztecMap<number, BlockStorage>;
2222

23-
/** Map block body hash to block body */
23+
/** Map block hash to block body */
2424
#blockBodies: AztecMap<string, Buffer>;
2525

2626
/** Stores L1 block number in which the last processed L2 block was included */
@@ -72,7 +72,7 @@ export class BlockStore {
7272
void this.#txIndex.set(tx.txHash.toString(), [block.data.number, i]);
7373
});
7474

75-
void this.#blockBodies.set(block.data.body.getTxsEffectsHash().toString('hex'), block.data.body.toBuffer());
75+
void this.#blockBodies.set(block.data.hash().toString(), block.data.body.toBuffer());
7676
}
7777

7878
void this.#lastSynchedL1Block.set(blocks[blocks.length - 1].l1.blockNumber);
@@ -92,7 +92,7 @@ export class BlockStore {
9292
return this.db.transaction(() => {
9393
const last = this.getSynchedL2BlockNumber();
9494
if (from != last) {
95-
throw new Error(`Can only remove from the tip`);
95+
throw new Error(`Can only unwind blocks from the tip (requested ${from} but current tip is ${last})`);
9696
}
9797

9898
for (let i = 0; i < blocksToUnwind; i++) {
@@ -106,7 +106,9 @@ export class BlockStore {
106106
block.data.body.txEffects.forEach(tx => {
107107
void this.#txIndex.delete(tx.txHash.toString());
108108
});
109-
void this.#blockBodies.delete(block.data.body.getTxsEffectsHash().toString('hex'));
109+
const blockHash = block.data.hash().toString();
110+
void this.#blockBodies.delete(blockHash);
111+
this.#log.debug(`Unwound block ${blockNumber} ${blockHash}`);
110112
}
111113

112114
return true;
@@ -154,10 +156,12 @@ export class BlockStore {
154156
private getBlockFromBlockStorage(blockStorage: BlockStorage) {
155157
const header = Header.fromBuffer(blockStorage.header);
156158
const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
157-
158-
const blockBodyBuffer = this.#blockBodies.get(header.contentCommitment.txsEffectsHash.toString('hex'));
159+
const blockHash = header.hash().toString();
160+
const blockBodyBuffer = this.#blockBodies.get(blockHash);
159161
if (blockBodyBuffer === undefined) {
160-
throw new Error('Body could not be retrieved');
162+
throw new Error(
163+
`Could not retrieve body for block ${header.globalVariables.blockNumber.toNumber()} ${blockHash}`,
164+
);
161165
}
162166
const body = Body.fromBuffer(blockBodyBuffer);
163167

yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export class MemoryArchiverStore implements ArchiverDataStore {
201201
public async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
202202
const last = await this.getSynchedL2BlockNumber();
203203
if (from != last) {
204-
throw new Error(`Can only remove the tip`);
204+
throw new Error(`Can only unwind blocks from the tip (requested ${from} but current tip is ${last})`);
205205
}
206206

207207
const stopAt = from - blocksToUnwind;

yarn-project/circuit-types/src/interfaces/archiver.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ describe('ArchiverApiSchema', () => {
230230

231231
it('addContractArtifact', async () => {
232232
await context.client.addContractArtifact(AztecAddress.random(), artifact);
233-
});
233+
}, 20_000);
234234

235235
it('getContract', async () => {
236236
const address = AztecAddress.random();

yarn-project/circuit-types/src/interfaces/aztec-node.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ describe('AztecNodeApiSchema', () => {
207207

208208
it('addContractArtifact', async () => {
209209
await context.client.addContractArtifact(AztecAddress.random(), artifact);
210-
});
210+
}, 20_000);
211211

212212
it('getLogs(Encrypted)', async () => {
213213
const response = await context.client.getLogs(1, 1, LogType.ENCRYPTED);

0 commit comments

Comments
 (0)