Skip to content

Commit b98e36b

Browse files
authored
refactor: more robust tagging index sync as sender (#17611)
In this PR I implement the approach that Nico originally described in [this post](https://forum.aztec.network/t/on-note-discovery-and-index-coordination/7165) for sender tagging index sync (for recipient it will be done in a followup PR). To summarize I do the following: 1. Associate the tagging index with a hash of a tx in which the corresponding tax was used, 2. I track the status of the given tx in the tagging data provider, 3. tagging index status can be either PENDING, FINALIZED or DROPPED_OR_REVERTED (this status is only symbolic as when we realize a corresponding tx has been dropped or reverted we just delete the pending index), 4. when choosing the next index to use when sending a private log I choose `HIGHEST_PENDING_INDEX + 1` (or `HIGHEST_FINALIZED_INDEX + 1` if there is no pending index), 5. if the chosen index is further than WINDOW_LENGTH away from the HIGHEST_FINALIZED_INDEX an error is thrown. This should be a good guarantee of us never losing a log because we are always looking for a WINDOW_LENGTH of logs. # Notes for reviewer - I separated `TaggingDataProvider` into `SenderTaggingDataProvider` and `RecipientTaggingDataProvider` because the algorithms are completely disjoint and it makes the code clearer. - Moved the `syncTaggedLogsAsSender` function to `pxe/src/tagging` directory and renamed it as `syncSenderTaggingIndexes`. Now `PrivateExecutionOracle` just calls this function. This is a step in the direction of deprecating `PXEOracleInterface` and it allowed me to write a reasonably clear unit tests. # Issues created in this PR - #17775 - #17776
2 parents 64fe3cb + eb5f940 commit b98e36b

28 files changed

+1844
-385
lines changed

yarn-project/end-to-end/src/e2e_pending_note_hashes_contract.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ describe('e2e_pending_note_hashes_contract', () => {
287287
});
288288

289289
it('Should handle overflowing the kernel data structures in nested calls', async () => {
290+
// This test verifies that a transaction can emit more notes than MAX_NOTE_HASHES_PER_TX without failing, since
291+
// the notes are nullified and will be squashed by the kernel reset circuit.
290292
const sender = owner;
291293
const notesPerIteration = Math.min(MAX_NOTE_HASHES_PER_CALL, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL);
292294
const minToNeedReset = Math.min(MAX_NOTE_HASHES_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX) + 1;

yarn-project/pxe/src/block_synchronizer/block_synchronizer.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import { type MockProxy, mock } from 'jest-mock-extended';
1111

1212
import { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js';
1313
import { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js';
14-
import { TaggingDataProvider } from '../storage/tagging_data_provider/tagging_data_provider.js';
14+
import { RecipientTaggingDataProvider } from '../storage/tagging_data_provider/recipient_tagging_data_provider.js';
1515
import { BlockSynchronizer } from './block_synchronizer.js';
1616

1717
describe('BlockSynchronizer', () => {
1818
let synchronizer: BlockSynchronizer;
1919
let tipsStore: L2TipsKVStore;
2020
let anchorBlockDataProvider: AnchorBlockDataProvider;
2121
let noteDataProvider: NoteDataProvider;
22-
let taggingDataProvider: TaggingDataProvider;
22+
let recipientTaggingDataProvider: RecipientTaggingDataProvider;
2323
let aztecNode: MockProxy<AztecNode>;
2424
let blockStream: MockProxy<L2BlockStream>;
2525

@@ -36,12 +36,12 @@ describe('BlockSynchronizer', () => {
3636
tipsStore = new L2TipsKVStore(store, 'pxe');
3737
anchorBlockDataProvider = new AnchorBlockDataProvider(store);
3838
noteDataProvider = await NoteDataProvider.create(store);
39-
taggingDataProvider = new TaggingDataProvider(store);
39+
recipientTaggingDataProvider = new RecipientTaggingDataProvider(store);
4040
synchronizer = new TestSynchronizer(
4141
aztecNode,
4242
anchorBlockDataProvider,
4343
noteDataProvider,
44-
taggingDataProvider,
44+
recipientTaggingDataProvider,
4545
tipsStore,
4646
);
4747
});
@@ -59,7 +59,7 @@ describe('BlockSynchronizer', () => {
5959
.spyOn(noteDataProvider, 'rollbackNotesAndNullifiers')
6060
.mockImplementation(() => Promise.resolve());
6161
const resetNoteSyncData = jest
62-
.spyOn(taggingDataProvider, 'resetNoteSyncData')
62+
.spyOn(recipientTaggingDataProvider, 'resetNoteSyncData')
6363
.mockImplementation(() => Promise.resolve());
6464
aztecNode.getBlockHeader.mockImplementation(async blockNumber =>
6565
(await L2Block.random(BlockNumber(blockNumber as number))).getBlockHeader(),

yarn-project/pxe/src/block_synchronizer/block_synchronizer.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { AztecNode } from '@aztec/stdlib/interfaces/client';
77
import type { PXEConfig } from '../config/index.js';
88
import type { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js';
99
import type { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js';
10-
import type { TaggingDataProvider } from '../storage/tagging_data_provider/tagging_data_provider.js';
10+
import type { RecipientTaggingDataProvider } from '../storage/tagging_data_provider/recipient_tagging_data_provider.js';
1111

1212
/**
1313
* The BlockSynchronizer class orchestrates synchronization between PXE and Aztec node, maintaining an up-to-date
@@ -23,7 +23,7 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
2323
private node: AztecNode,
2424
private anchorBlockDataProvider: AnchorBlockDataProvider,
2525
private noteDataProvider: NoteDataProvider,
26-
private taggingDataProvider: TaggingDataProvider,
26+
private recipientTaggingDataProvider: RecipientTaggingDataProvider,
2727
private l2TipsStore: L2TipsKVStore,
2828
config: Partial<Pick<PXEConfig, 'l2BlockBatchSize'>> = {},
2929
loggerOrSuffix?: string | Logger,
@@ -66,7 +66,10 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
6666
await this.noteDataProvider.rollbackNotesAndNullifiers(event.block.number, lastSynchedBlockNumber);
6767
// Remove all note tagging indexes to force a full resync. This is suboptimal, but unless we track the
6868
// block number in which each index is used it's all we can do.
69-
await this.taggingDataProvider.resetNoteSyncData();
69+
// Note: This is now unnecessary for the sender tagging data provider because the new algorithm handles reorgs.
70+
// TODO(#17775): Once this issue is implemented we will have the index-block number mapping, so we can
71+
// implement this more intelligently.
72+
await this.recipientTaggingDataProvider.resetNoteSyncData();
7073
// Update the header to the last block.
7174
const newHeader = await this.node.getBlockHeader(event.block.number);
7275
if (!newHeader) {

yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/
66
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
77
import type { L2Block } from '@aztec/stdlib/block';
88
import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract';
9+
import type { AztecNode } from '@aztec/stdlib/interfaces/client';
910
import type { KeyValidationRequest } from '@aztec/stdlib/kernel';
1011
import type { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs';
1112
import type { NoteStatus } from '@aztec/stdlib/note';
1213
import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
1314
import type { NodeStats } from '@aztec/stdlib/tx';
1415

16+
import type { SenderTaggingDataProvider } from '../storage/tagging_data_provider/sender_tagging_data_provider.js';
1517
import type { NoteData } from './oracle/interfaces.js';
1618
import type { MessageLoadOracleInputs } from './oracle/message_load_oracle_inputs.js';
1719

@@ -219,25 +221,6 @@ export interface ExecutionDataProvider {
219221
recipient: AztecAddress,
220222
): Promise<DirectionalAppTaggingSecret>;
221223

222-
/**
223-
* Updates the local index of the shared tagging secret of a (sender, recipient, contract) tuple if a log with
224-
* a larger index is found from the node.
225-
* @param secret - The secret that's unique for (sender, recipient, contract) tuple while the direction
226-
* of sender -> recipient matters.
227-
* @param contractAddress - The address of the contract that the logs are tagged for. Needs to be provided to store
228-
* because the function performs second round of siloing which is necessary because kernels do it as well (they silo
229-
* first field of the private log which corresponds to the tag).
230-
*/
231-
syncTaggedLogsAsSender(secret: DirectionalAppTaggingSecret, contractAddress: AztecAddress): Promise<void>;
232-
233-
/**
234-
* Returns the last used index when sending a log with a given secret.
235-
* @param secret - The directional app tagging secret.
236-
* @returns The last used index for the given directional app tagging secret, or undefined if we never sent a log
237-
* from this sender to a recipient in a given contract (implicitly included in the secret).
238-
*/
239-
getLastUsedIndexAsSender(secret: DirectionalAppTaggingSecret): Promise<number | undefined>;
240-
241224
/**
242225
* Synchronizes the private logs tagged with scoped addresses and all the senders in the address book. Stores the found
243226
* logs in CapsuleArray ready for a later retrieval in Aztec.nr.
@@ -332,4 +315,8 @@ export interface ExecutionDataProvider {
332315
* @returns The execution statistics.
333316
*/
334317
getStats(): ExecutionStats;
318+
319+
// Exposed when moving in the direction of #17776
320+
get aztecNode(): AztecNode;
321+
get senderTaggingDataProvider(): SenderTaggingDataProvider;
335322
}

yarn-project/pxe/src/contract_function_simulator/execution_tagging_index_cache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class ExecutionTaggingIndexCache {
2121
}
2222

2323
/**
24-
* Returns the pre tags that were used in this execution (and that need to be stored in the db).
24+
* Returns the pre-tags that were used in this execution (and that need to be stored in the db).
2525
*/
2626
public getUsedPreTags(): PreTag[] {
2727
return Array.from(this.taggingIndexMap.entries()).map(([secret, index]) => ({

yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
} from '@aztec/stdlib/contract';
4141
import { GasFees, GasSettings } from '@aztec/stdlib/gas';
4242
import { computeNoteHashNonce, computeSecretHash, computeUniqueNoteHash, siloNoteHash } from '@aztec/stdlib/hash';
43+
import type { AztecNode } from '@aztec/stdlib/interfaces/client';
4344
import { KeyValidationRequest } from '@aztec/stdlib/kernel';
4445
import { computeAppNullifierSecretKey, deriveKeys } from '@aztec/stdlib/keys';
4546
import { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs';
@@ -60,6 +61,7 @@ import { jest } from '@jest/globals';
6061
import { Matcher, type MatcherCreator, type MockProxy, mock } from 'jest-mock-extended';
6162
import { toFunctionSelector } from 'viem';
6263

64+
import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js';
6365
import { ContractFunctionSimulator } from '../contract_function_simulator.js';
6466
import type { ExecutionDataProvider } from '../execution_data_provider.js';
6567
import type { NoteData } from './interfaces.js';
@@ -102,6 +104,8 @@ describe('Private Execution test suite', () => {
102104
const simulator = new WASMSimulator();
103105

104106
let executionDataProvider: MockProxy<ExecutionDataProvider>;
107+
let senderTaggingDataProvider: MockProxy<SenderTaggingDataProvider>;
108+
let aztecNode: MockProxy<AztecNode>;
105109
let acirSimulator: ContractFunctionSimulator;
106110

107111
let anchorBlockHeader = BlockHeader.empty();
@@ -266,7 +270,30 @@ describe('Private Execution test suite', () => {
266270
beforeEach(async () => {
267271
trees = {};
268272
executionDataProvider = mock<ExecutionDataProvider>();
273+
senderTaggingDataProvider = mock<SenderTaggingDataProvider>();
274+
aztecNode = mock<AztecNode>();
269275
contracts = {};
276+
277+
// Mock the senderTaggingDataProvider getter
278+
Object.defineProperty(executionDataProvider, 'senderTaggingDataProvider', {
279+
get: () => senderTaggingDataProvider,
280+
});
281+
282+
// Mock the aztecNode getter
283+
Object.defineProperty(executionDataProvider, 'aztecNode', {
284+
get: () => aztecNode,
285+
});
286+
287+
// Mock sender tagging data provider methods
288+
senderTaggingDataProvider.getLastFinalizedIndex.mockResolvedValue(undefined);
289+
senderTaggingDataProvider.getLastUsedIndex.mockResolvedValue(undefined);
290+
senderTaggingDataProvider.getTxHashesOfPendingIndexes.mockResolvedValue([]);
291+
senderTaggingDataProvider.storePendingIndexes.mockResolvedValue();
292+
293+
// Mock aztec node methods - the return array needs to have the same length as the number of tags
294+
// on the input.
295+
aztecNode.getLogsByTags.mockImplementation((tags: Fr[]) => Promise.resolve(tags.map(() => [])));
296+
270297
executionDataProvider.getKeyValidationRequest.mockImplementation(
271298
async (pkMHash: Fr, contractAddress: AztecAddress) => {
272299
if (pkMHash.equals(await ownerCompleteAddress.publicKeys.masterNullifierPublicKey.hash())) {
@@ -303,9 +330,6 @@ describe('Private Execution test suite', () => {
303330
throw new Error(`Unknown address: ${address}. Recipient: ${recipient}, Owner: ${owner}`);
304331
});
305332

306-
executionDataProvider.getLastUsedIndexAsSender.mockImplementation((_secret: DirectionalAppTaggingSecret) => {
307-
return Promise.resolve(undefined);
308-
});
309333
executionDataProvider.getFunctionArtifact.mockImplementation(async (address, selector) => {
310334
const contract = contracts[address.toString()];
311335
if (!contract) {
@@ -323,9 +347,6 @@ describe('Private Execution test suite', () => {
323347
executionDataProvider.calculateDirectionalAppTaggingSecret.mockImplementation((_contract, _sender, _recipient) => {
324348
return Promise.resolve(DirectionalAppTaggingSecret.fromString('0x1'));
325349
});
326-
executionDataProvider.syncTaggedLogsAsSender.mockImplementation((_directionalAppTaggingSecret, _contractAddress) =>
327-
Promise.resolve(),
328-
);
329350
executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null));
330351

331352
executionDataProvider.getPublicStorageAt.mockImplementation(

yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
type TxContext,
2727
} from '@aztec/stdlib/tx';
2828

29+
import { syncSenderTaggingIndexes } from '../../tagging/sync/sync_sender_tagging_indexes.js';
2930
import { Tag } from '../../tagging/tag.js';
3031
import type { ExecutionDataProvider } from '../execution_data_provider.js';
3132
import type { ExecutionNoteCache } from '../execution_note_cache.js';
@@ -152,7 +153,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
152153
}
153154

154155
/**
155-
* Returns the pre tags that were used in this execution (and that need to be stored in the db).
156+
* Returns the pre-tags that were used in this execution (and that need to be stored in the db).
156157
*/
157158
public getUsedPreTags(): PreTag[] {
158159
return this.taggingIndexCache.getUsedPreTags();
@@ -224,11 +225,16 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
224225
if (lastUsedIndexInTx !== undefined) {
225226
return lastUsedIndexInTx + 1;
226227
} else {
228+
// TODO(#17776): Don't access the Aztec node and senderTaggingDataProvider via the executionDataProvider.
229+
const aztecNode = this.executionDataProvider.aztecNode;
230+
const senderTaggingDataProvider = this.executionDataProvider.senderTaggingDataProvider;
231+
227232
// This is a tagging secret we've not yet used in this tx, so first sync our store to make sure its indices
228233
// are up to date. We do this here because this store is not synced as part of the global sync because
229234
// that'd be wasteful as most tagging secrets are not used in each tx.
230-
await this.executionDataProvider.syncTaggedLogsAsSender(secret, this.contractAddress);
231-
const lastUsedIndex = await this.executionDataProvider.getLastUsedIndexAsSender(secret);
235+
await syncSenderTaggingIndexes(secret, this.contractAddress, aztecNode, senderTaggingDataProvider);
236+
237+
const lastUsedIndex = await senderTaggingDataProvider.getLastUsedIndex(secret);
232238
// If lastUsedIndex is undefined, we've never used this secret, so start from 0
233239
// Otherwise, the next index to use is one past the last used index
234240
return lastUsedIndex === undefined ? 0 : lastUsedIndex + 1;

0 commit comments

Comments
 (0)