@aztec/txe 0.0.1-commit.fcb71a6 → 0.0.1-commit.ff7989d6c

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 (64) hide show
  1. package/dest/index.d.ts +1 -1
  2. package/dest/index.d.ts.map +1 -1
  3. package/dest/index.js +82 -50
  4. package/dest/oracle/interfaces.d.ts +4 -4
  5. package/dest/oracle/interfaces.d.ts.map +1 -1
  6. package/dest/oracle/txe_oracle_public_context.d.ts +3 -3
  7. package/dest/oracle/txe_oracle_public_context.d.ts.map +1 -1
  8. package/dest/oracle/txe_oracle_public_context.js +6 -6
  9. package/dest/oracle/txe_oracle_top_level_context.d.ts +6 -6
  10. package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
  11. package/dest/oracle/txe_oracle_top_level_context.js +107 -40
  12. package/dest/rpc_translator.d.ts +21 -15
  13. package/dest/rpc_translator.d.ts.map +1 -1
  14. package/dest/rpc_translator.js +78 -53
  15. package/dest/state_machine/archiver.d.ts +20 -67
  16. package/dest/state_machine/archiver.d.ts.map +1 -1
  17. package/dest/state_machine/archiver.js +59 -178
  18. package/dest/state_machine/dummy_p2p_client.d.ts +19 -14
  19. package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
  20. package/dest/state_machine/dummy_p2p_client.js +38 -23
  21. package/dest/state_machine/global_variable_builder.d.ts +2 -2
  22. package/dest/state_machine/global_variable_builder.d.ts.map +1 -1
  23. package/dest/state_machine/global_variable_builder.js +1 -1
  24. package/dest/state_machine/index.d.ts +5 -5
  25. package/dest/state_machine/index.d.ts.map +1 -1
  26. package/dest/state_machine/index.js +35 -12
  27. package/dest/state_machine/mock_epoch_cache.d.ts +9 -6
  28. package/dest/state_machine/mock_epoch_cache.d.ts.map +1 -1
  29. package/dest/state_machine/mock_epoch_cache.js +14 -7
  30. package/dest/state_machine/synchronizer.d.ts +3 -3
  31. package/dest/state_machine/synchronizer.d.ts.map +1 -1
  32. package/dest/txe_session.d.ts +6 -6
  33. package/dest/txe_session.d.ts.map +1 -1
  34. package/dest/txe_session.js +92 -24
  35. package/dest/util/encoding.d.ts +17 -17
  36. package/dest/util/txe_public_contract_data_source.d.ts +2 -3
  37. package/dest/util/txe_public_contract_data_source.d.ts.map +1 -1
  38. package/dest/util/txe_public_contract_data_source.js +5 -22
  39. package/dest/utils/block_creation.d.ts +4 -4
  40. package/dest/utils/block_creation.d.ts.map +1 -1
  41. package/dest/utils/block_creation.js +18 -5
  42. package/dest/utils/tx_effect_creation.d.ts +2 -3
  43. package/dest/utils/tx_effect_creation.d.ts.map +1 -1
  44. package/dest/utils/tx_effect_creation.js +3 -6
  45. package/package.json +16 -16
  46. package/src/index.ts +83 -49
  47. package/src/oracle/interfaces.ts +3 -3
  48. package/src/oracle/txe_oracle_public_context.ts +6 -8
  49. package/src/oracle/txe_oracle_top_level_context.ts +131 -91
  50. package/src/rpc_translator.ts +81 -55
  51. package/src/state_machine/archiver.ts +54 -220
  52. package/src/state_machine/dummy_p2p_client.ts +54 -31
  53. package/src/state_machine/global_variable_builder.ts +1 -1
  54. package/src/state_machine/index.ts +49 -11
  55. package/src/state_machine/mock_epoch_cache.ts +15 -11
  56. package/src/state_machine/synchronizer.ts +2 -2
  57. package/src/txe_session.ts +99 -80
  58. package/src/util/txe_public_contract_data_source.ts +10 -36
  59. package/src/utils/block_creation.ts +19 -16
  60. package/src/utils/tx_effect_creation.ts +3 -11
  61. package/dest/util/txe_contract_store.d.ts +0 -12
  62. package/dest/util/txe_contract_store.d.ts.map +0 -1
  63. package/dest/util/txe_contract_store.js +0 -22
  64. package/src/util/txe_contract_store.ts +0 -36
package/src/index.ts CHANGED
@@ -9,9 +9,12 @@ import { Fr } from '@aztec/aztec.js/fields';
9
9
  import { PublicKeys, deriveKeys } from '@aztec/aztec.js/keys';
10
10
  import { createSafeJsonRpcServer } from '@aztec/foundation/json-rpc/server';
11
11
  import type { Logger } from '@aztec/foundation/log';
12
- import { type ProtocolContract, protocolContractNames } from '@aztec/protocol-contracts';
12
+ import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
13
+ import { protocolContractNames } from '@aztec/protocol-contracts';
13
14
  import { BundledProtocolContractsProvider } from '@aztec/protocol-contracts/providers/bundle';
15
+ import { ContractStore } from '@aztec/pxe/server';
14
16
  import { computeArtifactHash } from '@aztec/stdlib/contract';
17
+ import type { ContractArtifactWithHash } from '@aztec/stdlib/contract';
15
18
  import type { ApiSchemaFor } from '@aztec/stdlib/schemas';
16
19
  import { zodFor } from '@aztec/stdlib/schemas';
17
20
 
@@ -33,18 +36,24 @@ import {
33
36
  fromSingle,
34
37
  toSingle,
35
38
  } from './util/encoding.js';
36
- import type { ContractArtifactWithHash } from './util/txe_contract_store.js';
37
39
 
38
40
  const sessions = new Map<number, TXESession>();
39
41
 
40
42
  /*
41
43
  * TXE typically has to load the same contract artifacts over and over again for multiple tests,
42
- * so we cache them here to avoid both loading them from disk repeatedly and computing their artifact hashes
44
+ * so we cache them here to avoid loading from disk repeatedly.
45
+ *
46
+ * The in-flight map coalesces concurrent requests for the same cache key so that
47
+ * computeArtifactHash (very expensive) is only run once even under parallelism.
43
48
  */
44
49
  const TXEArtifactsCache = new Map<
45
50
  string,
46
51
  { artifact: ContractArtifactWithHash; instance: ContractInstanceWithAddress }
47
52
  >();
53
+ const TXEArtifactsCacheInFlight = new Map<
54
+ string,
55
+ Promise<{ artifact: ContractArtifactWithHash; instance: ContractInstanceWithAddress }>
56
+ >();
48
57
 
49
58
  type TXEForeignCallInput = {
50
59
  session_id: number;
@@ -68,7 +77,7 @@ const TXEForeignCallInputSchema = zodFor<TXEForeignCallInput>()(
68
77
  );
69
78
 
70
79
  class TXEDispatcher {
71
- private protocolContracts!: ProtocolContract[];
80
+ private contractStore!: ContractStore;
72
81
 
73
82
  constructor(private logger: Logger) {}
74
83
 
@@ -135,29 +144,36 @@ class TXEDispatcher {
135
144
  this.logger.debug(`Using cached artifact for ${cacheKey}`);
136
145
  ({ artifact, instance } = TXEArtifactsCache.get(cacheKey)!);
137
146
  } else {
138
- this.logger.debug(`Loading compiled artifact ${artifactPath}`);
139
- const artifactJSON = JSON.parse(await readFile(artifactPath, 'utf-8')) as NoirCompiledContract;
140
- const artifactWithoutHash = loadContractArtifact(artifactJSON);
141
- artifact = {
142
- ...artifactWithoutHash,
143
- // Artifact hash is *very* expensive to compute, so we do it here once
144
- // and the TXE contract data provider can cache it
145
- artifactHash: await computeArtifactHash(artifactWithoutHash),
146
- };
147
- this.logger.debug(
148
- `Deploy ${
149
- artifact.name
150
- } with initializer ${initializer}(${decodedArgs}) and public keys hash ${publicKeysHash.toString()}`,
151
- );
152
- instance = await getContractInstanceFromInstantiationParams(artifact, {
153
- constructorArgs: decodedArgs,
154
- skipArgsDecoding: true,
155
- salt: Fr.ONE,
156
- publicKeys,
157
- constructorArtifact: initializer ? initializer : undefined,
158
- deployer: AztecAddress.ZERO,
159
- });
160
- TXEArtifactsCache.set(cacheKey, { artifact, instance });
147
+ if (!TXEArtifactsCacheInFlight.has(cacheKey)) {
148
+ this.logger.debug(`Loading compiled artifact ${artifactPath}`);
149
+ const compute = async () => {
150
+ const artifactJSON = JSON.parse(await readFile(artifactPath, 'utf-8')) as NoirCompiledContract;
151
+ const artifactWithoutHash = loadContractArtifact(artifactJSON);
152
+ const computedArtifact: ContractArtifactWithHash = {
153
+ ...artifactWithoutHash,
154
+ // Artifact hash is *very* expensive to compute, so we do it here once
155
+ // and the TXE contract data provider can cache it
156
+ artifactHash: await computeArtifactHash(artifactWithoutHash),
157
+ };
158
+ this.logger.debug(
159
+ `Deploy ${computedArtifact.name} with initializer ${initializer}(${decodedArgs}) and public keys hash ${publicKeysHash.toString()}`,
160
+ );
161
+ const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
162
+ constructorArgs: decodedArgs,
163
+ skipArgsDecoding: true,
164
+ salt: Fr.ONE,
165
+ publicKeys,
166
+ constructorArtifact: initializer ? initializer : undefined,
167
+ deployer: AztecAddress.ZERO,
168
+ });
169
+ const result = { artifact: computedArtifact, instance: computedInstance };
170
+ TXEArtifactsCache.set(cacheKey, result);
171
+ TXEArtifactsCacheInFlight.delete(cacheKey);
172
+ return result;
173
+ };
174
+ TXEArtifactsCacheInFlight.set(cacheKey, compute());
175
+ }
176
+ ({ artifact, instance } = await TXEArtifactsCacheInFlight.get(cacheKey)!);
161
177
  }
162
178
 
163
179
  inputs.splice(0, 1, artifact, instance, toSingle(secret));
@@ -175,23 +191,35 @@ class TXEDispatcher {
175
191
  this.logger.debug(`Using cached artifact for ${cacheKey}`);
176
192
  ({ artifact, instance } = TXEArtifactsCache.get(cacheKey)!);
177
193
  } else {
178
- const keys = await deriveKeys(secret);
179
- const args = [keys.publicKeys.masterIncomingViewingPublicKey.x, keys.publicKeys.masterIncomingViewingPublicKey.y];
180
- artifact = {
181
- ...SchnorrAccountContractArtifact,
182
- // Artifact hash is *very* expensive to compute, so we do it here once
183
- // and the TXE contract data provider can cache it
184
- artifactHash: await computeArtifactHash(SchnorrAccountContractArtifact),
185
- };
186
- instance = await getContractInstanceFromInstantiationParams(artifact, {
187
- constructorArgs: args,
188
- skipArgsDecoding: true,
189
- salt: Fr.ONE,
190
- publicKeys: keys.publicKeys,
191
- constructorArtifact: 'constructor',
192
- deployer: AztecAddress.ZERO,
193
- });
194
- TXEArtifactsCache.set(cacheKey, { artifact, instance });
194
+ if (!TXEArtifactsCacheInFlight.has(cacheKey)) {
195
+ const compute = async () => {
196
+ const keys = await deriveKeys(secret);
197
+ const args = [
198
+ keys.publicKeys.masterIncomingViewingPublicKey.x,
199
+ keys.publicKeys.masterIncomingViewingPublicKey.y,
200
+ ];
201
+ const computedArtifact: ContractArtifactWithHash = {
202
+ ...SchnorrAccountContractArtifact,
203
+ // Artifact hash is *very* expensive to compute, so we do it here once
204
+ // and the TXE contract data provider can cache it
205
+ artifactHash: await computeArtifactHash(SchnorrAccountContractArtifact),
206
+ };
207
+ const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
208
+ constructorArgs: args,
209
+ skipArgsDecoding: true,
210
+ salt: Fr.ONE,
211
+ publicKeys: keys.publicKeys,
212
+ constructorArtifact: 'constructor',
213
+ deployer: AztecAddress.ZERO,
214
+ });
215
+ const result = { artifact: computedArtifact, instance: computedInstance };
216
+ TXEArtifactsCache.set(cacheKey, result);
217
+ TXEArtifactsCacheInFlight.delete(cacheKey);
218
+ return result;
219
+ };
220
+ TXEArtifactsCacheInFlight.set(cacheKey, compute());
221
+ }
222
+ ({ artifact, instance } = await TXEArtifactsCacheInFlight.get(cacheKey)!);
195
223
  }
196
224
 
197
225
  inputs.splice(0, 0, artifact, instance);
@@ -204,12 +232,18 @@ class TXEDispatcher {
204
232
 
205
233
  if (!sessions.has(sessionId)) {
206
234
  this.logger.debug(`Creating new session ${sessionId}`);
207
- if (!this.protocolContracts) {
208
- this.protocolContracts = await Promise.all(
209
- protocolContractNames.map(name => new BundledProtocolContractsProvider().getProtocolContractArtifact(name)),
210
- );
235
+ if (!this.contractStore) {
236
+ const kvStore = await openTmpStore('txe-contracts');
237
+ this.contractStore = new ContractStore(kvStore);
238
+ const provider = new BundledProtocolContractsProvider();
239
+ for (const name of protocolContractNames) {
240
+ const { instance, artifact } = await provider.getProtocolContractArtifact(name);
241
+ await this.contractStore.addContractArtifact(artifact);
242
+ await this.contractStore.addContractInstance(instance);
243
+ }
244
+ this.logger.debug('Registered protocol contracts in shared contract store');
211
245
  }
212
- sessions.set(sessionId, await TXESession.init(this.protocolContracts));
246
+ sessions.set(sessionId, await TXESession.init(this.contractStore));
213
247
  }
214
248
 
215
249
  switch (functionName) {
@@ -33,9 +33,9 @@ export interface IAvmExecutionOracle {
33
33
  avmOpcodeVersion(): Promise<Fr>;
34
34
  avmOpcodeEmitNullifier(nullifier: Fr): Promise<void>;
35
35
  avmOpcodeEmitNoteHash(noteHash: Fr): Promise<void>;
36
- avmOpcodeNullifierExists(innerNullifier: Fr, targetAddress: AztecAddress): Promise<boolean>;
36
+ avmOpcodeNullifierExists(siloedNullifier: Fr): Promise<boolean>;
37
37
  avmOpcodeStorageWrite(slot: Fr, value: Fr): Promise<void>;
38
- avmOpcodeStorageRead(slot: Fr): Promise<Fr>;
38
+ avmOpcodeStorageRead(slot: Fr, contractAddress: AztecAddress): Promise<Fr>;
39
39
  }
40
40
 
41
41
  /**
@@ -72,7 +72,7 @@ export interface ITxeExecutionOracle {
72
72
  argsHash: Fr,
73
73
  isStaticCall: boolean,
74
74
  ): Promise<Fr[]>;
75
- txeSimulateUtilityFunction(
75
+ txeExecuteUtilityFunction(
76
76
  targetContractAddress: AztecAddress,
77
77
  functionSelector: FunctionSelector,
78
78
  args: Fr[],
@@ -78,13 +78,11 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle {
78
78
  this.transientUniqueNoteHashes.push(siloedNoteHash);
79
79
  }
80
80
 
81
- async avmOpcodeNullifierExists(innerNullifier: Fr, targetAddress: AztecAddress): Promise<boolean> {
82
- const nullifier = await siloNullifier(targetAddress, innerNullifier!);
83
-
81
+ async avmOpcodeNullifierExists(siloedNullifier: Fr): Promise<boolean> {
84
82
  const treeIndex = (
85
- await this.forkedWorldTrees.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()])
83
+ await this.forkedWorldTrees.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()])
86
84
  )[0];
87
- const transientIndex = this.transientSiloedNullifiers.find(n => n.equals(nullifier));
85
+ const transientIndex = this.transientSiloedNullifiers.find(n => n.equals(siloedNullifier));
88
86
 
89
87
  return treeIndex !== undefined || transientIndex !== undefined;
90
88
  }
@@ -101,8 +99,8 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle {
101
99
  ]);
102
100
  }
103
101
 
104
- async avmOpcodeStorageRead(slot: Fr): Promise<Fr> {
105
- const leafSlot = await computePublicDataTreeLeafSlot(this.contractAddress, slot);
102
+ async avmOpcodeStorageRead(slot: Fr, contractAddress: AztecAddress): Promise<Fr> {
103
+ const leafSlot = await computePublicDataTreeLeafSlot(contractAddress, slot);
106
104
 
107
105
  const lowLeafResult = await this.forkedWorldTrees.getPreviousValueIndex(
108
106
  MerkleTreeId.PUBLIC_DATA_TREE,
@@ -119,7 +117,7 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle {
119
117
  )) as PublicDataTreeLeafPreimage
120
118
  ).leaf.value;
121
119
 
122
- this.logger.debug('AVM storage read', { slot, value });
120
+ this.logger.debug('AVM storage read', { slot, contractAddress, value });
123
121
 
124
122
  return value;
125
123
  }
@@ -12,9 +12,11 @@ import { Fr } from '@aztec/foundation/curves/bn254';
12
12
  import { LogLevels, type Logger, applyStringFormatting, createLogger } from '@aztec/foundation/log';
13
13
  import { TestDateProvider } from '@aztec/foundation/timer';
14
14
  import type { KeyStore } from '@aztec/key-store';
15
+ import type { AccessScopes } from '@aztec/pxe/client/lazy';
15
16
  import {
16
17
  AddressStore,
17
18
  CapsuleStore,
19
+ type ContractStore,
18
20
  NoteStore,
19
21
  ORACLE_VERSION,
20
22
  PrivateEventStore,
@@ -83,7 +85,6 @@ import { ForkCheckpoint } from '@aztec/world-state';
83
85
  import { DEFAULT_ADDRESS } from '../constants.js';
84
86
  import type { TXEStateMachine } from '../state_machine/index.js';
85
87
  import type { TXEAccountStore } from '../util/txe_account_store.js';
86
- import type { TXEContractStore } from '../util/txe_contract_store.js';
87
88
  import { TXEPublicContractDataSource } from '../util/txe_public_contract_data_source.js';
88
89
  import { getSingleTxBlockRequestHash, insertTxEffectIntoWorldTrees, makeTXEBlock } from '../utils/block_creation.js';
89
90
  import type { ITxeExecutionOracle } from './interfaces.js';
@@ -96,7 +97,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
96
97
 
97
98
  constructor(
98
99
  private stateMachine: TXEStateMachine,
99
- private contractStore: TXEContractStore,
100
+ private contractStore: ContractStore,
100
101
  private noteStore: NoteStore,
101
102
  private keyStore: KeyStore,
102
103
  private addressStore: AddressStore,
@@ -106,6 +107,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
106
107
  private senderAddressBookStore: SenderAddressBookStore,
107
108
  private capsuleStore: CapsuleStore,
108
109
  private privateEventStore: PrivateEventStore,
110
+ private jobId: string,
109
111
  private nextBlockTimestamp: bigint,
110
112
  private version: Fr,
111
113
  private chainId: Fr,
@@ -130,13 +132,14 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
130
132
  }
131
133
 
132
134
  // We instruct users to debug contracts via this oracle, so it makes sense that they'd expect it to also work in tests
133
- utilityDebugLog(level: number, message: string, fields: Fr[]): void {
135
+ utilityLog(level: number, message: string, fields: Fr[]): Promise<void> {
134
136
  if (!LogLevels[level]) {
135
- throw new Error(`Invalid debug log level: ${level}`);
137
+ throw new Error(`Invalid log level: ${level}`);
136
138
  }
137
139
  const levelName = LogLevels[level];
138
140
 
139
141
  this.logger[levelName](`${applyStringFormatting(message, fields)}`, { module: `${this.logger.module}:debug_log` });
142
+ return Promise.resolve();
140
143
  }
141
144
 
142
145
  txeGetDefaultAddress(): AztecAddress {
@@ -156,7 +159,8 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
156
159
  }
157
160
 
158
161
  async txeGetLastTxEffects() {
159
- const block = await this.stateMachine.archiver.getL2Block('latest');
162
+ const latestBlockNumber = await this.stateMachine.archiver.getBlockNumber();
163
+ const block = await this.stateMachine.archiver.getBlock(latestBlockNumber);
160
164
 
161
165
  if (block!.body.txEffects.length != 1) {
162
166
  // Note that calls like env.mine() will result in blocks with no transactions, hitting this
@@ -207,7 +211,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
207
211
  await this.txeAddAccount(artifact, instance, secret);
208
212
  } else {
209
213
  await this.contractStore.addContractInstance(instance);
210
- await this.contractStore.addContractArtifact(instance.currentContractClassId, artifact);
214
+ await this.contractStore.addContractArtifact(artifact);
211
215
  this.logger.debug(`Deployed ${artifact.name} at ${instance.address}`);
212
216
  }
213
217
  }
@@ -217,7 +221,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
217
221
 
218
222
  this.logger.debug(`Deployed ${artifact.name} at ${instance.address}`);
219
223
  await this.contractStore.addContractInstance(instance);
220
- await this.contractStore.addContractArtifact(instance.currentContractClassId, artifact);
224
+ await this.contractStore.addContractArtifact(artifact);
221
225
 
222
226
  const completeAddress = await this.keyStore.addAccount(secret, partialAddress);
223
227
  await this.accountStore.setAccount(completeAddress.address, completeAddress);
@@ -294,12 +298,24 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
294
298
  throw new Error(message);
295
299
  }
296
300
 
301
+ // When `from` is the zero address (e.g. when deploying a new account contract), we return an
302
+ // empty scope list which acts as deny-all: no notes are visible and no keys are accessible.
303
+ const effectiveScopes = from.isZero() ? [] : [from];
304
+
297
305
  // Sync notes before executing private function to discover notes from previous transactions
298
- const utilityExecutor = async (call: FunctionCall) => {
299
- await this.executeUtilityCall(call);
306
+ const utilityExecutor = async (call: FunctionCall, execScopes: AccessScopes) => {
307
+ await this.executeUtilityCall(call, execScopes);
300
308
  };
301
309
 
302
- await this.contractStore.syncPrivateState(targetContractAddress, functionSelector, utilityExecutor);
310
+ const blockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
311
+ await this.stateMachine.contractSyncService.ensureContractSynced(
312
+ targetContractAddress,
313
+ functionSelector,
314
+ utilityExecutor,
315
+ blockHeader,
316
+ this.jobId,
317
+ effectiveScopes,
318
+ );
303
319
 
304
320
  const blockNumber = await this.txeGetNextBlockNumber();
305
321
 
@@ -311,50 +327,48 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
311
327
 
312
328
  const txContext = new TxContext(this.chainId, this.version, gasSettings);
313
329
 
314
- const blockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
315
-
316
330
  const protocolNullifier = await computeProtocolNullifier(getSingleTxBlockRequestHash(blockNumber));
317
331
  const noteCache = new ExecutionNoteCache(protocolNullifier);
332
+ // In production, the account contract sets the min revertible counter before calling the app function.
333
+ // Since TXE bypasses the account contract, we simulate this by setting minRevertibleSideEffectCounter to 1,
334
+ // marking all side effects as revertible.
335
+ const minRevertibleSideEffectCounter = 1;
336
+ await noteCache.setMinRevertibleSideEffectCounter(minRevertibleSideEffectCounter);
318
337
  const taggingIndexCache = new ExecutionTaggingIndexCache();
319
338
 
320
339
  const simulator = new WASMSimulator();
321
340
 
322
- const privateExecutionOracle = new PrivateExecutionOracle(
341
+ const privateExecutionOracle = new PrivateExecutionOracle({
323
342
  argsHash,
324
343
  txContext,
325
344
  callContext,
326
- /** Header of a block whose state is used during private execution (not the block the transaction is included in). */
327
- blockHeader,
345
+ anchorBlockHeader: blockHeader,
328
346
  utilityExecutor,
329
- /** List of transient auth witnesses to be used during this simulation */
330
- Array.from(this.authwits.values()),
331
- /** List of transient auth witnesses to be used during this simulation */
332
- [],
333
- HashedValuesCache.create([new HashedValues(args, argsHash)]),
347
+ authWitnesses: Array.from(this.authwits.values()),
348
+ capsules: [],
349
+ executionCache: HashedValuesCache.create([new HashedValues(args, argsHash)]),
334
350
  noteCache,
335
351
  taggingIndexCache,
336
- this.contractStore,
337
- this.noteStore,
338
- this.keyStore,
339
- this.addressStore,
340
- this.stateMachine.node,
341
- this.stateMachine.anchorBlockStore,
342
- this.senderTaggingStore,
343
- this.recipientTaggingStore,
344
- this.senderAddressBookStore,
345
- this.capsuleStore,
346
- this.privateEventStore,
347
- 0,
348
- 1,
349
- undefined, // log
350
- undefined, // scopes
351
- /**
352
- * In TXE, the typical transaction entrypoint is skipped, so we need to simulate the actions that such a
353
- * contract would perform, including setting senderForTags.
354
- */
355
- from,
352
+ contractStore: this.contractStore,
353
+ noteStore: this.noteStore,
354
+ keyStore: this.keyStore,
355
+ addressStore: this.addressStore,
356
+ aztecNode: this.stateMachine.node,
357
+ senderTaggingStore: this.senderTaggingStore,
358
+ recipientTaggingStore: this.recipientTaggingStore,
359
+ senderAddressBookStore: this.senderAddressBookStore,
360
+ capsuleStore: this.capsuleStore,
361
+ privateEventStore: this.privateEventStore,
362
+ contractSyncService: this.stateMachine.contractSyncService,
363
+ jobId: this.jobId,
364
+ totalPublicCalldataCount: 0,
365
+ sideEffectCounter: minRevertibleSideEffectCounter,
366
+ scopes: effectiveScopes,
367
+ // In TXE, the typical transaction entrypoint is skipped, so we need to simulate the actions that such a
368
+ // contract would perform, including setting senderForTags.
369
+ senderForTags: from,
356
370
  simulator,
357
- );
371
+ });
358
372
 
359
373
  // Note: This is a slight modification of simulator.run without any of the checks. Maybe we should modify simulator.run with a boolean value to skip checks.
360
374
  let result: PrivateExecutionResult;
@@ -381,19 +395,22 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
381
395
  }),
382
396
  );
383
397
 
384
- // TXE's top level context does not track side effect counters, and as such, minRevertibleSideEffectCounter is always 0.
385
- // This has the unfortunate consequence of always producing revertible nullifiers, which means we
386
- // must set the firstNullifierHint to Fr.ZERO so the txRequestHash is always used as nonce generator
387
- result = new PrivateExecutionResult(executionResult, Fr.ZERO, publicFunctionsCalldata);
398
+ noteCache.finish();
399
+ const nonceGenerator = noteCache.getNonceGenerator();
400
+ result = new PrivateExecutionResult(executionResult, nonceGenerator, publicFunctionsCalldata);
388
401
  } catch (err) {
389
402
  throw createSimulationError(err instanceof Error ? err : new Error('Unknown error during private execution'));
390
403
  }
391
404
 
392
- // According to the protocol rules, the nonce generator for the note hashes
393
- // can either be the first nullifier in the tx or the hash of the initial tx request
394
- // if there are none.
395
- const nonceGenerator = result.firstNullifier.equals(Fr.ZERO) ? protocolNullifier : result.firstNullifier;
396
- const { publicInputs } = await generateSimulatedProvingResult(result, nonceGenerator, this.contractStore);
405
+ // According to the protocol rules, there must be at least one nullifier in the tx. The first nullifier is used as
406
+ // the nonce generator for the note hashes.
407
+ // We pass the non-zero minRevertibleSideEffectCounter to make sure the side effects are split correctly.
408
+ const { publicInputs } = await generateSimulatedProvingResult(
409
+ result,
410
+ (addr, sel) => this.contractStore.getDebugFunctionName(addr, sel),
411
+ this.stateMachine.node,
412
+ minRevertibleSideEffectCounter,
413
+ );
397
414
 
398
415
  const globals = makeGlobalVariables();
399
416
  globals.blockNumber = blockNumber;
@@ -404,7 +421,11 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
404
421
 
405
422
  const forkedWorldTrees = await this.stateMachine.synchronizer.nativeWorldStateService.fork();
406
423
 
407
- const contractsDB = new PublicContractsDB(new TXEPublicContractDataSource(blockNumber, this.contractStore));
424
+ const bindings = this.logger.getBindings();
425
+ const contractsDB = new PublicContractsDB(
426
+ new TXEPublicContractDataSource(blockNumber, this.contractStore),
427
+ bindings,
428
+ );
408
429
  const guardedMerkleTrees = new GuardedMerkleTreeOperations(forkedWorldTrees);
409
430
  const config = PublicSimulatorConfig.from({
410
431
  skipFeeEnforcement: true,
@@ -417,8 +438,10 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
417
438
  globals,
418
439
  guardedMerkleTrees,
419
440
  contractsDB,
420
- new CppPublicTxSimulator(guardedMerkleTrees, contractsDB, globals, config),
441
+ new CppPublicTxSimulator(guardedMerkleTrees, contractsDB, globals, config, bindings),
421
442
  new TestDateProvider(),
443
+ undefined,
444
+ createLogger('simulator:public-processor', bindings),
422
445
  );
423
446
 
424
447
  const tx = await Tx.create({
@@ -515,7 +538,11 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
515
538
 
516
539
  const forkedWorldTrees = await this.stateMachine.synchronizer.nativeWorldStateService.fork();
517
540
 
518
- const contractsDB = new PublicContractsDB(new TXEPublicContractDataSource(blockNumber, this.contractStore));
541
+ const bindings2 = this.logger.getBindings();
542
+ const contractsDB = new PublicContractsDB(
543
+ new TXEPublicContractDataSource(blockNumber, this.contractStore),
544
+ bindings2,
545
+ );
519
546
  const guardedMerkleTrees = new GuardedMerkleTreeOperations(forkedWorldTrees);
520
547
  const config = PublicSimulatorConfig.from({
521
548
  skipFeeEnforcement: true,
@@ -524,8 +551,16 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
524
551
  collectStatistics: false,
525
552
  collectCallMetadata: true,
526
553
  });
527
- const simulator = new CppPublicTxSimulator(guardedMerkleTrees, contractsDB, globals, config);
528
- const processor = new PublicProcessor(globals, guardedMerkleTrees, contractsDB, simulator, new TestDateProvider());
554
+ const simulator = new CppPublicTxSimulator(guardedMerkleTrees, contractsDB, globals, config, bindings2);
555
+ const processor = new PublicProcessor(
556
+ globals,
557
+ guardedMerkleTrees,
558
+ contractsDB,
559
+ simulator,
560
+ new TestDateProvider(),
561
+ undefined,
562
+ createLogger('simulator:public-processor', bindings2),
563
+ );
529
564
 
530
565
  // We're simulating a scenario in which private execution immediately enqueues a public call and halts. The private
531
566
  // kernel init would in this case inject a nullifier with the transaction request hash as a non-revertible
@@ -556,7 +591,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
556
591
  constantData,
557
592
  /*gasUsed=*/ new Gas(0, 0),
558
593
  /*feePayer=*/ AztecAddress.zero(),
559
- /*includeByTimestamp=*/ 0n,
594
+ /*expirationTimestamp=*/ 0n,
560
595
  inputsForPublic,
561
596
  undefined,
562
597
  );
@@ -624,36 +659,40 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
624
659
  return returnValues ?? [];
625
660
  }
626
661
 
627
- async txeSimulateUtilityFunction(
628
- targetContractAddress: AztecAddress,
629
- functionSelector: FunctionSelector,
630
- args: Fr[],
631
- ) {
662
+ async txeExecuteUtilityFunction(targetContractAddress: AztecAddress, functionSelector: FunctionSelector, args: Fr[]) {
632
663
  const artifact = await this.contractStore.getFunctionArtifact(targetContractAddress, functionSelector);
633
664
  if (!artifact) {
634
665
  throw new Error(`Cannot call ${functionSelector} as there is no artifact found at ${targetContractAddress}.`);
635
666
  }
636
667
 
637
668
  // Sync notes before executing utility function to discover notes from previous transactions
638
- await this.contractStore.syncPrivateState(targetContractAddress, functionSelector, async call => {
639
- await this.executeUtilityCall(call);
640
- });
641
-
642
- const call = new FunctionCall(
643
- artifact.name,
669
+ const blockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
670
+ await this.stateMachine.contractSyncService.ensureContractSynced(
644
671
  targetContractAddress,
645
672
  functionSelector,
646
- FunctionType.UTILITY,
647
- false,
648
- false,
649
- args,
650
- [],
673
+ async (call, execScopes) => {
674
+ await this.executeUtilityCall(call, execScopes);
675
+ },
676
+ blockHeader,
677
+ this.jobId,
678
+ 'ALL_SCOPES',
651
679
  );
652
680
 
653
- return this.executeUtilityCall(call);
681
+ const call = FunctionCall.from({
682
+ name: artifact.name,
683
+ to: targetContractAddress,
684
+ selector: functionSelector,
685
+ type: FunctionType.UTILITY,
686
+ hideMsgSender: false,
687
+ isStatic: false,
688
+ args,
689
+ returnTypes: [],
690
+ });
691
+
692
+ return this.executeUtilityCall(call, 'ALL_SCOPES');
654
693
  }
655
694
 
656
- private async executeUtilityCall(call: FunctionCall): Promise<Fr[]> {
695
+ private async executeUtilityCall(call: FunctionCall, scopes: AccessScopes): Promise<Fr[]> {
657
696
  const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(call.to, call.selector);
658
697
  if (entryPointArtifact.functionType !== FunctionType.UTILITY) {
659
698
  throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`);
@@ -666,22 +705,23 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
666
705
 
667
706
  try {
668
707
  const anchorBlockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
669
- const oracle = new UtilityExecutionOracle(
670
- call.to,
671
- [],
672
- [],
708
+ const oracle = new UtilityExecutionOracle({
709
+ contractAddress: call.to,
710
+ authWitnesses: [],
711
+ capsules: [],
673
712
  anchorBlockHeader,
674
- this.contractStore,
675
- this.noteStore,
676
- this.keyStore,
677
- this.addressStore,
678
- this.stateMachine.node,
679
- this.stateMachine.anchorBlockStore,
680
- this.recipientTaggingStore,
681
- this.senderAddressBookStore,
682
- this.capsuleStore,
683
- this.privateEventStore,
684
- );
713
+ contractStore: this.contractStore,
714
+ noteStore: this.noteStore,
715
+ keyStore: this.keyStore,
716
+ addressStore: this.addressStore,
717
+ aztecNode: this.stateMachine.node,
718
+ recipientTaggingStore: this.recipientTaggingStore,
719
+ senderAddressBookStore: this.senderAddressBookStore,
720
+ capsuleStore: this.capsuleStore,
721
+ privateEventStore: this.privateEventStore,
722
+ jobId: this.jobId,
723
+ scopes,
724
+ });
685
725
  const acirExecutionResult = await new WASMSimulator()
686
726
  .executeUserCircuit(toACVMWitness(0, call.args), entryPointArtifact, new Oracle(oracle).toACIRCallback())
687
727
  .catch((err: Error) => {
@@ -697,10 +737,10 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
697
737
  );
698
738
  });
699
739
 
700
- this.logger.verbose(`Utility simulation for ${call.to}.${call.selector} completed`);
740
+ this.logger.verbose(`Utility execution for ${call.to}.${call.selector} completed`);
701
741
  return witnessMapToFields(acirExecutionResult.returnWitness);
702
742
  } catch (err) {
703
- throw createSimulationError(err instanceof Error ? err : new Error('Unknown error during utility simulation'));
743
+ throw createSimulationError(err instanceof Error ? err : new Error('Unknown error during utility execution'));
704
744
  }
705
745
  }
706
746