@aztec/sequencer-client 0.26.3 → 0.26.5

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 (49) hide show
  1. package/dest/block_builder/solo_block_builder.d.ts +5 -4
  2. package/dest/block_builder/solo_block_builder.d.ts.map +1 -1
  3. package/dest/block_builder/solo_block_builder.js +75 -30
  4. package/dest/client/sequencer-client.d.ts.map +1 -1
  5. package/dest/client/sequencer-client.js +29 -3
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +4 -2
  8. package/dest/index.d.ts +2 -0
  9. package/dest/index.d.ts.map +1 -1
  10. package/dest/index.js +2 -1
  11. package/dest/publisher/l1-publisher.js +5 -5
  12. package/dest/publisher/viem-tx-sender.js +2 -2
  13. package/dest/sequencer/public_processor.d.ts +3 -1
  14. package/dest/sequencer/public_processor.d.ts.map +1 -1
  15. package/dest/sequencer/public_processor.js +4 -3
  16. package/dest/sequencer/sequencer.js +3 -3
  17. package/dest/simulator/acvm_native.d.ts +20 -0
  18. package/dest/simulator/acvm_native.d.ts.map +1 -0
  19. package/dest/simulator/acvm_native.js +96 -0
  20. package/dest/simulator/acvm_wasm.d.ts +7 -0
  21. package/dest/simulator/acvm_wasm.d.ts.map +1 -0
  22. package/dest/simulator/acvm_wasm.js +23 -0
  23. package/dest/simulator/index.d.ts +1 -0
  24. package/dest/simulator/index.d.ts.map +1 -1
  25. package/dest/simulator/index.js +2 -2
  26. package/dest/simulator/public_kernel.d.ts +4 -0
  27. package/dest/simulator/public_kernel.d.ts.map +1 -1
  28. package/dest/simulator/public_kernel.js +16 -6
  29. package/dest/simulator/rollup.d.ts +4 -0
  30. package/dest/simulator/rollup.d.ts.map +1 -1
  31. package/dest/simulator/rollup.js +16 -20
  32. package/dest/simulator/simulation_provider.d.ts +9 -0
  33. package/dest/simulator/simulation_provider.d.ts.map +1 -0
  34. package/dest/simulator/simulation_provider.js +2 -0
  35. package/package.json +15 -13
  36. package/src/block_builder/solo_block_builder.ts +109 -50
  37. package/src/client/sequencer-client.ts +37 -2
  38. package/src/config.ts +4 -0
  39. package/src/index.ts +2 -0
  40. package/src/publisher/l1-publisher.ts +4 -4
  41. package/src/publisher/viem-tx-sender.ts +1 -1
  42. package/src/sequencer/public_processor.ts +3 -1
  43. package/src/sequencer/sequencer.ts +2 -2
  44. package/src/simulator/acvm_native.ts +112 -0
  45. package/src/simulator/acvm_wasm.ts +31 -0
  46. package/src/simulator/index.ts +1 -0
  47. package/src/simulator/public_kernel.ts +31 -7
  48. package/src/simulator/rollup.ts +31 -19
  49. package/src/simulator/simulation_provider.ts +10 -0
@@ -1,4 +1,5 @@
1
1
  import { Body, ContractData, L2Block, MerkleTreeId, PublicDataWrite, TxEffect, TxL2Logs } from '@aztec/circuit-types';
2
+ import { CircuitSimulationStats } from '@aztec/circuit-types/stats';
2
3
  import {
3
4
  ARCHIVE_HEIGHT,
4
5
  AppendOnlyTreeSnapshot,
@@ -52,6 +53,7 @@ import { padArrayEnd } from '@aztec/foundation/collection';
52
53
  import { Fr } from '@aztec/foundation/fields';
53
54
  import { createDebugLogger } from '@aztec/foundation/log';
54
55
  import { Tuple, assertLength, toFriendlyJSON } from '@aztec/foundation/serialize';
56
+ import { elapsed } from '@aztec/foundation/timer';
55
57
  import { MerkleTreeOperations } from '@aztec/world-state';
56
58
 
57
59
  import chunk from 'lodash.chunk';
@@ -138,11 +140,11 @@ export class SoloBlockBuilder implements BlockBuilder {
138
140
  body: blockBody,
139
141
  });
140
142
 
141
- if (!l2Block.body.getCalldataHash().equals(circuitsOutput.header.contentCommitment.txsHash)) {
143
+ if (!l2Block.body.getTxsEffectsHash().equals(circuitsOutput.header.contentCommitment.txsEffectsHash)) {
142
144
  throw new Error(
143
- `Calldata hash mismatch, ${l2Block.body
144
- .getCalldataHash()
145
- .toString('hex')} == ${circuitsOutput.header.contentCommitment.txsHash.toString('hex')} `,
145
+ `Txs effects hash mismatch, ${l2Block.body
146
+ .getTxsEffectsHash()
147
+ .toString('hex')} == ${circuitsOutput.header.contentCommitment.txsEffectsHash.toString('hex')} `,
146
148
  );
147
149
  }
148
150
 
@@ -189,22 +191,66 @@ export class SoloBlockBuilder implements BlockBuilder {
189
191
  // padArrayEnd throws if the array is already full. Otherwise it pads till we reach the required size
190
192
  const newL1ToL2MessagesTuple = padArrayEnd(newL1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
191
193
 
192
- // Run the base rollup circuits for the txs
193
- const baseRollupOutputs: [BaseOrMergeRollupPublicInputs, Proof][] = [];
194
+ // Perform all tree insertions and retrieve snapshots for all base rollups
195
+ const baseRollupInputs: BaseRollupInputs[] = [];
196
+ const treeSnapshots: Map<MerkleTreeId, AppendOnlyTreeSnapshot>[] = [];
194
197
  for (const tx of txs) {
195
- baseRollupOutputs.push(await this.baseRollupCircuit(tx, globalVariables));
198
+ const input = await this.buildBaseRollupInput(tx, globalVariables);
199
+ baseRollupInputs.push(input);
200
+ const promises = [
201
+ MerkleTreeId.NOTE_HASH_TREE,
202
+ MerkleTreeId.CONTRACT_TREE,
203
+ MerkleTreeId.NULLIFIER_TREE,
204
+ MerkleTreeId.PUBLIC_DATA_TREE,
205
+ ].map(async (id: MerkleTreeId) => {
206
+ return { key: id, value: await this.getTreeSnapshot(id) };
207
+ });
208
+ const snapshots: Map<MerkleTreeId, AppendOnlyTreeSnapshot> = new Map(
209
+ (await Promise.all(promises)).map(obj => [obj.key, obj.value]),
210
+ );
211
+ treeSnapshots.push(snapshots);
212
+ }
213
+
214
+ // Run the base rollup circuits for the txs in parallel
215
+ const baseRollupOutputs: Promise<[BaseOrMergeRollupPublicInputs, Proof]>[] = [];
216
+ for (let i = 0; i < txs.length; i++) {
217
+ baseRollupOutputs.push(this.baseRollupCircuit(txs[i], baseRollupInputs[i], treeSnapshots[i]));
196
218
  }
197
219
 
198
220
  // Run merge rollups in layers until we have only two outputs
199
- let mergeRollupInputs: [BaseOrMergeRollupPublicInputs, Proof][] = baseRollupOutputs;
200
- let mergeRollupOutputs: [BaseOrMergeRollupPublicInputs, Proof][] = [];
221
+ // All merge circuits for each layer are simulated in parallel
222
+ const [duration, mergeInputs] = await elapsed(() => Promise.all(baseRollupOutputs));
223
+ for (let i = 0; i < mergeInputs.length; i++) {
224
+ this.debug(`Simulated base rollup circuit`, {
225
+ eventName: 'circuit-simulation',
226
+ circuitName: 'base-rollup',
227
+ duration: duration / mergeInputs.length,
228
+ inputSize: baseRollupInputs[i].toBuffer().length,
229
+ outputSize: mergeInputs[i][0].toBuffer().length,
230
+ } satisfies CircuitSimulationStats);
231
+ }
232
+ let mergeRollupInputs: [BaseOrMergeRollupPublicInputs, Proof][] = mergeInputs;
201
233
  while (mergeRollupInputs.length > 2) {
234
+ const mergeInputStructs: MergeRollupInputs[] = [];
202
235
  for (const pair of chunk(mergeRollupInputs, 2)) {
203
236
  const [r1, r2] = pair;
204
- mergeRollupOutputs.push(await this.mergeRollupCircuit(r1, r2));
237
+ mergeInputStructs.push(this.createMergeRollupInputs(r1, r2));
238
+ }
239
+
240
+ const [duration, mergeOutputs] = await elapsed(() =>
241
+ Promise.all(mergeInputStructs.map(async input => await this.mergeRollupCircuit(input))),
242
+ );
243
+
244
+ for (let i = 0; i < mergeOutputs.length; i++) {
245
+ this.debug(`Simulated merge rollup circuit`, {
246
+ eventName: 'circuit-simulation',
247
+ circuitName: 'merge-rollup',
248
+ duration: duration / mergeOutputs.length,
249
+ inputSize: mergeInputStructs[i].toBuffer().length,
250
+ outputSize: mergeOutputs[i][0].toBuffer().length,
251
+ } satisfies CircuitSimulationStats);
205
252
  }
206
- mergeRollupInputs = mergeRollupOutputs;
207
- mergeRollupOutputs = [];
253
+ mergeRollupInputs = mergeOutputs;
208
254
  }
209
255
 
210
256
  // Run the root rollup with the last two merge rollups (or base, if no merge layers)
@@ -214,26 +260,29 @@ export class SoloBlockBuilder implements BlockBuilder {
214
260
 
215
261
  protected async baseRollupCircuit(
216
262
  tx: ProcessedTx,
217
- globalVariables: GlobalVariables,
263
+ inputs: BaseRollupInputs,
264
+ treeSnapshots: Map<MerkleTreeId, AppendOnlyTreeSnapshot>,
218
265
  ): Promise<[BaseOrMergeRollupPublicInputs, Proof]> {
219
266
  this.debug(`Running base rollup for ${tx.hash}`);
220
- const rollupInput = await this.buildBaseRollupInput(tx, globalVariables);
221
- const rollupOutput = await this.simulator.baseRollupCircuit(rollupInput);
222
- await this.validatePartialState(rollupOutput.end);
223
- const proof = await this.prover.getBaseRollupProof(rollupInput, rollupOutput);
267
+ const rollupOutput = await this.simulator.baseRollupCircuit(inputs);
268
+ this.validatePartialState(rollupOutput.end, treeSnapshots);
269
+ const proof = await this.prover.getBaseRollupProof(inputs, rollupOutput);
224
270
  return [rollupOutput, proof];
225
271
  }
226
272
 
227
- protected async mergeRollupCircuit(
273
+ protected createMergeRollupInputs(
228
274
  left: [BaseOrMergeRollupPublicInputs, Proof],
229
275
  right: [BaseOrMergeRollupPublicInputs, Proof],
230
- ): Promise<[BaseOrMergeRollupPublicInputs, Proof]> {
276
+ ) {
231
277
  const vk = this.getVerificationKey(left[0].rollupType);
232
278
  const mergeInputs = new MergeRollupInputs([
233
279
  this.getPreviousRollupDataFromPublicInputs(left[0], left[1], vk),
234
280
  this.getPreviousRollupDataFromPublicInputs(right[0], right[1], vk),
235
281
  ]);
282
+ return mergeInputs;
283
+ }
236
284
 
285
+ protected async mergeRollupCircuit(mergeInputs: MergeRollupInputs): Promise<[BaseOrMergeRollupPublicInputs, Proof]> {
237
286
  this.debug(`Running merge rollup circuit`);
238
287
  const output = await this.simulator.mergeRollupCircuit(mergeInputs);
239
288
  const proof = await this.prover.getMergeRollupProof(mergeInputs, output);
@@ -279,40 +328,50 @@ export class SoloBlockBuilder implements BlockBuilder {
279
328
  return [rootOutput, rootProof];
280
329
  }
281
330
 
282
- protected async validatePartialState(partialState: PartialStateReference) {
283
- await Promise.all([
284
- this.validateSimulatedTree(
285
- await this.getTreeSnapshot(MerkleTreeId.NOTE_HASH_TREE),
286
- partialState.noteHashTree,
287
- 'NoteHashTree',
288
- ),
289
- this.validateSimulatedTree(
290
- await this.getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE),
291
- partialState.nullifierTree,
292
- 'NullifierTree',
293
- ),
294
- this.validateSimulatedTree(
295
- await this.getTreeSnapshot(MerkleTreeId.CONTRACT_TREE),
296
- partialState.contractTree,
297
- 'ContractTree',
298
- ),
299
- this.validateSimulatedTree(
300
- await this.getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE),
301
- partialState.publicDataTree,
302
- 'PublicDataTree',
303
- ),
304
- ]);
331
+ protected validatePartialState(
332
+ partialState: PartialStateReference,
333
+ treeSnapshots: Map<MerkleTreeId, AppendOnlyTreeSnapshot>,
334
+ ) {
335
+ this.validateSimulatedTree(
336
+ treeSnapshots.get(MerkleTreeId.NOTE_HASH_TREE)!,
337
+ partialState.noteHashTree,
338
+ 'NoteHashTree',
339
+ );
340
+ this.validateSimulatedTree(
341
+ treeSnapshots.get(MerkleTreeId.NULLIFIER_TREE)!,
342
+ partialState.nullifierTree,
343
+ 'NullifierTree',
344
+ );
345
+ this.validateSimulatedTree(
346
+ treeSnapshots.get(MerkleTreeId.CONTRACT_TREE)!,
347
+ partialState.contractTree,
348
+ 'ContractTree',
349
+ );
350
+ this.validateSimulatedTree(
351
+ treeSnapshots.get(MerkleTreeId.PUBLIC_DATA_TREE)!,
352
+ partialState.publicDataTree,
353
+ 'PublicDataTree',
354
+ );
305
355
  }
306
356
 
307
357
  protected async validateState(state: StateReference) {
308
- await Promise.all([
309
- this.validateSimulatedTree(
310
- await this.getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE),
311
- state.l1ToL2MessageTree,
312
- 'L1ToL2MessageTree',
313
- ),
314
- this.validatePartialState(state.partial),
315
- ]);
358
+ const promises = [
359
+ MerkleTreeId.NOTE_HASH_TREE,
360
+ MerkleTreeId.CONTRACT_TREE,
361
+ MerkleTreeId.NULLIFIER_TREE,
362
+ MerkleTreeId.PUBLIC_DATA_TREE,
363
+ ].map(async (id: MerkleTreeId) => {
364
+ return { key: id, value: await this.getTreeSnapshot(id) };
365
+ });
366
+ const snapshots: Map<MerkleTreeId, AppendOnlyTreeSnapshot> = new Map(
367
+ (await Promise.all(promises)).map(obj => [obj.key, obj.value]),
368
+ );
369
+ this.validatePartialState(state.partial, snapshots);
370
+ this.validateSimulatedTree(
371
+ await this.getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE),
372
+ state.l1ToL2MessageTree,
373
+ 'L1ToL2MessageTree',
374
+ );
316
375
  }
317
376
 
318
377
  // Validate that the roots of all local trees match the output of the root circuit simulation
@@ -1,7 +1,10 @@
1
1
  import { ContractDataSource, L1ToL2MessageSource, L2BlockSource } from '@aztec/circuit-types';
2
+ import { createDebugLogger } from '@aztec/foundation/log';
2
3
  import { P2P } from '@aztec/p2p';
3
4
  import { WorldStateSynchronizer } from '@aztec/world-state';
4
5
 
6
+ import * as fs from 'fs/promises';
7
+
5
8
  import { SoloBlockBuilder } from '../block_builder/solo_block_builder.js';
6
9
  import { SequencerClientConfig } from '../config.js';
7
10
  import { getGlobalVariableBuilder } from '../global_variable_builder/index.js';
@@ -10,7 +13,32 @@ import { EmptyRollupProver } from '../prover/empty.js';
10
13
  import { getL1Publisher } from '../publisher/index.js';
11
14
  import { Sequencer, SequencerConfig } from '../sequencer/index.js';
12
15
  import { PublicProcessorFactory } from '../sequencer/public_processor.js';
16
+ import { NativeACVMSimulator } from '../simulator/acvm_native.js';
17
+ import { WASMSimulator } from '../simulator/acvm_wasm.js';
13
18
  import { RealRollupCircuitSimulator } from '../simulator/rollup.js';
19
+ import { SimulationProvider } from '../simulator/simulation_provider.js';
20
+
21
+ const logger = createDebugLogger('aztec:sequencer-client');
22
+
23
+ /**
24
+ * Factory function to create a simulation provider. Will attempt to use native binary simulation falling back to WASM if unavailable.
25
+ * @param config - The provided sequencer client configuration
26
+ * @returns The constructed simulation provider
27
+ */
28
+ async function getSimulationProvider(config: SequencerClientConfig): Promise<SimulationProvider> {
29
+ if (config.acvmBinaryPath && config.acvmWorkingDirectory) {
30
+ try {
31
+ await fs.access(config.acvmBinaryPath, fs.constants.R_OK);
32
+ await fs.mkdir(config.acvmWorkingDirectory, { recursive: true });
33
+ logger(`Using native ACVM at ${config.acvmBinaryPath}`);
34
+ return new NativeACVMSimulator(config.acvmWorkingDirectory, config.acvmBinaryPath);
35
+ } catch {
36
+ logger(`Failed to access ACVM at ${config.acvmBinaryPath}, falling back to WASM`);
37
+ }
38
+ }
39
+ logger('Using WASM ACVM simulation');
40
+ return new WASMSimulator();
41
+ }
14
42
 
15
43
  /**
16
44
  * Encapsulates the full sequencer and publisher.
@@ -40,14 +68,21 @@ export class SequencerClient {
40
68
  const globalsBuilder = getGlobalVariableBuilder(config);
41
69
  const merkleTreeDb = worldStateSynchronizer.getLatest();
42
70
 
71
+ const simulationProvider = await getSimulationProvider(config);
72
+
43
73
  const blockBuilder = new SoloBlockBuilder(
44
74
  merkleTreeDb,
45
75
  getVerificationKeys(),
46
- new RealRollupCircuitSimulator(),
76
+ new RealRollupCircuitSimulator(simulationProvider),
47
77
  new EmptyRollupProver(),
48
78
  );
49
79
 
50
- const publicProcessorFactory = new PublicProcessorFactory(merkleTreeDb, contractDataSource, l1ToL2MessageSource);
80
+ const publicProcessorFactory = new PublicProcessorFactory(
81
+ merkleTreeDb,
82
+ contractDataSource,
83
+ l1ToL2MessageSource,
84
+ simulationProvider,
85
+ );
51
86
 
52
87
  const sequencer = new Sequencer(
53
88
  publisher,
package/src/config.ts CHANGED
@@ -48,6 +48,8 @@ export function getConfigEnvVars(): SequencerClientConfig {
48
48
  OUTBOX_CONTRACT_ADDRESS,
49
49
  COINBASE,
50
50
  FEE_RECIPIENT,
51
+ ACVM_WORKING_DIRECTORY,
52
+ ACVM_BINARY_PATH,
51
53
  } = process.env;
52
54
 
53
55
  const publisherPrivateKey: Hex = SEQ_PUBLISHER_PRIVATE_KEY
@@ -82,5 +84,7 @@ export function getConfigEnvVars(): SequencerClientConfig {
82
84
  // TODO: undefined should not be allowed for the following 2 values in PROD
83
85
  coinbase: COINBASE ? EthAddress.fromString(COINBASE) : undefined,
84
86
  feeRecipient: FEE_RECIPIENT ? AztecAddress.fromString(FEE_RECIPIENT) : undefined,
87
+ acvmWorkingDirectory: ACVM_WORKING_DIRECTORY ? ACVM_WORKING_DIRECTORY : undefined,
88
+ acvmBinaryPath: ACVM_BINARY_PATH ? ACVM_BINARY_PATH : undefined,
85
89
  };
86
90
  }
package/src/index.ts CHANGED
@@ -12,4 +12,6 @@ export * from './global_variable_builder/index.js';
12
12
  export { RealRollupCircuitSimulator } from './simulator/rollup.js';
13
13
  export { EmptyRollupProver } from './prover/empty.js';
14
14
  export { SoloBlockBuilder } from './block_builder/solo_block_builder.js';
15
+ export { WASMSimulator } from './simulator/acvm_wasm.js';
16
+ export { SimulationProvider } from './simulator/simulation_provider.js';
15
17
  export { makeProcessedTx, makeEmptyProcessedTx } from './sequencer/processed_tx.js';
@@ -172,15 +172,15 @@ export class L1Publisher implements L2BlockReceiver {
172
172
  }
173
173
 
174
174
  if (receipt.status) {
175
- let txsHash;
175
+ let txsEffectsHash;
176
176
  if (receipt.logs.length === 1) {
177
- // txsHash from IAvailabilityOracle.TxsPublished event
178
- txsHash = receipt.logs[0].data;
177
+ // txsEffectsHash from IAvailabilityOracle.TxsPublished event
178
+ txsEffectsHash = receipt.logs[0].data;
179
179
  } else {
180
180
  this.log(`Expected 1 log, got ${receipt.logs.length}`);
181
181
  }
182
182
 
183
- this.log.info(`Block txs effects published, txsHash: ${txsHash}`);
183
+ this.log.info(`Block txs effects published, txsEffectsHash: ${txsEffectsHash}`);
184
184
  break;
185
185
  }
186
186
 
@@ -87,7 +87,7 @@ export class ViemTxSender implements L1PublisherTxSender {
87
87
  }
88
88
 
89
89
  checkIfTxsAreAvailable(block: L2Block): Promise<boolean> {
90
- const args = [`0x${block.body.getCalldataHash().toString('hex')}`] as const;
90
+ const args = [`0x${block.body.getTxsEffectsHash().toString('hex')}`] as const;
91
91
  return this.availabilityOracleContract.read.isAvailable(args);
92
92
  }
93
93
 
@@ -11,6 +11,7 @@ import { PublicProver } from '../prover/index.js';
11
11
  import { PublicKernelCircuitSimulator } from '../simulator/index.js';
12
12
  import { ContractsDataSourcePublicDB, WorldStateDB, WorldStatePublicDB } from '../simulator/public_executor.js';
13
13
  import { RealPublicKernelCircuitSimulator } from '../simulator/public_kernel.js';
14
+ import { SimulationProvider } from '../simulator/simulation_provider.js';
14
15
  import { AbstractPhaseManager } from './abstract_phase_manager.js';
15
16
  import { PhaseManagerFactory } from './phase_manager_factory.js';
16
17
  import { FailedTx, ProcessedTx, makeEmptyProcessedTx, makeProcessedTx } from './processed_tx.js';
@@ -23,6 +24,7 @@ export class PublicProcessorFactory {
23
24
  private merkleTree: MerkleTreeOperations,
24
25
  private contractDataSource: ContractDataSource,
25
26
  private l1Tol2MessagesDataSource: L1ToL2MessageSource,
27
+ private simulator: SimulationProvider,
26
28
  ) {}
27
29
 
28
30
  /**
@@ -45,7 +47,7 @@ export class PublicProcessorFactory {
45
47
  return new PublicProcessor(
46
48
  this.merkleTree,
47
49
  publicExecutor,
48
- new RealPublicKernelCircuitSimulator(),
50
+ new RealPublicKernelCircuitSimulator(this.simulator),
49
51
  new EmptyPublicProver(),
50
52
  globalVariables,
51
53
  historicalHeader,
@@ -255,12 +255,12 @@ export class Sequencer {
255
255
  return;
256
256
  }
257
257
 
258
- const blockCalldataHash = block.body.getCalldataHash();
258
+ const txsEffectsHash = block.body.getTxsEffectsHash();
259
259
  this.log.info(`Publishing ${newContracts.length} contracts in block ${block.number}`);
260
260
 
261
261
  const publishedContractData = await this.publisher.processNewContractData(
262
262
  block.number,
263
- blockCalldataHash,
263
+ txsEffectsHash,
264
264
  newContracts,
265
265
  );
266
266
 
@@ -0,0 +1,112 @@
1
+ import { randomBytes } from '@aztec/foundation/crypto';
2
+ import { NoirCompiledCircuit } from '@aztec/types/noir';
3
+
4
+ import { WitnessMap } from '@noir-lang/types';
5
+ import * as proc from 'child_process';
6
+ import fs from 'fs/promises';
7
+
8
+ import { SimulationProvider } from './simulation_provider.js';
9
+
10
+ /**
11
+ * Parses a TOML format witness map string into a Map structure
12
+ * @param outputString - The witness map in TOML format
13
+ * @returns The parsed witness map
14
+ */
15
+ function parseIntoWitnessMap(outputString: string) {
16
+ const lines = outputString.split('\n');
17
+ return new Map<number, string>(
18
+ lines
19
+ .filter((line: string) => line.length)
20
+ .map((line: string) => {
21
+ const pair = line.replaceAll(' ', '').split('=');
22
+ return [Number(pair[0]), pair[1].replaceAll('"', '')];
23
+ }),
24
+ );
25
+ }
26
+
27
+ /**
28
+ *
29
+ * @param inputWitness - The circuit's input witness
30
+ * @param bytecode - The circuit buytecode
31
+ * @param workingDirectory - A directory to use for temporary files by the ACVM
32
+ * @param pathToAcvm - The path to the ACVm binary
33
+ * @returns The completed partial witness outputted from the circuit
34
+ */
35
+ export async function executeNativeCircuit(
36
+ inputWitness: WitnessMap,
37
+ bytecode: Buffer,
38
+ workingDirectory: string,
39
+ pathToAcvm: string,
40
+ ) {
41
+ const bytecodeFilename = 'bytecode';
42
+ const witnessFilename = 'input_witness.toml';
43
+
44
+ // convert the witness map to TOML format
45
+ let witnessMap = '';
46
+ inputWitness.forEach((value: string, key: number) => {
47
+ witnessMap = witnessMap.concat(`${key} = '${value}'\n`);
48
+ });
49
+
50
+ // In case the directory is still around from some time previously, remove it
51
+ await fs.rm(workingDirectory, { recursive: true, force: true });
52
+ // Create the new working directory
53
+ await fs.mkdir(workingDirectory, { recursive: true });
54
+ // Write the bytecode and input witness to the working directory
55
+ await fs.writeFile(`${workingDirectory}/${bytecodeFilename}`, bytecode);
56
+ await fs.writeFile(`${workingDirectory}/${witnessFilename}`, witnessMap);
57
+
58
+ // Execute the ACVM using the given args
59
+ const args = [
60
+ `execute`,
61
+ `--working-directory`,
62
+ `${workingDirectory}`,
63
+ `--bytecode`,
64
+ `${bytecodeFilename}`,
65
+ `--input-witness`,
66
+ `${witnessFilename}`,
67
+ `--print`,
68
+ ];
69
+ const processPromise = new Promise<string>((resolve, reject) => {
70
+ let outputWitness = Buffer.alloc(0);
71
+ let errorBuffer = Buffer.alloc(0);
72
+ const acvm = proc.spawn(pathToAcvm, args);
73
+ acvm.stdout.on('data', data => {
74
+ outputWitness = Buffer.concat([outputWitness, data]);
75
+ });
76
+ acvm.stderr.on('data', data => {
77
+ errorBuffer = Buffer.concat([errorBuffer, data]);
78
+ });
79
+ acvm.on('close', code => {
80
+ if (code === 0) {
81
+ resolve(outputWitness.toString('utf-8'));
82
+ } else {
83
+ reject(errorBuffer.toString('utf-8'));
84
+ }
85
+ });
86
+ });
87
+
88
+ try {
89
+ const output = await processPromise;
90
+ return parseIntoWitnessMap(output);
91
+ } finally {
92
+ // Clean up the working directory before we leave
93
+ await fs.rm(workingDirectory, { recursive: true, force: true });
94
+ }
95
+ }
96
+
97
+ export class NativeACVMSimulator implements SimulationProvider {
98
+ constructor(private workingDirectory: string, private pathToAcvm: string) {}
99
+ async simulateCircuit(input: WitnessMap, compiledCircuit: NoirCompiledCircuit): Promise<WitnessMap> {
100
+ // Execute the circuit on those initial witness values
101
+
102
+ // Decode the bytecode from base64 since the acvm does not know about base64 encoding
103
+ const decodedBytecode = Buffer.from(compiledCircuit.bytecode, 'base64');
104
+
105
+ // Provide a unique working directory so we don't get clashes with parallel executions
106
+ const directory = `${this.workingDirectory}/${randomBytes(32).toString('hex')}`;
107
+ // Execute the circuit
108
+ const _witnessMap = await executeNativeCircuit(input, decodedBytecode, directory, this.pathToAcvm);
109
+
110
+ return _witnessMap;
111
+ }
112
+ }
@@ -0,0 +1,31 @@
1
+ import { NoirCompiledCircuit } from '@aztec/types/noir';
2
+
3
+ import { WasmBlackBoxFunctionSolver, createBlackBoxSolver, executeCircuitWithBlackBoxSolver } from '@noir-lang/acvm_js';
4
+ import { WitnessMap } from '@noir-lang/types';
5
+
6
+ import { SimulationProvider } from './simulation_provider.js';
7
+
8
+ let solver: Promise<WasmBlackBoxFunctionSolver>;
9
+
10
+ const getSolver = (): Promise<WasmBlackBoxFunctionSolver> => {
11
+ if (!solver) {
12
+ solver = createBlackBoxSolver();
13
+ }
14
+ return solver;
15
+ };
16
+
17
+ export class WASMSimulator implements SimulationProvider {
18
+ async simulateCircuit(input: WitnessMap, compiledCircuit: NoirCompiledCircuit): Promise<WitnessMap> {
19
+ // Execute the circuit on those initial witness values
20
+ //
21
+ // Decode the bytecode from base64 since the acvm does not know about base64 encoding
22
+ const decodedBytecode = Buffer.from(compiledCircuit.bytecode, 'base64');
23
+ //
24
+ // Execute the circuit
25
+ const _witnessMap = await executeCircuitWithBlackBoxSolver(await getSolver(), decodedBytecode, input, () => {
26
+ throw Error('unexpected oracle during execution');
27
+ });
28
+
29
+ return _witnessMap;
30
+ }
31
+ }
@@ -55,3 +55,4 @@ export interface PublicKernelCircuitSimulator {
55
55
  */
56
56
  publicKernelCircuitTeardown(inputs: PublicKernelCircuitPrivateInputs): Promise<PublicKernelCircuitPublicInputs>;
57
57
  }
58
+ export * from './acvm_wasm.js';
@@ -3,12 +3,19 @@ import { PublicKernelCircuitPrivateInputs, PublicKernelCircuitPublicInputs } fro
3
3
  import { createDebugLogger } from '@aztec/foundation/log';
4
4
  import { elapsed } from '@aztec/foundation/timer';
5
5
  import {
6
- executePublicKernelAppLogic,
7
- executePublicKernelSetup,
8
- executePublicKernelTeardown,
6
+ PublicKernelAppLogicArtifact,
7
+ PublicKernelSetupArtifact,
8
+ PublicKernelTeardownArtifact,
9
+ convertPublicInnerRollupInputsToWitnessMap,
10
+ convertPublicInnerRollupOutputFromWitnessMap,
11
+ convertPublicSetupRollupInputsToWitnessMap,
12
+ convertPublicSetupRollupOutputFromWitnessMap,
13
+ convertPublicTailRollupInputsToWitnessMap,
14
+ convertPublicTailRollupOutputFromWitnessMap,
9
15
  } from '@aztec/noir-protocol-circuits-types';
10
16
 
11
- import { PublicKernelCircuitSimulator } from './index.js';
17
+ import { PublicKernelCircuitSimulator, WASMSimulator } from './index.js';
18
+ import { SimulationProvider } from './simulation_provider.js';
12
19
 
13
20
  /**
14
21
  * Implements the PublicKernelCircuitSimulator.
@@ -16,6 +23,11 @@ import { PublicKernelCircuitSimulator } from './index.js';
16
23
  export class RealPublicKernelCircuitSimulator implements PublicKernelCircuitSimulator {
17
24
  private log = createDebugLogger('aztec:public-kernel-simulator');
18
25
 
26
+ // Some circuits are so small it is faster to use WASM
27
+ private wasmSimulator: WASMSimulator = new WASMSimulator();
28
+
29
+ constructor(private simulator: SimulationProvider) {}
30
+
19
31
  /**
20
32
  * Simulates the public kernel setup circuit from its inputs.
21
33
  * @param input - Inputs to the circuit.
@@ -27,7 +39,11 @@ export class RealPublicKernelCircuitSimulator implements PublicKernelCircuitSimu
27
39
  if (!input.previousKernel.publicInputs.needsSetup) {
28
40
  throw new Error(`Expected previous kernel inputs to need setup`);
29
41
  }
30
- const [duration, result] = await elapsed(() => executePublicKernelSetup(input));
42
+ const inputWitness = convertPublicSetupRollupInputsToWitnessMap(input);
43
+ const [duration, witness] = await elapsed(() =>
44
+ this.wasmSimulator.simulateCircuit(inputWitness, PublicKernelSetupArtifact),
45
+ );
46
+ const result = convertPublicSetupRollupOutputFromWitnessMap(witness);
31
47
  this.log(`Simulated public kernel setup circuit`, {
32
48
  eventName: 'circuit-simulation',
33
49
  circuitName: 'public-kernel-setup',
@@ -49,7 +65,11 @@ export class RealPublicKernelCircuitSimulator implements PublicKernelCircuitSimu
49
65
  if (!input.previousKernel.publicInputs.needsAppLogic) {
50
66
  throw new Error(`Expected previous kernel inputs to need app logic`);
51
67
  }
52
- const [duration, result] = await elapsed(() => executePublicKernelAppLogic(input));
68
+ const inputWitness = convertPublicInnerRollupInputsToWitnessMap(input);
69
+ const [duration, witness] = await elapsed(() =>
70
+ this.wasmSimulator.simulateCircuit(inputWitness, PublicKernelAppLogicArtifact),
71
+ );
72
+ const result = convertPublicInnerRollupOutputFromWitnessMap(witness);
53
73
  this.log(`Simulated public kernel app logic circuit`, {
54
74
  eventName: 'circuit-simulation',
55
75
  circuitName: 'public-kernel-app-logic',
@@ -71,7 +91,11 @@ export class RealPublicKernelCircuitSimulator implements PublicKernelCircuitSimu
71
91
  if (!input.previousKernel.publicInputs.needsTeardown) {
72
92
  throw new Error(`Expected previous kernel inputs to need teardown`);
73
93
  }
74
- const [duration, result] = await elapsed(() => executePublicKernelTeardown(input));
94
+ const inputWitness = convertPublicTailRollupInputsToWitnessMap(input);
95
+ const [duration, witness] = await elapsed(() =>
96
+ this.wasmSimulator.simulateCircuit(inputWitness, PublicKernelTeardownArtifact),
97
+ );
98
+ const result = convertPublicTailRollupOutputFromWitnessMap(witness);
75
99
  this.log(`Simulated public kernel teardown circuit`, {
76
100
  eventName: 'circuit-simulation',
77
101
  circuitName: 'public-kernel-teardown',