@aztec/pxe 0.32.1 → 0.33.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 (31) hide show
  1. package/dest/database/kv_pxe_database.d.ts +1 -1
  2. package/dest/database/kv_pxe_database.d.ts.map +1 -1
  3. package/dest/database/pxe_database.d.ts +1 -1
  4. package/dest/database/pxe_database.d.ts.map +1 -1
  5. package/dest/kernel_prover/kernel_prover.d.ts +3 -42
  6. package/dest/kernel_prover/kernel_prover.d.ts.map +1 -1
  7. package/dest/kernel_prover/kernel_prover.js +9 -37
  8. package/dest/kernel_prover/proof_creator.d.ts +2 -2
  9. package/dest/kernel_prover/proof_creator.d.ts.map +1 -1
  10. package/dest/kernel_prover/proof_creator.js +4 -3
  11. package/dest/note_processor/note_processor.d.ts +7 -9
  12. package/dest/note_processor/note_processor.d.ts.map +1 -1
  13. package/dest/note_processor/note_processor.js +18 -20
  14. package/dest/pxe_http/pxe_http_server.d.ts.map +1 -1
  15. package/dest/pxe_http/pxe_http_server.js +3 -3
  16. package/dest/pxe_service/pxe_service.d.ts +3 -2
  17. package/dest/pxe_service/pxe_service.d.ts.map +1 -1
  18. package/dest/pxe_service/pxe_service.js +52 -34
  19. package/dest/pxe_service/test/pxe_test_suite.js +3 -3
  20. package/dest/synchronizer/synchronizer.d.ts.map +1 -1
  21. package/dest/synchronizer/synchronizer.js +9 -13
  22. package/package.json +22 -16
  23. package/src/database/kv_pxe_database.ts +1 -1
  24. package/src/database/pxe_database.ts +1 -1
  25. package/src/kernel_prover/kernel_prover.ts +22 -88
  26. package/src/kernel_prover/proof_creator.ts +7 -4
  27. package/src/note_processor/note_processor.ts +21 -26
  28. package/src/pxe_http/pxe_http_server.ts +2 -1
  29. package/src/pxe_service/pxe_service.ts +63 -40
  30. package/src/pxe_service/test/pxe_test_suite.ts +2 -2
  31. package/src/synchronizer/synchronizer.ts +9 -15
@@ -1,5 +1,4 @@
1
1
  import {
2
- type AztecAddress,
3
2
  CallRequest,
4
3
  Fr,
5
4
  type MAX_NEW_NOTE_HASHES_PER_TX,
@@ -9,10 +8,10 @@ import {
9
8
  MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL,
10
9
  NoteHashReadRequestMembershipWitness,
11
10
  PrivateCallData,
11
+ PrivateKernelCircuitPublicInputs,
12
+ PrivateKernelData,
12
13
  PrivateKernelInitCircuitPrivateInputs,
13
14
  PrivateKernelInnerCircuitPrivateInputs,
14
- PrivateKernelInnerCircuitPublicInputs,
15
- PrivateKernelInnerData,
16
15
  PrivateKernelTailCircuitPrivateInputs,
17
16
  type SideEffect,
18
17
  type SideEffectLinkedToNoteHash,
@@ -26,43 +25,12 @@ import { padArrayEnd } from '@aztec/foundation/collection';
26
25
  import { createDebugLogger } from '@aztec/foundation/log';
27
26
  import { assertLength, mapTuple } from '@aztec/foundation/serialize';
28
27
  import { pushTestData } from '@aztec/foundation/testing';
29
- import { type ExecutionResult, type NoteAndSlot } from '@aztec/simulator';
28
+ import { type ExecutionResult } from '@aztec/simulator';
30
29
 
31
30
  import { HintsBuilder } from './hints_builder.js';
32
31
  import { KernelProofCreator, type ProofCreator, type ProofOutput, type ProofOutputFinal } from './proof_creator.js';
33
32
  import { type ProvingDataOracle } from './proving_data_oracle.js';
34
33
 
35
- /**
36
- * Represents an output note data object.
37
- * Contains the contract address, new note data and commitment for the note,
38
- * resulting from the execution of a transaction in the Aztec network.
39
- */
40
- export interface OutputNoteData {
41
- /**
42
- * The address of the contract the note was created in.
43
- */
44
- contractAddress: AztecAddress;
45
- /**
46
- * The encrypted note data for an output note.
47
- */
48
- data: NoteAndSlot;
49
- /**
50
- * The unique value representing the note.
51
- */
52
- commitment: Fr;
53
- }
54
-
55
- /**
56
- * Represents the output data of the Kernel Prover.
57
- * Provides information about the newly created notes, along with the public inputs and proof.
58
- */
59
- export interface KernelProverOutput extends ProofOutputFinal {
60
- /**
61
- * An array of output notes containing the contract address, note data, and commitment for each new note.
62
- */
63
- outputNotes: OutputNoteData[];
64
- }
65
-
66
34
  /**
67
35
  * The KernelProver class is responsible for generating kernel proofs.
68
36
  * It takes a transaction request, its signature, and the simulation result as inputs, and outputs a proof
@@ -87,14 +55,13 @@ export class KernelProver {
87
55
  * @param executionResult - The execution result object containing nested executions and preimages.
88
56
  * @returns A Promise that resolves to a KernelProverOutput object containing proof, public inputs, and output notes.
89
57
  */
90
- async prove(txRequest: TxRequest, executionResult: ExecutionResult): Promise<KernelProverOutput> {
58
+ async prove(txRequest: TxRequest, executionResult: ExecutionResult): Promise<ProofOutputFinal> {
91
59
  const executionStack = [executionResult];
92
- const newNotes: { [commitmentStr: string]: OutputNoteData } = {};
93
60
  let firstIteration = true;
94
61
  let previousVerificationKey = VerificationKey.makeFake();
95
62
 
96
63
  let output: ProofOutput = {
97
- publicInputs: PrivateKernelInnerCircuitPublicInputs.empty(),
64
+ publicInputs: PrivateKernelCircuitPublicInputs.empty(),
98
65
  proof: makeEmptyProof(),
99
66
  };
100
67
 
@@ -147,7 +114,7 @@ export class KernelProver {
147
114
  output = await this.proofCreator.createProofInit(proofInput);
148
115
  } else {
149
116
  const previousVkMembershipWitness = await this.oracle.getVkMembershipWitness(previousVerificationKey);
150
- const previousKernelData = new PrivateKernelInnerData(
117
+ const previousKernelData = new PrivateKernelData(
151
118
  output.publicInputs,
152
119
  output.proof,
153
120
  previousVerificationKey,
@@ -158,15 +125,12 @@ export class KernelProver {
158
125
  pushTestData('private-kernel-inputs-inner', proofInput);
159
126
  output = await this.proofCreator.createProofInner(proofInput);
160
127
  }
161
- (await this.getNewNotes(currentExecution)).forEach(n => {
162
- newNotes[n.commitment.toString()] = n;
163
- });
164
128
  firstIteration = false;
165
129
  previousVerificationKey = privateCallData.vk;
166
130
  }
167
131
 
168
132
  const previousVkMembershipWitness = await this.oracle.getVkMembershipWitness(previousVerificationKey);
169
- const previousKernelData = new PrivateKernelInnerData(
133
+ const previousKernelData = new PrivateKernelData(
170
134
  output.publicInputs,
171
135
  output.proof,
172
136
  previousVerificationKey,
@@ -174,6 +138,20 @@ export class KernelProver {
174
138
  assertLength<Fr, typeof VK_TREE_HEIGHT>(previousVkMembershipWitness.siblingPath, VK_TREE_HEIGHT),
175
139
  );
176
140
 
141
+ const readNoteHashHints = this.hintsBuilder.getNoteHashReadRequestHints(
142
+ output.publicInputs.validationRequests.noteHashReadRequests,
143
+ output.publicInputs.end.newNoteHashes,
144
+ );
145
+
146
+ const nullifierReadRequestHints = await this.hintsBuilder.getNullifierReadRequestHints(
147
+ output.publicInputs.validationRequests.nullifierReadRequests,
148
+ output.publicInputs.end.newNullifiers,
149
+ );
150
+
151
+ const masterNullifierSecretKeys = await this.hintsBuilder.getMasterNullifierSecretKeys(
152
+ output.publicInputs.validationRequests.nullifierKeyValidationRequests,
153
+ );
154
+
177
155
  const [sortedNoteHashes, sortedNoteHashesIndexes] = this.hintsBuilder.sortSideEffects<
178
156
  SideEffect,
179
157
  typeof MAX_NEW_NOTE_HASHES_PER_TX
@@ -184,25 +162,11 @@ export class KernelProver {
184
162
  typeof MAX_NEW_NULLIFIERS_PER_TX
185
163
  >(output.publicInputs.end.newNullifiers);
186
164
 
187
- const readNoteHashHints = this.hintsBuilder.getNoteHashReadRequestHints(
188
- output.publicInputs.validationRequests.noteHashReadRequests,
189
- sortedNoteHashes,
190
- );
191
-
192
- const nullifierReadRequestHints = await this.hintsBuilder.getNullifierReadRequestHints(
193
- output.publicInputs.validationRequests.nullifierReadRequests,
194
- output.publicInputs.end.newNullifiers,
195
- );
196
-
197
165
  const nullifierNoteHashHints = this.hintsBuilder.getNullifierHints(
198
166
  mapTuple(sortedNullifiers, n => n.noteHash),
199
167
  sortedNoteHashes,
200
168
  );
201
169
 
202
- const masterNullifierSecretKeys = await this.hintsBuilder.getMasterNullifierSecretKeys(
203
- output.publicInputs.validationRequests.nullifierKeyValidationRequests,
204
- );
205
-
206
170
  this.log.debug(
207
171
  `Calling private kernel tail with hwm ${previousKernelData.publicInputs.minRevertibleSideEffectCounter}`,
208
172
  );
@@ -219,13 +183,7 @@ export class KernelProver {
219
183
  masterNullifierSecretKeys,
220
184
  );
221
185
  pushTestData('private-kernel-inputs-ordering', privateInputs);
222
- const outputFinal = await this.proofCreator.createProofTail(privateInputs);
223
-
224
- // Only return the notes whose commitment is in the commitments of the final proof.
225
- const finalNewCommitments = outputFinal.publicInputs.end.newNoteHashes;
226
- const outputNotes = finalNewCommitments.map(c => newNotes[c.value.toString()]).filter(c => !!c);
227
-
228
- return { ...outputFinal, outputNotes };
186
+ return await this.proofCreator.createProofTail(privateInputs);
229
187
  }
230
188
 
231
189
  private async createPrivateCallData(
@@ -282,28 +240,4 @@ export class KernelProver {
282
240
  acirHash,
283
241
  });
284
242
  }
285
-
286
- /**
287
- * Retrieves the new output notes for a given execution result.
288
- * The function maps over the new notes and associates them with their corresponding
289
- * commitments in the public inputs of the execution result. It also includes the contract address
290
- * from the call context of the public inputs.
291
- *
292
- * @param executionResult - The execution result object containing notes and public inputs.
293
- * @returns An array of OutputNoteData objects, each representing an output note with its associated data.
294
- */
295
- private async getNewNotes(executionResult: ExecutionResult): Promise<OutputNoteData[]> {
296
- const {
297
- callStackItem: { publicInputs },
298
- newNotes,
299
- } = executionResult;
300
- const contractAddress = publicInputs.callContext.storageContractAddress;
301
- // Assuming that for each new commitment there's an output note added to the execution result.
302
- const newNoteHashes = await this.proofCreator.getSiloedCommitments(publicInputs);
303
- return newNotes.map((data, i) => ({
304
- contractAddress,
305
- data,
306
- commitment: newNoteHashes[i],
307
- }));
308
- }
309
243
  }
@@ -1,9 +1,9 @@
1
1
  import { type CircuitSimulationStats } from '@aztec/circuit-types/stats';
2
2
  import {
3
3
  type PrivateCircuitPublicInputs,
4
+ type PrivateKernelCircuitPublicInputs,
4
5
  type PrivateKernelInitCircuitPrivateInputs,
5
6
  type PrivateKernelInnerCircuitPrivateInputs,
6
- type PrivateKernelInnerCircuitPublicInputs,
7
7
  type PrivateKernelTailCircuitPrivateInputs,
8
8
  type PrivateKernelTailCircuitPublicInputs,
9
9
  type Proof,
@@ -13,7 +13,7 @@ import { siloNoteHash } from '@aztec/circuits.js/hash';
13
13
  import { type Fr } from '@aztec/foundation/fields';
14
14
  import { createDebugLogger } from '@aztec/foundation/log';
15
15
  import { elapsed } from '@aztec/foundation/timer';
16
- import { executeInit, executeInner, executeTail } from '@aztec/noir-protocol-circuits-types';
16
+ import { executeInit, executeInner, executeTail, executeTailForPublic } from '@aztec/noir-protocol-circuits-types';
17
17
 
18
18
  /**
19
19
  * Represents the output of the proof creation process for init and inner private kernel circuit.
@@ -23,7 +23,7 @@ export interface ProofOutput {
23
23
  /**
24
24
  * The public inputs required for the proof generation process.
25
25
  */
26
- publicInputs: PrivateKernelInnerCircuitPublicInputs;
26
+ publicInputs: PrivateKernelCircuitPublicInputs;
27
27
  /**
28
28
  * The zk-SNARK proof for the kernel execution.
29
29
  */
@@ -138,7 +138,10 @@ export class KernelProofCreator implements ProofCreator {
138
138
  }
139
139
 
140
140
  public async createProofTail(privateInputs: PrivateKernelTailCircuitPrivateInputs): Promise<ProofOutputFinal> {
141
- const [duration, result] = await elapsed(() => executeTail(privateInputs));
141
+ const isForPublic = privateInputs.isForPublic();
142
+ const [duration, result] = await elapsed(() =>
143
+ isForPublic ? executeTailForPublic(privateInputs) : executeTail(privateInputs),
144
+ );
142
145
  this.log(`Simulated private kernel ordering`, {
143
146
  eventName: 'circuit-simulation',
144
147
  circuitName: 'private-kernel-ordering',
@@ -3,7 +3,7 @@ import {
3
3
  type EncryptedL2BlockL2Logs,
4
4
  type KeyStore,
5
5
  L1NotePayload,
6
- type L2BlockContext,
6
+ type L2Block,
7
7
  TaggedNote,
8
8
  } from '@aztec/circuit-types';
9
9
  import { type NoteProcessorStats } from '@aztec/circuit-types/stats';
@@ -25,9 +25,9 @@ import { produceNoteDao } from './produce_note_dao.js';
25
25
  */
26
26
  interface ProcessedData {
27
27
  /**
28
- * Holds L2 block and a cache of already requested tx hashes.
28
+ * Holds L2 block.
29
29
  */
30
- blockContext: L2BlockContext;
30
+ block: L2Block;
31
31
  /**
32
32
  * DAOs of processed notes.
33
33
  */
@@ -82,25 +82,20 @@ export class NoteProcessor {
82
82
  }
83
83
 
84
84
  /**
85
- * Process the given L2 block contexts and encrypted logs to update the note processor.
86
- * It synchronizes the user's account by decrypting the encrypted logs and processing
87
- * the transactions and auxiliary data associated with them.
88
- * Throws an error if the number of block contexts and encrypted logs do not match.
85
+ * Extracts new user-relevant notes from the information contained in the provided L2 blocks and encrypted logs.
89
86
  *
90
- * @param l2BlockContexts - An array of L2 block contexts to be processed.
91
- * @param encryptedL2BlockLogs - An array of encrypted logs associated with the L2 block contexts.
87
+ * @throws If the number of blocks and encrypted logs do not match.
88
+ * @param l2Blocks - L2 blocks to be processed.
89
+ * @param encryptedL2BlockLogs - Encrypted logs associated with the L2 blocks.
92
90
  * @returns A promise that resolves once the processing is completed.
93
91
  */
94
- public async process(
95
- l2BlockContexts: L2BlockContext[],
96
- encryptedL2BlockLogs: EncryptedL2BlockL2Logs[],
97
- ): Promise<void> {
98
- if (l2BlockContexts.length !== encryptedL2BlockLogs.length) {
92
+ public async process(l2Blocks: L2Block[], encryptedL2BlockLogs: EncryptedL2BlockL2Logs[]): Promise<void> {
93
+ if (l2Blocks.length !== encryptedL2BlockLogs.length) {
99
94
  throw new Error(
100
- `Number of blocks and EncryptedLogs is not equal. Received ${l2BlockContexts.length} blocks, ${encryptedL2BlockLogs.length} encrypted logs.`,
95
+ `Number of blocks and EncryptedLogs is not equal. Received ${l2Blocks.length} blocks, ${encryptedL2BlockLogs.length} encrypted logs.`,
101
96
  );
102
97
  }
103
- if (!l2BlockContexts.length) {
98
+ if (l2Blocks.length === 0) {
104
99
  return;
105
100
  }
106
101
 
@@ -113,9 +108,10 @@ export class NoteProcessor {
113
108
  for (let blockIndex = 0; blockIndex < encryptedL2BlockLogs.length; ++blockIndex) {
114
109
  this.stats.blocks++;
115
110
  const { txLogs } = encryptedL2BlockLogs[blockIndex];
116
- const blockContext = l2BlockContexts[blockIndex];
117
- const block = blockContext.block;
118
- const dataEndIndexForBlock = block.header.state.partial.noteHashTree.nextAvailableLeafIndex;
111
+ const block = l2Blocks[blockIndex];
112
+ const dataStartIndexForBlock =
113
+ block.header.state.partial.noteHashTree.nextAvailableLeafIndex -
114
+ block.body.numberOfTxsIncludingPadded * MAX_NEW_NOTE_HASHES_PER_TX;
119
115
 
120
116
  // We are using set for `userPertainingTxIndices` to avoid duplicates. This would happen in case there were
121
117
  // multiple encrypted logs in a tx pertaining to a user.
@@ -125,8 +121,7 @@ export class NoteProcessor {
125
121
  // Iterate over all the encrypted logs and try decrypting them. If successful, store the note.
126
122
  for (let indexOfTxInABlock = 0; indexOfTxInABlock < txLogs.length; ++indexOfTxInABlock) {
127
123
  this.stats.txs++;
128
- const dataStartIndexForTx =
129
- dataEndIndexForBlock - (txLogs.length - indexOfTxInABlock) * MAX_NEW_NOTE_HASHES_PER_TX;
124
+ const dataStartIndexForTx = dataStartIndexForBlock + indexOfTxInABlock * MAX_NEW_NOTE_HASHES_PER_TX;
130
125
  const newNoteHashes = block.body.txEffects[indexOfTxInABlock].noteHashes;
131
126
  // Note: Each tx generates a `TxL2Logs` object and for this reason we can rely on its index corresponding
132
127
  // to the index of a tx in a block.
@@ -139,7 +134,7 @@ export class NoteProcessor {
139
134
  if (taggedNote?.notePayload) {
140
135
  const { notePayload: payload } = taggedNote;
141
136
  // We have successfully decrypted the data.
142
- const txHash = blockContext.getTxHash(indexOfTxInABlock);
137
+ const txHash = block.body.txEffects[indexOfTxInABlock].txHash;
143
138
  try {
144
139
  const noteDao = await produceNoteDao(
145
140
  this.simulator,
@@ -178,7 +173,7 @@ export class NoteProcessor {
178
173
  }
179
174
 
180
175
  blocksAndNotes.push({
181
- blockContext: l2BlockContexts[blockIndex],
176
+ block: l2Blocks[blockIndex],
182
177
  noteDaos,
183
178
  });
184
179
  }
@@ -186,7 +181,7 @@ export class NoteProcessor {
186
181
  await this.processBlocksAndNotes(blocksAndNotes);
187
182
  await this.processDeferredNotes(deferredNoteDaos);
188
183
 
189
- const syncedToBlock = l2BlockContexts[l2BlockContexts.length - 1].block.number;
184
+ const syncedToBlock = l2Blocks[l2Blocks.length - 1].number;
190
185
  await this.db.setSynchedBlockNumberForPublicKey(this.publicKey, syncedToBlock);
191
186
 
192
187
  this.log(`Synched block ${syncedToBlock}`);
@@ -199,7 +194,7 @@ export class NoteProcessor {
199
194
  * transaction auxiliary data from the database. This function keeps track of new nullifiers
200
195
  * and ensures all other transactions are updated with newly settled block information.
201
196
  *
202
- * @param blocksAndNotes - Array of objects containing L2BlockContexts, user-pertaining transaction indices, and NoteDaos.
197
+ * @param blocksAndNotes - Array of objects containing L2 blocks, user-pertaining transaction indices, and NoteDaos.
203
198
  */
204
199
  private async processBlocksAndNotes(blocksAndNotes: ProcessedData[]) {
205
200
  const noteDaos = blocksAndNotes.flatMap(b => b.noteDaos);
@@ -215,7 +210,7 @@ export class NoteProcessor {
215
210
  }
216
211
 
217
212
  const newNullifiers: Fr[] = blocksAndNotes.flatMap(b =>
218
- b.blockContext.block.body.txEffects.flatMap(txEffect => txEffect.nullifiers),
213
+ b.block.body.txEffects.flatMap(txEffect => txEffect.nullifiers),
219
214
  );
220
215
  const removedNotes = await this.db.removeNullifiedNotes(newNullifiers, this.publicKey);
221
216
  removedNotes.forEach(noteDao => {
@@ -9,6 +9,7 @@ import {
9
9
  Note,
10
10
  NullifierMembershipWitness,
11
11
  type PXE,
12
+ SimulatedTx,
12
13
  Tx,
13
14
  TxEffect,
14
15
  TxExecutionRequest,
@@ -49,7 +50,7 @@ export function createPXERpcServer(pxeService: PXE): JsonRpcServer {
49
50
  TxEffect,
50
51
  LogId,
51
52
  },
52
- { Tx, TxReceipt, EncryptedL2BlockL2Logs, UnencryptedL2BlockL2Logs, NullifierMembershipWitness },
53
+ { SimulatedTx, Tx, TxReceipt, EncryptedL2BlockL2Logs, UnencryptedL2BlockL2Logs, NullifierMembershipWitness },
53
54
  ['start', 'stop'],
54
55
  );
55
56
  }
@@ -11,6 +11,7 @@ import {
11
11
  MerkleTreeId,
12
12
  type NoteFilter,
13
13
  type PXE,
14
+ SimulatedTx,
14
15
  SimulationError,
15
16
  Tx,
16
17
  type TxEffect,
@@ -27,8 +28,7 @@ import {
27
28
  CompleteAddress,
28
29
  FunctionData,
29
30
  type GrumpkinPrivateKey,
30
- MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX,
31
- MAX_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX,
31
+ MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX,
32
32
  type PartialAddress,
33
33
  type PrivateKernelTailCircuitPublicInputs,
34
34
  type PublicCallRequest,
@@ -71,7 +71,7 @@ export class PXEService implements PXE {
71
71
  private simulator: AcirSimulator;
72
72
  private log: DebugLogger;
73
73
  private nodeVersion: string;
74
- // serialize synchronizer and calls to simulateTx.
74
+ // serialize synchronizer and calls to proveTx.
75
75
  // ensures that state is not changed while simulating
76
76
  private jobQueue = new SerialQueue();
77
77
 
@@ -388,26 +388,43 @@ export class PXEService implements PXE {
388
388
  return await this.node.getBlock(blockNumber);
389
389
  }
390
390
 
391
- public async simulateTx(txRequest: TxExecutionRequest, simulatePublic: boolean) {
391
+ public async proveTx(txRequest: TxExecutionRequest, simulatePublic: boolean) {
392
+ return (await this.simulateTx(txRequest, simulatePublic)).tx;
393
+ }
394
+
395
+ public async simulateTx(
396
+ txRequest: TxExecutionRequest,
397
+ simulatePublic: boolean,
398
+ msgSender: AztecAddress | undefined = undefined,
399
+ ) {
392
400
  if (!txRequest.functionData.isPrivate) {
393
401
  throw new Error(`Public entrypoints are not allowed`);
394
402
  }
395
-
396
- // all simulations must be serialized w.r.t. the synchronizer
397
403
  return await this.jobQueue.put(async () => {
398
404
  const timer = new Timer();
399
- const tx = await this.#simulateAndProve(txRequest);
400
- this.log(`Processed private part of ${tx.getTxHash()}`, {
401
- eventName: 'tx-pxe-processing',
402
- duration: timer.ms(),
403
- ...tx.getStats(),
404
- } satisfies TxPXEProcessingStats);
405
+ const simulatedTx = await this.#simulateAndProve(txRequest, msgSender);
406
+ // We log only if the msgSender is undefined, as simulating with a different msgSender
407
+ // is unlikely to be a real transaction, and likely to be only used to read data.
408
+ // Meaning that it will not necessarily have produced a nullifier (and thus have no TxHash)
409
+ // If we log, the `getTxHash` function will throw.
410
+
411
+ if (!msgSender) {
412
+ this.log(`Processed private part of ${simulatedTx.tx.getTxHash()}`, {
413
+ eventName: 'tx-pxe-processing',
414
+ duration: timer.ms(),
415
+ ...simulatedTx.tx.getStats(),
416
+ } satisfies TxPXEProcessingStats);
417
+ }
418
+
405
419
  if (simulatePublic) {
406
- await this.#simulatePublicCalls(tx);
420
+ // Only one transaction, so we can take index 0.
421
+ simulatedTx.publicReturnValues = (await this.#simulatePublicCalls(simulatedTx.tx))[0];
407
422
  }
408
- this.log.info(`Executed local simulation for ${tx.getTxHash()}`);
409
423
 
410
- return tx;
424
+ if (!msgSender) {
425
+ this.log.info(`Executed local simulation for ${simulatedTx.tx.getTxHash()}`);
426
+ }
427
+ return simulatedTx;
411
428
  });
412
429
  }
413
430
 
@@ -524,14 +541,14 @@ export class PXEService implements PXE {
524
541
  };
525
542
  }
526
543
 
527
- async #simulate(txRequest: TxExecutionRequest): Promise<ExecutionResult> {
544
+ async #simulate(txRequest: TxExecutionRequest, msgSender?: AztecAddress): Promise<ExecutionResult> {
528
545
  // TODO - Pause syncing while simulating.
529
546
 
530
547
  const { contractAddress, functionArtifact, portalContract } = await this.#getSimulationParameters(txRequest);
531
548
 
532
549
  this.log('Executing simulator...');
533
550
  try {
534
- const result = await this.simulator.run(txRequest, functionArtifact, contractAddress, portalContract);
551
+ const result = await this.simulator.run(txRequest, functionArtifact, contractAddress, portalContract, msgSender);
535
552
  this.log('Simulation completed!');
536
553
  return result;
537
554
  } catch (err) {
@@ -575,7 +592,7 @@ export class PXEService implements PXE {
575
592
  */
576
593
  async #simulatePublicCalls(tx: Tx) {
577
594
  try {
578
- await this.node.simulatePublicCalls(tx);
595
+ return await this.node.simulatePublicCalls(tx);
579
596
  } catch (err) {
580
597
  // Try to fill in the noir call stack since the PXE may have access to the debug metadata
581
598
  if (err instanceof SimulationError) {
@@ -599,28 +616,26 @@ export class PXEService implements PXE {
599
616
 
600
617
  /**
601
618
  * Simulate a transaction, generate a kernel proof, and create a private transaction object.
602
- * The function takes in a transaction request and an ECDSA signature. It simulates the transaction,
603
- * then generates a kernel proof using the simulation result. Finally, it creates a private
619
+ * The function takes in a transaction request, simulates it, and then generates a kernel proof
620
+ * using the simulation result. Finally, it creates a private
604
621
  * transaction object with the generated proof and public inputs. If a new contract address is provided,
605
622
  * the function will also include the new contract's public functions in the transaction object.
606
623
  *
607
624
  * @param txExecutionRequest - The transaction request to be simulated and proved.
608
625
  * @param signature - The ECDSA signature for the transaction request.
609
- * @returns A private transaction object containing the proof, public inputs, and encrypted logs.
626
+ * @param msgSender - (Optional) The message sender to use for the simulation.
627
+ * @returns An object tract contains:
628
+ * A private transaction object containing the proof, public inputs, and encrypted logs.
629
+ * The return values of the private execution
610
630
  */
611
- async #simulateAndProve(txExecutionRequest: TxExecutionRequest) {
612
- // TODO - Pause syncing while simulating.
613
-
631
+ async #simulateAndProve(txExecutionRequest: TxExecutionRequest, msgSender?: AztecAddress) {
614
632
  // Get values that allow us to reconstruct the block hash
615
- const executionResult = await this.#simulate(txExecutionRequest);
633
+ const executionResult = await this.#simulate(txExecutionRequest, msgSender);
616
634
 
617
635
  const kernelOracle = new KernelOracle(this.contractDataOracle, this.keyStore, this.node);
618
636
  const kernelProver = new KernelProver(kernelOracle);
619
637
  this.log(`Executing kernel prover...`);
620
638
  const { proof, publicInputs } = await kernelProver.prove(txExecutionRequest.toTxRequest(), executionResult);
621
- this.log(
622
- `Needs setup: ${publicInputs.needsSetup}, needs app logic: ${publicInputs.needsAppLogic}, needs teardown: ${publicInputs.needsTeardown}`,
623
- );
624
639
 
625
640
  const encryptedLogs = new EncryptedTxL2Logs(collectEncryptedLogs(executionResult));
626
641
  const unencryptedLogs = new UnencryptedTxL2Logs(collectUnencryptedLogs(executionResult));
@@ -630,7 +645,8 @@ export class PXEService implements PXE {
630
645
  // TODO(#757): Enforce proper ordering of enqueued public calls
631
646
  await this.patchPublicCallStackOrdering(publicInputs, enqueuedPublicFunctions);
632
647
 
633
- return new Tx(publicInputs, proof, encryptedLogs, unencryptedLogs, enqueuedPublicFunctions);
648
+ const tx = new Tx(publicInputs, proof, encryptedLogs, unencryptedLogs, enqueuedPublicFunctions);
649
+ return new SimulatedTx(tx, [executionResult.returnValues]);
634
650
  }
635
651
 
636
652
  /**
@@ -681,55 +697,62 @@ export class PXEService implements PXE {
681
697
  publicInputs: PrivateKernelTailCircuitPublicInputs,
682
698
  enqueuedPublicCalls: PublicCallRequest[],
683
699
  ) {
700
+ if (!publicInputs.forPublic) {
701
+ return;
702
+ }
703
+
684
704
  const enqueuedPublicCallStackItems = await Promise.all(enqueuedPublicCalls.map(c => c.toCallRequest()));
685
705
 
686
706
  // Validate all items in enqueued public calls are in the kernel emitted stack
687
707
  const enqueuedRevertiblePublicCallStackItems = enqueuedPublicCallStackItems.filter(enqueued =>
688
- publicInputs.end.publicCallStack.find(item => item.equals(enqueued)),
708
+ publicInputs.forPublic!.end.publicCallStack.find(item => item.equals(enqueued)),
689
709
  );
690
710
 
691
- const revertibleStackSize = arrayNonEmptyLength(publicInputs.end.publicCallStack, item => item.isEmpty());
711
+ const revertibleStackSize = arrayNonEmptyLength(publicInputs.forPublic.end.publicCallStack, item => item.isEmpty());
692
712
 
693
713
  if (enqueuedRevertiblePublicCallStackItems.length !== revertibleStackSize) {
694
714
  throw new Error(
695
715
  `Enqueued revertible public function calls and revertible public call stack do not match.\nEnqueued calls: ${enqueuedRevertiblePublicCallStackItems
696
716
  .map(h => h.hash.toString())
697
- .join(', ')}\nPublic call stack: ${publicInputs.end.publicCallStack.map(i => i.toString()).join(', ')}`,
717
+ .join(', ')}\nPublic call stack: ${publicInputs.forPublic.end.publicCallStack
718
+ .map(i => i.toString())
719
+ .join(', ')}`,
698
720
  );
699
721
  }
700
722
 
701
723
  // Override kernel output
702
- publicInputs.end.publicCallStack = padArrayEnd(
724
+ publicInputs.forPublic.end.publicCallStack = padArrayEnd(
703
725
  enqueuedRevertiblePublicCallStackItems,
704
726
  CallRequest.empty(),
705
- MAX_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX,
727
+ MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX,
706
728
  );
707
729
 
708
730
  // Do the same for non-revertible
709
731
 
710
732
  const enqueuedNonRevertiblePublicCallStackItems = enqueuedPublicCallStackItems.filter(enqueued =>
711
- publicInputs.endNonRevertibleData.publicCallStack.find(item => item.equals(enqueued)),
733
+ publicInputs.forPublic!.endNonRevertibleData.publicCallStack.find(item => item.equals(enqueued)),
712
734
  );
713
735
 
714
- const nonRevertibleStackSize = arrayNonEmptyLength(publicInputs.endNonRevertibleData.publicCallStack, item =>
715
- item.isEmpty(),
736
+ const nonRevertibleStackSize = arrayNonEmptyLength(
737
+ publicInputs.forPublic.endNonRevertibleData.publicCallStack,
738
+ item => item.isEmpty(),
716
739
  );
717
740
 
718
741
  if (enqueuedNonRevertiblePublicCallStackItems.length !== nonRevertibleStackSize) {
719
742
  throw new Error(
720
743
  `Enqueued non-revertible public function calls and non-revertible public call stack do not match.\nEnqueued calls: ${enqueuedNonRevertiblePublicCallStackItems
721
744
  .map(h => h.hash.toString())
722
- .join(', ')}\nPublic call stack: ${publicInputs.endNonRevertibleData.publicCallStack
745
+ .join(', ')}\nPublic call stack: ${publicInputs.forPublic.endNonRevertibleData.publicCallStack
723
746
  .map(i => i.toString())
724
747
  .join(', ')}`,
725
748
  );
726
749
  }
727
750
 
728
751
  // Override kernel output
729
- publicInputs.endNonRevertibleData.publicCallStack = padArrayEnd(
752
+ publicInputs.forPublic.endNonRevertibleData.publicCallStack = padArrayEnd(
730
753
  enqueuedNonRevertiblePublicCallStackItems,
731
754
  CallRequest.empty(),
732
- MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX,
755
+ MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX,
733
756
  );
734
757
  }
735
758
 
@@ -135,12 +135,12 @@ export const pxeTestSuite = (testName: string, pxeSetup: () => Promise<PXE>) =>
135
135
  authWitnesses: [],
136
136
  });
137
137
 
138
- await expect(async () => await pxe.simulateTx(txExecutionRequest, false)).rejects.toThrow(
138
+ await expect(async () => await pxe.proveTx(txExecutionRequest, false)).rejects.toThrow(
139
139
  'Public entrypoints are not allowed',
140
140
  );
141
141
  });
142
142
 
143
- // Note: Not testing a successful run of `simulateTx`, `sendTx`, `getTxReceipt` and `viewTx` here as it requires
143
+ // Note: Not testing a successful run of `proveTx`, `sendTx`, `getTxReceipt` and `viewTx` here as it requires
144
144
  // a larger setup and it's sufficiently tested in the e2e tests.
145
145
 
146
146
  it('throws when getting public storage for non-existent contract', async () => {