Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions yarn-project/pxe/src/logs/log_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});
79 changes: 34 additions & 45 deletions yarn-project/pxe/src/logs/log_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<LogRetrievalResponse | null> {
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<LogRetrievalResponse | null> {
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.`,
);
}

Expand Down
Loading