@aztec/simulator 0.0.1-commit.04852196a → 0.0.1-commit.04d373f

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 (84) hide show
  1. package/dest/private/acvm_wasm.d.ts +1 -1
  2. package/dest/private/acvm_wasm.d.ts.map +1 -1
  3. package/dest/private/acvm_wasm.js +3 -1
  4. package/dest/public/avm/avm_simulator.js +1 -1
  5. package/dest/public/avm/calldata.d.ts +1 -1
  6. package/dest/public/avm/calldata.d.ts.map +1 -1
  7. package/dest/public/avm/calldata.js +3 -2
  8. package/dest/public/avm/fixtures/base_avm_simulation_tester.d.ts +2 -1
  9. package/dest/public/avm/fixtures/base_avm_simulation_tester.d.ts.map +1 -1
  10. package/dest/public/avm/fixtures/base_avm_simulation_tester.js +17 -2
  11. package/dest/public/avm/fixtures/utils.d.ts +1 -1
  12. package/dest/public/avm/fixtures/utils.d.ts.map +1 -1
  13. package/dest/public/avm/fixtures/utils.js +3 -0
  14. package/dest/public/avm/opcodes/contract.d.ts +3 -2
  15. package/dest/public/avm/opcodes/contract.d.ts.map +1 -1
  16. package/dest/public/avm/opcodes/contract.js +5 -1
  17. package/dest/public/avm/opcodes/ec_add.d.ts +2 -4
  18. package/dest/public/avm/opcodes/ec_add.d.ts.map +1 -1
  19. package/dest/public/avm/opcodes/ec_add.js +13 -27
  20. package/dest/public/contracts_db_checkpoint.d.ts +2 -2
  21. package/dest/public/contracts_db_checkpoint.d.ts.map +1 -1
  22. package/dest/public/contracts_db_checkpoint.js +1 -1
  23. package/dest/public/fixtures/bulk_test.d.ts +1 -1
  24. package/dest/public/fixtures/bulk_test.d.ts.map +1 -1
  25. package/dest/public/fixtures/bulk_test.js +31 -4
  26. package/dest/public/fixtures/custom_bytecode_tests.d.ts +3 -1
  27. package/dest/public/fixtures/custom_bytecode_tests.d.ts.map +1 -1
  28. package/dest/public/fixtures/custom_bytecode_tests.js +61 -1
  29. package/dest/public/fixtures/opcode_spammer.d.ts +1 -1
  30. package/dest/public/fixtures/opcode_spammer.d.ts.map +1 -1
  31. package/dest/public/fixtures/opcode_spammer.js +2 -10
  32. package/dest/public/fixtures/public_tx_simulation_tester.d.ts +1 -1
  33. package/dest/public/fixtures/public_tx_simulation_tester.d.ts.map +1 -1
  34. package/dest/public/fixtures/public_tx_simulation_tester.js +3 -3
  35. package/dest/public/fixtures/utils.d.ts +1 -1
  36. package/dest/public/fixtures/utils.d.ts.map +1 -1
  37. package/dest/public/fixtures/utils.js +14 -15
  38. package/dest/public/hinting_db_sources.d.ts +4 -4
  39. package/dest/public/hinting_db_sources.d.ts.map +1 -1
  40. package/dest/public/hinting_db_sources.js +7 -6
  41. package/dest/public/public_db_sources.d.ts +6 -3
  42. package/dest/public/public_db_sources.d.ts.map +1 -1
  43. package/dest/public/public_db_sources.js +16 -10
  44. package/dest/public/public_processor/guarded_merkle_tree.d.ts +4 -4
  45. package/dest/public/public_processor/guarded_merkle_tree.d.ts.map +1 -1
  46. package/dest/public/public_processor/guarded_merkle_tree.js +4 -4
  47. package/dest/public/public_processor/public_processor.d.ts +4 -3
  48. package/dest/public/public_processor/public_processor.d.ts.map +1 -1
  49. package/dest/public/public_processor/public_processor.js +67 -44
  50. package/dest/public/public_processor/public_processor_metrics.d.ts +4 -1
  51. package/dest/public/public_processor/public_processor_metrics.d.ts.map +1 -1
  52. package/dest/public/public_processor/public_processor_metrics.js +8 -0
  53. package/dest/public/public_tx_simulator/contract_provider_for_cpp.d.ts +1 -1
  54. package/dest/public/public_tx_simulator/contract_provider_for_cpp.d.ts.map +1 -1
  55. package/dest/public/public_tx_simulator/contract_provider_for_cpp.js +3 -2
  56. package/dest/public/public_tx_simulator/public_tx_context.d.ts +7 -3
  57. package/dest/public/public_tx_simulator/public_tx_context.d.ts.map +1 -1
  58. package/dest/public/public_tx_simulator/public_tx_context.js +8 -10
  59. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts +6 -2
  60. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts.map +1 -1
  61. package/dest/public/public_tx_simulator/public_tx_simulator.js +12 -7
  62. package/package.json +16 -16
  63. package/src/private/acvm_wasm.ts +4 -1
  64. package/src/public/avm/avm_simulator.ts +1 -1
  65. package/src/public/avm/calldata.ts +3 -2
  66. package/src/public/avm/fixtures/base_avm_simulation_tester.ts +22 -2
  67. package/src/public/avm/fixtures/utils.ts +3 -0
  68. package/src/public/avm/opcodes/contract.ts +5 -1
  69. package/src/public/avm/opcodes/ec_add.ts +12 -31
  70. package/src/public/avm/opcodes/external_calls.ts +1 -1
  71. package/src/public/contracts_db_checkpoint.ts +1 -1
  72. package/src/public/fixtures/bulk_test.ts +29 -4
  73. package/src/public/fixtures/custom_bytecode_tests.ts +139 -1
  74. package/src/public/fixtures/opcode_spammer.ts +4 -8
  75. package/src/public/fixtures/public_tx_simulation_tester.ts +4 -9
  76. package/src/public/fixtures/utils.ts +21 -18
  77. package/src/public/hinting_db_sources.ts +9 -6
  78. package/src/public/public_db_sources.ts +21 -14
  79. package/src/public/public_processor/guarded_merkle_tree.ts +5 -5
  80. package/src/public/public_processor/public_processor.ts +86 -52
  81. package/src/public/public_processor/public_processor_metrics.ts +12 -0
  82. package/src/public/public_tx_simulator/contract_provider_for_cpp.ts +3 -2
  83. package/src/public/public_tx_simulator/public_tx_context.ts +8 -10
  84. package/src/public/public_tx_simulator/public_tx_simulator.ts +11 -7
@@ -2,10 +2,8 @@ import {
2
2
  CONTRACT_CLASS_PUBLISHED_MAGIC_VALUE,
3
3
  CONTRACT_CLASS_REGISTRY_CONTRACT_ADDRESS,
4
4
  CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS,
5
- DEFAULT_DA_GAS_LIMIT,
6
- DEFAULT_L2_GAS_LIMIT,
7
- DEFAULT_TEARDOWN_DA_GAS_LIMIT,
8
- DEFAULT_TEARDOWN_L2_GAS_LIMIT,
5
+ MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT,
6
+ MAX_PROCESSABLE_L2_GAS,
9
7
  PRIVATE_LOG_SIZE_IN_FIELDS,
10
8
  } from '@aztec/constants';
11
9
  import { padArrayEnd } from '@aztec/foundation/collection';
@@ -14,7 +12,13 @@ import { CONTRACT_INSTANCE_PUBLISHED_EVENT_TAG } from '@aztec/protocol-contracts
14
12
  import { bufferAsFields } from '@aztec/stdlib/abi';
15
13
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
16
14
  import type { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/stdlib/contract';
17
- import { Gas, GasFees, GasSettings } from '@aztec/stdlib/gas';
15
+ import {
16
+ FALLBACK_TEARDOWN_DA_GAS_LIMIT,
17
+ FALLBACK_TEARDOWN_L2_GAS_LIMIT,
18
+ Gas,
19
+ GasFees,
20
+ GasSettings,
21
+ } from '@aztec/stdlib/gas';
18
22
  import { siloNullifier } from '@aztec/stdlib/hash';
19
23
  import {
20
24
  LogHash,
@@ -69,7 +73,7 @@ export async function createTxForPublicCalls(
69
73
  "Can't create public tx with no enqueued calls",
70
74
  );
71
75
  // use max limits
72
- gasLimits = gasLimits ?? new Gas(DEFAULT_DA_GAS_LIMIT, DEFAULT_L2_GAS_LIMIT);
76
+ gasLimits = gasLimits ?? new Gas(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, MAX_PROCESSABLE_L2_GAS);
73
77
 
74
78
  const forPublic = PartialPrivateTailPublicInputsForPublic.empty();
75
79
 
@@ -129,7 +133,7 @@ export async function createTxForPublicCalls(
129
133
 
130
134
  const maxFeesPerGas = feePayer.isZero() ? GasFees.empty() : new GasFees(10, 10);
131
135
  const teardownGasLimits = teardownCallRequest
132
- ? new Gas(DEFAULT_TEARDOWN_DA_GAS_LIMIT, DEFAULT_TEARDOWN_L2_GAS_LIMIT)
136
+ ? new Gas(FALLBACK_TEARDOWN_DA_GAS_LIMIT, FALLBACK_TEARDOWN_L2_GAS_LIMIT)
133
137
  : Gas.empty();
134
138
  const gasSettings = new GasSettings(gasLimits, teardownGasLimits, maxFeesPerGas, GasFees.empty());
135
139
  const txContext = new TxContext(Fr.zero(), Fr.zero(), gasSettings);
@@ -164,7 +168,7 @@ export async function createTxForPrivateOnly(
164
168
  gasUsedByPrivate: Gas = new Gas(10, 10),
165
169
  ): Promise<Tx> {
166
170
  // use max limits
167
- const gasLimits = new Gas(DEFAULT_DA_GAS_LIMIT, DEFAULT_L2_GAS_LIMIT);
171
+ const gasLimits = new Gas(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, MAX_PROCESSABLE_L2_GAS);
168
172
 
169
173
  const forRollup = PartialPrivateTailPublicInputsForRollup.empty();
170
174
 
@@ -229,17 +233,15 @@ export async function addNewContractInstanceToTx(
229
233
  contractInstance: ContractInstanceWithAddress,
230
234
  skipNullifierInsertion = false,
231
235
  ) {
232
- // can't use publicKeys.toFields() because it includes isInfinite which
233
- // is not broadcast in such private logs
236
+ // Only ivpk_m is broadcast as a point (x, y); the other five keys are hashes.
234
237
  const publicKeysAsFields = [
235
- contractInstance.publicKeys.masterNullifierPublicKey.x,
236
- contractInstance.publicKeys.masterNullifierPublicKey.y,
237
- contractInstance.publicKeys.masterIncomingViewingPublicKey.x,
238
- contractInstance.publicKeys.masterIncomingViewingPublicKey.y,
239
- contractInstance.publicKeys.masterOutgoingViewingPublicKey.x,
240
- contractInstance.publicKeys.masterOutgoingViewingPublicKey.y,
241
- contractInstance.publicKeys.masterTaggingPublicKey.x,
242
- contractInstance.publicKeys.masterTaggingPublicKey.y,
238
+ contractInstance.publicKeys.npkMHash,
239
+ contractInstance.publicKeys.ivpkM.x,
240
+ contractInstance.publicKeys.ivpkM.y,
241
+ contractInstance.publicKeys.ovpkMHash,
242
+ contractInstance.publicKeys.tpkMHash,
243
+ contractInstance.publicKeys.mspkMHash,
244
+ contractInstance.publicKeys.fbpkMHash,
243
245
  ];
244
246
  const logFields = [
245
247
  CONTRACT_INSTANCE_PUBLISHED_EVENT_TAG,
@@ -248,6 +250,7 @@ export async function addNewContractInstanceToTx(
248
250
  new Fr(contractInstance.salt),
249
251
  contractInstance.currentContractClassId,
250
252
  contractInstance.initializationHash,
253
+ contractInstance.immutablesHash,
251
254
  ...publicKeysAsFields,
252
255
  contractInstance.deployer.toField(),
253
256
  ];
@@ -83,6 +83,7 @@ export class HintingPublicContractsDB implements PublicContractsDBInterface {
83
83
  instance.currentContractClassId,
84
84
  instance.originalContractClassId,
85
85
  instance.initializationHash,
86
+ instance.immutablesHash,
86
87
  instance.publicKeys,
87
88
  ),
88
89
  );
@@ -410,12 +411,12 @@ export class HintingMerkleWriteOperations implements MerkleTreeWriteOperations {
410
411
  }
411
412
  }
412
413
 
413
- public async createCheckpoint(): Promise<void> {
414
+ public async createCheckpoint(): Promise<number> {
414
415
  const actionCounter = this.checkpointActionCounter++;
415
416
  const oldCheckpointId = this.getCurrentCheckpointId();
416
417
  const treesStateHash = await this.getTreesStateHash();
417
418
 
418
- await this.db.createCheckpoint();
419
+ const depth = await this.db.createCheckpoint();
419
420
  this.checkpointStack.push(this.nextCheckpointId++);
420
421
  const newCheckpointId = this.getCurrentCheckpointId();
421
422
 
@@ -424,14 +425,16 @@ export class HintingMerkleWriteOperations implements MerkleTreeWriteOperations {
424
425
  HintingMerkleWriteOperations.log.trace(
425
426
  `[createCheckpoint:${actionCounter}] Checkpoint evolved ${oldCheckpointId} -> ${newCheckpointId} at trees state ${treesStateHash}.`,
426
427
  );
428
+
429
+ return depth;
427
430
  }
428
431
 
429
- public commitAllCheckpoints(): Promise<void> {
430
- throw new Error('commitAllCheckpoints is not supported in HintingMerkleWriteOperations.');
432
+ public commitAllCheckpointsTo(_depth: number): Promise<void> {
433
+ throw new Error('commitAllCheckpointsTo is not supported in HintingMerkleWriteOperations.');
431
434
  }
432
435
 
433
- public revertAllCheckpoints(): Promise<void> {
434
- throw new Error('revertAllCheckpoints is not supported in HintingMerkleWriteOperations.');
436
+ public revertAllCheckpointsTo(_depth: number): Promise<void> {
437
+ throw new Error('revertAllCheckpointsTo is not supported in HintingMerkleWriteOperations.');
435
438
  }
436
439
 
437
440
  public async commitCheckpoint(): Promise<void> {
@@ -55,10 +55,11 @@ export class PublicContractsDB implements PublicContractsDBInterface {
55
55
  this.log = createLogger('simulator:contracts-data-source', bindings);
56
56
  }
57
57
 
58
- public async addContracts(contractDeploymentData: ContractDeploymentData): Promise<void> {
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
- await this.addContractClassesFromEvents(
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 async addNewContracts(tx: Tx): Promise<void> {
73
+ public addNewContracts(tx: Tx): void {
73
74
  const contractDeploymentData = AllContractDeploymentData.fromTx(tx);
74
- await this.addContracts(contractDeploymentData.getNonRevertibleContractDeploymentData());
75
- await this.addContracts(contractDeploymentData.getRevertibleContractDeploymentData());
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.deepCopy();
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 async addContractClassesFromEvents(
186
+ private addContractClassesFromEvents(
178
187
  contractClassEvents: ContractClassPublishedEvent[],
179
188
  state: ContractsDbCheckpoint,
180
189
  ) {
181
- await Promise.all(
182
- contractClassEvents.map(async (event: ContractClassPublishedEvent) => {
183
- this.log.debug(`Adding class ${event.contractClassId.toString()} to contract state`);
184
- const contractClass = await event.toContractClassPublic();
185
- state.addClass(event.contractClassId, contractClass);
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(
@@ -134,7 +134,7 @@ export class GuardedMerkleTreeOperations implements MerkleTreeWriteOperations {
134
134
  ): Promise<(BlockNumber | undefined)[]> {
135
135
  return this.guardAndPush(() => this.target.getBlockNumbersForLeafIndices(treeId, leafIndices));
136
136
  }
137
- createCheckpoint(): Promise<void> {
137
+ createCheckpoint(): Promise<number> {
138
138
  return this.guardAndPush(() => this.target.createCheckpoint());
139
139
  }
140
140
  commitCheckpoint(): Promise<void> {
@@ -143,11 +143,11 @@ export class GuardedMerkleTreeOperations implements MerkleTreeWriteOperations {
143
143
  revertCheckpoint(): Promise<void> {
144
144
  return this.guardAndPush(() => this.target.revertCheckpoint());
145
145
  }
146
- commitAllCheckpoints(): Promise<void> {
147
- return this.guardAndPush(() => this.target.commitAllCheckpoints());
146
+ commitAllCheckpointsTo(depth: number): Promise<void> {
147
+ return this.guardAndPush(() => this.target.commitAllCheckpointsTo(depth));
148
148
  }
149
- revertAllCheckpoints(): Promise<void> {
150
- return this.guardAndPush(() => this.target.revertAllCheckpoints());
149
+ revertAllCheckpointsTo(depth: number): Promise<void> {
150
+ return this.guardAndPush(() => this.target.revertAllCheckpointsTo(depth));
151
151
  }
152
152
  findSiblingPaths<ID extends MerkleTreeId>(
153
153
  treeId: ID,
@@ -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, executeTimeout } from '@aztec/foundation/timer';
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 skipFeeEnforcement - Allows disabling balance checks for fee estimations.
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', bindings),
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.
@@ -161,7 +170,7 @@ export class PublicProcessor implements Traceable {
161
170
  limits: PublicProcessorLimits = {},
162
171
  validator: PublicProcessorValidator = {},
163
172
  ): Promise<[ProcessedTx[], FailedTx[], Tx[], NestedProcessReturnValues[], DebugLog[]]> {
164
- const { maxTransactions, deadline, maxBlockGas, maxBlobFields, isBuildingProposal } = limits;
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,11 +193,15 @@ 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
206
  const txHash = tx.getTxHash().toString();
192
207
 
@@ -224,11 +239,6 @@ export class PublicProcessor implements Traceable {
224
239
  failed.push({ tx, error: new Error(`Tx failed preprocess validation: ${reason}`) });
225
240
  returns.push(new NestedProcessReturnValues([]));
226
241
  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
242
  } else {
233
243
  this.log.trace(`Tx ${txHash.toString()} is valid before processing.`);
234
244
  }
@@ -244,7 +254,9 @@ export class PublicProcessor implements Traceable {
244
254
  this.contractsDB.createCheckpoint();
245
255
 
246
256
  try {
247
- const [processedTx, returnValues, txDebugLogs] = await this.processTx(tx, deadline);
257
+ const [txProcessingTimeMs, [processedTx, returnValues, txDebugLogs]] = await elapsed(() =>
258
+ this.processTx(tx, deadline, signal),
259
+ );
248
260
 
249
261
  // Inject a fake processing failure after N txs if requested
250
262
  const fakeThrowAfter = this.opts.fakeThrowAfterProcessingTxCount;
@@ -265,8 +277,12 @@ export class PublicProcessor implements Traceable {
265
277
  txBlobFields,
266
278
  totalBlobFields,
267
279
  maxBlobFields,
280
+ txProcessingTimeMs,
268
281
  },
269
282
  );
283
+ silentlySkippedCount += 1;
284
+ totalSilentlySkippedDurationMs += txProcessingTimeMs;
285
+ this.metrics.recordSilentlySkipped(txProcessingTimeMs);
270
286
  // Need to revert the checkpoint here and don't go any further
271
287
  await checkpoint.revert();
272
288
  this.contractsDB.revertCheckpoint();
@@ -306,16 +322,15 @@ export class PublicProcessor implements Traceable {
306
322
  totalBlockGas = totalBlockGas.add(processedTx.gasUsed.totalGas);
307
323
  totalSizeInBytes += txSize;
308
324
  totalBlobFields += txBlobFields;
325
+
326
+ // Commit the tx-level contracts checkpoint on success
327
+ this.contractsDB.commitCheckpoint();
309
328
  } catch (err: any) {
310
- if (err?.name === 'PublicProcessorTimeoutError') {
311
- this.log.warn(`Stopping tx processing due to timeout.`);
312
- // We hit the transaction execution deadline.
313
- // There may still be a transaction executing on a worker thread (C++ via NAPI).
314
- // Signal cancellation AND WAIT for the simulation to actually stop.
315
- // This is critical because C++ might be in the middle of a slow operation (e.g., pad_trees)
316
- // and won't check the cancellation flag until that operation completes.
317
- // Without waiting, we'd proceed to revert checkpoints while C++ is still writing to state.
318
- // 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.
319
334
  await this.publicTxSimulator.cancel?.();
320
335
 
321
336
  // Now stop the guarded fork to prevent any further TS-side access to the world state.
@@ -325,14 +340,10 @@ export class PublicProcessor implements Traceable {
325
340
  // 1. At least one outstanding checkpoint that has not been committed (the one created before we processed the tx).
326
341
  // 2. Possible state updates on that checkpoint or any others created during execution.
327
342
 
328
- // First we revert a checkpoint as managed by the ForkCheckpoint. This will revert whatever is the current checkpoint
329
- // which may not be the one originally created by this object. But that is ok, we do this to fulfil the ForkCheckpoint
330
- // lifecycle expectations and ensure it doesn't attempt to commit later on.
331
- await checkpoint.revert();
332
-
333
- // Now we want to revert any/all remaining checkpoints, destroying any outstanding state updates.
334
- // This needs to be done directly on the underlying fork as the guarded fork has been stopped.
335
- 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();
336
347
 
337
348
  // Revert any contracts added to the DB for the tx.
338
349
  this.contractsDB.revertCheckpoint();
@@ -344,9 +355,9 @@ export class PublicProcessor implements Traceable {
344
355
  break;
345
356
  }
346
357
 
347
- // Roll back state to start of TX before proceeding to next TX
348
- await checkpoint.revert();
349
- await this.guardedMerkleTree.getUnderlyingFork().revertAllCheckpoints();
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();
350
361
  this.contractsDB.revertCheckpoint();
351
362
  const errorMessage = err instanceof Error || err instanceof AssertionError ? err.message : 'Unknown error';
352
363
  this.log.warn(`Failed to process tx ${txHash.toString()}: ${errorMessage} ${err?.stack}`);
@@ -358,7 +369,6 @@ export class PublicProcessor implements Traceable {
358
369
  } finally {
359
370
  // Base case is we always commit the checkpoint. Using the ForkCheckpoint means this has no effect if the tx was previously reverted
360
371
  await checkpoint.commit();
361
- this.contractsDB.commitCheckpointOkIfNone();
362
372
  }
363
373
  }
364
374
 
@@ -366,13 +376,24 @@ export class PublicProcessor implements Traceable {
366
376
  const rate = duration > 0 ? totalPublicGas.l2Gas / duration : 0;
367
377
  this.metrics.recordAllTxs(totalPublicGas, rate);
368
378
 
369
- this.log.info(`Processed ${result.length} successful txs and ${failed.length} failed txs in ${duration}s`, {
370
- duration,
371
- rate,
372
- totalPublicGas,
373
- totalBlockGas,
374
- totalSizeInBytes,
375
- });
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
+ );
376
397
 
377
398
  return [result, failed, usedTxs, returns, debugLogs];
378
399
  }
@@ -397,9 +418,10 @@ export class PublicProcessor implements Traceable {
397
418
  private async processTx(
398
419
  tx: Tx,
399
420
  deadline: Date | undefined,
421
+ signal: AbortSignal | undefined,
400
422
  ): Promise<[ProcessedTx, NestedProcessReturnValues[], DebugLog[]]> {
401
423
  const [time, [processedTx, returnValues, debugLogs]] = await elapsed(() =>
402
- this.processTxWithinDeadline(tx, deadline),
424
+ this.processTxWithinDeadline(tx, deadline, signal),
403
425
  );
404
426
 
405
427
  this.log.verbose(
@@ -453,10 +475,11 @@ export class PublicProcessor implements Traceable {
453
475
  this.metrics.recordTreeInsertions(Number(treeInsertionEnd - treeInsertionStart) / 1_000);
454
476
  }
455
477
 
456
- /** Processes the given tx within deadline. Returns timeout if deadline is hit. */
478
+ /** Processes the given tx within deadline or until the signal is aborted. */
457
479
  private async processTxWithinDeadline(
458
480
  tx: Tx,
459
481
  deadline: Date | undefined,
482
+ signal: AbortSignal | undefined,
460
483
  ): Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined, DebugLog[]]> {
461
484
  const innerProcessFn: () => Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined, DebugLog[]]> =
462
485
  tx.hasPublicCalls() ? () => this.processTxWithPublicCalls(tx) : () => this.processPrivateOnlyTx(tx);
@@ -473,27 +496,38 @@ export class PublicProcessor implements Traceable {
473
496
  }
474
497
  : innerProcessFn;
475
498
 
476
- if (!deadline) {
499
+ const processingSignal = this.getProcessingSignal(tx, deadline, signal);
500
+ if (!processingSignal) {
477
501
  return await processFn();
478
502
  }
479
503
 
480
- const txHash = tx.getTxHash();
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
+
481
517
  const timeout = +deadline - this.dateProvider.now();
482
518
  if (timeout <= 0) {
483
519
  throw new PublicProcessorTimeoutError();
484
520
  }
485
521
 
522
+ const txHash = tx.getTxHash();
486
523
  this.log.debug(`Processing tx ${txHash.toString()} within ${timeout}ms`, {
487
524
  deadline: deadline.toISOString(),
488
525
  now: new Date(this.dateProvider.now()).toISOString(),
489
526
  txHash,
490
527
  });
491
528
 
492
- return await executeTimeout(
493
- () => processFn(),
494
- timeout,
495
- () => new PublicProcessorTimeoutError(),
496
- );
529
+ const timeoutSignal = AbortSignal.timeout(timeout);
530
+ return signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
497
531
  }
498
532
 
499
533
  /**
@@ -550,7 +584,7 @@ export class PublicProcessor implements Traceable {
550
584
  // Fee payment insertion has already been done. Do the rest.
551
585
  await this.doTreeInsertionsForPrivateOnlyTx(processedTx);
552
586
 
553
- await this.contractsDB.addNewContracts(tx);
587
+ this.contractsDB.addNewContracts(tx);
554
588
 
555
589
  return [processedTx, undefined, []];
556
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.addContracts`);
65
- await this.contractsDB.addContracts(contractDeploymentData);
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.APP_LOGIC_REVERTED;
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
- * as if the entire teardown gas limit was consumed.
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);