Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2ebb3c0
make capsule store work with staged writes
mverzilli Jan 8, 2026
e770876
fix race condition in appendToCapsuleArray
mverzilli Jan 9, 2026
31f7ff9
make performance tests include commits
mverzilli Jan 9, 2026
72d5306
add tests for staged writes
mverzilli Jan 9, 2026
1de3908
address code review feedback
mverzilli Jan 9, 2026
d6bd80a
make sender_tagging_store work with staged writes (wip)
mverzilli Jan 9, 2026
e91d195
fix unit tests
mverzilli Jan 9, 2026
0151b30
fix typo
mverzilli Jan 9, 2026
6c4d6f4
implement commit and discardStage
mverzilli Jan 9, 2026
822741d
make RecipientTaggingStore support staged writes
mverzilli Jan 9, 2026
6d75924
wire tagging stores to job coordinator
mverzilli Jan 9, 2026
61fb3e8
add staged write tests for RecipientTaggingStore
mverzilli Jan 9, 2026
5e5f612
staged writes in private event store
mverzilli Jan 12, 2026
f3cd540
add specific tests for staged writes commit/discard
mverzilli Jan 12, 2026
19251bc
hook private event store to job coordinator
mverzilli Jan 12, 2026
ab40add
staged writes on note store
mverzilli Jan 12, 2026
b848dd5
more shakeable import
mverzilli Jan 12, 2026
a74c467
Merge branch 'next' into martin/staged-writes-in-tagging-stores
mverzilli Jan 13, 2026
d848a01
code review
mverzilli Jan 13, 2026
1bdfca4
code review
mverzilli Jan 13, 2026
693b8f1
code review on sender_tagging_store tests
mverzilli Jan 13, 2026
9ec4573
code review recipient_tagging_store
mverzilli Jan 13, 2026
7e9da28
code review sender_tagging_store
mverzilli Jan 13, 2026
ed89a43
reintroduce transactions for note store
mverzilli Jan 13, 2026
07cff5b
poc: locks for intra job concurrency
mverzilli Jan 13, 2026
e0523c9
lint
mverzilli Jan 13, 2026
e3c729d
Merge branch 'next' into martin/staged-writes-in-tagging-stores
mverzilli Jan 14, 2026
418470b
Merge branch 'martin/staged-writes-in-tagging-stores' into martin/sta…
mverzilli Jan 14, 2026
6366614
make getPrivateEvents work on committed data only
mverzilli Jan 14, 2026
67dabeb
parallelize event commit
mverzilli Jan 14, 2026
7d413eb
remove old comment
mverzilli Jan 14, 2026
ab099e5
better comment for why rollback is not in a job context
mverzilli Jan 14, 2026
f7c0388
merge
mverzilli Jan 14, 2026
17914e7
fix failing unit test
mverzilli Jan 14, 2026
f1c663e
Merge branch 'martin/staged-writes-in-private-events' into martin/sta…
mverzilli Jan 14, 2026
d433359
merge
mverzilli Jan 14, 2026
2c45d2e
minor style refactor
mverzilli Jan 14, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP

const pendingNullifiers = this.noteCache.getNullifiers(this.callContext.contractAddress);

const noteService = new NoteService(this.noteStore, this.aztecNode, this.anchorBlockStore);
const noteService = new NoteService(this.noteStore, this.aztecNode, this.anchorBlockStore, this.jobId);
const dbNotes = await noteService.getNotes(
this.callContext.contractAddress,
owner,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
offset: number,
status: NoteStatus,
): Promise<NoteData[]> {
const noteService = new NoteService(this.noteStore, this.aztecNode, this.anchorBlockStore);
const noteService = new NoteService(this.noteStore, this.aztecNode, this.anchorBlockStore, this.jobId);

const dbNotes = await noteService.getNotes(this.contractAddress, owner, storageSlot, status, this.scopes);
return pickNotes<NoteData>(dbNotes, {
Expand Down Expand Up @@ -359,7 +359,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra

await logService.syncTaggedLogs(this.contractAddress, pendingTaggedLogArrayBaseSlot, this.scopes);

const noteService = new NoteService(this.noteStore, this.aztecNode, this.anchorBlockStore);
const noteService = new NoteService(this.noteStore, this.aztecNode, this.anchorBlockStore, this.jobId);
await noteService.syncNoteNullifiers(this.contractAddress);
}

Expand Down Expand Up @@ -393,7 +393,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
await this.capsuleStore.readCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot, this.jobId)
).map(EventValidationRequest.fromFields);

const noteService = new NoteService(this.noteStore, this.aztecNode, this.anchorBlockStore);
const noteService = new NoteService(this.noteStore, this.aztecNode, this.anchorBlockStore, this.jobId);
const noteDeliveries = noteValidationRequests.map(request =>
noteService.deliverNote(
request.contractAddress,
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/pxe/src/debug/pxe_debug_utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomBytes } from '@aztec/foundation/crypto/random';
import type { NoteDao, NotesFilter } from '@aztec/stdlib/note';

import type { PXE } from '../pxe.js';
Expand Down Expand Up @@ -43,6 +44,6 @@ export class PXEDebugUtils {
const call = await this.contractStore.getFunctionCall('sync_private_state', [], filter.contractAddress);
await this.#pxe.simulateUtility(call);

return this.noteStore.getNotes(filter);
return this.noteStore.getNotes(filter, randomBytes(8).toString('hex'));
}
}
81 changes: 48 additions & 33 deletions yarn-project/pxe/src/notes/note_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('NoteService', () => {
contractAddress = await AztecAddress.random();

// Check that there are no notes in the database
const notes = await noteStore.getNotes({ contractAddress });
const notes = await noteStore.getNotes({ contractAddress }, 'test');
expect(notes).toHaveLength(0);

// Check that the expected number of accounts is present
Expand All @@ -54,7 +54,7 @@ describe('NoteService', () => {

recipient = await keyStore.addAccount(new Fr(69), Fr.random());

noteService = new NoteService(noteStore, aztecNode, anchorBlockStore);
noteService = new NoteService(noteStore, aztecNode, anchorBlockStore, 'test');
});

it('should remove notes that have been nullified', async () => {
Expand All @@ -66,7 +66,7 @@ describe('NoteService', () => {
jest.spyOn(noteStore, 'applyNullifiers');

// Add the note to storage
await noteStore.addNotes([noteDao], recipient.address);
await noteStore.addNotes([noteDao], recipient.address, 'test');

// Set up the nullifier in the merkle tree
const nullifierIndex = randomDataInBlock(123n);
Expand All @@ -76,11 +76,14 @@ describe('NoteService', () => {
await noteService.syncNoteNullifiers(contractAddress);

// Verify the note was removed by checking storage
const remainingNotes = await noteStore.getNotes({
contractAddress,
status: NoteStatus.ACTIVE,
scopes: [recipient.address],
});
const remainingNotes = await noteStore.getNotes(
{
contractAddress,
status: NoteStatus.ACTIVE,
scopes: [recipient.address],
},
'test',
);
expect(remainingNotes).toHaveLength(0);

// Verify the note was removed by checking the spy
Expand All @@ -92,7 +95,7 @@ describe('NoteService', () => {
const noteDao = await NoteDao.random({ contractAddress });

// Add the note to storage
await noteStore.addNotes([noteDao], recipient.address);
await noteStore.addNotes([noteDao], recipient.address, 'test');

// No nullifier found in merkle tree
aztecNode.findLeavesIndexes.mockResolvedValue([undefined]);
Expand All @@ -101,11 +104,14 @@ describe('NoteService', () => {
await noteService.syncNoteNullifiers(contractAddress);

// Verify note still exists
const remainingNotes = await noteStore.getNotes({
contractAddress,
status: NoteStatus.ACTIVE,
scopes: [recipient.address],
});
const remainingNotes = await noteStore.getNotes(
{
contractAddress,
status: NoteStatus.ACTIVE,
scopes: [recipient.address],
},
'test',
);
expect(remainingNotes).toHaveLength(1);
expect(remainingNotes[0]).toEqual(noteDao);
});
Expand All @@ -118,7 +124,7 @@ describe('NoteService', () => {
const noteDao = await NoteDao.random({ contractAddress });

// Add the note to storage
await noteStore.addNotes([noteDao], recipient.address);
await noteStore.addNotes([noteDao], recipient.address, 'test');

// Mock nullifier to only exist after synced block
aztecNode.findLeavesIndexes.mockImplementation(blockNum => {
Expand All @@ -132,11 +138,14 @@ describe('NoteService', () => {
await noteService.syncNoteNullifiers(contractAddress);

// Verify note still exists
const remainingNotes = await noteStore.getNotes({
contractAddress,
status: NoteStatus.ACTIVE,
scopes: [recipient.address],
});
const remainingNotes = await noteStore.getNotes(
{
contractAddress,
status: NoteStatus.ACTIVE,
scopes: [recipient.address],
},
'test',
);
expect(remainingNotes).toHaveLength(1);
expect(remainingNotes[0]).toEqual(noteDao);
});
Expand All @@ -157,8 +166,8 @@ describe('NoteService', () => {
// Verify applyNullifiers was called once for all accounts
expect(getNotesSpy).toHaveBeenCalledTimes(1);

// Verify getNotes was called with the correct contract address
expect(getNotesSpy).toHaveBeenCalledWith(expect.objectContaining({ contractAddress }));
// Verify getNotes was called with the correct contract address and jobId
expect(getNotesSpy).toHaveBeenCalledWith(expect.objectContaining({ contractAddress }), 'test');
});

describe('deliverNote', () => {
Expand Down Expand Up @@ -248,7 +257,7 @@ describe('NoteService', () => {
);

// Verify note was stored
const notes = await noteStore.getNotes({ contractAddress, scopes: [recipient.address] });
const notes = await noteStore.getNotes({ contractAddress, scopes: [recipient.address] }, 'test');

expect(notes).toHaveLength(1);
expect(notes[0].noteHash.equals(noteHash)).toBe(true);
Expand Down Expand Up @@ -334,19 +343,25 @@ describe('NoteService', () => {

// Now we verify that the note is stored as nullified by checking it can be retrieved only with
// the ACTIVE_OR_NULLIFIED status on the input.
const allNotes = await noteStore.getNotes({
contractAddress,
scopes: [recipient.address],
status: NoteStatus.ACTIVE_OR_NULLIFIED,
});
const allNotes = await noteStore.getNotes(
{
contractAddress,
scopes: [recipient.address],
status: NoteStatus.ACTIVE_OR_NULLIFIED,
},
'test',
);
expect(allNotes).toHaveLength(1);
expect(allNotes[0].noteHash.equals(noteHash)).toBe(true);

const activeNotes = await noteStore.getNotes({
contractAddress,
scopes: [recipient.address],
status: NoteStatus.ACTIVE,
});
const activeNotes = await noteStore.getNotes(
{
contractAddress,
scopes: [recipient.address],
status: NoteStatus.ACTIVE,
},
'test',
);
expect(activeNotes).toHaveLength(0);
});
});
Expand Down
33 changes: 21 additions & 12 deletions yarn-project/pxe/src/notes/note_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class NoteService {
private readonly noteStore: NoteStore,
private readonly aztecNode: AztecNode,
private readonly anchorBlockStore: AnchorBlockStore,
private readonly jobId: string,
) {}

/**
Expand All @@ -33,13 +34,16 @@ export class NoteService {
status: NoteStatus,
scopes?: AztecAddress[],
) {
const noteDaos = await this.noteStore.getNotes({
contractAddress,
owner,
storageSlot,
status,
scopes,
});
const noteDaos = await this.noteStore.getNotes(
{
contractAddress,
owner,
storageSlot,
status,
scopes,
},
this.jobId,
);
return noteDaos.map(
({ contractAddress, owner, storageSlot, randomness, noteNonce, note, noteHash, siloedNullifier }) => ({
contractAddress,
Expand Down Expand Up @@ -70,7 +74,7 @@ export class NoteService {
public async syncNoteNullifiers(contractAddress: AztecAddress): Promise<void> {
const syncedBlockNumber = (await this.anchorBlockStore.getBlockHeader()).getBlockNumber();

const contractNotes = await this.noteStore.getNotes({ contractAddress });
const contractNotes = await this.noteStore.getNotes({ contractAddress }, this.jobId);

if (contractNotes.length === 0) {
return;
Expand Down Expand Up @@ -104,7 +108,7 @@ export class NoteService {
})
.filter(nullifier => nullifier !== undefined) as DataInBlock<Fr>[];

await this.noteStore.applyNullifiers(foundNullifiers);
await this.noteStore.applyNullifiers(foundNullifiers, this.jobId);
}

public async deliverNote(
Expand Down Expand Up @@ -180,12 +184,17 @@ export class NoteService {
);

// The note was found by `recipient`, so we use that as the scope when storing the note.
await this.noteStore.addNotes([noteDao], recipient);
await this.noteStore.addNotes([noteDao], recipient, this.jobId);

if (nullifierIndex !== undefined) {
// We found nullifier index which implies that the note has already been nullified.
const { data: _, ...blockHashAndNum } = nullifierIndex;
await this.noteStore.applyNullifiers([{ data: siloedNullifier, ...blockHashAndNum }]);
// Only apply nullifier if the note isn't already nullified
// (it might have been nullified by a previous sync operation in this job)
const alreadyNullified = await this.noteStore.isNoteNullified(noteDao, this.jobId);
if (!alreadyNullified) {
const { data: _, ...blockHashAndNum } = nullifierIndex;
await this.noteStore.applyNullifiers([{ data: siloedNullifier, ...blockHashAndNum }], this.jobId);
}
}
}
}
8 changes: 7 additions & 1 deletion yarn-project/pxe/src/pxe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,13 @@ export class PXE {
);

const jobCoordinator = new JobCoordinator(store);
jobCoordinator.registerStores([capsuleStore, senderTaggingStore, recipientTaggingStore, privateEventStore]);
jobCoordinator.registerStores([
capsuleStore,
senderTaggingStore,
recipientTaggingStore,
privateEventStore,
noteStore,
]);

const debugUtils = new PXEDebugUtils(contractStore, noteStore);

Expand Down
Loading