@aztec/simulator 0.0.1-commit.96dac018d → 0.0.1-commit.993d240
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.
- package/dest/private/acvm_wasm.d.ts +1 -1
- package/dest/private/acvm_wasm.d.ts.map +1 -1
- package/dest/private/acvm_wasm.js +3 -1
- package/dest/private/circuit_recording/circuit_recorder.js +2 -2
- package/dest/public/avm/avm_simulator.js +1 -1
- package/dest/public/avm/calldata.d.ts +1 -1
- package/dest/public/avm/calldata.d.ts.map +1 -1
- package/dest/public/avm/calldata.js +3 -2
- package/dest/public/avm/fixtures/base_avm_simulation_tester.d.ts +2 -1
- package/dest/public/avm/fixtures/base_avm_simulation_tester.d.ts.map +1 -1
- package/dest/public/avm/fixtures/base_avm_simulation_tester.js +17 -2
- package/dest/public/avm/fixtures/utils.d.ts +1 -1
- package/dest/public/avm/fixtures/utils.d.ts.map +1 -1
- package/dest/public/avm/fixtures/utils.js +3 -0
- package/dest/public/avm/opcodes/contract.d.ts +3 -2
- package/dest/public/avm/opcodes/contract.d.ts.map +1 -1
- package/dest/public/avm/opcodes/contract.js +5 -1
- package/dest/public/avm/opcodes/ec_add.d.ts +2 -4
- package/dest/public/avm/opcodes/ec_add.d.ts.map +1 -1
- package/dest/public/avm/opcodes/ec_add.js +13 -27
- package/dest/public/contracts_db_checkpoint.d.ts +2 -2
- package/dest/public/contracts_db_checkpoint.d.ts.map +1 -1
- package/dest/public/contracts_db_checkpoint.js +1 -1
- package/dest/public/fixtures/bulk_test.d.ts +1 -1
- package/dest/public/fixtures/bulk_test.d.ts.map +1 -1
- package/dest/public/fixtures/bulk_test.js +31 -4
- package/dest/public/fixtures/custom_bytecode_tests.d.ts +3 -1
- package/dest/public/fixtures/custom_bytecode_tests.d.ts.map +1 -1
- package/dest/public/fixtures/custom_bytecode_tests.js +61 -1
- package/dest/public/fixtures/opcode_spammer.d.ts +1 -1
- package/dest/public/fixtures/opcode_spammer.d.ts.map +1 -1
- package/dest/public/fixtures/opcode_spammer.js +2 -10
- package/dest/public/fixtures/public_tx_simulation_tester.d.ts +6 -5
- package/dest/public/fixtures/public_tx_simulation_tester.d.ts.map +1 -1
- package/dest/public/fixtures/public_tx_simulation_tester.js +10 -10
- package/dest/public/fixtures/utils.d.ts +2 -2
- package/dest/public/fixtures/utils.d.ts.map +1 -1
- package/dest/public/fixtures/utils.js +15 -16
- package/dest/public/hinting_db_sources.d.ts +4 -4
- package/dest/public/hinting_db_sources.d.ts.map +1 -1
- package/dest/public/hinting_db_sources.js +7 -6
- package/dest/public/public_db_sources.d.ts +6 -3
- package/dest/public/public_db_sources.d.ts.map +1 -1
- package/dest/public/public_db_sources.js +16 -10
- package/dest/public/public_processor/guarded_merkle_tree.d.ts +4 -4
- package/dest/public/public_processor/guarded_merkle_tree.d.ts.map +1 -1
- package/dest/public/public_processor/guarded_merkle_tree.js +4 -4
- package/dest/public/public_processor/public_processor.d.ts +5 -4
- package/dest/public/public_processor/public_processor.d.ts.map +1 -1
- package/dest/public/public_processor/public_processor.js +93 -68
- package/dest/public/public_processor/public_processor_metrics.d.ts +4 -1
- package/dest/public/public_processor/public_processor_metrics.d.ts.map +1 -1
- package/dest/public/public_processor/public_processor_metrics.js +8 -0
- package/dest/public/public_tx_simulator/contract_provider_for_cpp.d.ts +1 -1
- package/dest/public/public_tx_simulator/contract_provider_for_cpp.d.ts.map +1 -1
- package/dest/public/public_tx_simulator/contract_provider_for_cpp.js +3 -2
- package/dest/public/public_tx_simulator/public_tx_context.d.ts +7 -3
- package/dest/public/public_tx_simulator/public_tx_context.d.ts.map +1 -1
- package/dest/public/public_tx_simulator/public_tx_context.js +8 -10
- package/dest/public/public_tx_simulator/public_tx_simulator.d.ts +6 -2
- package/dest/public/public_tx_simulator/public_tx_simulator.d.ts.map +1 -1
- package/dest/public/public_tx_simulator/public_tx_simulator.js +12 -7
- package/package.json +16 -16
- package/src/private/acvm_wasm.ts +4 -1
- package/src/private/circuit_recording/circuit_recorder.ts +2 -2
- package/src/public/avm/avm_simulator.ts +1 -1
- package/src/public/avm/calldata.ts +3 -2
- package/src/public/avm/fixtures/base_avm_simulation_tester.ts +22 -2
- package/src/public/avm/fixtures/utils.ts +3 -0
- package/src/public/avm/opcodes/contract.ts +5 -1
- package/src/public/avm/opcodes/ec_add.ts +12 -31
- package/src/public/avm/opcodes/external_calls.ts +1 -1
- package/src/public/contracts_db_checkpoint.ts +1 -1
- package/src/public/fixtures/bulk_test.ts +29 -4
- package/src/public/fixtures/custom_bytecode_tests.ts +139 -1
- package/src/public/fixtures/opcode_spammer.ts +4 -8
- package/src/public/fixtures/public_tx_simulation_tester.ts +21 -11
- package/src/public/fixtures/utils.ts +22 -18
- package/src/public/hinting_db_sources.ts +9 -6
- package/src/public/public_db_sources.ts +21 -14
- package/src/public/public_processor/guarded_merkle_tree.ts +5 -5
- package/src/public/public_processor/public_processor.ts +120 -80
- package/src/public/public_processor/public_processor_metrics.ts +12 -0
- package/src/public/public_tx_simulator/contract_provider_for_cpp.ts +3 -2
- package/src/public/public_tx_simulator/public_tx_context.ts +8 -10
- package/src/public/public_tx_simulator/public_tx_simulator.ts +11 -7
|
@@ -3,7 +3,7 @@ import { padArrayEnd } from '@aztec/foundation/collection';
|
|
|
3
3
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
4
|
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
5
5
|
import { sleep } from '@aztec/foundation/sleep';
|
|
6
|
-
import { DateProvider, Timer, elapsed,
|
|
6
|
+
import { DateProvider, Timer, elapsed, execWithSignal } from '@aztec/foundation/timer';
|
|
7
7
|
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
8
8
|
import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-registry';
|
|
9
9
|
import { computeFeePayerBalanceLeafSlot, computeFeePayerBalanceStorageSlot } from '@aztec/protocol-contracts/fee-juice';
|
|
@@ -76,17 +76,15 @@ export class PublicProcessorFactory {
|
|
|
76
76
|
/**
|
|
77
77
|
* Creates a new instance of a PublicProcessor.
|
|
78
78
|
* @param globalVariables - The global variables for the block being processed.
|
|
79
|
-
* @param
|
|
79
|
+
* @param contractsDB - Optional pre-populated contracts DB; a fresh one is constructed if omitted.
|
|
80
80
|
* @returns A new instance of a PublicProcessor.
|
|
81
81
|
*/
|
|
82
82
|
public create(
|
|
83
83
|
merkleTree: MerkleTreeWriteOperations,
|
|
84
84
|
globalVariables: GlobalVariables,
|
|
85
85
|
config: PublicSimulatorConfig,
|
|
86
|
+
contractsDB: PublicContractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings()),
|
|
86
87
|
): PublicProcessor {
|
|
87
|
-
const bindings = this.log.getBindings();
|
|
88
|
-
const contractsDB = new PublicContractsDB(this.contractDataSource, bindings);
|
|
89
|
-
|
|
90
88
|
const guardedFork = new GuardedMerkleTreeOperations(merkleTree);
|
|
91
89
|
const publicTxSimulator = this.createPublicTxSimulator(guardedFork, contractsDB, globalVariables, config);
|
|
92
90
|
|
|
@@ -97,7 +95,7 @@ export class PublicProcessorFactory {
|
|
|
97
95
|
publicTxSimulator,
|
|
98
96
|
this.dateProvider,
|
|
99
97
|
this.telemetryClient,
|
|
100
|
-
createLogger('simulator:public-processor',
|
|
98
|
+
createLogger('simulator:public-processor', this.log.getBindings()),
|
|
101
99
|
);
|
|
102
100
|
}
|
|
103
101
|
|
|
@@ -125,6 +123,17 @@ class PublicProcessorTimeoutError extends Error {
|
|
|
125
123
|
}
|
|
126
124
|
}
|
|
127
125
|
|
|
126
|
+
class PublicProcessorAbortError extends Error {
|
|
127
|
+
constructor(message: string = 'Aborted while processing tx') {
|
|
128
|
+
super(message);
|
|
129
|
+
this.name = 'PublicProcessorAbortError';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isPublicProcessorInterruptError(err: any) {
|
|
134
|
+
return err?.name === 'PublicProcessorTimeoutError' || err?.name === 'PublicProcessorAbortError';
|
|
135
|
+
}
|
|
136
|
+
|
|
128
137
|
/**
|
|
129
138
|
* Converts Txs lifted from the P2P module into ProcessedTx objects by executing
|
|
130
139
|
* any public function calls in them. Txs with private calls only are unaffected.
|
|
@@ -160,8 +169,8 @@ export class PublicProcessor implements Traceable {
|
|
|
160
169
|
txs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
161
170
|
limits: PublicProcessorLimits = {},
|
|
162
171
|
validator: PublicProcessorValidator = {},
|
|
163
|
-
): Promise<[ProcessedTx[], FailedTx[], Tx[], NestedProcessReturnValues[],
|
|
164
|
-
const { maxTransactions,
|
|
172
|
+
): Promise<[ProcessedTx[], FailedTx[], Tx[], NestedProcessReturnValues[], DebugLog[]]> {
|
|
173
|
+
const { maxTransactions, deadline, maxBlockGas, maxBlobFields, isBuildingProposal, signal } = limits;
|
|
165
174
|
const { preprocessValidator, nullifierCache } = validator;
|
|
166
175
|
const result: ProcessedTx[] = [];
|
|
167
176
|
const usedTxs: Tx[] = [];
|
|
@@ -174,6 +183,8 @@ export class PublicProcessor implements Traceable {
|
|
|
174
183
|
let totalPublicGas = new Gas(0, 0);
|
|
175
184
|
let totalBlockGas = new Gas(0, 0);
|
|
176
185
|
let totalBlobFields = 0;
|
|
186
|
+
let silentlySkippedCount = 0;
|
|
187
|
+
let totalSilentlySkippedDurationMs = 0;
|
|
177
188
|
|
|
178
189
|
for await (const tx of txs) {
|
|
179
190
|
// Only process up to the max tx limit
|
|
@@ -182,28 +193,33 @@ export class PublicProcessor implements Traceable {
|
|
|
182
193
|
break;
|
|
183
194
|
}
|
|
184
195
|
|
|
185
|
-
// Bail if we've hit the deadline
|
|
196
|
+
// Bail if we've hit the deadline or have been interrupted.
|
|
186
197
|
if (deadline && this.dateProvider.now() > +deadline) {
|
|
187
198
|
this.log.warn(`Stopping tx processing due to timeout.`);
|
|
188
199
|
break;
|
|
189
200
|
}
|
|
201
|
+
if (signal?.aborted) {
|
|
202
|
+
this.log.warn(`Stopping tx processing due to abort signal.`);
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
190
205
|
|
|
191
|
-
// Skip this tx if it'd exceed max block size
|
|
192
206
|
const txHash = tx.getTxHash().toString();
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
207
|
+
|
|
208
|
+
// Skip this tx if its estimated blob fields would exceed the limit.
|
|
209
|
+
// Only done during proposal building: during re-execution we must process the exact txs from the proposal.
|
|
210
|
+
const txBlobFields = tx.getPrivateTxEffectsSizeInFields();
|
|
211
|
+
if (isBuildingProposal && maxBlobFields !== undefined && totalBlobFields + txBlobFields > maxBlobFields) {
|
|
212
|
+
this.log.warn(
|
|
213
|
+
`Skipping tx ${txHash} with ${txBlobFields} fields from private side effects due to blob fields limit`,
|
|
214
|
+
{ txHash, txBlobFields, totalBlobFields, maxBlobFields },
|
|
215
|
+
);
|
|
201
216
|
continue;
|
|
202
217
|
}
|
|
203
218
|
|
|
204
|
-
// Skip this tx if its gas limit would exceed the block gas limit
|
|
219
|
+
// Skip this tx if its gas limit would exceed the block gas limit (either da or l2).
|
|
220
|
+
// Only done during proposal building: during re-execution we must process the exact txs from the proposal.
|
|
205
221
|
const txGasLimit = tx.data.constants.txContext.gasSettings.gasLimits;
|
|
206
|
-
if (maxBlockGas !== undefined && totalBlockGas.add(txGasLimit).gtAny(maxBlockGas)) {
|
|
222
|
+
if (isBuildingProposal && maxBlockGas !== undefined && totalBlockGas.add(txGasLimit).gtAny(maxBlockGas)) {
|
|
207
223
|
this.log.warn(`Skipping processing of tx ${txHash} due to block gas limit`, {
|
|
208
224
|
txHash,
|
|
209
225
|
txGasLimit,
|
|
@@ -223,11 +239,6 @@ export class PublicProcessor implements Traceable {
|
|
|
223
239
|
failed.push({ tx, error: new Error(`Tx failed preprocess validation: ${reason}`) });
|
|
224
240
|
returns.push(new NestedProcessReturnValues([]));
|
|
225
241
|
continue;
|
|
226
|
-
} else if (result.result === 'skipped') {
|
|
227
|
-
const reason = result.reason.join(', ');
|
|
228
|
-
this.log.debug(`Skipping tx ${txHash.toString()} due to pre-process validation: ${reason}`);
|
|
229
|
-
returns.push(new NestedProcessReturnValues([]));
|
|
230
|
-
continue;
|
|
231
242
|
} else {
|
|
232
243
|
this.log.trace(`Tx ${txHash.toString()} is valid before processing.`);
|
|
233
244
|
}
|
|
@@ -243,7 +254,9 @@ export class PublicProcessor implements Traceable {
|
|
|
243
254
|
this.contractsDB.createCheckpoint();
|
|
244
255
|
|
|
245
256
|
try {
|
|
246
|
-
const [processedTx, returnValues, txDebugLogs] = await
|
|
257
|
+
const [txProcessingTimeMs, [processedTx, returnValues, txDebugLogs]] = await elapsed(() =>
|
|
258
|
+
this.processTx(tx, deadline, signal),
|
|
259
|
+
);
|
|
247
260
|
|
|
248
261
|
// Inject a fake processing failure after N txs if requested
|
|
249
262
|
const fakeThrowAfter = this.opts.fakeThrowAfterProcessingTxCount;
|
|
@@ -252,23 +265,9 @@ export class PublicProcessor implements Traceable {
|
|
|
252
265
|
}
|
|
253
266
|
|
|
254
267
|
const txBlobFields = processedTx.txEffect.getNumBlobFields();
|
|
255
|
-
|
|
256
|
-
// If the actual size of this tx would exceed block size, skip it
|
|
257
268
|
const txSize = txBlobFields * Fr.SIZE_IN_BYTES;
|
|
258
|
-
if (maxBlockSize !== undefined && totalSizeInBytes + txSize > maxBlockSize) {
|
|
259
|
-
this.log.debug(`Skipping processed tx ${txHash} sized ${txSize} due to max block size.`, {
|
|
260
|
-
txHash,
|
|
261
|
-
sizeInBytes: txSize,
|
|
262
|
-
totalSizeInBytes,
|
|
263
|
-
maxBlockSize,
|
|
264
|
-
});
|
|
265
|
-
// Need to revert the checkpoint here and don't go any further
|
|
266
|
-
await checkpoint.revert();
|
|
267
|
-
this.contractsDB.revertCheckpoint();
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
269
|
|
|
271
|
-
// If the actual blob fields of this tx would exceed the limit, skip it
|
|
270
|
+
// If the actual blob fields of this tx would exceed the limit, skip it.
|
|
272
271
|
// Note: maxBlobFields already accounts for block end blob fields and previous blocks in checkpoint.
|
|
273
272
|
if (maxBlobFields !== undefined && totalBlobFields + txBlobFields > maxBlobFields) {
|
|
274
273
|
this.log.debug(
|
|
@@ -278,14 +277,37 @@ export class PublicProcessor implements Traceable {
|
|
|
278
277
|
txBlobFields,
|
|
279
278
|
totalBlobFields,
|
|
280
279
|
maxBlobFields,
|
|
280
|
+
txProcessingTimeMs,
|
|
281
281
|
},
|
|
282
282
|
);
|
|
283
|
+
silentlySkippedCount += 1;
|
|
284
|
+
totalSilentlySkippedDurationMs += txProcessingTimeMs;
|
|
285
|
+
this.metrics.recordSilentlySkipped(txProcessingTimeMs);
|
|
283
286
|
// Need to revert the checkpoint here and don't go any further
|
|
284
287
|
await checkpoint.revert();
|
|
285
288
|
this.contractsDB.revertCheckpoint();
|
|
286
289
|
continue;
|
|
287
290
|
}
|
|
288
291
|
|
|
292
|
+
// During re-execution, check if the actual gas used by this tx would push the block over the gas limit.
|
|
293
|
+
// Unlike the proposal-building check (which uses declared gas limits pessimistically before processing),
|
|
294
|
+
// this uses actual gas and stops processing when the limit is exceeded.
|
|
295
|
+
if (
|
|
296
|
+
!isBuildingProposal &&
|
|
297
|
+
maxBlockGas !== undefined &&
|
|
298
|
+
totalBlockGas.add(processedTx.gasUsed.totalGas).gtAny(maxBlockGas)
|
|
299
|
+
) {
|
|
300
|
+
this.log.warn(`Stopping re-execution since tx ${txHash} would push block gas over limit`, {
|
|
301
|
+
txHash,
|
|
302
|
+
txGas: processedTx.gasUsed.totalGas,
|
|
303
|
+
totalBlockGas,
|
|
304
|
+
maxBlockGas,
|
|
305
|
+
});
|
|
306
|
+
await checkpoint.revert();
|
|
307
|
+
this.contractsDB.revertCheckpoint();
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
|
|
289
311
|
// FIXME(fcarreiro): it's ugly to have to notify the validator of nullifiers.
|
|
290
312
|
// I'd rather pass the validators the processedTx as well and let them deal with it.
|
|
291
313
|
nullifierCache?.addNullifiers(processedTx.txEffect.nullifiers.map(n => n.toBuffer()));
|
|
@@ -300,16 +322,15 @@ export class PublicProcessor implements Traceable {
|
|
|
300
322
|
totalBlockGas = totalBlockGas.add(processedTx.gasUsed.totalGas);
|
|
301
323
|
totalSizeInBytes += txSize;
|
|
302
324
|
totalBlobFields += txBlobFields;
|
|
325
|
+
|
|
326
|
+
// Commit the tx-level contracts checkpoint on success
|
|
327
|
+
this.contractsDB.commitCheckpoint();
|
|
303
328
|
} catch (err: any) {
|
|
304
|
-
if (err
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
//
|
|
308
|
-
// Signal cancellation AND WAIT for the simulation to actually stop.
|
|
309
|
-
// This is critical because C++ might be in the middle of a slow operation (e.g., pad_trees)
|
|
310
|
-
// and won't check the cancellation flag until that operation completes.
|
|
311
|
-
// Without waiting, we'd proceed to revert checkpoints while C++ is still writing to state.
|
|
312
|
-
// Wait for C++ to stop gracefully.
|
|
329
|
+
if (isPublicProcessorInterruptError(err)) {
|
|
330
|
+
const interruptReason = err.name === 'PublicProcessorTimeoutError' ? 'timeout' : 'abort signal';
|
|
331
|
+
this.log.warn(`Stopping tx processing due to ${interruptReason}.`);
|
|
332
|
+
// The tx may still be executing on a worker thread (C++ via NAPI).
|
|
333
|
+
// Signal cancellation AND WAIT for the simulation to actually stop before touching fork checkpoints.
|
|
313
334
|
await this.publicTxSimulator.cancel?.();
|
|
314
335
|
|
|
315
336
|
// Now stop the guarded fork to prevent any further TS-side access to the world state.
|
|
@@ -319,14 +340,10 @@ export class PublicProcessor implements Traceable {
|
|
|
319
340
|
// 1. At least one outstanding checkpoint that has not been committed (the one created before we processed the tx).
|
|
320
341
|
// 2. Possible state updates on that checkpoint or any others created during execution.
|
|
321
342
|
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
await checkpoint.
|
|
326
|
-
|
|
327
|
-
// Now we want to revert any/all remaining checkpoints, destroying any outstanding state updates.
|
|
328
|
-
// This needs to be done directly on the underlying fork as the guarded fork has been stopped.
|
|
329
|
-
await this.guardedMerkleTree.getUnderlyingFork().revertAllCheckpoints();
|
|
343
|
+
// Revert all checkpoints at or above this checkpoint's depth (inclusive), destroying any outstanding state
|
|
344
|
+
// updates from this tx and any nested checkpoints created during execution. This preserves any checkpoints
|
|
345
|
+
// created by callers below our depth.
|
|
346
|
+
await checkpoint.revertToCheckpoint();
|
|
330
347
|
|
|
331
348
|
// Revert any contracts added to the DB for the tx.
|
|
332
349
|
this.contractsDB.revertCheckpoint();
|
|
@@ -338,9 +355,9 @@ export class PublicProcessor implements Traceable {
|
|
|
338
355
|
break;
|
|
339
356
|
}
|
|
340
357
|
|
|
341
|
-
// Roll back state to start of TX before proceeding to next TX
|
|
342
|
-
|
|
343
|
-
await
|
|
358
|
+
// Roll back state to start of TX before proceeding to next TX.
|
|
359
|
+
// Reverts all checkpoints at or above this checkpoint's depth, preserving any caller checkpoints below.
|
|
360
|
+
await checkpoint.revertToCheckpoint();
|
|
344
361
|
this.contractsDB.revertCheckpoint();
|
|
345
362
|
const errorMessage = err instanceof Error || err instanceof AssertionError ? err.message : 'Unknown error';
|
|
346
363
|
this.log.warn(`Failed to process tx ${txHash.toString()}: ${errorMessage} ${err?.stack}`);
|
|
@@ -352,7 +369,6 @@ export class PublicProcessor implements Traceable {
|
|
|
352
369
|
} finally {
|
|
353
370
|
// Base case is we always commit the checkpoint. Using the ForkCheckpoint means this has no effect if the tx was previously reverted
|
|
354
371
|
await checkpoint.commit();
|
|
355
|
-
this.contractsDB.commitCheckpointOkIfNone();
|
|
356
372
|
}
|
|
357
373
|
}
|
|
358
374
|
|
|
@@ -360,15 +376,26 @@ export class PublicProcessor implements Traceable {
|
|
|
360
376
|
const rate = duration > 0 ? totalPublicGas.l2Gas / duration : 0;
|
|
361
377
|
this.metrics.recordAllTxs(totalPublicGas, rate);
|
|
362
378
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
379
|
+
const silentlySkippedDurationMs = Math.round(totalSilentlySkippedDurationMs);
|
|
380
|
+
this.log.info(
|
|
381
|
+
`Processed ${result.length} successful txs and ${failed.length} failed txs ` +
|
|
382
|
+
`(${silentlySkippedCount} silently skipped, ${silentlySkippedDurationMs}ms wasted) ` +
|
|
383
|
+
`in ${duration}s`,
|
|
384
|
+
{
|
|
385
|
+
blockNumber: this.globalVariables.blockNumber,
|
|
386
|
+
successfulCount: result.length,
|
|
387
|
+
failedCount: failed.length,
|
|
388
|
+
duration,
|
|
389
|
+
rate,
|
|
390
|
+
totalPublicGas,
|
|
391
|
+
totalBlockGas,
|
|
392
|
+
totalSizeInBytes,
|
|
393
|
+
silentlySkippedCount,
|
|
394
|
+
silentlySkippedDurationMs,
|
|
395
|
+
},
|
|
396
|
+
);
|
|
370
397
|
|
|
371
|
-
return [result, failed, usedTxs, returns,
|
|
398
|
+
return [result, failed, usedTxs, returns, debugLogs];
|
|
372
399
|
}
|
|
373
400
|
|
|
374
401
|
private async checkWorldStateUnchanged(
|
|
@@ -391,9 +418,10 @@ export class PublicProcessor implements Traceable {
|
|
|
391
418
|
private async processTx(
|
|
392
419
|
tx: Tx,
|
|
393
420
|
deadline: Date | undefined,
|
|
421
|
+
signal: AbortSignal | undefined,
|
|
394
422
|
): Promise<[ProcessedTx, NestedProcessReturnValues[], DebugLog[]]> {
|
|
395
423
|
const [time, [processedTx, returnValues, debugLogs]] = await elapsed(() =>
|
|
396
|
-
this.processTxWithinDeadline(tx, deadline),
|
|
424
|
+
this.processTxWithinDeadline(tx, deadline, signal),
|
|
397
425
|
);
|
|
398
426
|
|
|
399
427
|
this.log.verbose(
|
|
@@ -447,10 +475,11 @@ export class PublicProcessor implements Traceable {
|
|
|
447
475
|
this.metrics.recordTreeInsertions(Number(treeInsertionEnd - treeInsertionStart) / 1_000);
|
|
448
476
|
}
|
|
449
477
|
|
|
450
|
-
/** Processes the given tx within deadline
|
|
478
|
+
/** Processes the given tx within deadline or until the signal is aborted. */
|
|
451
479
|
private async processTxWithinDeadline(
|
|
452
480
|
tx: Tx,
|
|
453
481
|
deadline: Date | undefined,
|
|
482
|
+
signal: AbortSignal | undefined,
|
|
454
483
|
): Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined, DebugLog[]]> {
|
|
455
484
|
const innerProcessFn: () => Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined, DebugLog[]]> =
|
|
456
485
|
tx.hasPublicCalls() ? () => this.processTxWithPublicCalls(tx) : () => this.processPrivateOnlyTx(tx);
|
|
@@ -467,27 +496,38 @@ export class PublicProcessor implements Traceable {
|
|
|
467
496
|
}
|
|
468
497
|
: innerProcessFn;
|
|
469
498
|
|
|
470
|
-
|
|
499
|
+
const processingSignal = this.getProcessingSignal(tx, deadline, signal);
|
|
500
|
+
if (!processingSignal) {
|
|
471
501
|
return await processFn();
|
|
472
502
|
}
|
|
473
503
|
|
|
474
|
-
|
|
504
|
+
return await execWithSignal(
|
|
505
|
+
() => processFn(),
|
|
506
|
+
processingSignal,
|
|
507
|
+
signal =>
|
|
508
|
+
signal.reason?.name === 'TimeoutError' ? new PublicProcessorTimeoutError() : new PublicProcessorAbortError(),
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private getProcessingSignal(tx: Tx, deadline: Date | undefined, signal: AbortSignal | undefined) {
|
|
513
|
+
if (!deadline) {
|
|
514
|
+
return signal;
|
|
515
|
+
}
|
|
516
|
+
|
|
475
517
|
const timeout = +deadline - this.dateProvider.now();
|
|
476
518
|
if (timeout <= 0) {
|
|
477
519
|
throw new PublicProcessorTimeoutError();
|
|
478
520
|
}
|
|
479
521
|
|
|
522
|
+
const txHash = tx.getTxHash();
|
|
480
523
|
this.log.debug(`Processing tx ${txHash.toString()} within ${timeout}ms`, {
|
|
481
524
|
deadline: deadline.toISOString(),
|
|
482
525
|
now: new Date(this.dateProvider.now()).toISOString(),
|
|
483
526
|
txHash,
|
|
484
527
|
});
|
|
485
528
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
timeout,
|
|
489
|
-
() => new PublicProcessorTimeoutError(),
|
|
490
|
-
);
|
|
529
|
+
const timeoutSignal = AbortSignal.timeout(timeout);
|
|
530
|
+
return signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
491
531
|
}
|
|
492
532
|
|
|
493
533
|
/**
|
|
@@ -544,7 +584,7 @@ export class PublicProcessor implements Traceable {
|
|
|
544
584
|
// Fee payment insertion has already been done. Do the rest.
|
|
545
585
|
await this.doTreeInsertionsForPrivateOnlyTx(processedTx);
|
|
546
586
|
|
|
547
|
-
|
|
587
|
+
this.contractsDB.addNewContracts(tx);
|
|
548
588
|
|
|
549
589
|
return [processedTx, undefined, []];
|
|
550
590
|
}
|
|
@@ -30,6 +30,9 @@ export class PublicProcessorMetrics {
|
|
|
30
30
|
|
|
31
31
|
private treeInsertionDuration: Histogram;
|
|
32
32
|
|
|
33
|
+
private silentlySkippedCount: UpDownCounter;
|
|
34
|
+
private silentlySkippedDuration: Histogram;
|
|
35
|
+
|
|
33
36
|
constructor(client: TelemetryClient, name = 'PublicProcessor') {
|
|
34
37
|
this.tracer = client.getTracer(name);
|
|
35
38
|
const meter = client.getMeter(name);
|
|
@@ -60,6 +63,10 @@ export class PublicProcessorMetrics {
|
|
|
60
63
|
this.gasRate = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_GAS_RATE);
|
|
61
64
|
|
|
62
65
|
this.treeInsertionDuration = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_TREE_INSERTION);
|
|
66
|
+
|
|
67
|
+
this.silentlySkippedCount = createUpDownCounterWithDefault(meter, Metrics.PUBLIC_PROCESSOR_SILENTLY_SKIPPED_COUNT);
|
|
68
|
+
|
|
69
|
+
this.silentlySkippedDuration = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_SILENTLY_SKIPPED_DURATION);
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
recordPhaseDuration(phaseName: TxExecutionPhase, durationMs: number) {
|
|
@@ -123,4 +130,9 @@ export class PublicProcessorMetrics {
|
|
|
123
130
|
recordTreeInsertions(durationUs: number) {
|
|
124
131
|
this.treeInsertionDuration.record(Math.ceil(durationUs));
|
|
125
132
|
}
|
|
133
|
+
|
|
134
|
+
recordSilentlySkipped(durationMs: number) {
|
|
135
|
+
this.silentlySkippedCount.add(1);
|
|
136
|
+
this.silentlySkippedDuration.record(Math.ceil(durationMs));
|
|
137
|
+
}
|
|
126
138
|
}
|
|
@@ -52,6 +52,7 @@ export class ContractProviderForCpp implements ContractProvider {
|
|
|
52
52
|
return serializeWithMessagePack(contractClass);
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
+
// eslint-disable-next-line require-await
|
|
55
56
|
public addContracts = async (contractDeploymentDataBuffer: Buffer): Promise<void> => {
|
|
56
57
|
this.log.trace(`Contract provider callback: addContracts`);
|
|
57
58
|
|
|
@@ -61,8 +62,8 @@ export class ContractProviderForCpp implements ContractProvider {
|
|
|
61
62
|
const contractDeploymentData = ContractDeploymentData.fromPlainObject(rawData);
|
|
62
63
|
|
|
63
64
|
// Add contracts to the contracts DB
|
|
64
|
-
this.log.trace(`Calling contractsDB.
|
|
65
|
-
|
|
65
|
+
this.log.trace(`Calling contractsDB.addContractsFromLogs`);
|
|
66
|
+
this.contractsDB.addContractsFromLogs(contractDeploymentData);
|
|
66
67
|
};
|
|
67
68
|
|
|
68
69
|
public getBytecodeCommitment = async (classId: string): Promise<Buffer | undefined> => {
|
|
@@ -174,14 +174,8 @@ export class PublicTxContext {
|
|
|
174
174
|
}
|
|
175
175
|
if (phase === TxExecutionPhase.SETUP) {
|
|
176
176
|
this.log.warn(`Setup phase reverted! The transaction will be thrown out.`);
|
|
177
|
-
} else if (phase === TxExecutionPhase.APP_LOGIC) {
|
|
178
|
-
this.revertCode = RevertCode.
|
|
179
|
-
} else if (phase === TxExecutionPhase.TEARDOWN) {
|
|
180
|
-
if (this.revertCode.equals(RevertCode.APP_LOGIC_REVERTED)) {
|
|
181
|
-
this.revertCode = RevertCode.BOTH_REVERTED;
|
|
182
|
-
} else {
|
|
183
|
-
this.revertCode = RevertCode.TEARDOWN_REVERTED;
|
|
184
|
-
}
|
|
177
|
+
} else if (phase === TxExecutionPhase.APP_LOGIC || phase === TxExecutionPhase.TEARDOWN) {
|
|
178
|
+
this.revertCode = RevertCode.REVERTED;
|
|
185
179
|
}
|
|
186
180
|
}
|
|
187
181
|
|
|
@@ -247,8 +241,12 @@ export class PublicTxContext {
|
|
|
247
241
|
}
|
|
248
242
|
|
|
249
243
|
/**
|
|
250
|
-
* The gasUsed by public and private,
|
|
251
|
-
*
|
|
244
|
+
* The gasUsed by public and private, as if the entire teardown gas limit was consumed.
|
|
245
|
+
*
|
|
246
|
+
* This is intentional: teardown is used for gas accounting and refunds, so the transaction
|
|
247
|
+
* fee must be deterministic _before_ teardown executes. If fees depended on teardown's actual
|
|
248
|
+
* consumption there would be a circular dependency. Billing the full teardown gas limit
|
|
249
|
+
* (set by the user) makes the fee known in advance and available to the teardown function.
|
|
252
250
|
*/
|
|
253
251
|
getTotalGasUsed(): Gas {
|
|
254
252
|
return this.gasUsedByPrivate.add(this.gasUsedByPublic);
|
|
@@ -27,7 +27,6 @@ import { type PublicContractsDB, PublicTreesDB } from '../public_db_sources.js';
|
|
|
27
27
|
import {
|
|
28
28
|
L2ToL1MessageLimitReachedError,
|
|
29
29
|
NoteHashLimitReachedError,
|
|
30
|
-
NullifierCollisionError,
|
|
31
30
|
NullifierLimitReachedError,
|
|
32
31
|
} from '../side_effect_errors.js';
|
|
33
32
|
import type { PublicPersistableStateManager } from '../state_manager/state_manager.js';
|
|
@@ -147,7 +146,8 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface {
|
|
|
147
146
|
hintingContractsDB.createCheckpoint();
|
|
148
147
|
|
|
149
148
|
try {
|
|
150
|
-
// This
|
|
149
|
+
// This may throw: a side-effect limit error triggers a soft revert (caught below), while a
|
|
150
|
+
// nullifier collision is unrecoverable and propagates out of simulate() to throw out the tx.
|
|
151
151
|
await this.insertRevertiblesFromPrivate(context);
|
|
152
152
|
|
|
153
153
|
// Only proceed with app logic if there was no revert during revertible insertion.
|
|
@@ -401,7 +401,7 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface {
|
|
|
401
401
|
// However, things work as expected because later calls to getters on the hintingContractsDB
|
|
402
402
|
// will pick up the new contracts and will generate the necessary hints.
|
|
403
403
|
// So, a consumer of the hints will always see the new contracts.
|
|
404
|
-
|
|
404
|
+
this.contractsDB.addContractsFromLogs(context.nonRevertibleContractDeploymentData);
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
/**
|
|
@@ -409,9 +409,13 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface {
|
|
|
409
409
|
* Throws TxSimRevertibleInsertionsRevert if there is some checked error during revertible insertions.
|
|
410
410
|
* This function checks for the following errors:
|
|
411
411
|
* - NullifierLimitReachedError
|
|
412
|
-
* - NullifierCollisionError
|
|
413
412
|
* - NoteHashLimitReachedError
|
|
414
413
|
* - L2ToL1MessageLimitReachedError
|
|
414
|
+
*
|
|
415
|
+
* Note: NullifierCollisionError is intentionally NOT caught here. A nullifier collision
|
|
416
|
+
* during revertible insertions is unprovable (the nullifier originated from private, so
|
|
417
|
+
* a collision indicates the tx should never have been proposed). It propagates as-is to
|
|
418
|
+
* make the transaction unrecoverable, matching the AVM circuit behavior.
|
|
415
419
|
*/
|
|
416
420
|
protected async insertRevertiblesFromPrivate(context: PublicTxContext) {
|
|
417
421
|
const stateManager = context.state.getActiveStateManager();
|
|
@@ -421,7 +425,7 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface {
|
|
|
421
425
|
await stateManager.writeSiloedNullifier(siloedNullifier);
|
|
422
426
|
}
|
|
423
427
|
} catch (e: any) {
|
|
424
|
-
if (e instanceof NullifierLimitReachedError
|
|
428
|
+
if (e instanceof NullifierLimitReachedError) {
|
|
425
429
|
context.revert(
|
|
426
430
|
TxExecutionPhase.APP_LOGIC,
|
|
427
431
|
new SimulationError(
|
|
@@ -431,7 +435,7 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface {
|
|
|
431
435
|
);
|
|
432
436
|
throw new TxSimRevertibleInsertionsRevert();
|
|
433
437
|
} else {
|
|
434
|
-
// Unchecked/unknown error - re-throw as-is
|
|
438
|
+
// Unchecked/unknown error or NullifierCollisionError (unrecoverable) - re-throw as-is
|
|
435
439
|
throw e;
|
|
436
440
|
}
|
|
437
441
|
}
|
|
@@ -486,7 +490,7 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface {
|
|
|
486
490
|
// However, things work as expected because later calls to getters on the hintingContractsDB
|
|
487
491
|
// will pick up the new contracts and will generate the necessary hints.
|
|
488
492
|
// So, a consumer of the hints will always see the new contracts.
|
|
489
|
-
|
|
493
|
+
this.contractsDB.addContractsFromLogs(context.revertibleContractDeploymentData);
|
|
490
494
|
}
|
|
491
495
|
|
|
492
496
|
private async payFee(context: PublicTxContext) {
|