@aztec/txe 0.0.1-commit.6d3c34e → 0.0.1-commit.7ac86ea28

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/constants.d.ts +1 -2
  2. package/dest/constants.d.ts.map +1 -1
  3. package/dest/constants.js +0 -1
  4. package/dest/index.d.ts +1 -1
  5. package/dest/index.d.ts.map +1 -1
  6. package/dest/index.js +82 -50
  7. package/dest/oracle/interfaces.d.ts +4 -4
  8. package/dest/oracle/interfaces.d.ts.map +1 -1
  9. package/dest/oracle/txe_oracle_public_context.d.ts +3 -3
  10. package/dest/oracle/txe_oracle_public_context.d.ts.map +1 -1
  11. package/dest/oracle/txe_oracle_public_context.js +6 -6
  12. package/dest/oracle/txe_oracle_top_level_context.d.ts +6 -6
  13. package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
  14. package/dest/oracle/txe_oracle_top_level_context.js +108 -41
  15. package/dest/rpc_translator.d.ts +21 -15
  16. package/dest/rpc_translator.d.ts.map +1 -1
  17. package/dest/rpc_translator.js +78 -53
  18. package/dest/state_machine/archiver.d.ts +20 -69
  19. package/dest/state_machine/archiver.d.ts.map +1 -1
  20. package/dest/state_machine/archiver.js +36 -178
  21. package/dest/state_machine/dummy_p2p_client.d.ts +15 -11
  22. package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
  23. package/dest/state_machine/dummy_p2p_client.js +27 -15
  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 +8 -6
  28. package/dest/state_machine/mock_epoch_cache.d.ts.map +1 -1
  29. package/dest/state_machine/mock_epoch_cache.js +11 -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 +93 -25
  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 -4
  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/constants.ts +0 -1
  47. package/src/index.ts +83 -49
  48. package/src/oracle/interfaces.ts +3 -3
  49. package/src/oracle/txe_oracle_public_context.ts +6 -8
  50. package/src/oracle/txe_oracle_top_level_context.ts +132 -94
  51. package/src/rpc_translator.ts +81 -55
  52. package/src/state_machine/archiver.ts +37 -234
  53. package/src/state_machine/dummy_p2p_client.ts +39 -21
  54. package/src/state_machine/index.ts +49 -11
  55. package/src/state_machine/mock_epoch_cache.ts +11 -11
  56. package/src/state_machine/synchronizer.ts +2 -2
  57. package/src/txe_session.ts +100 -84
  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,
@@ -80,10 +82,9 @@ import {
80
82
  import type { UInt64 } from '@aztec/stdlib/types';
81
83
  import { ForkCheckpoint } from '@aztec/world-state';
82
84
 
83
- import { DEFAULT_ADDRESS, TXE_JOB_ID } from '../constants.js';
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,51 +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
- TXE_JOB_ID,
348
- 0,
349
- 1,
350
- undefined, // log
351
- undefined, // scopes
352
- /**
353
- * In TXE, the typical transaction entrypoint is skipped, so we need to simulate the actions that such a
354
- * contract would perform, including setting senderForTags.
355
- */
356
- 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,
357
370
  simulator,
358
- );
371
+ });
359
372
 
360
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.
361
374
  let result: PrivateExecutionResult;
@@ -382,19 +395,22 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
382
395
  }),
383
396
  );
384
397
 
385
- // TXE's top level context does not track side effect counters, and as such, minRevertibleSideEffectCounter is always 0.
386
- // This has the unfortunate consequence of always producing revertible nullifiers, which means we
387
- // must set the firstNullifierHint to Fr.ZERO so the txRequestHash is always used as nonce generator
388
- result = new PrivateExecutionResult(executionResult, Fr.ZERO, publicFunctionsCalldata);
398
+ noteCache.finish();
399
+ const nonceGenerator = noteCache.getNonceGenerator();
400
+ result = new PrivateExecutionResult(executionResult, nonceGenerator, publicFunctionsCalldata);
389
401
  } catch (err) {
390
402
  throw createSimulationError(err instanceof Error ? err : new Error('Unknown error during private execution'));
391
403
  }
392
404
 
393
- // According to the protocol rules, the nonce generator for the note hashes
394
- // can either be the first nullifier in the tx or the hash of the initial tx request
395
- // if there are none.
396
- const nonceGenerator = result.firstNullifier.equals(Fr.ZERO) ? protocolNullifier : result.firstNullifier;
397
- 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
+ );
398
414
 
399
415
  const globals = makeGlobalVariables();
400
416
  globals.blockNumber = blockNumber;
@@ -405,7 +421,11 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
405
421
 
406
422
  const forkedWorldTrees = await this.stateMachine.synchronizer.nativeWorldStateService.fork();
407
423
 
408
- 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
+ );
409
429
  const guardedMerkleTrees = new GuardedMerkleTreeOperations(forkedWorldTrees);
410
430
  const config = PublicSimulatorConfig.from({
411
431
  skipFeeEnforcement: true,
@@ -418,8 +438,10 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
418
438
  globals,
419
439
  guardedMerkleTrees,
420
440
  contractsDB,
421
- new CppPublicTxSimulator(guardedMerkleTrees, contractsDB, globals, config),
441
+ new CppPublicTxSimulator(guardedMerkleTrees, contractsDB, globals, config, bindings),
422
442
  new TestDateProvider(),
443
+ undefined,
444
+ createLogger('simulator:public-processor', bindings),
423
445
  );
424
446
 
425
447
  const tx = await Tx.create({
@@ -516,7 +538,11 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
516
538
 
517
539
  const forkedWorldTrees = await this.stateMachine.synchronizer.nativeWorldStateService.fork();
518
540
 
519
- 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
+ );
520
546
  const guardedMerkleTrees = new GuardedMerkleTreeOperations(forkedWorldTrees);
521
547
  const config = PublicSimulatorConfig.from({
522
548
  skipFeeEnforcement: true,
@@ -525,8 +551,16 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
525
551
  collectStatistics: false,
526
552
  collectCallMetadata: true,
527
553
  });
528
- const simulator = new CppPublicTxSimulator(guardedMerkleTrees, contractsDB, globals, config);
529
- 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
+ );
530
564
 
531
565
  // We're simulating a scenario in which private execution immediately enqueues a public call and halts. The private
532
566
  // kernel init would in this case inject a nullifier with the transaction request hash as a non-revertible
@@ -557,7 +591,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
557
591
  constantData,
558
592
  /*gasUsed=*/ new Gas(0, 0),
559
593
  /*feePayer=*/ AztecAddress.zero(),
560
- /*includeByTimestamp=*/ 0n,
594
+ /*expirationTimestamp=*/ 0n,
561
595
  inputsForPublic,
562
596
  undefined,
563
597
  );
@@ -625,36 +659,40 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
625
659
  return returnValues ?? [];
626
660
  }
627
661
 
628
- async txeSimulateUtilityFunction(
629
- targetContractAddress: AztecAddress,
630
- functionSelector: FunctionSelector,
631
- args: Fr[],
632
- ) {
662
+ async txeExecuteUtilityFunction(targetContractAddress: AztecAddress, functionSelector: FunctionSelector, args: Fr[]) {
633
663
  const artifact = await this.contractStore.getFunctionArtifact(targetContractAddress, functionSelector);
634
664
  if (!artifact) {
635
665
  throw new Error(`Cannot call ${functionSelector} as there is no artifact found at ${targetContractAddress}.`);
636
666
  }
637
667
 
638
668
  // Sync notes before executing utility function to discover notes from previous transactions
639
- await this.contractStore.syncPrivateState(targetContractAddress, functionSelector, async call => {
640
- await this.executeUtilityCall(call);
641
- });
642
-
643
- const call = new FunctionCall(
644
- artifact.name,
669
+ const blockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
670
+ await this.stateMachine.contractSyncService.ensureContractSynced(
645
671
  targetContractAddress,
646
672
  functionSelector,
647
- FunctionType.UTILITY,
648
- false,
649
- false,
650
- args,
651
- [],
673
+ async (call, execScopes) => {
674
+ await this.executeUtilityCall(call, execScopes);
675
+ },
676
+ blockHeader,
677
+ this.jobId,
678
+ 'ALL_SCOPES',
652
679
  );
653
680
 
654
- 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');
655
693
  }
656
694
 
657
- private async executeUtilityCall(call: FunctionCall): Promise<Fr[]> {
695
+ private async executeUtilityCall(call: FunctionCall, scopes: AccessScopes): Promise<Fr[]> {
658
696
  const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(call.to, call.selector);
659
697
  if (entryPointArtifact.functionType !== FunctionType.UTILITY) {
660
698
  throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`);
@@ -667,23 +705,23 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
667
705
 
668
706
  try {
669
707
  const anchorBlockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
670
- const oracle = new UtilityExecutionOracle(
671
- call.to,
672
- [],
673
- [],
708
+ const oracle = new UtilityExecutionOracle({
709
+ contractAddress: call.to,
710
+ authWitnesses: [],
711
+ capsules: [],
674
712
  anchorBlockHeader,
675
- this.contractStore,
676
- this.noteStore,
677
- this.keyStore,
678
- this.addressStore,
679
- this.stateMachine.node,
680
- this.stateMachine.anchorBlockStore,
681
- this.recipientTaggingStore,
682
- this.senderAddressBookStore,
683
- this.capsuleStore,
684
- this.privateEventStore,
685
- TXE_JOB_ID,
686
- );
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
+ });
687
725
  const acirExecutionResult = await new WASMSimulator()
688
726
  .executeUserCircuit(toACVMWitness(0, call.args), entryPointArtifact, new Oracle(oracle).toACIRCallback())
689
727
  .catch((err: Error) => {
@@ -699,10 +737,10 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
699
737
  );
700
738
  });
701
739
 
702
- this.logger.verbose(`Utility simulation for ${call.to}.${call.selector} completed`);
740
+ this.logger.verbose(`Utility execution for ${call.to}.${call.selector} completed`);
703
741
  return witnessMapToFields(acirExecutionResult.returnWitness);
704
742
  } catch (err) {
705
- 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'));
706
744
  }
707
745
  }
708
746