From 50cad2830feefdc41aa2333900c5c7704a7b3195 Mon Sep 17 00:00:00 2001 From: Nico Chamo Date: Fri, 8 May 2026 08:33:55 -0300 Subject: [PATCH] refactor(pxe): batch log RPC calls in LogService.fetchLogsByTag --- yarn-project/pxe/src/logs/log_service.test.ts | 35 ++++++++ yarn-project/pxe/src/logs/log_service.ts | 79 ++++++++----------- 2 files changed, 69 insertions(+), 45 deletions(-) diff --git a/yarn-project/pxe/src/logs/log_service.test.ts b/yarn-project/pxe/src/logs/log_service.test.ts index d2b5a334f4af..fbaef16f94d9 100644 --- a/yarn-project/pxe/src/logs/log_service.test.ts +++ b/yarn-project/pxe/src/logs/log_service.test.ts @@ -146,5 +146,40 @@ describe('LogService', () => { /Got a log retrieval request from/, ); }); + + it('batches multiple requests into single RPC calls', async () => { + const tag1 = Tag.random(); + const tag2 = Tag.random(); + const tag3 = Tag.random(); + + const publicLog1 = randomTxScopedPrivateL2Log(); + const privateLog2 = randomTxScopedPrivateL2Log(); + + aztecNode.getPublicLogsByTagsFromContract.mockResolvedValue([[publicLog1], [], []]); + aztecNode.getPrivateLogsByTags.mockResolvedValue([[], [privateLog2], []]); + + const requests = [ + new LogRetrievalRequest(contractAddress, tag1), + new LogRetrievalRequest(contractAddress, tag2), + new LogRetrievalRequest(contractAddress, tag3), + ]; + + const responses = await logService.fetchLogsByTag(contractAddress, requests); + + expect(responses).toHaveLength(3); + expect(responses[0]).toEqual(expect.objectContaining({ txHash: publicLog1.txHash })); + expect(responses[1]).toEqual(expect.objectContaining({ txHash: privateLog2.txHash })); + expect(responses[2]).toBeNull(); + + expect(aztecNode.getPublicLogsByTagsFromContract).toHaveBeenCalledTimes(1); + expect(aztecNode.getPrivateLogsByTags).toHaveBeenCalledTimes(1); + }); + + it('returns empty array for empty requests', async () => { + const responses = await logService.fetchLogsByTag(contractAddress, []); + expect(responses).toEqual([]); + expect(aztecNode.getPublicLogsByTagsFromContract).not.toHaveBeenCalled(); + expect(aztecNode.getPrivateLogsByTags).not.toHaveBeenCalled(); + }); }); }); diff --git a/yarn-project/pxe/src/logs/log_service.ts b/yarn-project/pxe/src/logs/log_service.ts index 6f8fe80bf49f..e9cdfdbc9782 100644 --- a/yarn-project/pxe/src/logs/log_service.ts +++ b/yarn-project/pxe/src/logs/log_service.ts @@ -3,7 +3,12 @@ import type { KeyStore } from '@aztec/key-store'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { L2TipsProvider } from '@aztec/stdlib/block'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; -import { ExtendedDirectionalAppTaggingSecret, PendingTaggedLog, SiloedTag, Tag } from '@aztec/stdlib/logs'; +import { + ExtendedDirectionalAppTaggingSecret, + PendingTaggedLog, + SiloedTag, + type TxScopedL2Log, +} from '@aztec/stdlib/logs'; import type { BlockHeader } from '@aztec/stdlib/tx'; import type { LogRetrievalRequest } from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; @@ -44,62 +49,46 @@ export class LogService { } } - return await Promise.all( - logRetrievalRequests.map(async request => { - const [publicLog, privateLog] = await Promise.all([ - this.#getPublicLogByTag(request.tag, request.contractAddress), - this.#getPrivateLogByTag(await SiloedTag.computeFromTagAndApp(request.tag, request.contractAddress)), - ]); - - if (publicLog !== null && privateLog !== null) { - this.log.warn( - `Found both a public and private log for tag ${request.tag} from contract ${request.contractAddress}. This may indicate a contract bug. Returning the public log.`, - ); - } - - return publicLog ?? privateLog; - }), - ); - } + if (logRetrievalRequests.length === 0) { + return []; + } - async #getPublicLogByTag(tag: Tag, contractAddress: AztecAddress): Promise { const anchorBlockHash = await this.anchorBlockHeader.hash(); - const allLogsPerTag = await getAllPublicLogsByTagsFromContract( - this.aztecNode, - contractAddress, - [tag], - anchorBlockHash, + const tags = logRetrievalRequests.map(r => r.tag); + const siloedTags = await Promise.all( + logRetrievalRequests.map(r => SiloedTag.computeFromTagAndApp(r.tag, r.contractAddress)), ); - const logsForTag = allLogsPerTag[0]; - if (logsForTag.length === 0) { - return null; - } else if (logsForTag.length > 1) { - this.log.warn( - `Expected at most 1 public log for tag ${tag} and contract ${contractAddress.toString()}, got ${logsForTag.length}. This may indicate a contract bug. Returning the first log.`, + const [allPublicLogsPerTag, allPrivateLogsPerTag] = await Promise.all([ + getAllPublicLogsByTagsFromContract(this.aztecNode, contractAddress, tags, anchorBlockHash), + getAllPrivateLogsByTags(this.aztecNode, siloedTags, anchorBlockHash), + ]); + + return logRetrievalRequests.map((request, i) => { + const publicLog = this.#extractSingleLog( + allPublicLogsPerTag[i], + `public log for tag ${request.tag} and contract ${request.contractAddress.toString()}`, ); - } + const privateLog = this.#extractSingleLog(allPrivateLogsPerTag[i], `private log for tag ${siloedTags[i]}`); - const scopedLog = logsForTag[0]; + if (publicLog !== null && privateLog !== null) { + this.log.warn( + `Found both a public and private log for tag ${request.tag} from contract ${request.contractAddress}. This may indicate a contract bug. Returning the public log.`, + ); + } - return new LogRetrievalResponse( - scopedLog.logData.slice(1), // Skip the tag - scopedLog.txHash, - scopedLog.noteHashes, - scopedLog.firstNullifier, - ); + return publicLog ?? privateLog; + }); } - async #getPrivateLogByTag(siloedTag: SiloedTag): Promise { - const anchorBlockHash = await this.anchorBlockHeader.hash(); - const allLogsPerTag = await getAllPrivateLogsByTags(this.aztecNode, [siloedTag], anchorBlockHash); - const logsForTag = allLogsPerTag[0]; - + #extractSingleLog(logsForTag: TxScopedL2Log[], description: string): LogRetrievalResponse | null { if (logsForTag.length === 0) { return null; - } else if (logsForTag.length > 1) { + } + + if (logsForTag.length > 1) { this.log.warn( - `Expected at most 1 private log for tag ${siloedTag}, got ${logsForTag.length}. This may indicate a contract bug. Returning the first log.`, + `Expected at most 1 ${description}, got ${logsForTag.length}. This may indicate a contract bug. Returning the first log.`, ); }