@aztec/simulator 0.0.1-commit.7b97ef96e → 0.0.1-commit.7cbc774

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 (102) 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/private/circuit_recording/circuit_recorder.js +2 -2
  5. package/dest/public/avm/avm_simulator.js +1 -1
  6. package/dest/public/avm/calldata.d.ts +1 -1
  7. package/dest/public/avm/calldata.d.ts.map +1 -1
  8. package/dest/public/avm/calldata.js +3 -2
  9. package/dest/public/avm/fixtures/base_avm_simulation_tester.d.ts +2 -1
  10. package/dest/public/avm/fixtures/base_avm_simulation_tester.d.ts.map +1 -1
  11. package/dest/public/avm/fixtures/base_avm_simulation_tester.js +17 -2
  12. package/dest/public/avm/fixtures/utils.d.ts +1 -1
  13. package/dest/public/avm/fixtures/utils.d.ts.map +1 -1
  14. package/dest/public/avm/fixtures/utils.js +3 -0
  15. package/dest/public/avm/opcodes/contract.d.ts +3 -2
  16. package/dest/public/avm/opcodes/contract.d.ts.map +1 -1
  17. package/dest/public/avm/opcodes/contract.js +5 -1
  18. package/dest/public/avm/opcodes/ec_add.d.ts +2 -4
  19. package/dest/public/avm/opcodes/ec_add.d.ts.map +1 -1
  20. package/dest/public/avm/opcodes/ec_add.js +13 -27
  21. package/dest/public/contracts_db_checkpoint.d.ts +2 -2
  22. package/dest/public/contracts_db_checkpoint.d.ts.map +1 -1
  23. package/dest/public/contracts_db_checkpoint.js +1 -1
  24. package/dest/public/fixtures/bulk_test.d.ts +1 -1
  25. package/dest/public/fixtures/bulk_test.d.ts.map +1 -1
  26. package/dest/public/fixtures/bulk_test.js +31 -4
  27. package/dest/public/fixtures/custom_bytecode_tests.d.ts +3 -1
  28. package/dest/public/fixtures/custom_bytecode_tests.d.ts.map +1 -1
  29. package/dest/public/fixtures/custom_bytecode_tests.js +61 -1
  30. package/dest/public/fixtures/opcode_spammer.d.ts +1 -1
  31. package/dest/public/fixtures/opcode_spammer.d.ts.map +1 -1
  32. package/dest/public/fixtures/opcode_spammer.js +2 -10
  33. package/dest/public/fixtures/public_tx_simulation_tester.d.ts +6 -5
  34. package/dest/public/fixtures/public_tx_simulation_tester.d.ts.map +1 -1
  35. package/dest/public/fixtures/public_tx_simulation_tester.js +37 -10
  36. package/dest/public/fixtures/utils.d.ts +2 -2
  37. package/dest/public/fixtures/utils.d.ts.map +1 -1
  38. package/dest/public/fixtures/utils.js +15 -16
  39. package/dest/public/hinting_db_sources.d.ts +4 -4
  40. package/dest/public/hinting_db_sources.d.ts.map +1 -1
  41. package/dest/public/hinting_db_sources.js +7 -6
  42. package/dest/public/public_db_sources.d.ts +6 -3
  43. package/dest/public/public_db_sources.d.ts.map +1 -1
  44. package/dest/public/public_db_sources.js +16 -10
  45. package/dest/public/public_processor/guarded_merkle_tree.d.ts +4 -4
  46. package/dest/public/public_processor/guarded_merkle_tree.d.ts.map +1 -1
  47. package/dest/public/public_processor/guarded_merkle_tree.js +4 -4
  48. package/dest/public/public_processor/public_processor.d.ts +8 -6
  49. package/dest/public/public_processor/public_processor.d.ts.map +1 -1
  50. package/dest/public/public_processor/public_processor.js +98 -69
  51. package/dest/public/public_processor/public_processor_metrics.d.ts +4 -1
  52. package/dest/public/public_processor/public_processor_metrics.d.ts.map +1 -1
  53. package/dest/public/public_processor/public_processor_metrics.js +8 -0
  54. package/dest/public/public_tx_simulator/contract_provider_for_cpp.d.ts +1 -1
  55. package/dest/public/public_tx_simulator/contract_provider_for_cpp.d.ts.map +1 -1
  56. package/dest/public/public_tx_simulator/contract_provider_for_cpp.js +3 -2
  57. package/dest/public/public_tx_simulator/cpp_public_tx_simulator.d.ts +1 -1
  58. package/dest/public/public_tx_simulator/cpp_public_tx_simulator.d.ts.map +1 -1
  59. package/dest/public/public_tx_simulator/cpp_public_tx_simulator.js +2 -3
  60. package/dest/public/public_tx_simulator/cpp_public_tx_simulator_with_hinted_dbs.d.ts +1 -1
  61. package/dest/public/public_tx_simulator/cpp_public_tx_simulator_with_hinted_dbs.d.ts.map +1 -1
  62. package/dest/public/public_tx_simulator/cpp_public_tx_simulator_with_hinted_dbs.js +2 -2
  63. package/dest/public/public_tx_simulator/factories.d.ts +2 -2
  64. package/dest/public/public_tx_simulator/factories.d.ts.map +1 -1
  65. package/dest/public/public_tx_simulator/factories.js +2 -2
  66. package/dest/public/public_tx_simulator/public_tx_context.d.ts +7 -3
  67. package/dest/public/public_tx_simulator/public_tx_context.d.ts.map +1 -1
  68. package/dest/public/public_tx_simulator/public_tx_context.js +8 -10
  69. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts +6 -2
  70. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts.map +1 -1
  71. package/dest/public/public_tx_simulator/public_tx_simulator.js +14 -9
  72. package/dest/public/test_executor_metrics.d.ts +6 -1
  73. package/dest/public/test_executor_metrics.d.ts.map +1 -1
  74. package/dest/public/test_executor_metrics.js +22 -0
  75. package/package.json +16 -16
  76. package/src/private/acvm_wasm.ts +4 -1
  77. package/src/private/circuit_recording/circuit_recorder.ts +2 -2
  78. package/src/public/avm/avm_simulator.ts +1 -1
  79. package/src/public/avm/calldata.ts +3 -2
  80. package/src/public/avm/fixtures/base_avm_simulation_tester.ts +22 -2
  81. package/src/public/avm/fixtures/utils.ts +3 -0
  82. package/src/public/avm/opcodes/contract.ts +5 -1
  83. package/src/public/avm/opcodes/ec_add.ts +12 -31
  84. package/src/public/avm/opcodes/external_calls.ts +1 -1
  85. package/src/public/contracts_db_checkpoint.ts +1 -1
  86. package/src/public/fixtures/bulk_test.ts +29 -4
  87. package/src/public/fixtures/custom_bytecode_tests.ts +139 -1
  88. package/src/public/fixtures/opcode_spammer.ts +4 -8
  89. package/src/public/fixtures/public_tx_simulation_tester.ts +47 -6
  90. package/src/public/fixtures/utils.ts +22 -18
  91. package/src/public/hinting_db_sources.ts +9 -6
  92. package/src/public/public_db_sources.ts +21 -14
  93. package/src/public/public_processor/guarded_merkle_tree.ts +5 -5
  94. package/src/public/public_processor/public_processor.ts +124 -81
  95. package/src/public/public_processor/public_processor_metrics.ts +12 -0
  96. package/src/public/public_tx_simulator/contract_provider_for_cpp.ts +3 -2
  97. package/src/public/public_tx_simulator/cpp_public_tx_simulator.ts +2 -3
  98. package/src/public/public_tx_simulator/cpp_public_tx_simulator_with_hinted_dbs.ts +2 -2
  99. package/src/public/public_tx_simulator/factories.ts +2 -1
  100. package/src/public/public_tx_simulator/public_tx_context.ts +8 -10
  101. package/src/public/public_tx_simulator/public_tx_simulator.ts +14 -10
  102. package/src/public/test_executor_metrics.ts +24 -0
@@ -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,
@@ -62,13 +66,14 @@ export async function createTxForPublicCalls(
62
66
  feePayer = AztecAddress.zero(),
63
67
  gasUsedByPrivate: Gas = Gas.empty(),
64
68
  globals: GlobalVariables = GlobalVariables.empty(),
69
+ gasLimits?: Gas,
65
70
  ): Promise<Tx> {
66
71
  assert(
67
72
  setupCallRequests.length > 0 || appCallRequests.length > 0 || teardownCallRequest !== undefined,
68
73
  "Can't create public tx with no enqueued calls",
69
74
  );
70
75
  // use max limits
71
- const 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);
72
77
 
73
78
  const forPublic = PartialPrivateTailPublicInputsForPublic.empty();
74
79
 
@@ -128,7 +133,7 @@ export async function createTxForPublicCalls(
128
133
 
129
134
  const maxFeesPerGas = feePayer.isZero() ? GasFees.empty() : new GasFees(10, 10);
130
135
  const teardownGasLimits = teardownCallRequest
131
- ? 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)
132
137
  : Gas.empty();
133
138
  const gasSettings = new GasSettings(gasLimits, teardownGasLimits, maxFeesPerGas, GasFees.empty());
134
139
  const txContext = new TxContext(Fr.zero(), Fr.zero(), gasSettings);
@@ -163,7 +168,7 @@ export async function createTxForPrivateOnly(
163
168
  gasUsedByPrivate: Gas = new Gas(10, 10),
164
169
  ): Promise<Tx> {
165
170
  // use max limits
166
- 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);
167
172
 
168
173
  const forRollup = PartialPrivateTailPublicInputsForRollup.empty();
169
174
 
@@ -228,17 +233,15 @@ export async function addNewContractInstanceToTx(
228
233
  contractInstance: ContractInstanceWithAddress,
229
234
  skipNullifierInsertion = false,
230
235
  ) {
231
- // can't use publicKeys.toFields() because it includes isInfinite which
232
- // is not broadcast in such private logs
236
+ // Only ivpk_m is broadcast as a point (x, y); the other five keys are hashes.
233
237
  const publicKeysAsFields = [
234
- contractInstance.publicKeys.masterNullifierPublicKey.x,
235
- contractInstance.publicKeys.masterNullifierPublicKey.y,
236
- contractInstance.publicKeys.masterIncomingViewingPublicKey.x,
237
- contractInstance.publicKeys.masterIncomingViewingPublicKey.y,
238
- contractInstance.publicKeys.masterOutgoingViewingPublicKey.x,
239
- contractInstance.publicKeys.masterOutgoingViewingPublicKey.y,
240
- contractInstance.publicKeys.masterTaggingPublicKey.x,
241
- 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,
242
245
  ];
243
246
  const logFields = [
244
247
  CONTRACT_INSTANCE_PUBLISHED_EVENT_TAG,
@@ -247,6 +250,7 @@ export async function addNewContractInstanceToTx(
247
250
  new Fr(contractInstance.salt),
248
251
  contractInstance.currentContractClassId,
249
252
  contractInstance.initializationHash,
253
+ contractInstance.immutablesHash,
250
254
  ...publicKeysAsFields,
251
255
  contractInstance.deployer.toField(),
252
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';
@@ -25,7 +25,7 @@ import type {
25
25
  PublicProcessorValidator,
26
26
  SequencerConfig,
27
27
  } from '@aztec/stdlib/interfaces/server';
28
- import type { DebugLog } from '@aztec/stdlib/logs';
28
+ import { type DebugLog, type DebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
29
29
  import { ProvingRequestType } from '@aztec/stdlib/proofs';
30
30
  import { MerkleTreeId } from '@aztec/stdlib/trees';
31
31
  import {
@@ -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.
@@ -140,6 +149,7 @@ export class PublicProcessor implements Traceable {
140
149
  telemetryClient: TelemetryClient = getTelemetryClient(),
141
150
  private log: Logger,
142
151
  private opts: Pick<SequencerConfig, 'fakeProcessingDelayPerTxMs' | 'fakeThrowAfterProcessingTxCount'> = {},
152
+ private debugLogStore: DebugLogStore = new NullDebugLogStore(),
143
153
  ) {
144
154
  this.metrics = new PublicProcessorMetrics(telemetryClient, 'PublicProcessor');
145
155
  }
@@ -159,8 +169,8 @@ export class PublicProcessor implements Traceable {
159
169
  txs: Iterable<Tx> | AsyncIterable<Tx>,
160
170
  limits: PublicProcessorLimits = {},
161
171
  validator: PublicProcessorValidator = {},
162
- ): Promise<[ProcessedTx[], FailedTx[], Tx[], NestedProcessReturnValues[], number, DebugLog[]]> {
163
- const { maxTransactions, maxBlockSize, deadline, maxBlockGas, maxBlobFields } = limits;
172
+ ): Promise<[ProcessedTx[], FailedTx[], Tx[], NestedProcessReturnValues[], DebugLog[]]> {
173
+ const { maxTransactions, deadline, maxBlockGas, maxBlobFields, isBuildingProposal, signal } = limits;
164
174
  const { preprocessValidator, nullifierCache } = validator;
165
175
  const result: ProcessedTx[] = [];
166
176
  const usedTxs: Tx[] = [];
@@ -173,6 +183,8 @@ export class PublicProcessor implements Traceable {
173
183
  let totalPublicGas = new Gas(0, 0);
174
184
  let totalBlockGas = new Gas(0, 0);
175
185
  let totalBlobFields = 0;
186
+ let silentlySkippedCount = 0;
187
+ let totalSilentlySkippedDurationMs = 0;
176
188
 
177
189
  for await (const tx of txs) {
178
190
  // Only process up to the max tx limit
@@ -181,28 +193,33 @@ export class PublicProcessor implements Traceable {
181
193
  break;
182
194
  }
183
195
 
184
- // Bail if we've hit the deadline
196
+ // Bail if we've hit the deadline or have been interrupted.
185
197
  if (deadline && this.dateProvider.now() > +deadline) {
186
198
  this.log.warn(`Stopping tx processing due to timeout.`);
187
199
  break;
188
200
  }
201
+ if (signal?.aborted) {
202
+ this.log.warn(`Stopping tx processing due to abort signal.`);
203
+ break;
204
+ }
189
205
 
190
- // Skip this tx if it'd exceed max block size
191
206
  const txHash = tx.getTxHash().toString();
192
- const preTxSizeInBytes = tx.getEstimatedPrivateTxEffectsSize();
193
- if (maxBlockSize !== undefined && totalSizeInBytes + preTxSizeInBytes > maxBlockSize) {
194
- this.log.warn(`Skipping processing of tx ${txHash} sized ${preTxSizeInBytes} bytes due to block size limit`, {
195
- txHash,
196
- sizeInBytes: preTxSizeInBytes,
197
- totalSizeInBytes,
198
- maxBlockSize,
199
- });
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
+ );
200
216
  continue;
201
217
  }
202
218
 
203
- // 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.
204
221
  const txGasLimit = tx.data.constants.txContext.gasSettings.gasLimits;
205
- if (maxBlockGas !== undefined && totalBlockGas.add(txGasLimit).gtAny(maxBlockGas)) {
222
+ if (isBuildingProposal && maxBlockGas !== undefined && totalBlockGas.add(txGasLimit).gtAny(maxBlockGas)) {
206
223
  this.log.warn(`Skipping processing of tx ${txHash} due to block gas limit`, {
207
224
  txHash,
208
225
  txGasLimit,
@@ -222,11 +239,6 @@ export class PublicProcessor implements Traceable {
222
239
  failed.push({ tx, error: new Error(`Tx failed preprocess validation: ${reason}`) });
223
240
  returns.push(new NestedProcessReturnValues([]));
224
241
  continue;
225
- } else if (result.result === 'skipped') {
226
- const reason = result.reason.join(', ');
227
- this.log.debug(`Skipping tx ${txHash.toString()} due to pre-process validation: ${reason}`);
228
- returns.push(new NestedProcessReturnValues([]));
229
- continue;
230
242
  } else {
231
243
  this.log.trace(`Tx ${txHash.toString()} is valid before processing.`);
232
244
  }
@@ -242,7 +254,9 @@ export class PublicProcessor implements Traceable {
242
254
  this.contractsDB.createCheckpoint();
243
255
 
244
256
  try {
245
- const [processedTx, returnValues, txDebugLogs] = await this.processTx(tx, deadline);
257
+ const [txProcessingTimeMs, [processedTx, returnValues, txDebugLogs]] = await elapsed(() =>
258
+ this.processTx(tx, deadline, signal),
259
+ );
246
260
 
247
261
  // Inject a fake processing failure after N txs if requested
248
262
  const fakeThrowAfter = this.opts.fakeThrowAfterProcessingTxCount;
@@ -251,23 +265,9 @@ export class PublicProcessor implements Traceable {
251
265
  }
252
266
 
253
267
  const txBlobFields = processedTx.txEffect.getNumBlobFields();
254
-
255
- // If the actual size of this tx would exceed block size, skip it
256
268
  const txSize = txBlobFields * Fr.SIZE_IN_BYTES;
257
- if (maxBlockSize !== undefined && totalSizeInBytes + txSize > maxBlockSize) {
258
- this.log.debug(`Skipping processed tx ${txHash} sized ${txSize} due to max block size.`, {
259
- txHash,
260
- sizeInBytes: txSize,
261
- totalSizeInBytes,
262
- maxBlockSize,
263
- });
264
- // Need to revert the checkpoint here and don't go any further
265
- await checkpoint.revert();
266
- this.contractsDB.revertCheckpoint();
267
- continue;
268
- }
269
269
 
270
- // 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.
271
271
  // Note: maxBlobFields already accounts for block end blob fields and previous blocks in checkpoint.
272
272
  if (maxBlobFields !== undefined && totalBlobFields + txBlobFields > maxBlobFields) {
273
273
  this.log.debug(
@@ -277,14 +277,37 @@ export class PublicProcessor implements Traceable {
277
277
  txBlobFields,
278
278
  totalBlobFields,
279
279
  maxBlobFields,
280
+ txProcessingTimeMs,
280
281
  },
281
282
  );
283
+ silentlySkippedCount += 1;
284
+ totalSilentlySkippedDurationMs += txProcessingTimeMs;
285
+ this.metrics.recordSilentlySkipped(txProcessingTimeMs);
282
286
  // Need to revert the checkpoint here and don't go any further
283
287
  await checkpoint.revert();
284
288
  this.contractsDB.revertCheckpoint();
285
289
  continue;
286
290
  }
287
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
+
288
311
  // FIXME(fcarreiro): it's ugly to have to notify the validator of nullifiers.
289
312
  // I'd rather pass the validators the processedTx as well and let them deal with it.
290
313
  nullifierCache?.addNullifiers(processedTx.txEffect.nullifiers.map(n => n.toBuffer()));
@@ -293,20 +316,21 @@ export class PublicProcessor implements Traceable {
293
316
  returns = returns.concat(returnValues);
294
317
  debugLogs.push(...txDebugLogs);
295
318
 
319
+ this.debugLogStore.storeLogs(processedTx.hash.toString(), txDebugLogs);
320
+
296
321
  totalPublicGas = totalPublicGas.add(processedTx.gasUsed.publicGas);
297
322
  totalBlockGas = totalBlockGas.add(processedTx.gasUsed.totalGas);
298
323
  totalSizeInBytes += txSize;
299
324
  totalBlobFields += txBlobFields;
325
+
326
+ // Commit the tx-level contracts checkpoint on success
327
+ this.contractsDB.commitCheckpoint();
300
328
  } catch (err: any) {
301
- if (err?.name === 'PublicProcessorTimeoutError') {
302
- this.log.warn(`Stopping tx processing due to timeout.`);
303
- // We hit the transaction execution deadline.
304
- // There may still be a transaction executing on a worker thread (C++ via NAPI).
305
- // Signal cancellation AND WAIT for the simulation to actually stop.
306
- // This is critical because C++ might be in the middle of a slow operation (e.g., pad_trees)
307
- // and won't check the cancellation flag until that operation completes.
308
- // Without waiting, we'd proceed to revert checkpoints while C++ is still writing to state.
309
- // 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.
310
334
  await this.publicTxSimulator.cancel?.();
311
335
 
312
336
  // Now stop the guarded fork to prevent any further TS-side access to the world state.
@@ -316,14 +340,10 @@ export class PublicProcessor implements Traceable {
316
340
  // 1. At least one outstanding checkpoint that has not been committed (the one created before we processed the tx).
317
341
  // 2. Possible state updates on that checkpoint or any others created during execution.
318
342
 
319
- // First we revert a checkpoint as managed by the ForkCheckpoint. This will revert whatever is the current checkpoint
320
- // which may not be the one originally created by this object. But that is ok, we do this to fulfil the ForkCheckpoint
321
- // lifecycle expectations and ensure it doesn't attempt to commit later on.
322
- await checkpoint.revert();
323
-
324
- // Now we want to revert any/all remaining checkpoints, destroying any outstanding state updates.
325
- // This needs to be done directly on the underlying fork as the guarded fork has been stopped.
326
- 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();
327
347
 
328
348
  // Revert any contracts added to the DB for the tx.
329
349
  this.contractsDB.revertCheckpoint();
@@ -335,9 +355,9 @@ export class PublicProcessor implements Traceable {
335
355
  break;
336
356
  }
337
357
 
338
- // Roll back state to start of TX before proceeding to next TX
339
- await checkpoint.revert();
340
- 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();
341
361
  this.contractsDB.revertCheckpoint();
342
362
  const errorMessage = err instanceof Error || err instanceof AssertionError ? err.message : 'Unknown error';
343
363
  this.log.warn(`Failed to process tx ${txHash.toString()}: ${errorMessage} ${err?.stack}`);
@@ -349,7 +369,6 @@ export class PublicProcessor implements Traceable {
349
369
  } finally {
350
370
  // Base case is we always commit the checkpoint. Using the ForkCheckpoint means this has no effect if the tx was previously reverted
351
371
  await checkpoint.commit();
352
- this.contractsDB.commitCheckpointOkIfNone();
353
372
  }
354
373
  }
355
374
 
@@ -357,15 +376,26 @@ export class PublicProcessor implements Traceable {
357
376
  const rate = duration > 0 ? totalPublicGas.l2Gas / duration : 0;
358
377
  this.metrics.recordAllTxs(totalPublicGas, rate);
359
378
 
360
- this.log.info(`Processed ${result.length} successful txs and ${failed.length} failed txs in ${duration}s`, {
361
- duration,
362
- rate,
363
- totalPublicGas,
364
- totalBlockGas,
365
- totalSizeInBytes,
366
- });
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
+ );
367
397
 
368
- return [result, failed, usedTxs, returns, totalBlobFields, debugLogs];
398
+ return [result, failed, usedTxs, returns, debugLogs];
369
399
  }
370
400
 
371
401
  private async checkWorldStateUnchanged(
@@ -388,9 +418,10 @@ export class PublicProcessor implements Traceable {
388
418
  private async processTx(
389
419
  tx: Tx,
390
420
  deadline: Date | undefined,
421
+ signal: AbortSignal | undefined,
391
422
  ): Promise<[ProcessedTx, NestedProcessReturnValues[], DebugLog[]]> {
392
423
  const [time, [processedTx, returnValues, debugLogs]] = await elapsed(() =>
393
- this.processTxWithinDeadline(tx, deadline),
424
+ this.processTxWithinDeadline(tx, deadline, signal),
394
425
  );
395
426
 
396
427
  this.log.verbose(
@@ -444,10 +475,11 @@ export class PublicProcessor implements Traceable {
444
475
  this.metrics.recordTreeInsertions(Number(treeInsertionEnd - treeInsertionStart) / 1_000);
445
476
  }
446
477
 
447
- /** 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. */
448
479
  private async processTxWithinDeadline(
449
480
  tx: Tx,
450
481
  deadline: Date | undefined,
482
+ signal: AbortSignal | undefined,
451
483
  ): Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined, DebugLog[]]> {
452
484
  const innerProcessFn: () => Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined, DebugLog[]]> =
453
485
  tx.hasPublicCalls() ? () => this.processTxWithPublicCalls(tx) : () => this.processPrivateOnlyTx(tx);
@@ -464,27 +496,38 @@ export class PublicProcessor implements Traceable {
464
496
  }
465
497
  : innerProcessFn;
466
498
 
467
- if (!deadline) {
499
+ const processingSignal = this.getProcessingSignal(tx, deadline, signal);
500
+ if (!processingSignal) {
468
501
  return await processFn();
469
502
  }
470
503
 
471
- 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
+
472
517
  const timeout = +deadline - this.dateProvider.now();
473
518
  if (timeout <= 0) {
474
519
  throw new PublicProcessorTimeoutError();
475
520
  }
476
521
 
522
+ const txHash = tx.getTxHash();
477
523
  this.log.debug(`Processing tx ${txHash.toString()} within ${timeout}ms`, {
478
524
  deadline: deadline.toISOString(),
479
525
  now: new Date(this.dateProvider.now()).toISOString(),
480
526
  txHash,
481
527
  });
482
528
 
483
- return await executeTimeout(
484
- () => processFn(),
485
- timeout,
486
- () => new PublicProcessorTimeoutError(),
487
- );
529
+ const timeoutSignal = AbortSignal.timeout(timeout);
530
+ return signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
488
531
  }
489
532
 
490
533
  /**
@@ -541,7 +584,7 @@ export class PublicProcessor implements Traceable {
541
584
  // Fee payment insertion has already been done. Do the rest.
542
585
  await this.doTreeInsertionsForPrivateOnlyTx(processedTx);
543
586
 
544
- await this.contractsDB.addNewContracts(tx);
587
+ this.contractsDB.addNewContracts(tx);
545
588
 
546
589
  return [processedTx, undefined, []];
547
590
  }