@aztec/simulator 0.69.0 → 0.69.1

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.
@@ -136,29 +136,132 @@ export class PublicProcessor implements Traceable {
136
136
  * @returns The list of processed txs with their circuit simulation outputs.
137
137
  */
138
138
  public async process(
139
- txs: Tx[],
140
- maxTransactions = txs.length,
141
- txValidator?: TxValidator<ProcessedTx>,
142
- deadline?: Date,
139
+ txs: Iterable<Tx>,
140
+ limits: {
141
+ maxTransactions?: number;
142
+ maxBlockSize?: number;
143
+ maxBlockGas?: Gas;
144
+ deadline?: Date;
145
+ } = {},
146
+ validators: {
147
+ preprocessValidator?: TxValidator<Tx>;
148
+ postprocessValidator?: TxValidator<ProcessedTx>;
149
+ nullifierCache?: { addNullifiers: (nullifiers: Buffer[]) => void };
150
+ } = {},
143
151
  ): Promise<[ProcessedTx[], FailedTx[], NestedProcessReturnValues[]]> {
144
- // The processor modifies the tx objects in place, so we need to clone them.
145
- txs = txs.map(tx => Tx.clone(tx));
152
+ const { maxTransactions, maxBlockSize, deadline, maxBlockGas } = limits;
153
+ const { preprocessValidator, postprocessValidator, nullifierCache } = validators;
146
154
  const result: ProcessedTx[] = [];
147
155
  const failed: FailedTx[] = [];
148
- let returns: NestedProcessReturnValues[] = [];
149
- let totalGas = new Gas(0, 0);
150
156
  const timer = new Timer();
151
157
 
152
- for (const tx of txs) {
153
- // only process up to the limit of the block
154
- if (result.length >= maxTransactions) {
158
+ let totalSizeInBytes = 0;
159
+ let returns: NestedProcessReturnValues[] = [];
160
+ let totalPublicGas = new Gas(0, 0);
161
+ let totalBlockGas = new Gas(0, 0);
162
+
163
+ for (const origTx of txs) {
164
+ // Only process up to the max tx limit
165
+ if (maxTransactions !== undefined && result.length >= maxTransactions) {
166
+ this.log.debug(`Stopping tx processing due to reaching the max tx limit.`);
155
167
  break;
156
168
  }
169
+
170
+ // Bail if we've hit the deadline
171
+ if (deadline && this.dateProvider.now() > +deadline) {
172
+ this.log.warn(`Stopping tx processing due to timeout.`);
173
+ break;
174
+ }
175
+
176
+ // Skip this tx if it'd exceed max block size
177
+ const txHash = origTx.getTxHash().toString();
178
+ const preTxSizeInBytes = origTx.getEstimatedPrivateTxEffectsSize();
179
+ if (maxBlockSize !== undefined && totalSizeInBytes + preTxSizeInBytes > maxBlockSize) {
180
+ this.log.warn(`Skipping processing of tx ${txHash} sized ${preTxSizeInBytes} bytes due to block size limit`, {
181
+ txHash,
182
+ sizeInBytes: preTxSizeInBytes,
183
+ totalSizeInBytes,
184
+ maxBlockSize,
185
+ });
186
+ continue;
187
+ }
188
+
189
+ // Skip this tx if its gas limit would exceed the block gas limit
190
+ const txGasLimit = origTx.data.constants.txContext.gasSettings.gasLimits;
191
+ if (maxBlockGas !== undefined && totalBlockGas.add(txGasLimit).gtAny(maxBlockGas)) {
192
+ this.log.warn(`Skipping processing of tx ${txHash} due to block gas limit`, {
193
+ txHash,
194
+ txGasLimit,
195
+ totalBlockGas,
196
+ maxBlockGas,
197
+ });
198
+ continue;
199
+ }
200
+
201
+ // The processor modifies the tx objects in place, so we need to clone them.
202
+ const tx = Tx.clone(origTx);
203
+
204
+ // We validate the tx before processing it, to avoid unnecessary work.
205
+ if (preprocessValidator) {
206
+ const result = await preprocessValidator.validateTx(tx);
207
+ if (result.result === 'invalid') {
208
+ const reason = result.reason.join(', ');
209
+ this.log.warn(`Rejecting tx ${tx.getTxHash().toString()} due to pre-process validation fail: ${reason}`);
210
+ failed.push({ tx, error: new Error(`Tx failed preprocess validation: ${reason}`) });
211
+ returns.push(new NestedProcessReturnValues([]));
212
+ continue;
213
+ } else if (result.result === 'skipped') {
214
+ const reason = result.reason.join(', ');
215
+ this.log.warn(`Skipping tx ${tx.getTxHash().toString()} due to pre-process validation: ${reason}`);
216
+ returns.push(new NestedProcessReturnValues([]));
217
+ continue;
218
+ } else {
219
+ this.log.trace(`Tx ${tx.getTxHash().toString()} is valid before processing.`);
220
+ }
221
+ }
222
+
157
223
  try {
158
- const [processedTx, returnValues] = await this.processTx(tx, txValidator, deadline);
224
+ const [processedTx, returnValues] = await this.processTx(tx, deadline);
225
+
226
+ // If the actual size of this tx would exceed block size, skip it
227
+ const txSize = processedTx.txEffect.getDASize();
228
+ if (maxBlockSize !== undefined && totalSizeInBytes + txSize > maxBlockSize) {
229
+ this.log.warn(`Skipping processed tx ${txHash} sized ${txSize} due to max block size.`, {
230
+ txHash,
231
+ sizeInBytes: txSize,
232
+ totalSizeInBytes,
233
+ maxBlockSize,
234
+ });
235
+ continue;
236
+ }
237
+
238
+ // Re-validate the transaction
239
+ if (postprocessValidator) {
240
+ // Only accept processed transactions that are not double-spends,
241
+ // public functions emitting nullifiers would pass earlier check but fail here.
242
+ // Note that we're checking all nullifiers generated in the private execution twice,
243
+ // we could store the ones already checked and skip them here as an optimization.
244
+ // TODO(palla/txs): Can we get into this case? AVM validates this. We should be able to remove it.
245
+ const result = await postprocessValidator.validateTx(processedTx);
246
+ if (result.result !== 'valid') {
247
+ const reason = result.reason.join(', ');
248
+ this.log.error(`Rejecting tx ${processedTx.hash} after processing: ${reason}.`);
249
+ failed.push({ tx, error: new Error(`Tx failed post-process validation: ${reason}`) });
250
+ continue;
251
+ } else {
252
+ this.log.trace(`Tx ${tx.getTxHash().toString()} is valid post processing.`);
253
+ }
254
+ }
255
+
256
+ // Otherwise, commit tx state for the next tx to be processed
257
+ await this.commitTxState(processedTx);
258
+ nullifierCache?.addNullifiers(processedTx.txEffect.nullifiers.map(n => n.toBuffer()));
159
259
  result.push(processedTx);
160
260
  returns = returns.concat(returnValues);
161
- totalGas = totalGas.add(processedTx.gasUsed.publicGas);
261
+
262
+ totalPublicGas = totalPublicGas.add(processedTx.gasUsed.publicGas);
263
+ totalBlockGas = totalBlockGas.add(processedTx.gasUsed.totalGas);
264
+ totalSizeInBytes += txSize;
162
265
  } catch (err: any) {
163
266
  if (err?.name === 'PublicProcessorTimeoutError') {
164
267
  this.log.warn(`Stopping tx processing due to timeout.`);
@@ -173,18 +276,22 @@ export class PublicProcessor implements Traceable {
173
276
  }
174
277
 
175
278
  const duration = timer.s();
176
- const rate = duration > 0 ? totalGas.l2Gas / duration : 0;
177
- this.metrics.recordAllTxs(totalGas, rate);
279
+ const rate = duration > 0 ? totalPublicGas.l2Gas / duration : 0;
280
+ this.metrics.recordAllTxs(totalPublicGas, rate);
281
+
282
+ this.log.info(`Processed ${result.length} succesful txs and ${failed.length} txs in ${duration}ms`, {
283
+ duration,
284
+ rate,
285
+ totalPublicGas,
286
+ totalBlockGas,
287
+ totalSizeInBytes,
288
+ });
178
289
 
179
290
  return [result, failed, returns];
180
291
  }
181
292
 
182
293
  @trackSpan('PublicProcessor.processTx', tx => ({ [Attributes.TX_HASH]: tx.tryGetTxHash()?.toString() }))
183
- private async processTx(
184
- tx: Tx,
185
- txValidator?: TxValidator<ProcessedTx>,
186
- deadline?: Date,
187
- ): Promise<[ProcessedTx, NestedProcessReturnValues[]]> {
294
+ private async processTx(tx: Tx, deadline?: Date): Promise<[ProcessedTx, NestedProcessReturnValues[]]> {
188
295
  const [time, [processedTx, returnValues]] = await elapsed(() => this.processTxWithinDeadline(tx, deadline));
189
296
 
190
297
  this.log.verbose(
@@ -208,20 +315,14 @@ export class PublicProcessor implements Traceable {
208
315
  },
209
316
  );
210
317
 
318
+ return [processedTx, returnValues ?? []];
319
+ }
320
+
321
+ private async commitTxState(processedTx: ProcessedTx, txValidator?: TxValidator<ProcessedTx>): Promise<void> {
211
322
  // Commit the state updates from this transaction
323
+ // TODO(palla/txs): It seems like this doesn't do anything...?
212
324
  await this.worldStateDB.commit();
213
325
 
214
- // Re-validate the transaction
215
- if (txValidator) {
216
- // Only accept processed transactions that are not double-spends,
217
- // public functions emitting nullifiers would pass earlier check but fail here.
218
- // Note that we're checking all nullifiers generated in the private execution twice,
219
- // we could store the ones already checked and skip them here as an optimization.
220
- const [_, invalid] = await txValidator.validateTxs([processedTx]);
221
- if (invalid.length) {
222
- throw new Error(`Transaction ${invalid[0].hash} invalid after processing public functions`);
223
- }
224
- }
225
326
  // Update the state so that the next tx in the loop has the correct .startState
226
327
  // NB: before this change, all .startStates were actually incorrect, but the issue was never caught because we either:
227
328
  // a) had only 1 tx with public calls per block, so this loop had len 1
@@ -255,8 +356,6 @@ export class PublicProcessor implements Traceable {
255
356
  );
256
357
  const treeInsertionEnd = process.hrtime.bigint();
257
358
  this.metrics.recordTreeInsertions(Number(treeInsertionEnd - treeInsertionStart) / 1_000);
258
-
259
- return [processedTx, returnValues ?? []];
260
359
  }
261
360
 
262
361
  /** Processes the given tx within deadline. Returns timeout if deadline is hit. */
@@ -459,5 +459,5 @@ function fetchTxHash(nonRevertibleAccumulatedData: PrivateToPublicAccumulatedDat
459
459
  if (!firstNullifier || firstNullifier.isZero()) {
460
460
  throw new Error(`Cannot get tx hash since first nullifier is missing`);
461
461
  }
462
- return new TxHash(firstNullifier.toBuffer());
462
+ return new TxHash(firstNullifier);
463
463
  }