@aztec/simulator 5.0.0-private.20260319 → 5.0.0-rc.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.
- 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/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 +1 -1
- package/dest/public/fixtures/public_tx_simulation_tester.d.ts.map +1 -1
- package/dest/public/fixtures/public_tx_simulation_tester.js +4 -2
- package/dest/public/fixtures/utils.d.ts +1 -1
- package/dest/public/fixtures/utils.d.ts.map +1 -1
- package/dest/public/fixtures/utils.js +15 -14
- package/dest/public/hinting_db_sources.d.ts +1 -1
- package/dest/public/hinting_db_sources.d.ts.map +1 -1
- package/dest/public/hinting_db_sources.js +1 -1
- 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/public_processor.d.ts +4 -3
- package/dest/public/public_processor/public_processor.d.ts.map +1 -1
- package/dest/public/public_processor/public_processor.js +78 -34
- 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 -15
- package/src/private/acvm_wasm.ts +4 -1
- 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 +4 -10
- package/src/public/fixtures/utils.ts +17 -19
- package/src/public/hinting_db_sources.ts +1 -0
- package/src/public/public_db_sources.ts +21 -14
- package/src/public/public_processor/public_processor.ts +98 -41
- 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
|
@@ -55,10 +55,11 @@ export class PublicContractsDB implements PublicContractsDBInterface {
|
|
|
55
55
|
this.log = createLogger('simulator:contracts-data-source', bindings);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
/** Parses raw log data from the C++/NAPI bridge and inserts the resulting contracts into the current checkpoint. */
|
|
59
|
+
public addContractsFromLogs(contractDeploymentData: ContractDeploymentData): void {
|
|
59
60
|
const currentState = this.getCurrentState();
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
this.addContractClassesFromEvents(
|
|
62
63
|
ContractClassPublishedEvent.extractContractClassEvents(contractDeploymentData.getContractClassLogs()),
|
|
63
64
|
currentState,
|
|
64
65
|
);
|
|
@@ -69,10 +70,18 @@ export class PublicContractsDB implements PublicContractsDBInterface {
|
|
|
69
70
|
);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
public
|
|
73
|
+
public addNewContracts(tx: Tx): void {
|
|
73
74
|
const contractDeploymentData = AllContractDeploymentData.fromTx(tx);
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
this.addContractsFromLogs(contractDeploymentData.getNonRevertibleContractDeploymentData());
|
|
76
|
+
this.addContractsFromLogs(contractDeploymentData.getRevertibleContractDeploymentData());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Inserts typed contract instances directly into the current checkpoint. */
|
|
80
|
+
public addContracts(contractInstances?: ContractInstanceWithAddress[]): void {
|
|
81
|
+
const currentState = this.getCurrentState();
|
|
82
|
+
for (const instance of contractInstances ?? []) {
|
|
83
|
+
currentState.addInstance(instance.address, instance);
|
|
84
|
+
}
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
/**
|
|
@@ -81,7 +90,7 @@ export class PublicContractsDB implements PublicContractsDBInterface {
|
|
|
81
90
|
*/
|
|
82
91
|
public createCheckpoint(): void {
|
|
83
92
|
const currentState = this.getCurrentState();
|
|
84
|
-
const newState = currentState.
|
|
93
|
+
const newState = currentState.fork();
|
|
85
94
|
this.contractStateStack.push(newState);
|
|
86
95
|
}
|
|
87
96
|
|
|
@@ -174,17 +183,15 @@ export class PublicContractsDB implements PublicContractsDBInterface {
|
|
|
174
183
|
return await this.dataSource.getDebugFunctionName(address, selector);
|
|
175
184
|
}
|
|
176
185
|
|
|
177
|
-
private
|
|
186
|
+
private addContractClassesFromEvents(
|
|
178
187
|
contractClassEvents: ContractClassPublishedEvent[],
|
|
179
188
|
state: ContractsDbCheckpoint,
|
|
180
189
|
) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}),
|
|
187
|
-
);
|
|
190
|
+
for (const event of contractClassEvents) {
|
|
191
|
+
this.log.debug(`Adding class ${event.contractClassId.toString()} to contract state`);
|
|
192
|
+
const contractClass = event.toContractClassPublic();
|
|
193
|
+
state.addClass(event.contractClassId, contractClass);
|
|
194
|
+
}
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
private addContractInstancesFromEvents(
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
MAX_NOTE_HASHES_PER_TX,
|
|
3
|
+
MAX_NULLIFIERS_PER_TX,
|
|
4
|
+
MAX_TX_BLOB_DATA_SIZE_IN_FIELDS,
|
|
5
|
+
NULLIFIER_SUBTREE_HEIGHT,
|
|
6
|
+
} from '@aztec/constants';
|
|
2
7
|
import { padArrayEnd } from '@aztec/foundation/collection';
|
|
3
8
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
9
|
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
5
10
|
import { sleep } from '@aztec/foundation/sleep';
|
|
6
|
-
import { DateProvider, Timer, elapsed,
|
|
11
|
+
import { DateProvider, Timer, elapsed, execWithSignal } from '@aztec/foundation/timer';
|
|
7
12
|
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
8
13
|
import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-registry';
|
|
9
14
|
import { computeFeePayerBalanceLeafSlot, computeFeePayerBalanceStorageSlot } from '@aztec/protocol-contracts/fee-juice';
|
|
@@ -76,17 +81,15 @@ export class PublicProcessorFactory {
|
|
|
76
81
|
/**
|
|
77
82
|
* Creates a new instance of a PublicProcessor.
|
|
78
83
|
* @param globalVariables - The global variables for the block being processed.
|
|
79
|
-
* @param
|
|
84
|
+
* @param contractsDB - Optional pre-populated contracts DB; a fresh one is constructed if omitted.
|
|
80
85
|
* @returns A new instance of a PublicProcessor.
|
|
81
86
|
*/
|
|
82
87
|
public create(
|
|
83
88
|
merkleTree: MerkleTreeWriteOperations,
|
|
84
89
|
globalVariables: GlobalVariables,
|
|
85
90
|
config: PublicSimulatorConfig,
|
|
91
|
+
contractsDB: PublicContractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings()),
|
|
86
92
|
): PublicProcessor {
|
|
87
|
-
const bindings = this.log.getBindings();
|
|
88
|
-
const contractsDB = new PublicContractsDB(this.contractDataSource, bindings);
|
|
89
|
-
|
|
90
93
|
const guardedFork = new GuardedMerkleTreeOperations(merkleTree);
|
|
91
94
|
const publicTxSimulator = this.createPublicTxSimulator(guardedFork, contractsDB, globalVariables, config);
|
|
92
95
|
|
|
@@ -97,7 +100,7 @@ export class PublicProcessorFactory {
|
|
|
97
100
|
publicTxSimulator,
|
|
98
101
|
this.dateProvider,
|
|
99
102
|
this.telemetryClient,
|
|
100
|
-
createLogger('simulator:public-processor',
|
|
103
|
+
createLogger('simulator:public-processor', this.log.getBindings()),
|
|
101
104
|
);
|
|
102
105
|
}
|
|
103
106
|
|
|
@@ -125,6 +128,17 @@ class PublicProcessorTimeoutError extends Error {
|
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
130
|
|
|
131
|
+
class PublicProcessorAbortError extends Error {
|
|
132
|
+
constructor(message: string = 'Aborted while processing tx') {
|
|
133
|
+
super(message);
|
|
134
|
+
this.name = 'PublicProcessorAbortError';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isPublicProcessorInterruptError(err: any) {
|
|
139
|
+
return err?.name === 'PublicProcessorTimeoutError' || err?.name === 'PublicProcessorAbortError';
|
|
140
|
+
}
|
|
141
|
+
|
|
128
142
|
/**
|
|
129
143
|
* Converts Txs lifted from the P2P module into ProcessedTx objects by executing
|
|
130
144
|
* any public function calls in them. Txs with private calls only are unaffected.
|
|
@@ -161,7 +175,7 @@ export class PublicProcessor implements Traceable {
|
|
|
161
175
|
limits: PublicProcessorLimits = {},
|
|
162
176
|
validator: PublicProcessorValidator = {},
|
|
163
177
|
): Promise<[ProcessedTx[], FailedTx[], Tx[], NestedProcessReturnValues[], DebugLog[]]> {
|
|
164
|
-
const { maxTransactions, deadline, maxBlockGas, maxBlobFields, isBuildingProposal } = limits;
|
|
178
|
+
const { maxTransactions, deadline, maxBlockGas, maxBlobFields, isBuildingProposal, signal } = limits;
|
|
165
179
|
const { preprocessValidator, nullifierCache } = validator;
|
|
166
180
|
const result: ProcessedTx[] = [];
|
|
167
181
|
const usedTxs: Tx[] = [];
|
|
@@ -174,6 +188,8 @@ export class PublicProcessor implements Traceable {
|
|
|
174
188
|
let totalPublicGas = new Gas(0, 0);
|
|
175
189
|
let totalBlockGas = new Gas(0, 0);
|
|
176
190
|
let totalBlobFields = 0;
|
|
191
|
+
let silentlySkippedCount = 0;
|
|
192
|
+
let totalSilentlySkippedDurationMs = 0;
|
|
177
193
|
|
|
178
194
|
for await (const tx of txs) {
|
|
179
195
|
// Only process up to the max tx limit
|
|
@@ -182,11 +198,15 @@ export class PublicProcessor implements Traceable {
|
|
|
182
198
|
break;
|
|
183
199
|
}
|
|
184
200
|
|
|
185
|
-
// Bail if we've hit the deadline
|
|
201
|
+
// Bail if we've hit the deadline or have been interrupted.
|
|
186
202
|
if (deadline && this.dateProvider.now() > +deadline) {
|
|
187
203
|
this.log.warn(`Stopping tx processing due to timeout.`);
|
|
188
204
|
break;
|
|
189
205
|
}
|
|
206
|
+
if (signal?.aborted) {
|
|
207
|
+
this.log.warn(`Stopping tx processing due to abort signal.`);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
190
210
|
|
|
191
211
|
const txHash = tx.getTxHash().toString();
|
|
192
212
|
|
|
@@ -224,11 +244,6 @@ export class PublicProcessor implements Traceable {
|
|
|
224
244
|
failed.push({ tx, error: new Error(`Tx failed preprocess validation: ${reason}`) });
|
|
225
245
|
returns.push(new NestedProcessReturnValues([]));
|
|
226
246
|
continue;
|
|
227
|
-
} else if (result.result === 'skipped') {
|
|
228
|
-
const reason = result.reason.join(', ');
|
|
229
|
-
this.log.debug(`Skipping tx ${txHash.toString()} due to pre-process validation: ${reason}`);
|
|
230
|
-
returns.push(new NestedProcessReturnValues([]));
|
|
231
|
-
continue;
|
|
232
247
|
} else {
|
|
233
248
|
this.log.trace(`Tx ${txHash.toString()} is valid before processing.`);
|
|
234
249
|
}
|
|
@@ -244,7 +259,9 @@ export class PublicProcessor implements Traceable {
|
|
|
244
259
|
this.contractsDB.createCheckpoint();
|
|
245
260
|
|
|
246
261
|
try {
|
|
247
|
-
const [processedTx, returnValues, txDebugLogs] = await
|
|
262
|
+
const [txProcessingTimeMs, [processedTx, returnValues, txDebugLogs]] = await elapsed(() =>
|
|
263
|
+
this.processTx(tx, deadline, signal),
|
|
264
|
+
);
|
|
248
265
|
|
|
249
266
|
// Inject a fake processing failure after N txs if requested
|
|
250
267
|
const fakeThrowAfter = this.opts.fakeThrowAfterProcessingTxCount;
|
|
@@ -255,6 +272,22 @@ export class PublicProcessor implements Traceable {
|
|
|
255
272
|
const txBlobFields = processedTx.txEffect.getNumBlobFields();
|
|
256
273
|
const txSize = txBlobFields * Fr.SIZE_IN_BYTES;
|
|
257
274
|
|
|
275
|
+
// A single tx's effects must fit within the per-tx blob encoding: the rollup circuit encodes each
|
|
276
|
+
// tx into a fixed [Field; MAX_TX_BLOB_DATA_SIZE_IN_FIELDS] array, so a larger tx effect cannot be
|
|
277
|
+
// proven. The per-category side-effect limits already guarantee this upstream, so reaching here means
|
|
278
|
+
// the tx is malformed; reject it as invalid rather than letting it poison proving.
|
|
279
|
+
if (txBlobFields > MAX_TX_BLOB_DATA_SIZE_IN_FIELDS) {
|
|
280
|
+
const error = new Error(
|
|
281
|
+
`Tx ${txHash} produced ${txBlobFields} blob fields, exceeding the per-tx maximum of ${MAX_TX_BLOB_DATA_SIZE_IN_FIELDS}`,
|
|
282
|
+
);
|
|
283
|
+
this.log.error(error.message, { txHash, txBlobFields });
|
|
284
|
+
await checkpoint.revert();
|
|
285
|
+
this.contractsDB.revertCheckpoint();
|
|
286
|
+
failed.push({ tx, error });
|
|
287
|
+
returns.push(new NestedProcessReturnValues([]));
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
258
291
|
// If the actual blob fields of this tx would exceed the limit, skip it.
|
|
259
292
|
// Note: maxBlobFields already accounts for block end blob fields and previous blocks in checkpoint.
|
|
260
293
|
if (maxBlobFields !== undefined && totalBlobFields + txBlobFields > maxBlobFields) {
|
|
@@ -265,8 +298,12 @@ export class PublicProcessor implements Traceable {
|
|
|
265
298
|
txBlobFields,
|
|
266
299
|
totalBlobFields,
|
|
267
300
|
maxBlobFields,
|
|
301
|
+
txProcessingTimeMs,
|
|
268
302
|
},
|
|
269
303
|
);
|
|
304
|
+
silentlySkippedCount += 1;
|
|
305
|
+
totalSilentlySkippedDurationMs += txProcessingTimeMs;
|
|
306
|
+
this.metrics.recordSilentlySkipped(txProcessingTimeMs);
|
|
270
307
|
// Need to revert the checkpoint here and don't go any further
|
|
271
308
|
await checkpoint.revert();
|
|
272
309
|
this.contractsDB.revertCheckpoint();
|
|
@@ -310,15 +347,11 @@ export class PublicProcessor implements Traceable {
|
|
|
310
347
|
// Commit the tx-level contracts checkpoint on success
|
|
311
348
|
this.contractsDB.commitCheckpoint();
|
|
312
349
|
} catch (err: any) {
|
|
313
|
-
if (err
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
//
|
|
317
|
-
// Signal cancellation AND WAIT for the simulation to actually stop.
|
|
318
|
-
// This is critical because C++ might be in the middle of a slow operation (e.g., pad_trees)
|
|
319
|
-
// and won't check the cancellation flag until that operation completes.
|
|
320
|
-
// Without waiting, we'd proceed to revert checkpoints while C++ is still writing to state.
|
|
321
|
-
// Wait for C++ to stop gracefully.
|
|
350
|
+
if (isPublicProcessorInterruptError(err)) {
|
|
351
|
+
const interruptReason = err.name === 'PublicProcessorTimeoutError' ? 'timeout' : 'abort signal';
|
|
352
|
+
this.log.warn(`Stopping tx processing due to ${interruptReason}.`);
|
|
353
|
+
// The tx may still be executing on a worker thread (C++ via NAPI).
|
|
354
|
+
// Signal cancellation AND WAIT for the simulation to actually stop before touching fork checkpoints.
|
|
322
355
|
await this.publicTxSimulator.cancel?.();
|
|
323
356
|
|
|
324
357
|
// Now stop the guarded fork to prevent any further TS-side access to the world state.
|
|
@@ -364,13 +397,24 @@ export class PublicProcessor implements Traceable {
|
|
|
364
397
|
const rate = duration > 0 ? totalPublicGas.l2Gas / duration : 0;
|
|
365
398
|
this.metrics.recordAllTxs(totalPublicGas, rate);
|
|
366
399
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
400
|
+
const silentlySkippedDurationMs = Math.round(totalSilentlySkippedDurationMs);
|
|
401
|
+
this.log.info(
|
|
402
|
+
`Processed ${result.length} successful txs and ${failed.length} failed txs ` +
|
|
403
|
+
`(${silentlySkippedCount} silently skipped, ${silentlySkippedDurationMs}ms wasted) ` +
|
|
404
|
+
`in ${duration}s`,
|
|
405
|
+
{
|
|
406
|
+
blockNumber: this.globalVariables.blockNumber,
|
|
407
|
+
successfulCount: result.length,
|
|
408
|
+
failedCount: failed.length,
|
|
409
|
+
duration,
|
|
410
|
+
rate,
|
|
411
|
+
totalPublicGas,
|
|
412
|
+
totalBlockGas,
|
|
413
|
+
totalSizeInBytes,
|
|
414
|
+
silentlySkippedCount,
|
|
415
|
+
silentlySkippedDurationMs,
|
|
416
|
+
},
|
|
417
|
+
);
|
|
374
418
|
|
|
375
419
|
return [result, failed, usedTxs, returns, debugLogs];
|
|
376
420
|
}
|
|
@@ -395,9 +439,10 @@ export class PublicProcessor implements Traceable {
|
|
|
395
439
|
private async processTx(
|
|
396
440
|
tx: Tx,
|
|
397
441
|
deadline: Date | undefined,
|
|
442
|
+
signal: AbortSignal | undefined,
|
|
398
443
|
): Promise<[ProcessedTx, NestedProcessReturnValues[], DebugLog[]]> {
|
|
399
444
|
const [time, [processedTx, returnValues, debugLogs]] = await elapsed(() =>
|
|
400
|
-
this.processTxWithinDeadline(tx, deadline),
|
|
445
|
+
this.processTxWithinDeadline(tx, deadline, signal),
|
|
401
446
|
);
|
|
402
447
|
|
|
403
448
|
this.log.verbose(
|
|
@@ -451,10 +496,11 @@ export class PublicProcessor implements Traceable {
|
|
|
451
496
|
this.metrics.recordTreeInsertions(Number(treeInsertionEnd - treeInsertionStart) / 1_000);
|
|
452
497
|
}
|
|
453
498
|
|
|
454
|
-
/** Processes the given tx within deadline
|
|
499
|
+
/** Processes the given tx within deadline or until the signal is aborted. */
|
|
455
500
|
private async processTxWithinDeadline(
|
|
456
501
|
tx: Tx,
|
|
457
502
|
deadline: Date | undefined,
|
|
503
|
+
signal: AbortSignal | undefined,
|
|
458
504
|
): Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined, DebugLog[]]> {
|
|
459
505
|
const innerProcessFn: () => Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined, DebugLog[]]> =
|
|
460
506
|
tx.hasPublicCalls() ? () => this.processTxWithPublicCalls(tx) : () => this.processPrivateOnlyTx(tx);
|
|
@@ -471,27 +517,38 @@ export class PublicProcessor implements Traceable {
|
|
|
471
517
|
}
|
|
472
518
|
: innerProcessFn;
|
|
473
519
|
|
|
474
|
-
|
|
520
|
+
const processingSignal = this.getProcessingSignal(tx, deadline, signal);
|
|
521
|
+
if (!processingSignal) {
|
|
475
522
|
return await processFn();
|
|
476
523
|
}
|
|
477
524
|
|
|
478
|
-
|
|
525
|
+
return await execWithSignal(
|
|
526
|
+
() => processFn(),
|
|
527
|
+
processingSignal,
|
|
528
|
+
signal =>
|
|
529
|
+
signal.reason?.name === 'TimeoutError' ? new PublicProcessorTimeoutError() : new PublicProcessorAbortError(),
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
private getProcessingSignal(tx: Tx, deadline: Date | undefined, signal: AbortSignal | undefined) {
|
|
534
|
+
if (!deadline) {
|
|
535
|
+
return signal;
|
|
536
|
+
}
|
|
537
|
+
|
|
479
538
|
const timeout = +deadline - this.dateProvider.now();
|
|
480
539
|
if (timeout <= 0) {
|
|
481
540
|
throw new PublicProcessorTimeoutError();
|
|
482
541
|
}
|
|
483
542
|
|
|
543
|
+
const txHash = tx.getTxHash();
|
|
484
544
|
this.log.debug(`Processing tx ${txHash.toString()} within ${timeout}ms`, {
|
|
485
545
|
deadline: deadline.toISOString(),
|
|
486
546
|
now: new Date(this.dateProvider.now()).toISOString(),
|
|
487
547
|
txHash,
|
|
488
548
|
});
|
|
489
549
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
timeout,
|
|
493
|
-
() => new PublicProcessorTimeoutError(),
|
|
494
|
-
);
|
|
550
|
+
const timeoutSignal = AbortSignal.timeout(timeout);
|
|
551
|
+
return signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
495
552
|
}
|
|
496
553
|
|
|
497
554
|
/**
|
|
@@ -548,7 +605,7 @@ export class PublicProcessor implements Traceable {
|
|
|
548
605
|
// Fee payment insertion has already been done. Do the rest.
|
|
549
606
|
await this.doTreeInsertionsForPrivateOnlyTx(processedTx);
|
|
550
607
|
|
|
551
|
-
|
|
608
|
+
this.contractsDB.addNewContracts(tx);
|
|
552
609
|
|
|
553
610
|
return [processedTx, undefined, []];
|
|
554
611
|
}
|
|
@@ -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) {
|