Skip to content

Commit 93f9315

Browse files
authored
feat: PXE adds note processors for stored accounts (AztecProtocol#3673)
This PR makes the PXE add note processors for each stored key in the keystore when it is started. This way any previously stored accounts start syncing with the chain immediately.
1 parent 43aa603 commit 93f9315

File tree

10 files changed

+161
-66
lines changed

10 files changed

+161
-66
lines changed

yarn-project/archiver/src/archiver/archiver.ts

+12-13
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,6 @@ export class Archiver implements L2BlockSource, L2LogsSource, ContractDataSource
4747
*/
4848
private runningPromise?: RunningPromise;
4949

50-
/**
51-
* Next L1 block number to fetch `L2BlockProcessed` logs from (i.e. `fromBlock` in eth_getLogs).
52-
*/
53-
private nextL2BlockFromL1Block = 0n;
54-
5550
/**
5651
* Use this to track logged block in order to avoid repeating the same message.
5752
*/
@@ -220,11 +215,21 @@ export class Archiver implements L2BlockSource, L2LogsSource, ContractDataSource
220215
this.publicClient,
221216
this.rollupAddress,
222217
blockUntilSynced,
223-
this.nextL2BlockFromL1Block,
218+
lastProcessedL1BlockNumber + 1n,
224219
currentL1BlockNumber,
225220
nextExpectedL2BlockNum,
226221
);
227222

223+
if (retrievedBlocks.retrievedData.length === 0) {
224+
return;
225+
} else {
226+
this.log(
227+
`Retrieved ${retrievedBlocks.retrievedData.length} new L2 blocks between L1 blocks ${
228+
lastProcessedL1BlockNumber + 1n
229+
} and ${currentL1BlockNumber}.`,
230+
);
231+
}
232+
228233
// create the block number -> block hash mapping to ensure we retrieve the appropriate events
229234
const blockHashMapping: { [key: number]: Buffer | undefined } = {};
230235
retrievedBlocks.retrievedData.forEach((block: L2Block) => {
@@ -234,13 +239,10 @@ export class Archiver implements L2BlockSource, L2LogsSource, ContractDataSource
234239
this.publicClient,
235240
this.contractDeploymentEmitterAddress,
236241
blockUntilSynced,
237-
this.nextL2BlockFromL1Block,
242+
lastProcessedL1BlockNumber + 1n,
238243
currentL1BlockNumber,
239244
blockHashMapping,
240245
);
241-
if (retrievedBlocks.retrievedData.length === 0) {
242-
return;
243-
}
244246

245247
this.log(`Retrieved ${retrievedBlocks.retrievedData.length} block(s) from chain`);
246248

@@ -280,9 +282,6 @@ export class Archiver implements L2BlockSource, L2LogsSource, ContractDataSource
280282
);
281283
}),
282284
);
283-
284-
// set the L1 block for the next search
285-
this.nextL2BlockFromL1Block = retrievedBlocks.nextEthBlockNumber;
286285
}
287286

288287
/**

yarn-project/pxe/src/config/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { fileURLToPath } from 'url';
1010
export interface PXEServiceConfig {
1111
/** The interval to wait between polling for new blocks. */
1212
l2BlockPollingIntervalMS: number;
13-
/** L2 block to start scanning from */
13+
/** L2 block to start scanning from for new accounts */
1414
l2StartingBlock: number;
1515

1616
/** Where to store PXE data. If not set will store in memory */

yarn-project/pxe/src/database/kv_pxe_database.ts

+26-8
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
import { AztecAddress, BlockHeader, CompleteAddress } from '@aztec/circuits.js';
2-
import { Fr } from '@aztec/foundation/fields';
2+
import { Fr, Point } from '@aztec/foundation/fields';
33
import { AztecArray, AztecKVStore, AztecMap, AztecMultiMap, AztecSingleton } from '@aztec/kv-store';
44
import { ContractDao, MerkleTreeId, NoteFilter, PublicKey } from '@aztec/types';
55

66
import { NoteDao } from './note_dao.js';
77
import { PxeDatabase } from './pxe_database.js';
88

99
/** Serialized structure of a block header */
10-
type SerializedBlockHeader = {
10+
type SynchronizedBlock = {
1111
/** The tree roots when the block was created */
1212
roots: Record<MerkleTreeId, string>;
1313
/** The hash of the global variables */
1414
globalVariablesHash: string;
15+
/** The block number */
16+
blockNumber: number;
1517
};
1618

1719
/**
1820
* A PXE database backed by LMDB.
1921
*/
2022
export class KVPxeDatabase implements PxeDatabase {
21-
#blockHeader: AztecSingleton<SerializedBlockHeader>;
23+
#synchronizedBlock: AztecSingleton<SynchronizedBlock>;
2224
#addresses: AztecArray<Buffer>;
2325
#addressIndex: AztecMap<string, number>;
2426
#authWitnesses: AztecMap<string, Buffer[]>;
@@ -30,6 +32,7 @@ export class KVPxeDatabase implements PxeDatabase {
3032
#notesByStorageSlot: AztecMultiMap<string, number>;
3133
#notesByTxHash: AztecMultiMap<string, number>;
3234
#notesByOwner: AztecMultiMap<string, number>;
35+
#syncedBlockPerPublicKey: AztecMap<string, number>;
3336
#db: AztecKVStore;
3437

3538
constructor(db: AztecKVStore) {
@@ -40,9 +43,11 @@ export class KVPxeDatabase implements PxeDatabase {
4043

4144
this.#authWitnesses = db.createMap('auth_witnesses');
4245
this.#capsules = db.createArray('capsules');
43-
this.#blockHeader = db.createSingleton('block_header');
4446
this.#contracts = db.createMap('contracts');
4547

48+
this.#synchronizedBlock = db.createSingleton('block_header');
49+
this.#syncedBlockPerPublicKey = db.createMap('synced_block_per_public_key');
50+
4651
this.#notes = db.createArray('notes');
4752
this.#nullifiedNotes = db.createMap('nullified_notes');
4853

@@ -173,7 +178,7 @@ export class KVPxeDatabase implements PxeDatabase {
173178
}
174179

175180
getTreeRoots(): Record<MerkleTreeId, Fr> {
176-
const roots = this.#blockHeader.get()?.roots;
181+
const roots = this.#synchronizedBlock.get()?.roots;
177182
if (!roots) {
178183
throw new Error(`Tree roots not set`);
179184
}
@@ -188,8 +193,9 @@ export class KVPxeDatabase implements PxeDatabase {
188193
};
189194
}
190195

191-
async setBlockHeader(blockHeader: BlockHeader): Promise<void> {
192-
await this.#blockHeader.set({
196+
async setBlockData(blockNumber: number, blockHeader: BlockHeader): Promise<void> {
197+
await this.#synchronizedBlock.set({
198+
blockNumber,
193199
globalVariablesHash: blockHeader.globalVariablesHash.toString(),
194200
roots: {
195201
[MerkleTreeId.NOTE_HASH_TREE]: blockHeader.noteHashTreeRoot.toString(),
@@ -202,8 +208,12 @@ export class KVPxeDatabase implements PxeDatabase {
202208
});
203209
}
204210

211+
getBlockNumber(): number | undefined {
212+
return this.#synchronizedBlock.get()?.blockNumber;
213+
}
214+
205215
getBlockHeader(): BlockHeader {
206-
const value = this.#blockHeader.get();
216+
const value = this.#synchronizedBlock.get();
207217
if (!value) {
208218
throw new Error(`Block header not set`);
209219
}
@@ -261,6 +271,14 @@ export class KVPxeDatabase implements PxeDatabase {
261271
return Promise.resolve(Array.from(this.#addresses).map(v => CompleteAddress.fromBuffer(v)));
262272
}
263273

274+
getSynchedBlockNumberForPublicKey(publicKey: Point): number | undefined {
275+
return this.#syncedBlockPerPublicKey.get(publicKey.toString());
276+
}
277+
278+
setSynchedBlockNumberForPublicKey(publicKey: Point, blockNumber: number): Promise<boolean> {
279+
return this.#syncedBlockPerPublicKey.set(publicKey.toString(), blockNumber);
280+
}
281+
264282
estimateSize(): number {
265283
const notesSize = Array.from(this.#getAllNonNullifiedNotes()).reduce((sum, note) => sum + note.getSize(), 0);
266284
const authWitsSize = Array.from(this.#authWitnesses.values()).reduce(

yarn-project/pxe/src/database/memory_db.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { BlockHeader, CompleteAddress, PublicKey } from '@aztec/circuits.js';
22
import { AztecAddress } from '@aztec/foundation/aztec-address';
3-
import { Fr } from '@aztec/foundation/fields';
3+
import { Fr, Point } from '@aztec/foundation/fields';
44
import { createDebugLogger } from '@aztec/foundation/log';
55
import { MerkleTreeId, NoteFilter } from '@aztec/types';
66

@@ -18,8 +18,10 @@ export class MemoryDB extends MemoryContractDatabase implements PxeDatabase {
1818
private notesTable: NoteDao[] = [];
1919
private treeRoots: Record<MerkleTreeId, Fr> | undefined;
2020
private globalVariablesHash: Fr | undefined;
21+
private blockNumber: number | undefined;
2122
private addresses: CompleteAddress[] = [];
2223
private authWitnesses: Record<string, Fr[]> = {};
24+
private syncedBlockPerPublicKey = new Map<string, number>();
2325
// A capsule is a "blob" of data that is passed to the contract through an oracle.
2426
// We are using a stack to keep track of the capsules that are passed to the contract.
2527
private capsuleStack: Fr[][] = [];
@@ -134,8 +136,9 @@ export class MemoryDB extends MemoryContractDatabase implements PxeDatabase {
134136
);
135137
}
136138

137-
public setBlockHeader(blockHeader: BlockHeader): Promise<void> {
139+
public setBlockData(blockNumber: number, blockHeader: BlockHeader): Promise<void> {
138140
this.globalVariablesHash = blockHeader.globalVariablesHash;
141+
this.blockNumber = blockNumber;
139142
this.setTreeRoots({
140143
[MerkleTreeId.NOTE_HASH_TREE]: blockHeader.noteHashTreeRoot,
141144
[MerkleTreeId.NULLIFIER_TREE]: blockHeader.nullifierTreeRoot,
@@ -148,6 +151,10 @@ export class MemoryDB extends MemoryContractDatabase implements PxeDatabase {
148151
return Promise.resolve();
149152
}
150153

154+
public getBlockNumber(): number | undefined {
155+
return this.blockNumber;
156+
}
157+
151158
public addCompleteAddress(completeAddress: CompleteAddress): Promise<boolean> {
152159
const accountIndex = this.addresses.findIndex(r => r.address.equals(completeAddress.address));
153160
if (accountIndex !== -1) {
@@ -174,6 +181,15 @@ export class MemoryDB extends MemoryContractDatabase implements PxeDatabase {
174181
return Promise.resolve(this.addresses);
175182
}
176183

184+
getSynchedBlockNumberForPublicKey(publicKey: Point): number | undefined {
185+
return this.syncedBlockPerPublicKey.get(publicKey.toString());
186+
}
187+
188+
setSynchedBlockNumberForPublicKey(publicKey: Point, blockNumber: number): Promise<boolean> {
189+
this.syncedBlockPerPublicKey.set(publicKey.toString(), blockNumber);
190+
return Promise.resolve(true);
191+
}
192+
177193
public estimateSize() {
178194
const notesSize = this.notesTable.reduce((sum, note) => sum + note.getSize(), 0);
179195
const treeRootsSize = this.treeRoots ? Object.entries(this.treeRoots).length * Fr.SIZE_IN_BYTES : 0;

yarn-project/pxe/src/database/pxe_database.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,22 @@ export interface PxeDatabase extends ContractDatabase {
7979
*/
8080
getTreeRoots(): Record<MerkleTreeId, Fr>;
8181

82+
/**
83+
* Gets the most recently processed block number.
84+
* @returns The most recently processed block number or undefined if never synched.
85+
*/
86+
getBlockNumber(): number | undefined;
87+
8288
/**
8389
* Retrieve the stored Block Header from the database.
8490
* The function returns a Promise that resolves to the Block Header.
8591
* This data is required to reproduce block attestations.
8692
* Throws an error if the block header is not available within the database.
8793
*
8894
* note: this data is a combination of the tree roots and the global variables hash.
95+
*
96+
* @returns The Block Header.
97+
* @throws If no block have been processed yet.
8998
*/
9099
getBlockHeader(): BlockHeader;
91100

@@ -94,10 +103,11 @@ export interface PxeDatabase extends ContractDatabase {
94103
* This function updates the 'global variables hash' and `tree roots` property of the instance
95104
* Note that this will overwrite any existing hash or roots in the database.
96105
*
106+
* @param blockNumber - The block number of the most recent block
97107
* @param blockHeader - An object containing the most recent block header.
98108
* @returns A Promise that resolves when the hash has been successfully updated in the database.
99109
*/
100-
setBlockHeader(blockHeader: BlockHeader): Promise<void>;
110+
setBlockData(blockNumber: number, blockHeader: BlockHeader): Promise<void>;
101111

102112
/**
103113
* Adds complete address to the database.
@@ -121,6 +131,19 @@ export interface PxeDatabase extends ContractDatabase {
121131
*/
122132
getCompleteAddresses(): Promise<CompleteAddress[]>;
123133

134+
/**
135+
* Updates up to which block number we have processed notes for a given public key.
136+
* @param publicKey - The public key to set the synched block number for.
137+
* @param blockNumber - The block number to set.
138+
*/
139+
setSynchedBlockNumberForPublicKey(publicKey: PublicKey, blockNumber: number): Promise<boolean>;
140+
141+
/**
142+
* Get the synched block number for a given public key.
143+
* @param publicKey - The public key to get the synched block number for.
144+
*/
145+
getSynchedBlockNumberForPublicKey(publicKey: PublicKey): number | undefined;
146+
124147
/**
125148
* Returns the estimated size in bytes of this db.
126149
* @returns The estimated size in bytes of this db.

yarn-project/pxe/src/database/pxe_database_test_suite.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AztecAddress, BlockHeader, CompleteAddress } from '@aztec/circuits.js';
22
import { Fr, Point } from '@aztec/foundation/fields';
3-
import { MerkleTreeId, NoteFilter, randomTxHash } from '@aztec/types';
3+
import { INITIAL_L2_BLOCK_NUM, MerkleTreeId, NoteFilter, randomTxHash } from '@aztec/types';
44

55
import { NoteDao } from './note_dao.js';
66
import { randomNoteDao } from './note_dao.test.js';
@@ -155,13 +155,13 @@ export function describePxeDatabase(getDatabase: () => PxeDatabase) {
155155
const blockHeader = BlockHeader.random();
156156
blockHeader.privateKernelVkTreeRoot = Fr.zero();
157157

158-
await database.setBlockHeader(blockHeader);
158+
await database.setBlockData(INITIAL_L2_BLOCK_NUM, blockHeader);
159159
expect(database.getBlockHeader()).toEqual(blockHeader);
160160
});
161161

162162
it('retrieves the merkle tree roots from the block', async () => {
163163
const blockHeader = BlockHeader.random();
164-
await database.setBlockHeader(blockHeader);
164+
await database.setBlockData(INITIAL_L2_BLOCK_NUM, blockHeader);
165165
expect(database.getTreeRoots()).toEqual({
166166
[MerkleTreeId.NOTE_HASH_TREE]: blockHeader.noteHashTreeRoot,
167167
[MerkleTreeId.NULLIFIER_TREE]: blockHeader.nullifierTreeRoot,

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

+22
Original file line numberDiff line numberDiff line change
@@ -223,4 +223,26 @@ describe('Note Processor', () => {
223223
addedNoteDaos.forEach(info => nonceSet.add(info.nonce.value));
224224
expect(nonceSet.size).toBe(notes.length);
225225
});
226+
227+
it('advances the block number', async () => {
228+
const { blockContexts, encryptedLogsArr } = mockData([[2]]);
229+
await noteProcessor.process(blockContexts, encryptedLogsArr);
230+
expect(noteProcessor.status.syncedToBlock).toEqual(blockContexts.at(-1)?.block.number);
231+
});
232+
233+
it('should restore the last block number processed and ignore the starting block', async () => {
234+
const { blockContexts, encryptedLogsArr } = mockData([[2]]);
235+
await noteProcessor.process(blockContexts, encryptedLogsArr);
236+
237+
const newNoteProcessor = new NoteProcessor(
238+
owner.getPublicKey(),
239+
keyStore,
240+
database,
241+
aztecNode,
242+
INITIAL_L2_BLOCK_NUM,
243+
simulator,
244+
);
245+
246+
expect(newNoteProcessor.status).toEqual(noteProcessor.status);
247+
});
226248
});

0 commit comments

Comments
 (0)