@aztec/simulator 0.68.0 → 0.68.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.
Files changed (66) hide show
  1. package/dest/avm/avm_memory_types.d.ts.map +1 -1
  2. package/dest/avm/avm_memory_types.js +21 -14
  3. package/dest/avm/avm_simulator.d.ts +1 -0
  4. package/dest/avm/avm_simulator.d.ts.map +1 -1
  5. package/dest/avm/avm_simulator.js +35 -18
  6. package/dest/avm/avm_tree.d.ts +0 -22
  7. package/dest/avm/avm_tree.d.ts.map +1 -1
  8. package/dest/avm/avm_tree.js +22 -81
  9. package/dest/avm/errors.d.ts +8 -1
  10. package/dest/avm/errors.d.ts.map +1 -1
  11. package/dest/avm/errors.js +13 -3
  12. package/dest/avm/journal/journal.d.ts +0 -4
  13. package/dest/avm/journal/journal.d.ts.map +1 -1
  14. package/dest/avm/journal/journal.js +1 -11
  15. package/dest/avm/journal/nullifiers.d.ts +0 -4
  16. package/dest/avm/journal/nullifiers.d.ts.map +1 -1
  17. package/dest/avm/journal/nullifiers.js +1 -11
  18. package/dest/avm/journal/public_storage.d.ts +1 -49
  19. package/dest/avm/journal/public_storage.d.ts.map +1 -1
  20. package/dest/avm/journal/public_storage.js +1 -19
  21. package/dest/avm/opcodes/addressing_mode.js +3 -3
  22. package/dest/avm/opcodes/ec_add.d.ts.map +1 -1
  23. package/dest/avm/opcodes/ec_add.js +5 -4
  24. package/dest/avm/opcodes/external_calls.js +2 -2
  25. package/dest/avm/opcodes/hashing.d.ts.map +1 -1
  26. package/dest/avm/opcodes/hashing.js +5 -5
  27. package/dest/avm/opcodes/misc.d.ts.map +1 -1
  28. package/dest/avm/opcodes/misc.js +3 -3
  29. package/dest/avm/opcodes/multi_scalar_mul.d.ts.map +1 -1
  30. package/dest/avm/opcodes/multi_scalar_mul.js +9 -6
  31. package/dest/public/bytecode_errors.d.ts +4 -0
  32. package/dest/public/bytecode_errors.d.ts.map +1 -0
  33. package/dest/public/bytecode_errors.js +7 -0
  34. package/dest/public/enqueued_call_side_effect_trace.d.ts +6 -1
  35. package/dest/public/enqueued_call_side_effect_trace.d.ts.map +1 -1
  36. package/dest/public/enqueued_call_side_effect_trace.js +58 -9
  37. package/dest/public/fixtures/index.d.ts +13 -8
  38. package/dest/public/fixtures/index.d.ts.map +1 -1
  39. package/dest/public/fixtures/index.js +97 -35
  40. package/dest/public/public_processor.d.ts +9 -3
  41. package/dest/public/public_processor.d.ts.map +1 -1
  42. package/dest/public/public_processor.js +49 -19
  43. package/dest/public/side_effect_errors.js +2 -2
  44. package/dest/public/unique_class_ids.d.ts +37 -0
  45. package/dest/public/unique_class_ids.d.ts.map +1 -0
  46. package/dest/public/unique_class_ids.js +66 -0
  47. package/package.json +10 -10
  48. package/src/avm/avm_memory_types.ts +29 -13
  49. package/src/avm/avm_simulator.ts +45 -19
  50. package/src/avm/avm_tree.ts +29 -91
  51. package/src/avm/errors.ts +13 -2
  52. package/src/avm/journal/journal.ts +0 -23
  53. package/src/avm/journal/nullifiers.ts +0 -11
  54. package/src/avm/journal/public_storage.ts +2 -21
  55. package/src/avm/opcodes/addressing_mode.ts +2 -2
  56. package/src/avm/opcodes/ec_add.ts +4 -3
  57. package/src/avm/opcodes/external_calls.ts +1 -1
  58. package/src/avm/opcodes/hashing.ts +6 -4
  59. package/src/avm/opcodes/misc.ts +4 -3
  60. package/src/avm/opcodes/multi_scalar_mul.ts +10 -5
  61. package/src/public/bytecode_errors.ts +6 -0
  62. package/src/public/enqueued_call_side_effect_trace.ts +75 -7
  63. package/src/public/fixtures/index.ts +143 -45
  64. package/src/public/public_processor.ts +79 -15
  65. package/src/public/side_effect_errors.ts +1 -1
  66. package/src/public/unique_class_ids.ts +80 -0
@@ -1,4 +1,4 @@
1
- import { MerkleTreeId, PublicExecutionRequest, Tx } from '@aztec/circuit-types';
1
+ import { MerkleTreeId, type MerkleTreeWriteOperations, PublicExecutionRequest, Tx } from '@aztec/circuit-types';
2
2
  import {
3
3
  type AvmCircuitInputs,
4
4
  BlockHeader,
@@ -14,6 +14,7 @@ import {
14
14
  GasSettings,
15
15
  GlobalVariables,
16
16
  MAX_L2_GAS_PER_TX_PUBLIC_PORTION,
17
+ MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS,
17
18
  PartialPrivateTailPublicInputsForPublic,
18
19
  PrivateKernelTailCircuitPublicInputs,
19
20
  type PublicFunction,
@@ -31,63 +32,54 @@ import { AztecAddress } from '@aztec/foundation/aztec-address';
31
32
  import { Fr, Point } from '@aztec/foundation/fields';
32
33
  import { openTmpStore } from '@aztec/kv-store/lmdb';
33
34
  import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest';
34
- import { PublicTxSimulator, WorldStateDB } from '@aztec/simulator';
35
+ import {
36
+ AvmEphemeralForest,
37
+ AvmSimulator,
38
+ PublicEnqueuedCallSideEffectTrace,
39
+ PublicTxSimulator,
40
+ WorldStateDB,
41
+ } from '@aztec/simulator';
35
42
  import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
36
43
  import { MerkleTrees } from '@aztec/world-state';
37
44
 
38
45
  import { strict as assert } from 'assert';
39
46
 
47
+ import { initContext, initExecutionEnvironment, initPersistableStateManager } from '../../avm/fixtures/index.js';
48
+
49
+ const TIMESTAMP = new Fr(99833);
50
+
40
51
  export async function simulateAvmTestContractGenerateCircuitInputs(
41
52
  functionName: string,
42
- calldata: Fr[] = [],
53
+ args: Fr[] = [],
43
54
  expectRevert: boolean = false,
44
- skipContractDeployments: boolean = false,
55
+ contractDataSource = new MockedAvmTestContractDataSource(),
45
56
  assertionErrString?: string,
46
57
  ): Promise<AvmCircuitInputs> {
47
- const sender = AztecAddress.random();
48
- const functionSelector = getAvmTestContractFunctionSelector(functionName);
49
- calldata = [functionSelector.toField(), ...calldata];
50
-
51
- const globalVariables = GlobalVariables.empty();
52
- globalVariables.gasFees = GasFees.empty();
53
- globalVariables.timestamp = new Fr(99833);
58
+ const globals = GlobalVariables.empty();
59
+ globals.timestamp = TIMESTAMP;
54
60
 
55
- const telemetry = new NoopTelemetryClient();
56
- const merkleTrees = await (await MerkleTrees.new(openTmpStore(), telemetry)).fork();
57
- const contractDataSource = new MockedAvmTestContractDataSource(skipContractDeployments);
61
+ const merkleTrees = await (await MerkleTrees.new(openTmpStore(), new NoopTelemetryClient())).fork();
62
+ await contractDataSource.deployContracts(merkleTrees);
58
63
  const worldStateDB = new WorldStateDB(merkleTrees, contractDataSource);
59
64
 
60
- const contractInstance = contractDataSource.contractInstance;
61
-
62
- if (!skipContractDeployments) {
63
- const contractAddressNullifier = siloNullifier(
64
- AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
65
- contractInstance.address.toField(),
66
- );
67
- await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0);
68
- // other contract address used by the bulk test's GETCONTRACTINSTANCE test
69
- const otherContractAddressNullifier = siloNullifier(
70
- AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
71
- contractDataSource.otherContractInstance.address.toField(),
72
- );
73
- await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [otherContractAddressNullifier.toBuffer()], 0);
74
- }
75
-
76
65
  const simulator = new PublicTxSimulator(
77
66
  merkleTrees,
78
67
  worldStateDB,
79
68
  new NoopTelemetryClient(),
80
- globalVariables,
69
+ globals,
81
70
  /*doMerkleOperations=*/ true,
82
71
  );
83
72
 
73
+ const sender = AztecAddress.random();
74
+ const functionSelector = getAvmTestContractFunctionSelector(functionName);
75
+ args = [functionSelector.toField(), ...args];
84
76
  const callContext = new CallContext(
85
77
  sender,
86
- contractInstance.address,
78
+ contractDataSource.firstContractInstance.address,
87
79
  contractDataSource.fnSelector,
88
80
  /*isStaticCall=*/ false,
89
81
  );
90
- const executionRequest = new PublicExecutionRequest(callContext, calldata);
82
+ const executionRequest = new PublicExecutionRequest(callContext, args);
91
83
 
92
84
  const tx: Tx = createTxForPublicCall(executionRequest);
93
85
 
@@ -108,6 +100,46 @@ export async function simulateAvmTestContractGenerateCircuitInputs(
108
100
  return avmCircuitInputs;
109
101
  }
110
102
 
103
+ export async function simulateAvmTestContractCall(
104
+ functionName: string,
105
+ args: Fr[] = [],
106
+ expectRevert: boolean = false,
107
+ contractDataSource = new MockedAvmTestContractDataSource(),
108
+ ) {
109
+ const globals = GlobalVariables.empty();
110
+ globals.timestamp = TIMESTAMP;
111
+
112
+ const merkleTrees = await (await MerkleTrees.new(openTmpStore(), new NoopTelemetryClient())).fork();
113
+ await contractDataSource.deployContracts(merkleTrees);
114
+ const worldStateDB = new WorldStateDB(merkleTrees, contractDataSource);
115
+
116
+ const trace = new PublicEnqueuedCallSideEffectTrace();
117
+ const ephemeralTrees = await AvmEphemeralForest.create(worldStateDB.getMerkleInterface());
118
+ const persistableState = initPersistableStateManager({
119
+ worldStateDB,
120
+ trace,
121
+ merkleTrees: ephemeralTrees,
122
+ doMerkleOperations: true,
123
+ });
124
+
125
+ const sender = AztecAddress.random();
126
+ const functionSelector = getAvmTestContractFunctionSelector(functionName);
127
+ args = [functionSelector.toField(), ...args];
128
+ const environment = initExecutionEnvironment({
129
+ calldata: args,
130
+ globals,
131
+ address: contractDataSource.firstContractInstance.address,
132
+ sender,
133
+ });
134
+ const context = initContext({ env: environment, persistableState });
135
+
136
+ // First we simulate (though it's not needed in this simple case).
137
+ const simulator = new AvmSimulator(context);
138
+ const results = await simulator.execute();
139
+
140
+ expect(results.reverted).toBe(expectRevert);
141
+ }
142
+
111
143
  /**
112
144
  * Craft a carrier transaction for a public call for simulation by PublicTxSimulator.
113
145
  */
@@ -151,21 +183,45 @@ export function createTxForPublicCall(
151
183
 
152
184
  export class MockedAvmTestContractDataSource implements ContractDataSource {
153
185
  private fnName = 'public_dispatch';
186
+ public fnSelector: FunctionSelector = getAvmTestContractFunctionSelector(this.fnName);
154
187
  private bytecode: Buffer;
155
- public fnSelector: FunctionSelector;
156
188
  private publicFn: PublicFunction;
157
- private contractClass: ContractClassPublic;
158
- public contractInstance: ContractInstanceWithAddress;
159
189
  private bytecodeCommitment: Fr;
190
+
191
+ // maps contract class ID to class
192
+ private contractClasses: Map<string, ContractClassPublic> = new Map();
193
+ // maps contract instance address to instance
194
+ public contractInstances: Map<string, ContractInstanceWithAddress> = new Map();
195
+
196
+ public firstContractInstance: ContractInstanceWithAddress = SerializableContractInstance.default().withAddress(
197
+ AztecAddress.fromNumber(0),
198
+ );
199
+ public instanceSameClassAsFirstContract: ContractInstanceWithAddress =
200
+ SerializableContractInstance.default().withAddress(AztecAddress.fromNumber(0));
160
201
  public otherContractInstance: ContractInstanceWithAddress;
161
202
 
162
- constructor(private noContractsDeployed: boolean = false) {
203
+ constructor(private skipContractDeployments: boolean = false) {
163
204
  this.bytecode = getAvmTestContractBytecode(this.fnName);
164
205
  this.fnSelector = getAvmTestContractFunctionSelector(this.fnName);
165
206
  this.publicFn = { bytecode: this.bytecode, selector: this.fnSelector };
166
- this.contractClass = makeContractClassPublic(0, this.publicFn);
167
- this.contractInstance = makeContractInstanceFromClassId(this.contractClass.id);
168
207
  this.bytecodeCommitment = computePublicBytecodeCommitment(this.bytecode);
208
+
209
+ // create enough unique classes to hit the limit (plus two extra)
210
+ for (let i = 0; i < MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS + 1; i++) {
211
+ const contractClass = makeContractClassPublic(/*seed=*/ i, this.publicFn);
212
+ const contractInstance = makeContractInstanceFromClassId(contractClass.id, /*seed=*/ i);
213
+ this.contractClasses.set(contractClass.id.toString(), contractClass);
214
+ this.contractInstances.set(contractInstance.address.toString(), contractInstance);
215
+ if (i === 0) {
216
+ this.firstContractInstance = contractInstance;
217
+ }
218
+ }
219
+ // a contract with the same class but different instance/address as the first contract
220
+ this.instanceSameClassAsFirstContract = makeContractInstanceFromClassId(
221
+ this.firstContractInstance.contractClassId,
222
+ /*seed=*/ 1000,
223
+ );
224
+
169
225
  // The values here should match those in `avm_simulator.test.ts`
170
226
  // Used for GETCONTRACTINSTANCE test
171
227
  this.otherContractInstance = new SerializableContractInstance({
@@ -183,6 +239,46 @@ export class MockedAvmTestContractDataSource implements ContractDataSource {
183
239
  }).withAddress(AztecAddress.fromNumber(0x4444));
184
240
  }
185
241
 
242
+ async deployContracts(merkleTrees: MerkleTreeWriteOperations) {
243
+ if (!this.skipContractDeployments) {
244
+ for (const contractInstance of this.contractInstances.values()) {
245
+ const contractAddressNullifier = siloNullifier(
246
+ AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
247
+ contractInstance.address.toField(),
248
+ );
249
+ await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0);
250
+ }
251
+
252
+ const instanceSameClassAsFirstContractNullifier = siloNullifier(
253
+ AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
254
+ this.instanceSameClassAsFirstContract.address.toField(),
255
+ );
256
+ await merkleTrees.batchInsert(
257
+ MerkleTreeId.NULLIFIER_TREE,
258
+ [instanceSameClassAsFirstContractNullifier.toBuffer()],
259
+ 0,
260
+ );
261
+
262
+ // other contract address used by the bulk test's GETCONTRACTINSTANCE test
263
+ const otherContractAddressNullifier = siloNullifier(
264
+ AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
265
+ this.otherContractInstance.address.toField(),
266
+ );
267
+ await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [otherContractAddressNullifier.toBuffer()], 0);
268
+ }
269
+ }
270
+
271
+ public static async create(
272
+ merkleTrees: MerkleTreeWriteOperations,
273
+ skipContractDeployments: boolean = false,
274
+ ): Promise<MockedAvmTestContractDataSource> {
275
+ const dataSource = new MockedAvmTestContractDataSource(skipContractDeployments);
276
+ if (!skipContractDeployments) {
277
+ await dataSource.deployContracts(merkleTrees);
278
+ }
279
+ return dataSource;
280
+ }
281
+
186
282
  getPublicFunction(_address: AztecAddress, _selector: FunctionSelector): Promise<PublicFunction> {
187
283
  return Promise.resolve(this.publicFn);
188
284
  }
@@ -191,8 +287,8 @@ export class MockedAvmTestContractDataSource implements ContractDataSource {
191
287
  throw new Error('Method not implemented.');
192
288
  }
193
289
 
194
- getContractClass(_id: Fr): Promise<ContractClassPublic> {
195
- return Promise.resolve(this.contractClass);
290
+ getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
291
+ return Promise.resolve(this.contractClasses.get(id.toString()));
196
292
  }
197
293
 
198
294
  getBytecodeCommitment(_id: Fr): Promise<Fr> {
@@ -204,11 +300,13 @@ export class MockedAvmTestContractDataSource implements ContractDataSource {
204
300
  }
205
301
 
206
302
  getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
207
- if (!this.noContractsDeployed) {
208
- if (address.equals(this.contractInstance.address)) {
209
- return Promise.resolve(this.contractInstance);
210
- } else if (address.equals(this.otherContractInstance.address)) {
303
+ if (!this.skipContractDeployments) {
304
+ if (address.equals(this.otherContractInstance.address)) {
211
305
  return Promise.resolve(this.otherContractInstance);
306
+ } else if (address.equals(this.instanceSameClassAsFirstContract.address)) {
307
+ return Promise.resolve(this.instanceSameClassAsFirstContract);
308
+ } else {
309
+ return Promise.resolve(this.contractInstances.get(address.toString()));
212
310
  }
213
311
  }
214
312
  return Promise.resolve(undefined);
@@ -24,7 +24,7 @@ import {
24
24
  } from '@aztec/circuits.js';
25
25
  import { padArrayEnd } from '@aztec/foundation/collection';
26
26
  import { createLogger } from '@aztec/foundation/log';
27
- import { Timer } from '@aztec/foundation/timer';
27
+ import { type DateProvider, Timer, elapsed, executeTimeout } from '@aztec/foundation/timer';
28
28
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
29
29
  import { ContractClassRegisteredEvent } from '@aztec/protocol-contracts/class-registerer';
30
30
  import { Attributes, type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
@@ -38,7 +38,11 @@ import { PublicTxSimulator } from './public_tx_simulator.js';
38
38
  * Creates new instances of PublicProcessor given the provided merkle tree db and contract data source.
39
39
  */
40
40
  export class PublicProcessorFactory {
41
- constructor(private contractDataSource: ContractDataSource, private telemetryClient: TelemetryClient) {}
41
+ constructor(
42
+ private contractDataSource: ContractDataSource,
43
+ private dateProvider: DateProvider,
44
+ private telemetryClient: TelemetryClient,
45
+ ) {}
42
46
 
43
47
  /**
44
48
  * Creates a new instance of a PublicProcessor.
@@ -56,7 +60,7 @@ export class PublicProcessorFactory {
56
60
  const historicalHeader = maybeHistoricalHeader ?? merkleTree.getInitialHeader();
57
61
 
58
62
  const worldStateDB = new WorldStateDB(merkleTree, this.contractDataSource);
59
- const publicTxSimulator = new PublicTxSimulator(
63
+ const publicTxSimulator = this.createPublicTxSimulator(
60
64
  merkleTree,
61
65
  worldStateDB,
62
66
  this.telemetryClient,
@@ -71,9 +75,35 @@ export class PublicProcessorFactory {
71
75
  historicalHeader,
72
76
  worldStateDB,
73
77
  publicTxSimulator,
78
+ this.dateProvider,
74
79
  this.telemetryClient,
75
80
  );
76
81
  }
82
+
83
+ protected createPublicTxSimulator(
84
+ db: MerkleTreeWriteOperations,
85
+ worldStateDB: WorldStateDB,
86
+ telemetryClient: TelemetryClient,
87
+ globalVariables: GlobalVariables,
88
+ doMerkleOperations: boolean,
89
+ enforceFeePayment: boolean,
90
+ ) {
91
+ return new PublicTxSimulator(
92
+ db,
93
+ worldStateDB,
94
+ telemetryClient,
95
+ globalVariables,
96
+ doMerkleOperations,
97
+ enforceFeePayment,
98
+ );
99
+ }
100
+ }
101
+
102
+ class PublicProcessorTimeoutError extends Error {
103
+ constructor(message: string = 'Timed out while processing tx') {
104
+ super(message);
105
+ this.name = 'PublicProcessorTimeoutError';
106
+ }
77
107
  }
78
108
 
79
109
  /**
@@ -88,6 +118,7 @@ export class PublicProcessor implements Traceable {
88
118
  protected historicalHeader: BlockHeader,
89
119
  protected worldStateDB: WorldStateDB,
90
120
  protected publicTxSimulator: PublicTxSimulator,
121
+ private dateProvider: DateProvider,
91
122
  telemetryClient: TelemetryClient,
92
123
  private log = createLogger('simulator:public-processor'),
93
124
  ) {
@@ -108,6 +139,7 @@ export class PublicProcessor implements Traceable {
108
139
  txs: Tx[],
109
140
  maxTransactions = txs.length,
110
141
  txValidator?: TxValidator<ProcessedTx>,
142
+ deadline?: Date,
111
143
  ): Promise<[ProcessedTx[], FailedTx[], NestedProcessReturnValues[]]> {
112
144
  // The processor modifies the tx objects in place, so we need to clone them.
113
145
  txs = txs.map(tx => Tx.clone(tx));
@@ -123,18 +155,19 @@ export class PublicProcessor implements Traceable {
123
155
  break;
124
156
  }
125
157
  try {
126
- const [processedTx, returnValues] = await this.processTx(tx, txValidator);
158
+ const [processedTx, returnValues] = await this.processTx(tx, txValidator, deadline);
127
159
  result.push(processedTx);
128
160
  returns = returns.concat(returnValues);
129
161
  totalGas = totalGas.add(processedTx.gasUsed.publicGas);
130
162
  } catch (err: any) {
163
+ if (err?.name === 'PublicProcessorTimeoutError') {
164
+ this.log.warn(`Stopping tx processing due to timeout.`);
165
+ break;
166
+ }
131
167
  const errorMessage = err instanceof Error ? err.message : 'Unknown error';
132
168
  this.log.warn(`Failed to process tx ${tx.getTxHash()}: ${errorMessage} ${err?.stack}`);
133
169
 
134
- failed.push({
135
- tx,
136
- error: err instanceof Error ? err : new Error(errorMessage),
137
- });
170
+ failed.push({ tx, error: err instanceof Error ? err : new Error(errorMessage) });
138
171
  returns.push(new NestedProcessReturnValues([]));
139
172
  }
140
173
  }
@@ -150,15 +183,14 @@ export class PublicProcessor implements Traceable {
150
183
  private async processTx(
151
184
  tx: Tx,
152
185
  txValidator?: TxValidator<ProcessedTx>,
186
+ deadline?: Date,
153
187
  ): Promise<[ProcessedTx, NestedProcessReturnValues[]]> {
154
- const [processedTx, returnValues] = !tx.hasPublicCalls()
155
- ? await this.processPrivateOnlyTx(tx)
156
- : await this.processTxWithPublicCalls(tx);
188
+ const [time, [processedTx, returnValues]] = await elapsed(() => this.processTxWithinDeadline(tx, deadline));
157
189
 
158
190
  this.log.verbose(
159
191
  !tx.hasPublicCalls()
160
- ? `Processed tx ${processedTx.hash} with no public calls`
161
- : `Processed tx ${processedTx.hash} with ${tx.enqueuedPublicFunctionCalls.length} public calls`,
192
+ ? `Processed tx ${processedTx.hash} with no public calls in ${time}ms`
193
+ : `Processed tx ${processedTx.hash} with ${tx.enqueuedPublicFunctionCalls.length} public calls in ${time}ms`,
162
194
  {
163
195
  txHash: processedTx.hash,
164
196
  txFee: processedTx.txEffect.transactionFee.toBigInt(),
@@ -172,6 +204,7 @@ export class PublicProcessor implements Traceable {
172
204
  unencryptedLogCount: processedTx.txEffect.unencryptedLogs.getTotalLogCount(),
173
205
  privateLogCount: processedTx.txEffect.privateLogs.length,
174
206
  l2ToL1MessageCount: processedTx.txEffect.l2ToL1Msgs.length,
207
+ durationMs: time,
175
208
  },
176
209
  );
177
210
 
@@ -226,6 +259,37 @@ export class PublicProcessor implements Traceable {
226
259
  return [processedTx, returnValues ?? []];
227
260
  }
228
261
 
262
+ /** Processes the given tx within deadline. Returns timeout if deadline is hit. */
263
+ private async processTxWithinDeadline(
264
+ tx: Tx,
265
+ deadline?: Date,
266
+ ): Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined]> {
267
+ const processFn: () => Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined]> = tx.hasPublicCalls()
268
+ ? () => this.processTxWithPublicCalls(tx)
269
+ : () => this.processPrivateOnlyTx(tx);
270
+
271
+ if (!deadline) {
272
+ return await processFn();
273
+ }
274
+
275
+ const timeout = +deadline - this.dateProvider.now();
276
+ this.log.debug(`Processing tx ${tx.getTxHash().toString()} within ${timeout}ms`, {
277
+ deadline: deadline.toISOString(),
278
+ now: new Date(this.dateProvider.now()).toISOString(),
279
+ txHash: tx.getTxHash().toString(),
280
+ });
281
+
282
+ if (timeout < 0) {
283
+ throw new PublicProcessorTimeoutError();
284
+ }
285
+
286
+ return await executeTimeout(
287
+ () => processFn(),
288
+ timeout,
289
+ () => new PublicProcessorTimeoutError(),
290
+ );
291
+ }
292
+
229
293
  /**
230
294
  * Creates the public data write for paying the tx fee.
231
295
  * This is used in private only txs, since for txs with public calls
@@ -260,7 +324,7 @@ export class PublicProcessor implements Traceable {
260
324
  @trackSpan('PublicProcessor.processPrivateOnlyTx', (tx: Tx) => ({
261
325
  [Attributes.TX_HASH]: tx.getTxHash().toString(),
262
326
  }))
263
- private async processPrivateOnlyTx(tx: Tx): Promise<[ProcessedTx]> {
327
+ private async processPrivateOnlyTx(tx: Tx): Promise<[ProcessedTx, undefined]> {
264
328
  const gasFees = this.globalVariables.gasFees;
265
329
  const transactionFee = tx.data.gasUsed.computeFee(gasFees);
266
330
 
@@ -279,7 +343,7 @@ export class PublicProcessor implements Traceable {
279
343
  .filter(log => ContractClassRegisteredEvent.isContractClassRegisteredEvent(log.data))
280
344
  .map(log => ContractClassRegisteredEvent.fromLog(log.data)),
281
345
  );
282
- return [processedTx];
346
+ return [processedTx, undefined];
283
347
  }
284
348
 
285
349
  @trackSpan('PublicProcessor.processTxWithPublicCalls', tx => ({
@@ -1,6 +1,6 @@
1
1
  export class SideEffectLimitReachedError extends Error {
2
2
  constructor(sideEffectType: string, limit: number) {
3
- super(`Reached the limit on number of '${sideEffectType}' side effects: ${limit}`);
3
+ super(`Reached the limit (${limit}) on number of '${sideEffectType}' per tx`);
4
4
  this.name = 'SideEffectLimitReachedError';
5
5
  }
6
6
  }
@@ -0,0 +1,80 @@
1
+ import { MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS } from '@aztec/circuits.js';
2
+
3
+ import { strict as assert } from 'assert';
4
+
5
+ /**
6
+ * A class manage a de-duplicated set of class IDs that errors if you try to add a duplicate.
7
+ * Useful for bytecode retrieval hints to avoid duplicates in parent trace & grandparent trace....
8
+ */
9
+ export class UniqueClassIds {
10
+ private readonly classIds: Set<string> = new Set();
11
+
12
+ constructor(private readonly parent?: UniqueClassIds) {}
13
+
14
+ /**
15
+ * Create a fork that references this one as its parent
16
+ */
17
+ public fork() {
18
+ return new UniqueClassIds(/*parent=*/ this);
19
+ }
20
+
21
+ /**
22
+ * Check for a class ID here or in parent's (recursively).
23
+ *
24
+ * @param classId - the contract class ID (as a string) to check
25
+ * @returns boolean: whether the class ID is here
26
+ */
27
+ public has(classId: string): boolean {
28
+ // First try check this' classIds
29
+ let here = this.classIds.has(classId);
30
+ // Then try parent's
31
+ if (!here && this.parent) {
32
+ // Note: this will recurse to grandparent/etc until we reach top or find it
33
+ here = this.parent.has(classId);
34
+ }
35
+ return here;
36
+ }
37
+
38
+ /**
39
+ * Get the total number of classIds
40
+ */
41
+ public size(): number {
42
+ return this.classIds.size + (this.parent ? this.parent.size() : 0);
43
+ }
44
+
45
+ /**
46
+ * Add a class ID (if not already present) to the set.
47
+ *
48
+ * @param classId - the contract class ID (as a string)
49
+ */
50
+ public add(classId: string) {
51
+ assert(!this.has(classId), `Bug! Tried to add duplicate classId ${classId} to set of unique classIds.`);
52
+ if (!this.has(classId)) {
53
+ this.classIds.add(classId);
54
+ assert(
55
+ this.size() <= MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS,
56
+ `Bug! Surpassed limit (${MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS}) of unique contract class IDs used for bytecode retrievals.`,
57
+ );
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Merge in another set of unique class IDs into this one, but fail on duplicates.
63
+ *
64
+ * @param incoming: other unique class IDs
65
+ */
66
+ public acceptAndMerge(incoming: UniqueClassIds) {
67
+ for (const classId of incoming.classIds.keys()) {
68
+ assert(
69
+ !this.has(classId),
70
+ `Bug! Cannot merge classId ${classId} into set of unique classIds as it already exists.`,
71
+ );
72
+ this.classIds.add(classId);
73
+ }
74
+ // since set() has an assertion, and size() always checks parent, this should be impossible
75
+ assert(
76
+ this.size() <= MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS,
77
+ `Bug! Merging unique class Ids should never exceed the limit of ${MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS}.`,
78
+ );
79
+ }
80
+ }