Skip to content

Commit 445ca57

Browse files
committed
feat: integrating new log sync
Fixes #17775 In this PR I integrate the new recipient log sync algo and the old one gets dropped. The file structure is still a bit messy but that I will handle in a followup PR to make this PR not insane to review. I Introduced `SenderAddressBook` that was originally part of `RecipientTaggingDataProvider`. I decided to do this separation because I want the new log sync algo to not be aware of sender addresses etc. - I wanted it to deal only with `DirectionalAppTaggingSecret`s as it feels like a nice separation.
1 parent bfc24a2 commit 445ca57

30 files changed

+302
-791
lines changed

noir-projects/noir-contracts/contracts/test/pending_note_hashes_contract/src/main.nr

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -294,50 +294,66 @@ pub contract PendingNoteHashes {
294294
//}
295295

296296
#[external("private")]
297-
fn test_recursively_create_notes(
298-
owner: AztecAddress,
299-
sender: AztecAddress,
300-
how_many_recursions: u64,
301-
) {
302-
self.internal.create_max_notes(owner);
303-
304-
self.call_self.recursively_destroy_and_create_notes(owner, sender, how_many_recursions);
297+
fn test_recursively_create_notes(recipients: [AztecAddress; 10], how_many_recursions: u64) {
298+
let initial_offset: u64 = 0;
299+
self.internal.create_max_notes(recipients, initial_offset);
300+
301+
let max_notes = self.internal.max_notes_per_call() as u64;
302+
self.call_self.recursively_destroy_and_create_notes(
303+
recipients,
304+
how_many_recursions,
305+
max_notes,
306+
);
305307
}
306308

307309
#[external("private")]
308310
fn recursively_destroy_and_create_notes(
309-
owner: AztecAddress,
310-
sender: AztecAddress,
311+
recipients: [AztecAddress; 10],
311312
executions_left: u64,
313+
current_offset: u64,
312314
) {
313315
assert(executions_left > 0);
314316

315-
self.internal.destroy_max_notes(owner);
316-
self.internal.create_max_notes(owner);
317+
self.internal.destroy_max_notes(recipients);
318+
self.internal.create_max_notes(recipients, current_offset);
317319

318320
let executions_left = executions_left - 1;
319321

320322
if executions_left > 0 {
321-
self.call_self.recursively_destroy_and_create_notes(owner, sender, executions_left);
323+
let max_notes = self.internal.max_notes_per_call() as u64;
324+
self.call_self.recursively_destroy_and_create_notes(
325+
recipients,
326+
executions_left,
327+
current_offset + max_notes,
328+
);
322329
}
323330
}
324331

325332
#[internal("private")]
326-
fn create_max_notes(owner: AztecAddress) {
327-
let owner_balance = self.storage.balances.at(owner);
328-
333+
fn create_max_notes(recipients: [AztecAddress; 10], offset: u64) {
334+
// Distribute notes across recipients using global offset to ensure
335+
// no recipient receives more than 10 notes (UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN)
329336
for i in 0..self.internal.max_notes_per_call() {
337+
let global_index = offset + i as u64;
338+
let recipient_index = (global_index % 10) as u32;
339+
let recipient = recipients[recipient_index];
340+
let recipient_balance = self.storage.balances.at(recipient);
341+
330342
let note = FieldNote { value: i as Field };
331-
owner_balance.insert(note).deliver(MessageDelivery.CONSTRAINED_ONCHAIN);
343+
recipient_balance.insert(note).deliver(MessageDelivery.CONSTRAINED_ONCHAIN);
332344
}
333345
}
334346

335347
#[internal("private")]
336-
fn destroy_max_notes(owner: AztecAddress) {
337-
let owner_balance = self.storage.balances.at(owner);
338-
// Note that we're relying on PXE actually returning the notes, we're not constraining that any specific
339-
// number of notes are deleted.
340-
let _ = owner_balance.pop_notes(NoteGetterOptions::new());
348+
fn destroy_max_notes(recipients: [AztecAddress; 10]) {
349+
// Pop notes from all recipients
350+
for i in 0..10 {
351+
let recipient = recipients[i];
352+
let recipient_balance = self.storage.balances.at(recipient);
353+
// Note that we're relying on PXE actually returning the notes, we're not constraining that any specific
354+
// number of notes are deleted.
355+
let _ = recipient_balance.pop_notes(NoteGetterOptions::new());
356+
}
341357
}
342358

343359
#[internal("private")]

yarn-project/end-to-end/src/bench/bench_build_block.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ describe('benchmarks/build_block', () => {
3535
const TX_COUNT = 32;
3636
it(`builds a block with ${TX_COUNT} standard txs`, async () => {
3737
sequencer.updateConfig({ minTxsPerBlock: TX_COUNT });
38-
const sentTxs = sendTxs(TX_COUNT, context, contract);
38+
const sentTxs = await sendTxs(TX_COUNT, context, contract);
3939
await waitTxs(sentTxs, context);
4040
});
4141

4242
const TX_COUNT_HEAVY_COMPUTE = 8;
4343
it(`builds a block with ${TX_COUNT_HEAVY_COMPUTE} compute-heavy txs`, async () => {
4444
sequencer.updateConfig({ minTxsPerBlock: TX_COUNT_HEAVY_COMPUTE });
45-
const sentTxs = sendTxs(TX_COUNT_HEAVY_COMPUTE, context, contract, /*heavyPublicComput=*/ true);
45+
const sentTxs = await sendTxs(TX_COUNT_HEAVY_COMPUTE, context, contract, /*heavyPublicComput=*/ true);
4646
await waitTxs(sentTxs, context);
4747
});
4848
});

yarn-project/end-to-end/src/bench/utils.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { AztecNodeService } from '@aztec/aztec-node';
2+
import { AztecAddress } from '@aztec/aztec.js/addresses';
23
import { BatchCall, type SentTx, type WaitOpts } from '@aztec/aztec.js/contracts';
34
import { mean, stdDev, times } from '@aztec/foundation/collection';
45
import { BenchmarkingContract } from '@aztec/noir-test-contracts.js/Benchmarking';
@@ -103,19 +104,22 @@ function getMetricValues(points: BenchmarkDataPoint[]) {
103104
* @param heavyPublicCompute - Whether the transactions include heavy public compute (like a big sha256).
104105
* @returns A BatchCall instance.
105106
*/
106-
function makeCall(
107+
async function makeCall(
107108
index: number,
108109
context: EndToEndContext,
109110
contract: BenchmarkingContract,
110111
heavyPublicCompute: boolean,
111112
) {
112-
const [owner] = context.accounts;
113113
if (heavyPublicCompute) {
114114
return new BatchCall(context.wallet, [contract.methods.sha256_hash_1024(randomBytesAsBigInts(1024))]);
115115
} else {
116+
// We use random address for the new note owner because we can emit at most UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN
117+
// logs for a given sender-recipient-contract tuple.
118+
const ownerOfNewNote = await AztecAddress.random();
119+
const [ownerOfBalance] = context.accounts;
116120
return new BatchCall(context.wallet, [
117-
contract.methods.create_note(owner, index + 1),
118-
contract.methods.increment_balance(owner, index + 1),
121+
contract.methods.create_note(ownerOfNewNote, index + 1),
122+
contract.methods.increment_balance(ownerOfBalance, index + 1),
119123
]);
120124
}
121125
}
@@ -129,13 +133,13 @@ function makeCall(
129133
* @param heavyPublicCompute - Whether the transactions include heavy public compute (like a big sha256).
130134
* @returns Array of sent txs.
131135
*/
132-
export function sendTxs(
136+
export async function sendTxs(
133137
txCount: number,
134138
context: EndToEndContext,
135139
contract: BenchmarkingContract,
136140
heavyPublicCompute: boolean = false,
137-
): SentTx[] {
138-
const calls = times(txCount, index => makeCall(index, context, contract, heavyPublicCompute));
141+
): Promise<SentTx[]> {
142+
const calls = await Promise.all(times(txCount, index => makeCall(index, context, contract, heavyPublicCompute)));
139143
context.logger.info(`Creating ${txCount} txs`);
140144
const [from] = context.accounts;
141145
context.logger.info(`Sending ${txCount} txs`);

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { AztecAddress } from '@aztec/aztec.js/addresses';
2-
import { Fr } from '@aztec/aztec.js/fields';
1+
import { AztecAddress } from '@aztec/aztec.js/addresses';
2+
import { Fr, GrumpkinScalar } from '@aztec/aztec.js/fields';
33
import type { Logger } from '@aztec/aztec.js/log';
44
import type { AztecNode } from '@aztec/aztec.js/node';
55
import {
@@ -289,12 +289,23 @@ describe('e2e_pending_note_hashes_contract', () => {
289289
it('Should handle overflowing the kernel data structures in nested calls', async () => {
290290
// This test verifies that a transaction can emit more notes than MAX_NOTE_HASHES_PER_TX without failing, since
291291
// the notes are nullified and will be squashed by the kernel reset circuit.
292-
const sender = owner;
292+
293293
const notesPerIteration = Math.min(MAX_NOTE_HASHES_PER_CALL, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL);
294294
const minToNeedReset = Math.min(MAX_NOTE_HASHES_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX) + 1;
295295
const deployedContract = await deployContract();
296+
297+
// We use 10 different recipients to send private logs to in order to avoid exceeding
298+
// UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN logs emitted for any single sender-recipient pair.
299+
const recipients = (
300+
await Promise.all(
301+
Array.from({ length: 10 }, () =>
302+
wallet.createSchnorrAccount(Fr.random(), Fr.random(), GrumpkinScalar.random()),
303+
),
304+
)
305+
).map(a => a.address);
306+
296307
await deployedContract.methods
297-
.test_recursively_create_notes(owner, sender, Math.ceil(minToNeedReset / notesPerIteration))
308+
.test_recursively_create_notes(recipients, Math.ceil(minToNeedReset / notesPerIteration))
298309
.send({ from: owner })
299310
.wait();
300311
});

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

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@ 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 { RecipientTaggingDataProvider } from '../storage/tagging_data_provider/recipient_tagging_data_provider.js';
1514
import { BlockSynchronizer } from './block_synchronizer.js';
1615

1716
describe('BlockSynchronizer', () => {
1817
let synchronizer: BlockSynchronizer;
1918
let tipsStore: L2TipsKVStore;
2019
let anchorBlockDataProvider: AnchorBlockDataProvider;
2120
let noteDataProvider: NoteDataProvider;
22-
let recipientTaggingDataProvider: RecipientTaggingDataProvider;
2321
let aztecNode: MockProxy<AztecNode>;
2422
let blockStream: MockProxy<L2BlockStream>;
2523

@@ -36,14 +34,7 @@ describe('BlockSynchronizer', () => {
3634
tipsStore = new L2TipsKVStore(store, 'pxe');
3735
anchorBlockDataProvider = new AnchorBlockDataProvider(store);
3836
noteDataProvider = await NoteDataProvider.create(store);
39-
recipientTaggingDataProvider = new RecipientTaggingDataProvider(store);
40-
synchronizer = new TestSynchronizer(
41-
aztecNode,
42-
anchorBlockDataProvider,
43-
noteDataProvider,
44-
recipientTaggingDataProvider,
45-
tipsStore,
46-
);
37+
synchronizer = new TestSynchronizer(aztecNode, anchorBlockDataProvider, noteDataProvider, tipsStore);
4738
});
4839

4940
it('sets header from latest block', async () => {
@@ -58,9 +49,6 @@ describe('BlockSynchronizer', () => {
5849
const rollbackNotesAndNullifiers = jest
5950
.spyOn(noteDataProvider, 'rollbackNotesAndNullifiers')
6051
.mockImplementation(() => Promise.resolve());
61-
const resetNoteSyncData = jest
62-
.spyOn(recipientTaggingDataProvider, 'resetNoteSyncData')
63-
.mockImplementation(() => Promise.resolve());
6452
aztecNode.getBlockHeader.mockImplementation(async blockNumber =>
6553
(await L2Block.random(BlockNumber(blockNumber as number))).getBlockHeader(),
6654
);
@@ -72,6 +60,5 @@ describe('BlockSynchronizer', () => {
7260
await synchronizer.handleBlockStreamEvent({ type: 'chain-pruned', block: { number: BlockNumber(3), hash: '0x3' } });
7361

7462
expect(rollbackNotesAndNullifiers).toHaveBeenCalledWith(3, 4);
75-
expect(resetNoteSyncData).toHaveBeenCalled();
7663
});
7764
});

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ 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 { RecipientTaggingDataProvider } from '../storage/tagging_data_provider/recipient_tagging_data_provider.js';
1110

1211
/**
1312
* The BlockSynchronizer class orchestrates synchronization between PXE and Aztec node, maintaining an up-to-date
@@ -23,7 +22,6 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
2322
private node: AztecNode,
2423
private anchorBlockDataProvider: AnchorBlockDataProvider,
2524
private noteDataProvider: NoteDataProvider,
26-
private recipientTaggingDataProvider: RecipientTaggingDataProvider,
2725
private l2TipsStore: L2TipsKVStore,
2826
config: Partial<Pick<PXEConfig, 'l2BlockBatchSize'>> = {},
2927
loggerOrSuffix?: string | Logger,
@@ -64,12 +62,6 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
6462
// We first unnullify and then remove so that unnullified notes that were created after the block number end up deleted.
6563
const lastSynchedBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber();
6664
await this.noteDataProvider.rollbackNotesAndNullifiers(event.block.number, lastSynchedBlockNumber);
67-
// Remove all note tagging indexes to force a full resync. This is suboptimal, but unless we track the
68-
// block number in which each index is used it's all we can do.
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();
7365
// Update the header to the last block.
7466
const newHeader = await this.node.getBlockHeader(event.block.number);
7567
if (!newHeader) {

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,9 @@ import type { CapsuleDataProvider } from '../storage/capsule_data_provider/capsu
7777
import type { ContractDataProvider } from '../storage/contract_data_provider/contract_data_provider.js';
7878
import type { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js';
7979
import type { PrivateEventDataProvider } from '../storage/private_event_data_provider/private_event_data_provider.js';
80-
import type { RecipientTaggingDataProvider } from '../storage/tagging_data_provider/recipient_tagging_data_provider.js';
80+
import type { SenderAddressBook } from '../storage/tagging_data_provider/sender_address_book.js';
8181
import type { SenderTaggingDataProvider } from '../storage/tagging_data_provider/sender_tagging_data_provider.js';
82+
import type { RecipientTaggingDataProvider } from '../tagging/recipient_sync/recipient_tagging_data_provider.js';
8283
import { ExecutionNoteCache } from './execution_note_cache.js';
8384
import { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js';
8485
import { HashedValuesCache } from './hashed_values_cache.js';
@@ -103,6 +104,7 @@ export class ContractFunctionSimulator {
103104
private anchorBlockDataProvider: AnchorBlockDataProvider,
104105
private senderTaggingDataProvider: SenderTaggingDataProvider,
105106
private recipientTaggingDataProvider: RecipientTaggingDataProvider,
107+
private senderAddressBook: SenderAddressBook,
106108
private capsuleDataProvider: CapsuleDataProvider,
107109
private privateEventDataProvider: PrivateEventDataProvider,
108110
private simulator: CircuitSimulator,
@@ -183,6 +185,7 @@ export class ContractFunctionSimulator {
183185
this.anchorBlockDataProvider,
184186
this.senderTaggingDataProvider,
185187
this.recipientTaggingDataProvider,
188+
this.senderAddressBook,
186189
this.capsuleDataProvider,
187190
this.privateEventDataProvider,
188191
0, // totalPublicArgsCount
@@ -278,8 +281,8 @@ export class ContractFunctionSimulator {
278281
this.addressDataProvider,
279282
this.aztecNode,
280283
this.anchorBlockDataProvider,
281-
this.senderTaggingDataProvider,
282284
this.recipientTaggingDataProvider,
285+
this.senderAddressBook,
283286
this.capsuleDataProvider,
284287
this.privateEventDataProvider,
285288
undefined,

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ import type { CapsuleDataProvider } from '../../storage/capsule_data_provider/ca
1818
import type { ContractDataProvider } from '../../storage/contract_data_provider/contract_data_provider.js';
1919
import type { NoteDataProvider } from '../../storage/note_data_provider/note_data_provider.js';
2020
import type { PrivateEventDataProvider } from '../../storage/private_event_data_provider/private_event_data_provider.js';
21-
import type { RecipientTaggingDataProvider } from '../../storage/tagging_data_provider/recipient_tagging_data_provider.js';
21+
import type { SenderAddressBook } from '../../storage/tagging_data_provider/sender_address_book.js';
2222
import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js';
23+
import type { RecipientTaggingDataProvider } from '../../tagging/recipient_sync/recipient_tagging_data_provider.js';
2324
import { ContractFunctionSimulator } from '../contract_function_simulator.js';
2425
import { UtilityExecutionOracle } from './utility_execution_oracle.js';
2526

@@ -34,6 +35,7 @@ describe('Oracle Version Check test suite', () => {
3435
let anchorBlockDataProvider: ReturnType<typeof mock<AnchorBlockDataProvider>>;
3536
let senderTaggingDataProvider: ReturnType<typeof mock<SenderTaggingDataProvider>>;
3637
let recipientTaggingDataProvider: ReturnType<typeof mock<RecipientTaggingDataProvider>>;
38+
let senderAddressBook: ReturnType<typeof mock<SenderAddressBook>>;
3739
let capsuleDataProvider: ReturnType<typeof mock<CapsuleDataProvider>>;
3840
let privateEventDataProvider: ReturnType<typeof mock<PrivateEventDataProvider>>;
3941
let acirSimulator: ContractFunctionSimulator;
@@ -52,6 +54,7 @@ describe('Oracle Version Check test suite', () => {
5254
anchorBlockDataProvider = mock<AnchorBlockDataProvider>();
5355
senderTaggingDataProvider = mock<SenderTaggingDataProvider>();
5456
recipientTaggingDataProvider = mock<RecipientTaggingDataProvider>();
57+
senderAddressBook = mock<SenderAddressBook>();
5558
capsuleDataProvider = mock<CapsuleDataProvider>();
5659
privateEventDataProvider = mock<PrivateEventDataProvider>();
5760
utilityAssertCompatibleOracleVersionSpy = jest.spyOn(
@@ -69,10 +72,7 @@ describe('Oracle Version Check test suite', () => {
6972
senderTaggingDataProvider.getLastUsedIndex.mockResolvedValue(undefined);
7073
senderTaggingDataProvider.getTxHashesOfPendingIndexes.mockResolvedValue([]);
7174
senderTaggingDataProvider.storePendingIndexes.mockResolvedValue();
72-
recipientTaggingDataProvider.getSenderAddresses.mockResolvedValue([]);
73-
recipientTaggingDataProvider.getLastUsedIndexes.mockImplementation(secrets =>
74-
Promise.resolve(secrets.map(() => undefined)),
75-
);
75+
7676
noteDataProvider.getNotes.mockResolvedValue([]);
7777
keyStore.getAccounts.mockResolvedValue([]);
7878

@@ -100,6 +100,7 @@ describe('Oracle Version Check test suite', () => {
100100
anchorBlockDataProvider,
101101
senderTaggingDataProvider,
102102
recipientTaggingDataProvider,
103+
senderAddressBook,
103104
capsuleDataProvider,
104105
privateEventDataProvider,
105106
simulator,

0 commit comments

Comments
 (0)