From 7afc5471ff074289e6149fbdb033e926841ed86b Mon Sep 17 00:00:00 2001 From: benesjan <13470840+benesjan@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:10:44 +0000 Subject: [PATCH] refactor!: dropping pagination from getLogsByTags In this PR I drop pagination from `getLogsByTags` endpoint because pagination here doesn't really make sense because we get more logs per tag only if there are multiple devices sending logs from a given sender to a recipient. This is the final of 3 PRs in which I clean up the `getLogsByTags` endpoint: 1. First PR - including block timestamp in return value, 2. This is the second PR - I type the tag arg of the function to be `SiloedTag`, 3. in the last PR I will drop pagination from this endpoint. # Potential DoS vector Log tag is unconstrained and hence can be arbitrarily chosen by the sender. This introduces a potential DoS vector because the node currently loads all the logs into memory and returns them. Hence it could be feasible to re-use the same tag thousands of time and then spam a node by request for this tag. I think the solution here is to simply define the maximum number of logs a node is willing to store per tag in the db. Given that there is no legitimate use case for log reuse I think making it even as low as 5 would be fine. Note that this PR didn't introduce this DoS vector because even though I dropped the pagination here it didn't seem to be enforced before (if `limitPerTag` arg was undefined we just returned it all). For this reason I think we can merge it and tackle the issue in a followup PR. --- .../archiver/src/archiver/archiver.ts | 24 ++++-------- .../archiver/src/archiver/archiver_store.ts | 32 ++++++---------- .../kv_archiver_store/kv_archiver_store.ts | 12 ++---- .../archiver/kv_archiver_store/log_store.ts | 37 +++++-------------- .../aztec-node/src/aztec-node/server.ts | 12 ++---- .../stdlib/src/interfaces/archiver.ts | 4 +- .../stdlib/src/interfaces/aztec-node.ts | 37 ++++--------------- .../stdlib/src/interfaces/l2_logs_source.ts | 24 +++--------- 8 files changed, 52 insertions(+), 130 deletions(-) diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index 2f9381503b62..abc0705478a0 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -1415,16 +1415,12 @@ export class Archiver return this.store.getSettledTxReceipt(txHash); } - getPrivateLogsByTags(tags: SiloedTag[], logsPerTag?: number): Promise { - return this.store.getPrivateLogsByTags(tags, logsPerTag); + getPrivateLogsByTags(tags: SiloedTag[]): Promise { + return this.store.getPrivateLogsByTags(tags); } - getPublicLogsByTagsFromContract( - contractAddress: AztecAddress, - tags: Tag[], - logsPerTag?: number, - ): Promise { - return this.store.getPublicLogsByTagsFromContract(contractAddress, tags, logsPerTag); + getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise { + return this.store.getPublicLogsByTagsFromContract(contractAddress, tags); } /** @@ -2082,15 +2078,11 @@ export class ArchiverStoreHelper getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise { return this.store.getL1ToL2MessageIndex(l1ToL2Message); } - getPrivateLogsByTags(tags: SiloedTag[], logsPerTag?: number): Promise { - return this.store.getPrivateLogsByTags(tags, logsPerTag); + getPrivateLogsByTags(tags: SiloedTag[]): Promise { + return this.store.getPrivateLogsByTags(tags); } - getPublicLogsByTagsFromContract( - contractAddress: AztecAddress, - tags: Tag[], - logsPerTag?: number, - ): Promise { - return this.store.getPublicLogsByTagsFromContract(contractAddress, tags, logsPerTag); + getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise { + return this.store.getPublicLogsByTagsFromContract(contractAddress, tags); } getPublicLogs(filter: LogFilter): Promise { return this.store.getPublicLogs(filter); diff --git a/yarn-project/archiver/src/archiver/archiver_store.ts b/yarn-project/archiver/src/archiver/archiver_store.ts index f39230e8a0fb..60abca8653c7 100644 --- a/yarn-project/archiver/src/archiver/archiver_store.ts +++ b/yarn-project/archiver/src/archiver/archiver_store.ts @@ -206,27 +206,17 @@ export interface ArchiverDataStore { getTotalL1ToL2MessageCount(): Promise; /** - * Gets all private logs that match any of the received tags (i.e. logs with their first field equal to a SiloedTag). - * @param tags - The SiloedTags to filter the logs by. - * @param logsPerTag - The number of logs to return per tag. Defaults to everything - * @returns For each received tag, an array of matching private logs is returned. An empty array implies no logs match - * that tag. - */ - getPrivateLogsByTags(tags: SiloedTag[], logsPerTag?: number): Promise; - - /** - * Gets all public logs that match any of the received tags from the specified contract (i.e. logs with their first field equal to a Tag). - * @param contractAddress - The contract that emitted the public logs. - * @param tags - The Tags to filter the logs by. - * @param logsPerTag - The number of logs to return per tag. Defaults to everything - * @returns For each received tag, an array of matching public logs is returned. An empty array implies no logs match - * that tag. - */ - getPublicLogsByTagsFromContract( - contractAddress: AztecAddress, - tags: Tag[], - logsPerTag?: number, - ): Promise; + /** + * Gets all private logs that match any of the `tags`. For each tag, an array of matching logs is returned. An empty + * array implies no logs match that tag. + */ + getPrivateLogsByTags(tags: SiloedTag[]): Promise; + + /** + * Gets all public logs that match any of the `tags` from the specified contract. For each tag, an array of matching + * logs is returned. An empty array implies no logs match that tag. + */ + getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise; /** * Gets public logs based on the provided filter. 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 97ea4cadec6d..9c403cb349c9 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 @@ -318,21 +318,17 @@ export class KVArchiverDataStore implements ArchiverDataStore, ContractDataSourc return this.#messageStore.getL1ToL2Messages(checkpointNumber); } - getPrivateLogsByTags(tags: SiloedTag[], logsPerTag?: number): Promise { + getPrivateLogsByTags(tags: SiloedTag[]): Promise { try { - return this.#logStore.getPrivateLogsByTags(tags, logsPerTag); + return this.#logStore.getPrivateLogsByTags(tags); } catch (err) { return Promise.reject(err); } } - getPublicLogsByTagsFromContract( - contractAddress: AztecAddress, - tags: Tag[], - logsPerTag?: number, - ): Promise { + getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise { try { - return this.#logStore.getPublicLogsByTagsFromContract(contractAddress, tags, logsPerTag); + return this.#logStore.getPublicLogsByTagsFromContract(contractAddress, tags); } catch (err) { return Promise.reject(err); } 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 ba131d4c7b84..7b1b8b306738 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 @@ -284,46 +284,27 @@ export class LogStore { } /** - * Gets all private logs that match any of the received tags (i.e. logs with their first field equal to a SiloedTag). - * @param tags - The SiloedTags to filter the logs by. - * @param limitPerTag - The maximum number of logs to return per tag. - * @returns For each received tag, an array of matching private logs is returned. An empty array implies no logs match - * that tag. + * Gets all private logs that match any of the `tags`. For each tag, an array of matching logs is returned. An empty + * array implies no logs match that tag. */ - async getPrivateLogsByTags(tags: SiloedTag[], limitPerTag?: number): Promise { - if (limitPerTag !== undefined && limitPerTag <= 0) { - throw new TypeError('limitPerTag needs to be greater than 0'); - } + async getPrivateLogsByTags(tags: SiloedTag[]): Promise { const logs = await Promise.all(tags.map(tag => this.#privateLogsByTag.getAsync(tag.toString()))); - return logs.map( - logBuffers => logBuffers?.slice(0, limitPerTag).map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? [], - ); + + return logs.map(logBuffers => logBuffers?.map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? []); } /** - * Gets all public logs that match any of the received tags from the specified contract (i.e. logs with their first field equal to a Tag). - * @param contractAddress - The contract that emitted the public logs. - * @param tags - The Tags to filter the logs by. - * @param limitPerTag - The maximum number of logs to return per tag. - * @returns For each received tag, an array of matching public logs is returned. An empty array implies no logs match that tag. + * Gets all public logs that match any of the `tags` from the specified contract. For each tag, an array of matching + * logs is returned. An empty array implies no logs match that tag. */ - async getPublicLogsByTagsFromContract( - contractAddress: AztecAddress, - tags: Tag[], - limitPerTag?: number, - ): Promise { - if (limitPerTag !== undefined && limitPerTag <= 0) { - throw new TypeError('limitPerTag needs to be greater than 0'); - } + async getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise { const logs = await Promise.all( tags.map(tag => { const key = `${contractAddress.toString()}_${tag.value.toString()}`; return this.#publicLogsByContractAndTag.getAsync(key); }), ); - return logs.map( - logBuffers => logBuffers?.slice(0, limitPerTag).map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? [], - ); + return logs.map(logBuffers => logBuffers?.map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? []); } /** diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 87f78f581727..0d5d42b6d3ef 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -695,16 +695,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { return this.contractDataSource.getContract(address); } - public getPrivateLogsByTags(tags: SiloedTag[], logsPerTag?: number): Promise { - return this.logsSource.getPrivateLogsByTags(tags, logsPerTag); + public getPrivateLogsByTags(tags: SiloedTag[]): Promise { + return this.logsSource.getPrivateLogsByTags(tags); } - public getPublicLogsByTagsFromContract( - contractAddress: AztecAddress, - tags: Tag[], - logsPerTag?: number, - ): Promise { - return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags, logsPerTag); + public getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise { + return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags); } /** diff --git a/yarn-project/stdlib/src/interfaces/archiver.ts b/yarn-project/stdlib/src/interfaces/archiver.ts index 494501e72480..600a17322e7b 100644 --- a/yarn-project/stdlib/src/interfaces/archiver.ts +++ b/yarn-project/stdlib/src/interfaces/archiver.ts @@ -115,11 +115,11 @@ export const ArchiverApiSchema: ApiSchemaFor = { getL2Tips: z.function().args().returns(L2TipsSchema), getPrivateLogsByTags: z .function() - .args(z.array(SiloedTag.schema), optional(schemas.Integer)) + .args(z.array(SiloedTag.schema)) .returns(z.array(z.array(TxScopedL2Log.schema))), getPublicLogsByTagsFromContract: z .function() - .args(schemas.AztecAddress, z.array(Tag.schema), optional(schemas.Integer)) + .args(schemas.AztecAddress, z.array(Tag.schema)) .returns(z.array(z.array(TxScopedL2Log.schema))), getPublicLogs: z.function().args(LogFilterSchema).returns(GetPublicLogsResponseSchema), getContractClassLogs: z.function().args(LogFilterSchema).returns(GetContractClassLogsResponseSchema), diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.ts b/yarn-project/stdlib/src/interfaces/aztec-node.ts index 72f4da99cf11..016faa744320 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.ts @@ -338,29 +338,16 @@ export interface AztecNode getContractClassLogs(filter: LogFilter): Promise; /** - * Gets all private logs that match any of the received tags (i.e. logs with their first field equal to a SiloedTag). - * @param tags - The SiloedTags to filter the logs by. - * @param logsPerTag - How many logs to return per tag. Default 10 logs are returned for each tag - * @returns For each received tag, an array of matching private logs and metadata (e.g. tx hash) is returned. An empty - * array implies no logs match that tag. There can be multiple logs for 1 tag because tag reuse can happen - * --> e.g. when sending a note from multiple unsynched devices. + * Gets all private logs that match any of the `tags`. For each tag, an array of matching logs is returned. An empty + * array implies no logs match that tag. */ - getPrivateLogsByTags(tags: SiloedTag[], logsPerTag?: number): Promise; + getPrivateLogsByTags(tags: SiloedTag[]): Promise; /** - * Gets all public logs that match any of the received tags from the specified contract (i.e. logs with their first field equal to a Tag). - * @param contractAddress - The contract that emitted the public logs. - * @param tags - The Tags to filter the logs by. - * @param logsPerTag - How many logs to return per tag. Default 10 logs are returned for each tag - * @returns For each received tag, an array of matching public logs and metadata (e.g. tx hash) is returned. An empty - * array implies no logs match that tag. There can be multiple logs for 1 tag because tag reuse can happen - * --> e.g. when sending a note from multiple unsynched devices. + * Gets all public logs that match any of the `tags` from the specified contract. For each tag, an array of matching + * logs is returned. An empty array implies no logs match that tag. */ - getPublicLogsByTagsFromContract( - contractAddress: AztecAddress, - tags: Tag[], - logsPerTag?: number, - ): Promise; + getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise; /** * Method to submit a transaction to the p2p pool. @@ -496,7 +483,6 @@ export interface AztecNode getAllowedPublicSetup(): Promise; } -export const MAX_LOGS_PER_TAG = 10; const MAX_SIGNATURES_PER_REGISTER_CALL = 100; const MAX_SIGNATURE_LEN = 10000; @@ -618,19 +604,12 @@ export const AztecNodeApiSchema: ApiSchemaFor = { getPrivateLogsByTags: z .function() - .args( - z.array(SiloedTag.schema).max(MAX_RPC_LEN), - optional(z.number().gte(1).lte(MAX_LOGS_PER_TAG).default(MAX_LOGS_PER_TAG)), - ) + .args(z.array(SiloedTag.schema).max(MAX_RPC_LEN)) .returns(z.array(z.array(TxScopedL2Log.schema))), getPublicLogsByTagsFromContract: z .function() - .args( - schemas.AztecAddress, - z.array(Tag.schema).max(MAX_RPC_LEN), - optional(z.number().gte(1).lte(MAX_LOGS_PER_TAG).default(MAX_LOGS_PER_TAG)), - ) + .args(schemas.AztecAddress, z.array(Tag.schema).max(MAX_RPC_LEN)) .returns(z.array(z.array(TxScopedL2Log.schema))), sendTx: z.function().args(Tx.schema).returns(z.void()), diff --git a/yarn-project/stdlib/src/interfaces/l2_logs_source.ts b/yarn-project/stdlib/src/interfaces/l2_logs_source.ts index 120ad25fd7b0..b5a5749c0af2 100644 --- a/yarn-project/stdlib/src/interfaces/l2_logs_source.ts +++ b/yarn-project/stdlib/src/interfaces/l2_logs_source.ts @@ -12,28 +12,16 @@ import type { GetContractClassLogsResponse, GetPublicLogsResponse } from './get_ */ export interface L2LogsSource { /** - * Gets all private logs that match any of the received tags (i.e. logs with their first field equal to a SiloedTag). - * @param tags - The SiloedTags to filter the logs by. - * @param logsPerTag - The maximum number of logs to return for each tag. Default returns everything - * @returns For each received tag, an array of matching private logs is returned. An empty array implies no logs match - * that tag. + * Gets all private logs that match any of the `tags`. For each tag, an array of matching logs is returned. An empty + * array implies no logs match that tag. */ - getPrivateLogsByTags(tags: SiloedTag[], logsPerTag?: number): Promise; + getPrivateLogsByTags(tags: SiloedTag[]): Promise; /** - * Gets all public logs that match any of the received tags from the specified contract (i.e. logs with their first - * field equal to a Tag). - * @param contractAddress - The contract that emitted the public logs. - * @param tags - The Tags to filter the logs by. - * @param logsPerTag - The maximum number of logs to return for each tag. Default returns everything - * @returns For each received tag, an array of matching public logs is returned. An empty array implies no logs match - * that tag. + * Gets all public logs that match any of the `tags` from the specified contract. For each tag, an array of matching + * logs is returned. An empty array implies no logs match that tag. */ - getPublicLogsByTagsFromContract( - contractAddress: AztecAddress, - tags: Tag[], - logsPerTag?: number, - ): Promise; + getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise; /** * Gets public logs based on the provided filter.