Skip to content

Commit 6403d34

Browse files
committed
chore: unwinding work
1 parent 34301b6 commit 6403d34

File tree

11 files changed

+280
-101
lines changed

11 files changed

+280
-101
lines changed

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

+13-2
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,17 @@ enum Operation {
561561
* store would need to include otherwise while exposing fewer functions and logic directly to the archiver.
562562
*/
563563
class ArchiverStoreHelper
564-
implements Omit<ArchiverDataStore, 'addLogs' | 'addContractClasses' | 'addContractInstances' | 'addFunctions'>
564+
implements
565+
Omit<
566+
ArchiverDataStore,
567+
| 'addLogs'
568+
| 'deleteLogs'
569+
| 'addContractClasses'
570+
| 'deleteContractClasses'
571+
| 'addContractInstances'
572+
| 'deleteContractInstances'
573+
| 'addFunctions'
574+
>
565575
{
566576
#log = createDebugLogger('aztec:archiver:block-helper');
567577

@@ -699,7 +709,8 @@ class ArchiverStoreHelper
699709
await this.#updateDeployedContractInstances(blockLogs, block.data.number, Operation.Delete);
700710
}),
701711
)),
702-
await this.store.unwindBlocks(from, blocksToUnwind),
712+
this.store.deleteLogs(blocks.map(b => b.data)),
713+
this.store.unwindBlocks(from, blocksToUnwind),
703714
].every(Boolean);
704715
}
705716

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

+4
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export interface ArchiverDataStore {
8484
* @returns True if the operation is successful.
8585
*/
8686
addLogs(blocks: L2Block[]): Promise<boolean>;
87+
deleteLogs(blocks: L2Block[]): Promise<boolean>;
8788

8889
/**
8990
* Append L1 to L2 messages to the store.
@@ -170,6 +171,8 @@ export interface ArchiverDataStore {
170171
*/
171172
addContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean>;
172173

174+
deleteContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean>;
175+
173176
/**
174177
* Returns a contract class given its id, or undefined if not exists.
175178
* @param id - Id of the contract class.
@@ -183,6 +186,7 @@ export interface ArchiverDataStore {
183186
* @returns True if the operation is successful.
184187
*/
185188
addContractInstances(data: ContractInstanceWithAddress[], blockNumber: number): Promise<boolean>;
189+
deleteContractInstances(data: ContractInstanceWithAddress[], blockNumber: number): Promise<boolean>;
186190

187191
/**
188192
* Adds private functions to a contract class.

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

+86
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
5252
});
5353
});
5454

55+
describe('unwindBlocks', () => {
56+
it('unwinding blocks will remove blocks from the chain', async () => {
57+
await store.addBlocks(blocks);
58+
const blockNumber = await store.getSynchedL2BlockNumber();
59+
60+
expect(await store.getBlocks(blockNumber, 1)).toEqual([blocks[blocks.length - 1]]);
61+
62+
await store.unwindBlocks(blockNumber, 1);
63+
64+
expect(await store.getSynchedL2BlockNumber()).toBe(blockNumber - 1);
65+
expect(await store.getBlocks(blockNumber, 1)).toEqual([]);
66+
});
67+
});
68+
5569
describe('getBlocks', () => {
5670
beforeEach(async () => {
5771
await store.addBlocks(blocks);
@@ -120,12 +134,32 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
120134
});
121135
});
122136

137+
describe('deleteLogs', () => {
138+
it('deletes encrypted & unencrypted logs', async () => {
139+
const block = blocks[0].data;
140+
await store.addBlocks([blocks[0]]);
141+
await expect(store.addLogs([block])).resolves.toEqual(true);
142+
143+
expect((await store.getLogs(1, 1, LogType.NOTEENCRYPTED))[0]).toEqual(block.body.noteEncryptedLogs);
144+
expect((await store.getLogs(1, 1, LogType.ENCRYPTED))[0]).toEqual(block.body.encryptedLogs);
145+
expect((await store.getLogs(1, 1, LogType.UNENCRYPTED))[0]).toEqual(block.body.unencryptedLogs);
146+
147+
// This one is a pain for memory as we would never want to just delete memory in the middle.
148+
await store.deleteLogs([block]);
149+
150+
expect((await store.getLogs(1, 1, LogType.NOTEENCRYPTED))[0]).toEqual(undefined);
151+
expect((await store.getLogs(1, 1, LogType.ENCRYPTED))[0]).toEqual(undefined);
152+
expect((await store.getLogs(1, 1, LogType.UNENCRYPTED))[0]).toEqual(undefined);
153+
});
154+
});
155+
123156
describe.each([
124157
['note_encrypted', LogType.NOTEENCRYPTED],
125158
['encrypted', LogType.ENCRYPTED],
126159
['unencrypted', LogType.UNENCRYPTED],
127160
])('getLogs (%s)', (_, logType) => {
128161
beforeEach(async () => {
162+
await store.addBlocks(blocks);
129163
await store.addLogs(blocks.map(b => b.data));
130164
});
131165

@@ -167,6 +201,24 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
167201
it('returns undefined if tx is not found', async () => {
168202
await expect(store.getTxEffect(new TxHash(Fr.random().toBuffer()))).resolves.toBeUndefined();
169203
});
204+
205+
it.each([
206+
() => blocks[0].data.body.txEffects[0],
207+
() => blocks[9].data.body.txEffects[3],
208+
() => blocks[3].data.body.txEffects[1],
209+
() => blocks[5].data.body.txEffects[2],
210+
() => blocks[1].data.body.txEffects[0],
211+
])('tries to retrieves a previously stored transaction after deleted', async getExpectedTx => {
212+
await store.unwindBlocks(blocks.length, blocks.length);
213+
214+
const expectedTx = getExpectedTx();
215+
const actualTx = await store.getTxEffect(expectedTx.txHash);
216+
expect(actualTx).toEqual(undefined);
217+
});
218+
219+
it('returns undefined if tx is not found', async () => {
220+
await expect(store.getTxEffect(new TxHash(Fr.random().toBuffer()))).resolves.toBeUndefined();
221+
});
170222
});
171223

172224
describe('L1 to L2 Messages', () => {
@@ -237,6 +289,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
237289
it('returns undefined if contract instance is not found', async () => {
238290
await expect(store.getContractInstance(AztecAddress.random())).resolves.toBeUndefined();
239291
});
292+
293+
it('returns undefined if previously stored contract instances was deleted', async () => {
294+
await store.deleteContractInstances([contractInstance], blockNum);
295+
await expect(store.getContractInstance(contractInstance.address)).resolves.toBeUndefined();
296+
});
240297
});
241298

242299
describe('contractClasses', () => {
@@ -252,6 +309,17 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
252309
await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
253310
});
254311

312+
it('returns undefined if the initial deployed contract class was deleted', async () => {
313+
await store.deleteContractClasses([contractClass], blockNum);
314+
await expect(store.getContractClass(contractClass.id)).resolves.toBeUndefined();
315+
});
316+
317+
it('returns contract class if later "deployment" class was deleted', async () => {
318+
await store.addContractClasses([contractClass], blockNum + 1);
319+
await store.deleteContractClasses([contractClass], blockNum + 1);
320+
await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
321+
});
322+
255323
it('returns undefined if contract class is not found', async () => {
256324
await expect(store.getContractClass(Fr.random())).resolves.toBeUndefined();
257325
});
@@ -304,6 +372,24 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
304372
await store.addLogs(blocks.map(b => b.data));
305373
});
306374

375+
it('no logs returned if deleted ("txHash" filter param is respected variant)', async () => {
376+
// get random tx
377+
const targetBlockIndex = randomInt(numBlocks);
378+
const targetTxIndex = randomInt(txsPerBlock);
379+
const targetTxHash = blocks[targetBlockIndex].data.body.txEffects[targetTxIndex].txHash;
380+
381+
await Promise.all([
382+
store.unwindBlocks(blocks.length, blocks.length),
383+
store.deleteLogs(blocks.map(b => b.data)),
384+
]);
385+
386+
const response = await store.getUnencryptedLogs({ txHash: targetTxHash });
387+
const logs = response.logs;
388+
389+
expect(response.maxLogsHit).toBeFalsy();
390+
expect(logs.length).toEqual(0);
391+
});
392+
307393
it('"txHash" filter param is respected', async () => {
308394
// get random tx
309395
const targetBlockIndex = randomInt(numBlocks);

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export class BlockStore {
8989
// We should only allow deleting the very last ye, otherwise we can really get some messy shit.
9090
const last = this.getSynchedL2BlockNumber();
9191
if (from != last) {
92-
throw new Error(`Can only remove the tip`);
92+
throw new Error(`Can only remove from the tip`);
9393
}
9494

9595
for (let i = 0; i < blocksToUnwind; i++) {

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

+18-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/s
33
import { type AztecKVStore, type AztecMap } from '@aztec/kv-store';
44
import {
55
type ContractClassPublic,
6+
type ContractClassPublicWithBlockNumber,
67
type ExecutablePrivateFunctionWithMembershipProof,
78
type UnconstrainedFunctionWithMembershipProof,
89
} from '@aztec/types/contracts';
@@ -17,8 +18,18 @@ export class ContractClassStore {
1718
this.#contractClasses = db.openMap('archiver_contract_classes');
1819
}
1920

20-
addContractClass(contractClass: ContractClassPublic): Promise<void> {
21-
return this.#contractClasses.set(contractClass.id.toString(), serializeContractClassPublic(contractClass));
21+
async addContractClass(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
22+
await this.#contractClasses.setIfNotExists(
23+
contractClass.id.toString(),
24+
serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }),
25+
);
26+
}
27+
28+
async deleteContractClasses(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
29+
const restoredContractClass = this.#contractClasses.get(contractClass.id.toString());
30+
if (restoredContractClass && deserializeContractClassPublic(restoredContractClass).l2BlockNumber >= blockNumber) {
31+
await this.#contractClasses.delete(contractClass.id.toString());
32+
}
2233
}
2334

2435
getContractClass(id: Fr): ContractClassPublic | undefined {
@@ -44,7 +55,7 @@ export class ContractClassStore {
4455
const existingClass = deserializeContractClassPublic(existingClassBuffer);
4556
const { privateFunctions: existingPrivateFns, unconstrainedFunctions: existingUnconstrainedFns } = existingClass;
4657

47-
const updatedClass: Omit<ContractClassPublic, 'id'> = {
58+
const updatedClass: Omit<ContractClassPublicWithBlockNumber, 'id'> = {
4859
...existingClass,
4960
privateFunctions: [
5061
...existingPrivateFns,
@@ -63,8 +74,9 @@ export class ContractClassStore {
6374
}
6475
}
6576

66-
function serializeContractClassPublic(contractClass: Omit<ContractClassPublic, 'id'>): Buffer {
77+
function serializeContractClassPublic(contractClass: Omit<ContractClassPublicWithBlockNumber, 'id'>): Buffer {
6778
return serializeToBuffer(
79+
contractClass.l2BlockNumber,
6880
numToUInt8(contractClass.version),
6981
contractClass.artifactHash,
7082
contractClass.publicFunctions.length,
@@ -108,9 +120,10 @@ function serializeUnconstrainedFunction(fn: UnconstrainedFunctionWithMembershipP
108120
);
109121
}
110122

111-
function deserializeContractClassPublic(buffer: Buffer): Omit<ContractClassPublic, 'id'> {
123+
function deserializeContractClassPublic(buffer: Buffer): Omit<ContractClassPublicWithBlockNumber, 'id'> {
112124
const reader = BufferReader.asReader(buffer);
113125
return {
126+
l2BlockNumber: reader.readNumber(),
114127
version: reader.readUInt8() as 1,
115128
artifactHash: reader.readObject(Fr),
116129
publicFunctions: reader.readVector({

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

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export class ContractInstanceStore {
1919
);
2020
}
2121

22+
deleteContractInstance(contractInstance: ContractInstanceWithAddress): Promise<void> {
23+
return this.#contractInstances.delete(contractInstance.address.toString());
24+
}
25+
2226
getContractInstance(address: AztecAddress): ContractInstanceWithAddress | undefined {
2327
const contractInstance = this.#contractInstances.get(address.toString());
2428
return contractInstance && SerializableContractInstance.fromBuffer(contractInstance).withAddress(address);

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

+16-2
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,14 @@ export class KVArchiverDataStore implements ArchiverDataStore {
7474
return Promise.resolve(this.#contractInstanceStore.getContractInstance(address));
7575
}
7676

77-
async addContractClasses(data: ContractClassPublic[], _blockNumber: number): Promise<boolean> {
78-
return (await Promise.all(data.map(c => this.#contractClassStore.addContractClass(c)))).every(Boolean);
77+
async addContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean> {
78+
return (await Promise.all(data.map(c => this.#contractClassStore.addContractClass(c, blockNumber)))).every(Boolean);
79+
}
80+
81+
async deleteContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean> {
82+
return (await Promise.all(data.map(c => this.#contractClassStore.deleteContractClasses(c, blockNumber)))).every(
83+
Boolean,
84+
);
7985
}
8086

8187
addFunctions(
@@ -90,6 +96,10 @@ export class KVArchiverDataStore implements ArchiverDataStore {
9096
return (await Promise.all(data.map(c => this.#contractInstanceStore.addContractInstance(c)))).every(Boolean);
9197
}
9298

99+
async deleteContractInstances(data: ContractInstanceWithAddress[], _blockNumber: number): Promise<boolean> {
100+
return (await Promise.all(data.map(c => this.#contractInstanceStore.deleteContractInstance(c)))).every(Boolean);
101+
}
102+
93103
/**
94104
* Append new blocks to the store's list.
95105
* @param blocks - The L2 blocks to be added to the store and the last processed L1 block.
@@ -153,6 +163,10 @@ export class KVArchiverDataStore implements ArchiverDataStore {
153163
return this.#logStore.addLogs(blocks);
154164
}
155165

166+
deleteLogs(blocks: L2Block[]): Promise<boolean> {
167+
return this.#logStore.deleteLogs(blocks);
168+
}
169+
156170
/**
157171
* Append L1 to L2 messages to the store.
158172
* @param messages - The L1 to L2 messages to be added to the store and the last processed L1 block.

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

+13-9
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,21 @@ export class LogStore {
4444
addLogs(blocks: L2Block[]): Promise<boolean> {
4545
return this.db.transaction(() => {
4646
blocks.forEach(block => {
47-
if (block.body.noteEncryptedLogs) {
48-
void this.#noteEncryptedLogs.set(block.number, block.body.noteEncryptedLogs.toBuffer());
49-
}
47+
void this.#noteEncryptedLogs.set(block.number, block.body.noteEncryptedLogs.toBuffer());
48+
void this.#encryptedLogs.set(block.number, block.body.encryptedLogs.toBuffer());
49+
void this.#unencryptedLogs.set(block.number, block.body.unencryptedLogs.toBuffer());
50+
});
5051

51-
if (block.body.encryptedLogs) {
52-
void this.#encryptedLogs.set(block.number, block.body.encryptedLogs.toBuffer());
53-
}
52+
return true;
53+
});
54+
}
5455

55-
if (block.body.unencryptedLogs) {
56-
void this.#unencryptedLogs.set(block.number, block.body.unencryptedLogs.toBuffer());
57-
}
56+
deleteLogs(blocks: L2Block[]): Promise<boolean> {
57+
return this.db.transaction(() => {
58+
blocks.forEach(block => {
59+
void this.#noteEncryptedLogs.delete(block.number);
60+
void this.#encryptedLogs.delete(block.number);
61+
void this.#unencryptedLogs.delete(block.number);
5862
});
5963

6064
return true;

0 commit comments

Comments
 (0)