diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index e41fd09dc8cf..133f327e708b 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -561,7 +561,17 @@ enum Operation { * store would need to include otherwise while exposing fewer functions and logic directly to the archiver. */ class ArchiverStoreHelper - implements Omit + implements + Omit< + ArchiverDataStore, + | 'addLogs' + | 'deleteLogs' + | 'addContractClasses' + | 'deleteContractClasses' + | 'addContractInstances' + | 'deleteContractInstances' + | 'addFunctions' + > { #log = createDebugLogger('aztec:archiver:block-helper'); @@ -699,7 +709,8 @@ class ArchiverStoreHelper await this.#updateDeployedContractInstances(blockLogs, block.data.number, Operation.Delete); }), )), - await this.store.unwindBlocks(from, blocksToUnwind), + this.store.deleteLogs(blocks.map(b => b.data)), + this.store.unwindBlocks(from, blocksToUnwind), ].every(Boolean); } diff --git a/yarn-project/archiver/src/archiver/archiver_store.ts b/yarn-project/archiver/src/archiver/archiver_store.ts index 95e3376312d2..93f72a2b01f4 100644 --- a/yarn-project/archiver/src/archiver/archiver_store.ts +++ b/yarn-project/archiver/src/archiver/archiver_store.ts @@ -1,6 +1,4 @@ import { - type EncryptedL2BlockL2Logs, - type EncryptedNoteL2BlockL2Logs, type FromLogType, type GetUnencryptedLogsResponse, type InboxLeaf, @@ -86,6 +84,7 @@ export interface ArchiverDataStore { * @returns True if the operation is successful. */ addLogs(blocks: L2Block[]): Promise; + deleteLogs(blocks: L2Block[]): Promise; /** * Append L1 to L2 messages to the store. @@ -172,6 +171,8 @@ export interface ArchiverDataStore { */ addContractClasses(data: ContractClassPublic[], blockNumber: number): Promise; + deleteContractClasses(data: ContractClassPublic[], blockNumber: number): Promise; + /** * Returns a contract class given its id, or undefined if not exists. * @param id - Id of the contract class. @@ -185,6 +186,7 @@ export interface ArchiverDataStore { * @returns True if the operation is successful. */ addContractInstances(data: ContractInstanceWithAddress[], blockNumber: number): Promise; + deleteContractInstances(data: ContractInstanceWithAddress[], blockNumber: number): Promise; /** * Adds private functions to a contract class. diff --git a/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts b/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts index c8bddee5f8cb..f68b0a7c784d 100644 --- a/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts +++ b/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts @@ -52,6 +52,20 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch }); }); + describe('unwindBlocks', () => { + it('unwinding blocks will remove blocks from the chain', async () => { + await store.addBlocks(blocks); + const blockNumber = await store.getSynchedL2BlockNumber(); + + expect(await store.getBlocks(blockNumber, 1)).toEqual([blocks[blocks.length - 1]]); + + await store.unwindBlocks(blockNumber, 1); + + expect(await store.getSynchedL2BlockNumber()).toBe(blockNumber - 1); + expect(await store.getBlocks(blockNumber, 1)).toEqual([]); + }); + }); + describe('getBlocks', () => { beforeEach(async () => { await store.addBlocks(blocks); @@ -120,12 +134,32 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch }); }); + describe('deleteLogs', () => { + it('deletes encrypted & unencrypted logs', async () => { + const block = blocks[0].data; + await store.addBlocks([blocks[0]]); + await expect(store.addLogs([block])).resolves.toEqual(true); + + expect((await store.getLogs(1, 1, LogType.NOTEENCRYPTED))[0]).toEqual(block.body.noteEncryptedLogs); + expect((await store.getLogs(1, 1, LogType.ENCRYPTED))[0]).toEqual(block.body.encryptedLogs); + expect((await store.getLogs(1, 1, LogType.UNENCRYPTED))[0]).toEqual(block.body.unencryptedLogs); + + // This one is a pain for memory as we would never want to just delete memory in the middle. + await store.deleteLogs([block]); + + expect((await store.getLogs(1, 1, LogType.NOTEENCRYPTED))[0]).toEqual(undefined); + expect((await store.getLogs(1, 1, LogType.ENCRYPTED))[0]).toEqual(undefined); + expect((await store.getLogs(1, 1, LogType.UNENCRYPTED))[0]).toEqual(undefined); + }); + }); + describe.each([ ['note_encrypted', LogType.NOTEENCRYPTED], ['encrypted', LogType.ENCRYPTED], ['unencrypted', LogType.UNENCRYPTED], ])('getLogs (%s)', (_, logType) => { beforeEach(async () => { + await store.addBlocks(blocks); await store.addLogs(blocks.map(b => b.data)); }); @@ -167,6 +201,24 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch it('returns undefined if tx is not found', async () => { await expect(store.getTxEffect(new TxHash(Fr.random().toBuffer()))).resolves.toBeUndefined(); }); + + it.each([ + () => blocks[0].data.body.txEffects[0], + () => blocks[9].data.body.txEffects[3], + () => blocks[3].data.body.txEffects[1], + () => blocks[5].data.body.txEffects[2], + () => blocks[1].data.body.txEffects[0], + ])('tries to retrieves a previously stored transaction after deleted', async getExpectedTx => { + await store.unwindBlocks(blocks.length, blocks.length); + + const expectedTx = getExpectedTx(); + const actualTx = await store.getTxEffect(expectedTx.txHash); + expect(actualTx).toEqual(undefined); + }); + + it('returns undefined if tx is not found', async () => { + await expect(store.getTxEffect(new TxHash(Fr.random().toBuffer()))).resolves.toBeUndefined(); + }); }); describe('L1 to L2 Messages', () => { @@ -237,6 +289,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch it('returns undefined if contract instance is not found', async () => { await expect(store.getContractInstance(AztecAddress.random())).resolves.toBeUndefined(); }); + + it('returns undefined if previously stored contract instances was deleted', async () => { + await store.deleteContractInstances([contractInstance], blockNum); + await expect(store.getContractInstance(contractInstance.address)).resolves.toBeUndefined(); + }); }); describe('contractClasses', () => { @@ -252,6 +309,17 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass); }); + it('returns undefined if the initial deployed contract class was deleted', async () => { + await store.deleteContractClasses([contractClass], blockNum); + await expect(store.getContractClass(contractClass.id)).resolves.toBeUndefined(); + }); + + it('returns contract class if later "deployment" class was deleted', async () => { + await store.addContractClasses([contractClass], blockNum + 1); + await store.deleteContractClasses([contractClass], blockNum + 1); + await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass); + }); + it('returns undefined if contract class is not found', async () => { await expect(store.getContractClass(Fr.random())).resolves.toBeUndefined(); }); @@ -304,6 +372,24 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch await store.addLogs(blocks.map(b => b.data)); }); + it('no logs returned if deleted ("txHash" filter param is respected variant)', async () => { + // get random tx + const targetBlockIndex = randomInt(numBlocks); + const targetTxIndex = randomInt(txsPerBlock); + const targetTxHash = blocks[targetBlockIndex].data.body.txEffects[targetTxIndex].txHash; + + await Promise.all([ + store.unwindBlocks(blocks.length, blocks.length), + store.deleteLogs(blocks.map(b => b.data)), + ]); + + const response = await store.getUnencryptedLogs({ txHash: targetTxHash }); + const logs = response.logs; + + expect(response.maxLogsHit).toBeFalsy(); + expect(logs.length).toEqual(0); + }); + it('"txHash" filter param is respected', async () => { // get random tx const targetBlockIndex = randomInt(numBlocks); diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts index 38b37c32e045..5da5588b1a9c 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts @@ -89,7 +89,7 @@ export class BlockStore { // We should only allow deleting the very last ye, otherwise we can really get some messy shit. const last = this.getSynchedL2BlockNumber(); if (from != last) { - throw new Error(`Can only remove the tip`); + throw new Error(`Can only remove from the tip`); } for (let i = 0; i < blocksToUnwind; i++) { diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts index 8c14213d37e2..df0b7c80b9bd 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts @@ -3,6 +3,7 @@ import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/s import { type AztecKVStore, type AztecMap } from '@aztec/kv-store'; import { type ContractClassPublic, + type ContractClassPublicWithBlockNumber, type ExecutablePrivateFunctionWithMembershipProof, type UnconstrainedFunctionWithMembershipProof, } from '@aztec/types/contracts'; @@ -17,8 +18,18 @@ export class ContractClassStore { this.#contractClasses = db.openMap('archiver_contract_classes'); } - addContractClass(contractClass: ContractClassPublic): Promise { - return this.#contractClasses.set(contractClass.id.toString(), serializeContractClassPublic(contractClass)); + async addContractClass(contractClass: ContractClassPublic, blockNumber: number): Promise { + await this.#contractClasses.setIfNotExists( + contractClass.id.toString(), + serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }), + ); + } + + async deleteContractClasses(contractClass: ContractClassPublic, blockNumber: number): Promise { + const restoredContractClass = this.#contractClasses.get(contractClass.id.toString()); + if (restoredContractClass && deserializeContractClassPublic(restoredContractClass).l2BlockNumber >= blockNumber) { + await this.#contractClasses.delete(contractClass.id.toString()); + } } getContractClass(id: Fr): ContractClassPublic | undefined { @@ -44,7 +55,7 @@ export class ContractClassStore { const existingClass = deserializeContractClassPublic(existingClassBuffer); const { privateFunctions: existingPrivateFns, unconstrainedFunctions: existingUnconstrainedFns } = existingClass; - const updatedClass: Omit = { + const updatedClass: Omit = { ...existingClass, privateFunctions: [ ...existingPrivateFns, @@ -63,8 +74,9 @@ export class ContractClassStore { } } -function serializeContractClassPublic(contractClass: Omit): Buffer { +function serializeContractClassPublic(contractClass: Omit): Buffer { return serializeToBuffer( + contractClass.l2BlockNumber, numToUInt8(contractClass.version), contractClass.artifactHash, contractClass.publicFunctions.length, @@ -108,9 +120,10 @@ function serializeUnconstrainedFunction(fn: UnconstrainedFunctionWithMembershipP ); } -function deserializeContractClassPublic(buffer: Buffer): Omit { +function deserializeContractClassPublic(buffer: Buffer): Omit { const reader = BufferReader.asReader(buffer); return { + l2BlockNumber: reader.readNumber(), version: reader.readUInt8() as 1, artifactHash: reader.readObject(Fr), publicFunctions: reader.readVector({ diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/contract_instance_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/contract_instance_store.ts index 0c8b8597b654..9aa40c562712 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/contract_instance_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/contract_instance_store.ts @@ -19,6 +19,10 @@ export class ContractInstanceStore { ); } + deleteContractInstance(contractInstance: ContractInstanceWithAddress): Promise { + return this.#contractInstances.delete(contractInstance.address.toString()); + } + getContractInstance(address: AztecAddress): ContractInstanceWithAddress | undefined { const contractInstance = this.#contractInstances.get(address.toString()); return contractInstance && SerializableContractInstance.fromBuffer(contractInstance).withAddress(address); diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts index dad84a90b5bf..6c34f8e8220e 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts @@ -1,6 +1,4 @@ import { - type EncryptedL2BlockL2Logs, - type EncryptedNoteL2BlockL2Logs, type FromLogType, type GetUnencryptedLogsResponse, type InboxLeaf, @@ -11,7 +9,6 @@ import { type TxEffect, type TxHash, type TxReceipt, - type UnencryptedL2BlockL2Logs, } from '@aztec/circuit-types'; import { type Fr } from '@aztec/circuits.js'; import { type ContractArtifact } from '@aztec/foundation/abi'; @@ -77,8 +74,14 @@ export class KVArchiverDataStore implements ArchiverDataStore { return Promise.resolve(this.#contractInstanceStore.getContractInstance(address)); } - async addContractClasses(data: ContractClassPublic[], _blockNumber: number): Promise { - return (await Promise.all(data.map(c => this.#contractClassStore.addContractClass(c)))).every(Boolean); + async addContractClasses(data: ContractClassPublic[], blockNumber: number): Promise { + return (await Promise.all(data.map(c => this.#contractClassStore.addContractClass(c, blockNumber)))).every(Boolean); + } + + async deleteContractClasses(data: ContractClassPublic[], blockNumber: number): Promise { + return (await Promise.all(data.map(c => this.#contractClassStore.deleteContractClasses(c, blockNumber)))).every( + Boolean, + ); } addFunctions( @@ -93,6 +96,10 @@ export class KVArchiverDataStore implements ArchiverDataStore { return (await Promise.all(data.map(c => this.#contractInstanceStore.addContractInstance(c)))).every(Boolean); } + async deleteContractInstances(data: ContractInstanceWithAddress[], _blockNumber: number): Promise { + return (await Promise.all(data.map(c => this.#contractInstanceStore.deleteContractInstance(c)))).every(Boolean); + } + /** * Append new blocks to the store's list. * @param blocks - The L2 blocks to be added to the store and the last processed L1 block. @@ -156,6 +163,10 @@ export class KVArchiverDataStore implements ArchiverDataStore { return this.#logStore.addLogs(blocks); } + deleteLogs(blocks: L2Block[]): Promise { + return this.#logStore.deleteLogs(blocks); + } + /** * Append L1 to L2 messages to the store. * @param messages - The L1 to L2 messages to be added to the store and the last processed L1 block. diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/log_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/log_store.ts index 2f8a4ec3f1cb..58fbe849a02b 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/log_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/log_store.ts @@ -4,7 +4,7 @@ import { ExtendedUnencryptedL2Log, type FromLogType, type GetUnencryptedLogsResponse, - L2Block, + type L2Block, type L2BlockL2Logs, type LogFilter, LogId, @@ -44,17 +44,21 @@ export class LogStore { addLogs(blocks: L2Block[]): Promise { return this.db.transaction(() => { blocks.forEach(block => { - if (block.body.noteEncryptedLogs) { - void this.#noteEncryptedLogs.set(block.number, block.body.noteEncryptedLogs.toBuffer()); - } + void this.#noteEncryptedLogs.set(block.number, block.body.noteEncryptedLogs.toBuffer()); + void this.#encryptedLogs.set(block.number, block.body.encryptedLogs.toBuffer()); + void this.#unencryptedLogs.set(block.number, block.body.unencryptedLogs.toBuffer()); + }); - if (block.body.encryptedLogs) { - void this.#encryptedLogs.set(block.number, block.body.encryptedLogs.toBuffer()); - } + return true; + }); + } - if (block.body.unencryptedLogs) { - void this.#unencryptedLogs.set(block.number, block.body.unencryptedLogs.toBuffer()); - } + deleteLogs(blocks: L2Block[]): Promise { + return this.db.transaction(() => { + blocks.forEach(block => { + void this.#noteEncryptedLogs.delete(block.number); + void this.#encryptedLogs.delete(block.number); + void this.#unencryptedLogs.delete(block.number); }); return true; diff --git a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts index 4fb8c9e20e52..934bb43e2f05 100644 --- a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts @@ -20,6 +20,7 @@ import { type ContractArtifact } from '@aztec/foundation/abi'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; import { type ContractClassPublic, + type ContractClassPublicWithBlockNumber, type ContractInstanceWithAddress, type ExecutablePrivateFunctionWithMembershipProof, type UnconstrainedFunctionWithMembershipProof, @@ -44,23 +45,11 @@ export class MemoryArchiverStore implements ArchiverDataStore { */ private txEffects: TxEffect[] = []; - /** - * An array containing all the encrypted logs that have been fetched so far. - * Note: Index in the "outer" array equals to (corresponding L2 block's number - INITIAL_L2_BLOCK_NUM). - */ - private noteEncryptedLogsPerBlock: EncryptedNoteL2BlockL2Logs[] = []; + private noteEncryptedLogsPerBlock: Map = new Map(); - /** - * An array containing all the encrypted logs that have been fetched so far. - * Note: Index in the "outer" array equals to (corresponding L2 block's number - INITIAL_L2_BLOCK_NUM). - */ - private encryptedLogsPerBlock: EncryptedL2BlockL2Logs[] = []; + private encryptedLogsPerBlock: Map = new Map(); - /** - * An array containing all the unencrypted logs that have been fetched so far. - * Note: Index in the "outer" array equals to (corresponding L2 block's number - INITIAL_L2_BLOCK_NUM). - */ - private unencryptedLogsPerBlock: UnencryptedL2BlockL2Logs[] = []; + private unencryptedLogsPerBlock: Map = new Map(); /** * Contains all L1 to L2 messages. @@ -69,7 +58,7 @@ export class MemoryArchiverStore implements ArchiverDataStore { private contractArtifacts: Map = new Map(); - private contractClasses: Map = new Map(); + private contractClasses: Map = new Map(); private privateFunctions: Map = new Map(); @@ -128,9 +117,24 @@ export class MemoryArchiverStore implements ArchiverDataStore { return Promise.resolve(true); } - public addContractClasses(data: ContractClassPublic[], _blockNumber: number): Promise { + public addContractClasses(data: ContractClassPublic[], blockNumber: number): Promise { for (const contractClass of data) { - this.contractClasses.set(contractClass.id.toString(), contractClass); + if (!this.contractClasses.has(contractClass.id.toString())) { + this.contractClasses.set(contractClass.id.toString(), { + ...contractClass, + l2BlockNumber: blockNumber, + }); + } + } + return Promise.resolve(true); + } + + public deleteContractClasses(data: ContractClassPublic[], blockNumber: number): Promise { + for (const contractClass of data) { + const restored = this.contractClasses.get(contractClass.id.toString()); + if (restored && restored.l2BlockNumber >= blockNumber) { + this.contractClasses.delete(contractClass.id.toString()); + } } return Promise.resolve(true); } @@ -142,6 +146,13 @@ export class MemoryArchiverStore implements ArchiverDataStore { return Promise.resolve(true); } + public deleteContractInstances(data: ContractInstanceWithAddress[], _blockNumber: number): Promise { + for (const contractInstance of data) { + this.contractInstances.delete(contractInstance.address.toString()); + } + return Promise.resolve(true); + } + /** * Append new blocks to the store's list. * @param blocks - The L2 blocks to be added to the store and the last processed L1 block. @@ -172,17 +183,12 @@ export class MemoryArchiverStore implements ArchiverDataStore { throw new Error(`Can only remove the tip`); } - const blocks = await this.getBlocks(from - blocksToUnwind, blocksToUnwind); - if (blocks.length != blocksToUnwind) { - throw new Error(`Invalid number of blocks received ${blocks.length}`); - } - - while (blocks.length > 0) { - const block = blocks.pop(); - if (block === undefined) { + const stopAt = from - blocksToUnwind; + while ((await this.getSynchedL2BlockNumber()) > stopAt) { + const block = this.l2Blocks.pop(); + if (block == undefined) { break; } - this.l2Blocks.pop(); block.data.body.txEffects.forEach(() => this.txEffects.pop()); } @@ -196,17 +202,18 @@ export class MemoryArchiverStore implements ArchiverDataStore { */ addLogs(blocks: L2Block[]): Promise { blocks.forEach(block => { - if (block.body.noteEncryptedLogs) { - this.noteEncryptedLogsPerBlock[block.number - INITIAL_L2_BLOCK_NUM] = block.body.noteEncryptedLogs; - } - - if (block.body.encryptedLogs) { - this.encryptedLogsPerBlock[block.number - INITIAL_L2_BLOCK_NUM] = block.body.encryptedLogs; - } + this.noteEncryptedLogsPerBlock.set(block.number, block.body.noteEncryptedLogs); + this.encryptedLogsPerBlock.set(block.number, block.body.encryptedLogs); + this.unencryptedLogsPerBlock.set(block.number, block.body.unencryptedLogs); + }); + return Promise.resolve(true); + } - if (block.body.unencryptedLogs) { - this.unencryptedLogsPerBlock[block.number - INITIAL_L2_BLOCK_NUM] = block.body.unencryptedLogs; - } + deleteLogs(blocks: L2Block[]): Promise { + blocks.forEach(block => { + this.encryptedLogsPerBlock.delete(block.number); + this.noteEncryptedLogsPerBlock.delete(block.number); + this.unencryptedLogsPerBlock.delete(block.number); }); return Promise.resolve(true); @@ -327,7 +334,12 @@ export class MemoryArchiverStore implements ArchiverDataStore { if (from < INITIAL_L2_BLOCK_NUM || limit < 1) { throw new Error(`Invalid limit: ${limit}`); } - const logs = (() => { + + if (from > this.l2Blocks.length) { + throw new Error(`"from" cannot be in the future`); + } + + const logMap = (() => { switch (logType) { case LogType.ENCRYPTED: return this.encryptedLogsPerBlock; @@ -337,14 +349,24 @@ export class MemoryArchiverStore implements ArchiverDataStore { default: return this.unencryptedLogsPerBlock; } - })() as L2BlockL2Logs>[]; + })() as Map>>; - if (from > logs.length) { - return Promise.resolve([]); - } - const startIndex = from - INITIAL_L2_BLOCK_NUM; + const startIndex = from; const endIndex = startIndex + limit; - return Promise.resolve(logs.slice(startIndex, endIndex)); + const upper = Math.min(endIndex, this.l2Blocks.length + INITIAL_L2_BLOCK_NUM); + + const l = []; + for (let i = startIndex; i < upper; i++) { + const log = logMap.get(i); + if (log) { + l.push(log); + } else { + // I hate typescript sometimes + l.push(undefined as unknown as L2BlockL2Logs>); + } + } + + return Promise.resolve(l); } /** @@ -355,37 +377,37 @@ export class MemoryArchiverStore implements ArchiverDataStore { */ getUnencryptedLogs(filter: LogFilter): Promise { let txHash: TxHash | undefined; - let fromBlockIndex = 0; - let toBlockIndex = this.unencryptedLogsPerBlock.length; + let fromBlock = 0; + let toBlock = this.l2Blocks.length + INITIAL_L2_BLOCK_NUM; let txIndexInBlock = 0; let logIndexInTx = 0; if (filter.afterLog) { // Continuation parameter is set --> tx hash is ignored if (filter.fromBlock == undefined || filter.fromBlock <= filter.afterLog.blockNumber) { - fromBlockIndex = filter.afterLog.blockNumber - INITIAL_L2_BLOCK_NUM; + fromBlock = filter.afterLog.blockNumber; txIndexInBlock = filter.afterLog.txIndex; logIndexInTx = filter.afterLog.logIndex + 1; // We want to start from the next log } else { - fromBlockIndex = filter.fromBlock - INITIAL_L2_BLOCK_NUM; + fromBlock = filter.fromBlock; } } else { txHash = filter.txHash; if (filter.fromBlock !== undefined) { - fromBlockIndex = filter.fromBlock - INITIAL_L2_BLOCK_NUM; + fromBlock = filter.fromBlock; } } if (filter.toBlock !== undefined) { - toBlockIndex = filter.toBlock - INITIAL_L2_BLOCK_NUM; + toBlock = filter.toBlock; } // Ensure the indices are within block array bounds - fromBlockIndex = Math.max(fromBlockIndex, 0); - toBlockIndex = Math.min(toBlockIndex, this.unencryptedLogsPerBlock.length); + fromBlock = Math.max(fromBlock, INITIAL_L2_BLOCK_NUM); + toBlock = Math.min(toBlock, this.l2Blocks.length + INITIAL_L2_BLOCK_NUM); - if (fromBlockIndex > this.unencryptedLogsPerBlock.length || toBlockIndex < fromBlockIndex || toBlockIndex <= 0) { + if (fromBlock > this.l2Blocks.length || toBlock < fromBlock || toBlock <= 0) { return Promise.resolve({ logs: [], maxLogsHit: false, @@ -396,27 +418,30 @@ export class MemoryArchiverStore implements ArchiverDataStore { const logs: ExtendedUnencryptedL2Log[] = []; - for (; fromBlockIndex < toBlockIndex; fromBlockIndex++) { - const block = this.l2Blocks[fromBlockIndex]; - const blockLogs = this.unencryptedLogsPerBlock[fromBlockIndex]; - for (; txIndexInBlock < blockLogs.txLogs.length; txIndexInBlock++) { - const txLogs = blockLogs.txLogs[txIndexInBlock].unrollLogs(); - for (; logIndexInTx < txLogs.length; logIndexInTx++) { - const log = txLogs[logIndexInTx]; - if ( - (!txHash || block.data.body.txEffects[txIndexInBlock].txHash.equals(txHash)) && - (!contractAddress || log.contractAddress.equals(contractAddress)) - ) { - logs.push(new ExtendedUnencryptedL2Log(new LogId(block.data.number, txIndexInBlock, logIndexInTx), log)); - if (logs.length === this.maxLogs) { - return Promise.resolve({ - logs, - maxLogsHit: true, - }); + for (; fromBlock < toBlock; fromBlock++) { + const block = this.l2Blocks[fromBlock - INITIAL_L2_BLOCK_NUM]; + const blockLogs = this.unencryptedLogsPerBlock.get(fromBlock); + + if (blockLogs) { + for (; txIndexInBlock < blockLogs.txLogs.length; txIndexInBlock++) { + const txLogs = blockLogs.txLogs[txIndexInBlock].unrollLogs(); + for (; logIndexInTx < txLogs.length; logIndexInTx++) { + const log = txLogs[logIndexInTx]; + if ( + (!txHash || block.data.body.txEffects[txIndexInBlock].txHash.equals(txHash)) && + (!contractAddress || log.contractAddress.equals(contractAddress)) + ) { + logs.push(new ExtendedUnencryptedL2Log(new LogId(block.data.number, txIndexInBlock, logIndexInTx), log)); + if (logs.length === this.maxLogs) { + return Promise.resolve({ + logs, + maxLogsHit: true, + }); + } } } + logIndexInTx = 0; } - logIndexInTx = 0; } txIndexInBlock = 0; } diff --git a/yarn-project/end-to-end/src/e2e_synching.test.ts b/yarn-project/end-to-end/src/e2e_synching.test.ts index 7db29b59ca79..a56846f9594f 100644 --- a/yarn-project/end-to-end/src/e2e_synching.test.ts +++ b/yarn-project/end-to-end/src/e2e_synching.test.ts @@ -76,6 +76,12 @@ enum TxComplexity { Spam, } +type VariantDefinition = { + blockCount: number; + txCount: number; + txComplexity: TxComplexity; +}; + /** * Helper class that wraps a certain variant of test, provides functionality for * setting up the test state (e.g., funding accounts etc) and to generate a list of transactions. @@ -97,7 +103,15 @@ class TestVariant { private seed = 0n; - constructor(public blockCount: number, public txCount: number, public txComplexity: TxComplexity) {} + public blockCount: number; + public txCount: number; + public txComplexity: TxComplexity; + + constructor(def: VariantDefinition) { + this.blockCount = def.blockCount; + this.txCount = def.txCount; + this.txComplexity = def.txComplexity; + } setPXE(pxe: PXEService) { this.pxe = pxe; @@ -312,11 +326,11 @@ class TestVariant { * because each transaction is LARGE, so the block size in kb is hit. * I decided that 1/4 should be acceptable, and still small enough to work. */ -const variants: TestVariant[] = [ - new TestVariant(10, 36, TxComplexity.Deployment), - new TestVariant(10, 36, TxComplexity.PrivateTransfer), - new TestVariant(10, 36, TxComplexity.PublicTransfer), - new TestVariant(10, 9, TxComplexity.Spam), +const variants: VariantDefinition[] = [ + { blockCount: 10, txCount: 36, txComplexity: TxComplexity.Deployment }, + { blockCount: 10, txCount: 36, txComplexity: TxComplexity.PrivateTransfer }, + { blockCount: 10, txCount: 36, txComplexity: TxComplexity.PublicTransfer }, + { blockCount: 10, txCount: 9, txComplexity: TxComplexity.Spam }, ]; describe('e2e_synching', () => { @@ -324,10 +338,11 @@ describe('e2e_synching', () => { // of fixtures including multiple blocks with many transaction in. it.each(variants)( `Add blocks to the pending chain - %s`, - async (variant: TestVariant) => { + async (variantDef: VariantDefinition) => { if (!AZTEC_GENERATE_TEST_DATA) { return; } + const variant = new TestVariant(variantDef); // The setup is in here and not at the `before` since we are doing different setups depending on what mode we are running in. const { teardown, pxe, sequencer, aztecNode, wallet } = await setup(1, { salt: SALT, l1StartTime: START_TIME }); @@ -433,8 +448,8 @@ describe('e2e_synching', () => { }; describe('replay history and then do a fresh sync', () => { - it.each(variants)('vanilla - %s', async (variant: TestVariant) => { - await testTheVariant(variant); + it.each(variants)('vanilla - %s', async (variantDef: VariantDefinition) => { + await testTheVariant(new TestVariant(variantDef)); }); describe('a wild prune appears', () => { @@ -443,7 +458,7 @@ describe('e2e_synching', () => { return; } - const variant = variants[0]; + const variant = new TestVariant(variants[0]); const beforeSync = async (opts: Partial) => { const rollup = getContract({ @@ -480,7 +495,7 @@ describe('e2e_synching', () => { return; } - const variant = variants[0]; + const variant = new TestVariant(variants[0]); const beforeSync = async (opts: Partial) => { const rollup = getContract({ diff --git a/yarn-project/types/src/contracts/contract_class.ts b/yarn-project/types/src/contracts/contract_class.ts index a7869382f3f5..b19865aa4f9a 100644 --- a/yarn-project/types/src/contracts/contract_class.ts +++ b/yarn-project/types/src/contracts/contract_class.ts @@ -65,6 +65,9 @@ export type ContractClassPublic = { } & Pick & Omit; +/** The contract class with the block it was initially deployed at */ +export type ContractClassPublicWithBlockNumber = { l2BlockNumber: number } & ContractClassPublic; + /** Private function definition with executable bytecode. */ export interface ExecutablePrivateFunction extends PrivateFunction { /** ACIR and Brillig bytecode */