@aztec/pxe 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dest/database/deferred_note_dao.d.ts +4 -0
  2. package/dest/database/deferred_note_dao.d.ts.map +1 -1
  3. package/dest/database/deferred_note_dao.js +6 -3
  4. package/dest/database/note_dao.d.ts +4 -0
  5. package/dest/database/note_dao.d.ts.map +1 -1
  6. package/dest/database/note_dao.js +7 -2
  7. package/dest/database/pxe_database_test_suite.js +2 -2
  8. package/dest/kernel_prover/kernel_prover.js +9 -9
  9. package/dest/kernel_prover/proof_creator.d.ts +10 -10
  10. package/dest/kernel_prover/proof_creator.d.ts.map +1 -1
  11. package/dest/kernel_prover/proof_creator.js +4 -4
  12. package/dest/note_processor/note_processor.d.ts.map +1 -1
  13. package/dest/note_processor/note_processor.js +4 -4
  14. package/dest/note_processor/produce_note_dao.d.ts.map +1 -1
  15. package/dest/note_processor/produce_note_dao.js +4 -4
  16. package/dest/pxe_service/create_pxe_service.d.ts.map +1 -1
  17. package/dest/pxe_service/create_pxe_service.js +4 -1
  18. package/dest/pxe_service/pxe_service.d.ts.map +1 -1
  19. package/dest/pxe_service/pxe_service.js +5 -5
  20. package/package.json +13 -12
  21. package/src/bin/index.ts +42 -0
  22. package/src/config/index.ts +40 -0
  23. package/src/contract_data_oracle/index.ts +185 -0
  24. package/src/contract_data_oracle/private_functions_tree.ts +127 -0
  25. package/src/contract_database/index.ts +1 -0
  26. package/src/contract_database/memory_contract_database.ts +58 -0
  27. package/src/database/contracts/contract_artifact_db.ts +19 -0
  28. package/src/database/contracts/contract_instance_db.ts +18 -0
  29. package/src/database/deferred_note_dao.ts +55 -0
  30. package/src/database/index.ts +1 -0
  31. package/src/database/kv_pxe_database.ts +409 -0
  32. package/src/database/note_dao.ts +97 -0
  33. package/src/database/pxe_database.ts +162 -0
  34. package/src/database/pxe_database_test_suite.ts +258 -0
  35. package/src/index.ts +11 -0
  36. package/src/kernel_oracle/index.ts +61 -0
  37. package/src/kernel_prover/index.ts +2 -0
  38. package/src/kernel_prover/kernel_prover.ts +388 -0
  39. package/src/kernel_prover/proof_creator.ts +157 -0
  40. package/src/kernel_prover/proving_data_oracle.ts +76 -0
  41. package/src/note_processor/index.ts +1 -0
  42. package/src/note_processor/note_processor.ts +282 -0
  43. package/src/note_processor/produce_note_dao.ts +132 -0
  44. package/src/pxe_http/index.ts +1 -0
  45. package/src/pxe_http/pxe_http_server.ts +73 -0
  46. package/src/pxe_service/create_pxe_service.ts +52 -0
  47. package/src/pxe_service/index.ts +3 -0
  48. package/src/pxe_service/pxe_service.ts +756 -0
  49. package/src/pxe_service/test/pxe_test_suite.ts +138 -0
  50. package/src/simulator/index.ts +24 -0
  51. package/src/simulator_oracle/index.ts +210 -0
  52. package/src/synchronizer/index.ts +1 -0
  53. package/src/synchronizer/synchronizer.ts +427 -0
@@ -0,0 +1,756 @@
1
+ import {
2
+ AuthWitness,
3
+ AztecNode,
4
+ ContractDao,
5
+ ContractData,
6
+ DeployedContract,
7
+ ExtendedContractData,
8
+ ExtendedNote,
9
+ FunctionCall,
10
+ GetUnencryptedLogsResponse,
11
+ KeyStore,
12
+ L2Block,
13
+ L2Tx,
14
+ LogFilter,
15
+ MerkleTreeId,
16
+ NoteFilter,
17
+ PXE,
18
+ SimulationError,
19
+ Tx,
20
+ TxExecutionRequest,
21
+ TxHash,
22
+ TxL2Logs,
23
+ TxReceipt,
24
+ TxStatus,
25
+ getNewContractPublicFunctions,
26
+ isNoirCallStackUnresolved,
27
+ } from '@aztec/circuit-types';
28
+ import { TxPXEProcessingStats } from '@aztec/circuit-types/stats';
29
+ import {
30
+ AztecAddress,
31
+ CallRequest,
32
+ CompleteAddress,
33
+ FunctionData,
34
+ GrumpkinPrivateKey,
35
+ MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX,
36
+ PartialAddress,
37
+ PrivateKernelTailCircuitPublicInputs,
38
+ PublicCallRequest,
39
+ computeArtifactHash,
40
+ computeContractClassId,
41
+ computeSaltedInitializationHash,
42
+ getContractClassFromArtifact,
43
+ } from '@aztec/circuits.js';
44
+ import { computeCommitmentNonce, siloNullifier } from '@aztec/circuits.js/abis';
45
+ import { DecodedReturn, encodeArguments } from '@aztec/foundation/abi';
46
+ import { padArrayEnd } from '@aztec/foundation/collection';
47
+ import { Fr } from '@aztec/foundation/fields';
48
+ import { SerialQueue } from '@aztec/foundation/fifo';
49
+ import { DebugLogger, createDebugLogger } from '@aztec/foundation/log';
50
+ import { Timer } from '@aztec/foundation/timer';
51
+ import {
52
+ AcirSimulator,
53
+ ExecutionResult,
54
+ collectEncryptedLogs,
55
+ collectEnqueuedPublicFunctionCalls,
56
+ collectUnencryptedLogs,
57
+ resolveOpcodeLocations,
58
+ } from '@aztec/simulator';
59
+ import { ContractInstanceWithAddress } from '@aztec/types/contracts';
60
+ import { NodeInfo } from '@aztec/types/interfaces';
61
+
62
+ import { PXEServiceConfig, getPackageInfo } from '../config/index.js';
63
+ import { ContractDataOracle } from '../contract_data_oracle/index.js';
64
+ import { PxeDatabase } from '../database/index.js';
65
+ import { NoteDao } from '../database/note_dao.js';
66
+ import { KernelOracle } from '../kernel_oracle/index.js';
67
+ import { KernelProver } from '../kernel_prover/kernel_prover.js';
68
+ import { getAcirSimulator } from '../simulator/index.js';
69
+ import { Synchronizer } from '../synchronizer/index.js';
70
+
71
+ /**
72
+ * A Private eXecution Environment (PXE) implementation.
73
+ */
74
+ export class PXEService implements PXE {
75
+ private synchronizer: Synchronizer;
76
+ private contractDataOracle: ContractDataOracle;
77
+ private simulator: AcirSimulator;
78
+ private log: DebugLogger;
79
+ private nodeVersion: string;
80
+ // serialize synchronizer and calls to simulateTx.
81
+ // ensures that state is not changed while simulating
82
+ private jobQueue = new SerialQueue();
83
+
84
+ constructor(
85
+ private keyStore: KeyStore,
86
+ private node: AztecNode,
87
+ private db: PxeDatabase,
88
+ private config: PXEServiceConfig,
89
+ logSuffix?: string,
90
+ ) {
91
+ this.log = createDebugLogger(logSuffix ? `aztec:pxe_service_${logSuffix}` : `aztec:pxe_service`);
92
+ this.synchronizer = new Synchronizer(node, db, this.jobQueue, logSuffix);
93
+ this.contractDataOracle = new ContractDataOracle(db);
94
+ this.simulator = getAcirSimulator(db, node, keyStore, this.contractDataOracle);
95
+ this.nodeVersion = getPackageInfo().version;
96
+
97
+ this.jobQueue.start();
98
+ }
99
+
100
+ /**
101
+ * Starts the PXE Service by beginning the synchronization process between the Aztec node and the database.
102
+ *
103
+ * @returns A promise that resolves when the server has started successfully.
104
+ */
105
+ public async start() {
106
+ const { l2BlockPollingIntervalMS } = this.config;
107
+ await this.synchronizer.start(1, l2BlockPollingIntervalMS);
108
+ await this.restoreNoteProcessors();
109
+ const info = await this.getNodeInfo();
110
+ this.log.info(`Started PXE connected to chain ${info.chainId} version ${info.protocolVersion}`);
111
+ }
112
+
113
+ private async restoreNoteProcessors() {
114
+ const publicKeys = await this.keyStore.getAccounts();
115
+ const publicKeysSet = new Set(publicKeys.map(k => k.toString()));
116
+
117
+ const registeredAddresses = await this.db.getCompleteAddresses();
118
+
119
+ let count = 0;
120
+ for (const address of registeredAddresses) {
121
+ if (!publicKeysSet.has(address.publicKey.toString())) {
122
+ continue;
123
+ }
124
+
125
+ count++;
126
+ this.synchronizer.addAccount(address.publicKey, this.keyStore, this.config.l2StartingBlock);
127
+ }
128
+
129
+ if (count > 0) {
130
+ this.log(`Restored ${count} accounts`);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Stops the PXE Service, halting processing of new transactions and shutting down the synchronizer.
136
+ * This function ensures that all ongoing tasks are completed before stopping the server.
137
+ * It is useful for gracefully shutting down the server during maintenance or restarts.
138
+ *
139
+ * @returns A Promise resolving once the server has been stopped successfully.
140
+ */
141
+ public async stop() {
142
+ await this.jobQueue.cancel();
143
+ this.log.info('Cancelled Job Queue');
144
+ await this.synchronizer.stop();
145
+ this.log.info('Stopped Synchronizer');
146
+ }
147
+
148
+ /** Returns an estimate of the db size in bytes. */
149
+ public estimateDbSize() {
150
+ return this.db.estimateSize();
151
+ }
152
+
153
+ public addAuthWitness(witness: AuthWitness) {
154
+ return this.db.addAuthWitness(witness.requestHash, witness.witness);
155
+ }
156
+
157
+ public addCapsule(capsule: Fr[]) {
158
+ return this.db.addCapsule(capsule);
159
+ }
160
+
161
+ public getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
162
+ return this.db.getContractInstance(address);
163
+ }
164
+
165
+ public async registerAccount(privKey: GrumpkinPrivateKey, partialAddress: PartialAddress): Promise<CompleteAddress> {
166
+ const completeAddress = CompleteAddress.fromPrivateKeyAndPartialAddress(privKey, partialAddress);
167
+ const wasAdded = await this.db.addCompleteAddress(completeAddress);
168
+ if (wasAdded) {
169
+ const pubKey = await this.keyStore.addAccount(privKey);
170
+ this.synchronizer.addAccount(pubKey, this.keyStore, this.config.l2StartingBlock);
171
+ this.log.info(`Registered account ${completeAddress.address.toString()}`);
172
+ this.log.debug(`Registered account\n ${completeAddress.toReadableString()}`);
173
+ } else {
174
+ this.log.info(`Account:\n "${completeAddress.address.toString()}"\n already registered.`);
175
+ }
176
+ return completeAddress;
177
+ }
178
+
179
+ public async getRegisteredAccounts(): Promise<CompleteAddress[]> {
180
+ // Get complete addresses of both the recipients and the accounts
181
+ const addresses = await this.db.getCompleteAddresses();
182
+ // Filter out the addresses not corresponding to accounts
183
+ const accountPubKeys = await this.keyStore.getAccounts();
184
+ const accounts = addresses.filter(address => accountPubKeys.find(pubKey => pubKey.equals(address.publicKey)));
185
+ return accounts;
186
+ }
187
+
188
+ public async getRegisteredAccount(address: AztecAddress): Promise<CompleteAddress | undefined> {
189
+ const result = await this.getRegisteredAccounts();
190
+ const account = result.find(r => r.address.equals(address));
191
+ return Promise.resolve(account);
192
+ }
193
+
194
+ public async registerRecipient(recipient: CompleteAddress): Promise<void> {
195
+ const wasAdded = await this.db.addCompleteAddress(recipient);
196
+ if (wasAdded) {
197
+ this.log.info(`Added recipient:\n ${recipient.toReadableString()}`);
198
+ } else {
199
+ this.log.info(`Recipient:\n "${recipient.toReadableString()}"\n already registered.`);
200
+ }
201
+ }
202
+
203
+ public async getRecipients(): Promise<CompleteAddress[]> {
204
+ // Get complete addresses of both the recipients and the accounts
205
+ const addresses = await this.db.getCompleteAddresses();
206
+ // Filter out the addresses corresponding to accounts
207
+ const accountPubKeys = await this.keyStore.getAccounts();
208
+ const recipients = addresses.filter(address => !accountPubKeys.find(pubKey => pubKey.equals(address.publicKey)));
209
+ return recipients;
210
+ }
211
+
212
+ public async getRecipient(address: AztecAddress): Promise<CompleteAddress | undefined> {
213
+ const result = await this.getRecipients();
214
+ const recipient = result.find(r => r.address.equals(address));
215
+ return Promise.resolve(recipient);
216
+ }
217
+
218
+ public async addContracts(contracts: DeployedContract[]) {
219
+ const contractDaos = contracts.map(c => new ContractDao(c.artifact, c.instance));
220
+ await Promise.all(contractDaos.map(c => this.db.addContract(c)));
221
+ await this.addArtifactsAndInstancesFromDeployedContracts(contracts);
222
+ for (const contract of contractDaos) {
223
+ const instance = contract.instance;
224
+ const contractAztecAddress = instance.address;
225
+ const hasPortal = instance.portalContractAddress && !instance.portalContractAddress.isZero();
226
+ const portalInfo = hasPortal ? ` with portal ${instance.portalContractAddress.toChecksumString()}` : '';
227
+ this.log.info(`Added contract ${contract.name} at ${contractAztecAddress}${portalInfo}`);
228
+ await this.synchronizer.reprocessDeferredNotesForContract(contractAztecAddress);
229
+ }
230
+ }
231
+
232
+ private async addArtifactsAndInstancesFromDeployedContracts(contracts: DeployedContract[]) {
233
+ for (const contract of contracts) {
234
+ const artifact = contract.artifact;
235
+ const artifactHash = computeArtifactHash(artifact);
236
+ const contractClassId = computeContractClassId(getContractClassFromArtifact({ ...artifact, artifactHash }));
237
+ await this.db.addContractArtifact(contractClassId, artifact);
238
+ await this.db.addContractInstance(contract.instance);
239
+ }
240
+ }
241
+
242
+ public async getContracts(): Promise<AztecAddress[]> {
243
+ return (await this.db.getContracts()).map(c => c.instance.address);
244
+ }
245
+
246
+ public async getPublicStorageAt(contract: AztecAddress, slot: Fr) {
247
+ if ((await this.getContractData(contract)) === undefined) {
248
+ throw new Error(`Contract ${contract.toString()} is not deployed`);
249
+ }
250
+ return await this.node.getPublicStorageAt(contract, slot);
251
+ }
252
+
253
+ public async getNotes(filter: NoteFilter): Promise<ExtendedNote[]> {
254
+ const noteDaos = await this.db.getNotes(filter);
255
+
256
+ // TODO(benesjan): Refactor --> This type conversion is ugly but I decided to keep it this way for now because
257
+ // key derivation will affect all this
258
+ const extendedNotes = noteDaos.map(async dao => {
259
+ let owner = filter.owner;
260
+ if (owner === undefined) {
261
+ const completeAddresses = (await this.db.getCompleteAddresses()).find(address =>
262
+ address.publicKey.equals(dao.publicKey),
263
+ );
264
+ if (completeAddresses === undefined) {
265
+ throw new Error(`Cannot find complete address for public key ${dao.publicKey.toString()}`);
266
+ }
267
+ owner = completeAddresses.address;
268
+ }
269
+ return new ExtendedNote(dao.note, owner, dao.contractAddress, dao.storageSlot, dao.noteTypeId, dao.txHash);
270
+ });
271
+ return Promise.all(extendedNotes);
272
+ }
273
+
274
+ public async addNote(note: ExtendedNote) {
275
+ const { publicKey } = (await this.db.getCompleteAddress(note.owner)) ?? {};
276
+ if (!publicKey) {
277
+ throw new Error('Unknown account.');
278
+ }
279
+
280
+ const nonces = await this.getNoteNonces(note);
281
+ if (nonces.length === 0) {
282
+ throw new Error(`Cannot find the note in tx: ${note.txHash}.`);
283
+ }
284
+
285
+ for (const nonce of nonces) {
286
+ const { innerNoteHash, siloedNoteHash, uniqueSiloedNoteHash, innerNullifier } =
287
+ await this.simulator.computeNoteHashAndNullifier(
288
+ note.contractAddress,
289
+ nonce,
290
+ note.storageSlot,
291
+ note.noteTypeId,
292
+ note.note,
293
+ );
294
+
295
+ // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386)
296
+ // This can always be `uniqueSiloedNoteHash` once notes added from public also include nonces.
297
+ const noteHashToLookUp = nonce.isZero() ? siloedNoteHash : uniqueSiloedNoteHash;
298
+ const index = await this.node.findLeafIndex('latest', MerkleTreeId.NOTE_HASH_TREE, noteHashToLookUp);
299
+ if (index === undefined) {
300
+ throw new Error('Note does not exist.');
301
+ }
302
+
303
+ const siloedNullifier = siloNullifier(note.contractAddress, innerNullifier!);
304
+ const nullifierIndex = await this.node.findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, siloedNullifier);
305
+ if (nullifierIndex !== undefined) {
306
+ throw new Error('The note has been destroyed.');
307
+ }
308
+
309
+ await this.db.addNote(
310
+ new NoteDao(
311
+ note.note,
312
+ note.contractAddress,
313
+ note.storageSlot,
314
+ note.noteTypeId,
315
+ note.txHash,
316
+ nonce,
317
+ innerNoteHash,
318
+ siloedNullifier,
319
+ index,
320
+ publicKey,
321
+ ),
322
+ );
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Finds the nonce(s) for a given note.
328
+ * @param note - The note to find the nonces for.
329
+ * @returns The nonces of the note.
330
+ * @remarks More than a single nonce may be returned since there might be more than one nonce for a given note.
331
+ */
332
+ private async getNoteNonces(note: ExtendedNote): Promise<Fr[]> {
333
+ const tx = await this.node.getTx(note.txHash);
334
+ if (!tx) {
335
+ throw new Error(`Unknown tx: ${note.txHash}`);
336
+ }
337
+
338
+ const nonces: Fr[] = [];
339
+ const firstNullifier = tx.newNullifiers[0];
340
+ const commitments = tx.newCommitments;
341
+ for (let i = 0; i < commitments.length; ++i) {
342
+ const commitment = commitments[i];
343
+ if (commitment.equals(Fr.ZERO)) {
344
+ break;
345
+ }
346
+
347
+ const nonce = computeCommitmentNonce(firstNullifier, i);
348
+ const { siloedNoteHash, uniqueSiloedNoteHash } = await this.simulator.computeNoteHashAndNullifier(
349
+ note.contractAddress,
350
+ nonce,
351
+ note.storageSlot,
352
+ note.noteTypeId,
353
+ note.note,
354
+ );
355
+ // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386)
356
+ // Remove this once notes added from public also include nonces.
357
+ if (commitment.equals(siloedNoteHash)) {
358
+ nonces.push(Fr.ZERO);
359
+ break;
360
+ }
361
+ if (commitment.equals(uniqueSiloedNoteHash)) {
362
+ nonces.push(nonce);
363
+ }
364
+ }
365
+
366
+ return nonces;
367
+ }
368
+
369
+ public async getBlock(blockNumber: number): Promise<L2Block | undefined> {
370
+ // If a negative block number is provided the current block number is fetched.
371
+ if (blockNumber < 0) {
372
+ blockNumber = await this.node.getBlockNumber();
373
+ }
374
+ return await this.node.getBlock(blockNumber);
375
+ }
376
+
377
+ public async simulateTx(txRequest: TxExecutionRequest, simulatePublic: boolean) {
378
+ if (!txRequest.functionData.isPrivate) {
379
+ throw new Error(`Public entrypoints are not allowed`);
380
+ }
381
+ if (txRequest.functionData.isInternal === undefined) {
382
+ throw new Error(`Unspecified internal are not allowed`);
383
+ }
384
+
385
+ // all simulations must be serialized w.r.t. the synchronizer
386
+ return await this.jobQueue.put(async () => {
387
+ // We get the contract address from origin, since contract deployments are signalled as origin from their own address
388
+ // TODO: Is this ok? Should it be changed to be from ZERO?
389
+ const deployedContractAddress = txRequest.txContext.isContractDeploymentTx ? txRequest.origin : undefined;
390
+ const newContract = deployedContractAddress ? await this.db.getContract(deployedContractAddress) : undefined;
391
+
392
+ const timer = new Timer();
393
+ const tx = await this.#simulateAndProve(txRequest, newContract);
394
+ this.log(`Processed private part of ${tx.data.end.newNullifiers[0]}`, {
395
+ eventName: 'tx-pxe-processing',
396
+ duration: timer.ms(),
397
+ ...tx.getStats(),
398
+ } satisfies TxPXEProcessingStats);
399
+ if (simulatePublic) {
400
+ await this.#simulatePublicCalls(tx);
401
+ }
402
+ this.log.info(`Executed local simulation for ${await tx.getTxHash()}`);
403
+
404
+ return tx;
405
+ });
406
+ }
407
+
408
+ public async sendTx(tx: Tx): Promise<TxHash> {
409
+ const txHash = await tx.getTxHash();
410
+ if (await this.node.getTx(txHash)) {
411
+ throw new Error(`A settled tx with equal hash ${txHash.toString()} exists.`);
412
+ }
413
+ this.log.info(`Sending transaction ${txHash}`);
414
+ await this.node.sendTx(tx);
415
+ return txHash;
416
+ }
417
+
418
+ public async viewTx(
419
+ functionName: string,
420
+ args: any[],
421
+ to: AztecAddress,
422
+ _from?: AztecAddress,
423
+ ): Promise<DecodedReturn> {
424
+ // all simulations must be serialized w.r.t. the synchronizer
425
+ return await this.jobQueue.put(async () => {
426
+ // TODO - Should check if `from` has the permission to call the view function.
427
+ const functionCall = await this.#getFunctionCall(functionName, args, to);
428
+ const executionResult = await this.#simulateUnconstrained(functionCall);
429
+
430
+ // TODO - Return typed result based on the function artifact.
431
+ return executionResult;
432
+ });
433
+ }
434
+
435
+ public async getTxReceipt(txHash: TxHash): Promise<TxReceipt> {
436
+ let txReceipt = new TxReceipt(txHash, TxStatus.DROPPED, 'Tx dropped by P2P node.');
437
+
438
+ // We first check if the tx is in pending (instead of first checking if it is mined) because if we first check
439
+ // for mined and then for pending there could be a race condition where the tx is mined between the two checks
440
+ // and we would incorrectly return a TxReceipt with status DROPPED
441
+ const pendingTx = await this.node.getPendingTxByHash(txHash);
442
+ if (pendingTx) {
443
+ txReceipt = new TxReceipt(txHash, TxStatus.PENDING, '');
444
+ }
445
+
446
+ const settledTx = await this.node.getTx(txHash);
447
+ if (settledTx) {
448
+ const deployedContractAddress = settledTx.newContractData.find(
449
+ c => !c.contractAddress.equals(AztecAddress.ZERO),
450
+ )?.contractAddress;
451
+
452
+ txReceipt = new TxReceipt(
453
+ txHash,
454
+ TxStatus.MINED,
455
+ '',
456
+ settledTx.blockHash.toBuffer(),
457
+ settledTx.blockNumber,
458
+ deployedContractAddress,
459
+ );
460
+ }
461
+
462
+ return txReceipt;
463
+ }
464
+
465
+ public async getTx(txHash: TxHash): Promise<L2Tx | undefined> {
466
+ return await this.node.getTx(txHash);
467
+ }
468
+
469
+ async getBlockNumber(): Promise<number> {
470
+ return await this.node.getBlockNumber();
471
+ }
472
+
473
+ public async getExtendedContractData(contractAddress: AztecAddress): Promise<ExtendedContractData | undefined> {
474
+ return await this.node.getExtendedContractData(contractAddress);
475
+ }
476
+
477
+ public async getContractData(contractAddress: AztecAddress): Promise<ContractData | undefined> {
478
+ return await this.node.getContractData(contractAddress);
479
+ }
480
+
481
+ /**
482
+ * Gets unencrypted logs based on the provided filter.
483
+ * @param filter - The filter to apply to the logs.
484
+ * @returns The requested logs.
485
+ */
486
+ public getUnencryptedLogs(filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
487
+ return this.node.getUnencryptedLogs(filter);
488
+ }
489
+
490
+ async #getFunctionCall(functionName: string, args: any[], to: AztecAddress): Promise<FunctionCall> {
491
+ const contract = await this.db.getContract(to);
492
+ if (!contract) {
493
+ throw new Error(
494
+ `Unknown contract ${to}: add it to PXE Service by calling server.addContracts(...).\nSee docs for context: https://docs.aztec.network/developers/debugging/aztecnr-errors#unknown-contract-0x0-add-it-to-pxe-by-calling-serveraddcontracts`,
495
+ );
496
+ }
497
+
498
+ const functionDao = contract.functions.find(f => f.name === functionName);
499
+ if (!functionDao) {
500
+ throw new Error(`Unknown function ${functionName} in contract ${contract.name}.`);
501
+ }
502
+
503
+ return {
504
+ args: encodeArguments(functionDao, args),
505
+ functionData: FunctionData.fromAbi(functionDao),
506
+ to,
507
+ };
508
+ }
509
+
510
+ public async getNodeInfo(): Promise<NodeInfo> {
511
+ const [version, chainId, contractAddresses] = await Promise.all([
512
+ this.node.getVersion(),
513
+ this.node.getChainId(),
514
+ this.node.getL1ContractAddresses(),
515
+ ]);
516
+
517
+ const nodeInfo: NodeInfo = {
518
+ nodeVersion: this.nodeVersion,
519
+ chainId,
520
+ protocolVersion: version,
521
+ l1ContractAddresses: contractAddresses,
522
+ };
523
+ return nodeInfo;
524
+ }
525
+
526
+ /**
527
+ * Retrieves the simulation parameters required to run an ACIR simulation.
528
+ * This includes the contract address, function artifact, portal contract address, and historical tree roots.
529
+ *
530
+ * @param execRequest - The transaction request object containing details of the contract call.
531
+ * @returns An object containing the contract address, function artifact, portal contract address, and historical tree roots.
532
+ */
533
+ async #getSimulationParameters(execRequest: FunctionCall | TxExecutionRequest) {
534
+ const contractAddress = (execRequest as FunctionCall).to ?? (execRequest as TxExecutionRequest).origin;
535
+ const functionArtifact = await this.contractDataOracle.getFunctionArtifact(
536
+ contractAddress,
537
+ execRequest.functionData.selector,
538
+ );
539
+ const debug = await this.contractDataOracle.getFunctionDebugMetadata(
540
+ contractAddress,
541
+ execRequest.functionData.selector,
542
+ );
543
+ const portalContract = await this.contractDataOracle.getPortalContractAddress(contractAddress);
544
+
545
+ return {
546
+ contractAddress,
547
+ functionArtifact: {
548
+ ...functionArtifact,
549
+ debug,
550
+ },
551
+ portalContract,
552
+ };
553
+ }
554
+
555
+ async #simulate(txRequest: TxExecutionRequest): Promise<ExecutionResult> {
556
+ // TODO - Pause syncing while simulating.
557
+
558
+ const { contractAddress, functionArtifact, portalContract } = await this.#getSimulationParameters(txRequest);
559
+
560
+ this.log('Executing simulator...');
561
+ try {
562
+ const result = await this.simulator.run(txRequest, functionArtifact, contractAddress, portalContract);
563
+ this.log('Simulation completed!');
564
+ return result;
565
+ } catch (err) {
566
+ if (err instanceof SimulationError) {
567
+ await this.#enrichSimulationError(err);
568
+ }
569
+ throw err;
570
+ }
571
+ }
572
+
573
+ /**
574
+ * Simulate an unconstrained transaction on the given contract, without considering constraints set by ACIR.
575
+ * The simulation parameters are fetched using ContractDataOracle and executed using AcirSimulator.
576
+ * Returns the simulation result containing the outputs of the unconstrained function.
577
+ *
578
+ * @param execRequest - The transaction request object containing the target contract and function data.
579
+ * @returns The simulation result containing the outputs of the unconstrained function.
580
+ */
581
+ async #simulateUnconstrained(execRequest: FunctionCall) {
582
+ const { contractAddress, functionArtifact } = await this.#getSimulationParameters(execRequest);
583
+
584
+ this.log('Executing unconstrained simulator...');
585
+ try {
586
+ const result = await this.simulator.runUnconstrained(execRequest, functionArtifact, contractAddress);
587
+ this.log('Unconstrained simulation completed!');
588
+
589
+ return result;
590
+ } catch (err) {
591
+ if (err instanceof SimulationError) {
592
+ await this.#enrichSimulationError(err);
593
+ }
594
+ throw err;
595
+ }
596
+ }
597
+
598
+ /**
599
+ * Simulate the public part of a transaction.
600
+ * This allows to catch public execution errors before submitting the transaction.
601
+ * It can also be used for estimating gas in the future.
602
+ * @param tx - The transaction to be simulated.
603
+ */
604
+ async #simulatePublicCalls(tx: Tx) {
605
+ try {
606
+ await this.node.simulatePublicCalls(tx);
607
+ } catch (err) {
608
+ // Try to fill in the noir call stack since the PXE may have access to the debug metadata
609
+ if (err instanceof SimulationError) {
610
+ const callStack = err.getCallStack();
611
+ const originalFailingFunction = callStack[callStack.length - 1];
612
+ const debugInfo = await this.contractDataOracle.getFunctionDebugMetadata(
613
+ originalFailingFunction.contractAddress,
614
+ originalFailingFunction.functionSelector,
615
+ );
616
+ const noirCallStack = err.getNoirCallStack();
617
+ if (debugInfo && isNoirCallStackUnresolved(noirCallStack)) {
618
+ const parsedCallStack = resolveOpcodeLocations(noirCallStack, debugInfo);
619
+ err.setNoirCallStack(parsedCallStack);
620
+ }
621
+ await this.#enrichSimulationError(err);
622
+ }
623
+
624
+ throw err;
625
+ }
626
+ }
627
+
628
+ /**
629
+ * Simulate a transaction, generate a kernel proof, and create a private transaction object.
630
+ * The function takes in a transaction request and an ECDSA signature. It simulates the transaction,
631
+ * then generates a kernel proof using the simulation result. Finally, it creates a private
632
+ * transaction object with the generated proof and public inputs. If a new contract address is provided,
633
+ * the function will also include the new contract's public functions in the transaction object.
634
+ *
635
+ * @param txExecutionRequest - The transaction request to be simulated and proved.
636
+ * @param signature - The ECDSA signature for the transaction request.
637
+ * @param newContract - Optional. The address of a new contract to be included in the transaction object.
638
+ * @returns A private transaction object containing the proof, public inputs, and encrypted logs.
639
+ */
640
+ async #simulateAndProve(txExecutionRequest: TxExecutionRequest, newContract: ContractDao | undefined) {
641
+ // TODO - Pause syncing while simulating.
642
+
643
+ // Get values that allow us to reconstruct the block hash
644
+ const executionResult = await this.#simulate(txExecutionRequest);
645
+
646
+ const kernelOracle = new KernelOracle(this.contractDataOracle, this.keyStore, this.node);
647
+ const kernelProver = new KernelProver(kernelOracle);
648
+ this.log(`Executing kernel prover...`);
649
+ const { proof, publicInputs } = await kernelProver.prove(txExecutionRequest.toTxRequest(), executionResult);
650
+
651
+ const encryptedLogs = new TxL2Logs(collectEncryptedLogs(executionResult));
652
+ const unencryptedLogs = new TxL2Logs(collectUnencryptedLogs(executionResult));
653
+ const enqueuedPublicFunctions = collectEnqueuedPublicFunctionCalls(executionResult);
654
+
655
+ const extendedContractData = newContract
656
+ ? new ExtendedContractData(
657
+ new ContractData(newContract.instance.address, newContract.instance.portalContractAddress),
658
+ getNewContractPublicFunctions(newContract),
659
+ getContractClassFromArtifact(newContract).id,
660
+ computeSaltedInitializationHash(newContract.instance),
661
+ newContract.instance.publicKeysHash,
662
+ )
663
+ : ExtendedContractData.empty();
664
+
665
+ // HACK(#1639): Manually patches the ordering of the public call stack
666
+ // TODO(#757): Enforce proper ordering of enqueued public calls
667
+ await this.patchPublicCallStackOrdering(publicInputs, enqueuedPublicFunctions);
668
+
669
+ return new Tx(publicInputs, proof, encryptedLogs, unencryptedLogs, enqueuedPublicFunctions, [extendedContractData]);
670
+ }
671
+
672
+ /**
673
+ * Adds contract and function names to a simulation error.
674
+ * @param err - The error to enrich.
675
+ */
676
+ async #enrichSimulationError(err: SimulationError) {
677
+ // Maps contract addresses to the set of functions selectors that were in error.
678
+ // Using strings because map and set don't use .equals()
679
+ const mentionedFunctions: Map<string, Set<string>> = new Map();
680
+
681
+ err.getCallStack().forEach(({ contractAddress, functionSelector }) => {
682
+ if (!mentionedFunctions.has(contractAddress.toString())) {
683
+ mentionedFunctions.set(contractAddress.toString(), new Set());
684
+ }
685
+ mentionedFunctions.get(contractAddress.toString())!.add(functionSelector.toString());
686
+ });
687
+
688
+ await Promise.all(
689
+ [...mentionedFunctions.entries()].map(async ([contractAddress, selectors]) => {
690
+ const parsedContractAddress = AztecAddress.fromString(contractAddress);
691
+ const contract = await this.db.getContract(parsedContractAddress);
692
+ if (contract) {
693
+ err.enrichWithContractName(parsedContractAddress, contract.name);
694
+ selectors.forEach(selector => {
695
+ const functionArtifact = contract.functions.find(f => f.selector.toString() === selector);
696
+ if (functionArtifact) {
697
+ err.enrichWithFunctionName(parsedContractAddress, functionArtifact.selector, functionArtifact.name);
698
+ }
699
+ });
700
+ }
701
+ }),
702
+ );
703
+ }
704
+
705
+ // HACK(#1639): this is a hack to fix ordering of public calls enqueued in the call stack. Since the private kernel
706
+ // cannot keep track of side effects that happen after or before a nested call, we override the public call stack
707
+ // it emits with whatever we got from the simulator collected enqueued calls. As a sanity check, we at least verify
708
+ // that the elements are the same, so we are only tweaking their ordering.
709
+ // See yarn-project/end-to-end/src/e2e_ordering.test.ts
710
+ // See https://github.com/AztecProtocol/aztec-packages/issues/1615
711
+ // TODO(#757): Enforce proper ordering of enqueued public calls
712
+ private async patchPublicCallStackOrdering(
713
+ publicInputs: PrivateKernelTailCircuitPublicInputs,
714
+ enqueuedPublicCalls: PublicCallRequest[],
715
+ ) {
716
+ const enqueuedPublicCallStackItems = await Promise.all(enqueuedPublicCalls.map(c => c.toCallRequest()));
717
+ const { publicCallStack } = publicInputs.end;
718
+
719
+ // Validate all items in enqueued public calls are in the kernel emitted stack
720
+ const areEqual = enqueuedPublicCallStackItems.reduce(
721
+ (accum, enqueued) => accum && !!publicCallStack.find(item => item.equals(enqueued)),
722
+ true,
723
+ );
724
+
725
+ if (!areEqual) {
726
+ throw new Error(
727
+ `Enqueued public function calls and public call stack do not match.\nEnqueued calls: ${enqueuedPublicCallStackItems
728
+ .map(h => h.hash.toString())
729
+ .join(', ')}\nPublic call stack: ${publicCallStack.map(i => i.toString()).join(', ')}`,
730
+ );
731
+ }
732
+
733
+ // Override kernel output
734
+ publicInputs.end.publicCallStack = padArrayEnd(
735
+ enqueuedPublicCallStackItems,
736
+ CallRequest.empty(),
737
+ MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX,
738
+ );
739
+ }
740
+
741
+ public async isGlobalStateSynchronized() {
742
+ return await this.synchronizer.isGlobalStateSynchronized();
743
+ }
744
+
745
+ public async isAccountStateSynchronized(account: AztecAddress) {
746
+ return await this.synchronizer.isAccountStateSynchronized(account);
747
+ }
748
+
749
+ public getSyncStatus() {
750
+ return Promise.resolve(this.synchronizer.getSyncStatus());
751
+ }
752
+
753
+ public getKeyStore() {
754
+ return this.keyStore;
755
+ }
756
+ }