@aztec/pxe 0.0.1-commit.b64cb54f6 → 0.0.1-commit.b6e433891

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 (37) hide show
  1. package/dest/contract_function_simulator/contract_function_simulator.d.ts +6 -3
  2. package/dest/contract_function_simulator/contract_function_simulator.d.ts.map +1 -1
  3. package/dest/contract_function_simulator/contract_function_simulator.js +21 -3
  4. package/dest/contract_function_simulator/oracle/interfaces.d.ts +3 -2
  5. package/dest/contract_function_simulator/oracle/interfaces.d.ts.map +1 -1
  6. package/dest/contract_function_simulator/oracle/legacy_oracle_mappings.d.ts +1 -1
  7. package/dest/contract_function_simulator/oracle/legacy_oracle_mappings.d.ts.map +1 -1
  8. package/dest/contract_function_simulator/oracle/legacy_oracle_mappings.js +22 -32
  9. package/dest/contract_function_simulator/oracle/oracle.d.ts +3 -2
  10. package/dest/contract_function_simulator/oracle/oracle.d.ts.map +1 -1
  11. package/dest/contract_function_simulator/oracle/oracle.js +25 -4
  12. package/dest/contract_function_simulator/oracle/private_execution.js +3 -1
  13. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +1 -12
  14. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
  15. package/dest/contract_function_simulator/oracle/private_execution_oracle.js +0 -14
  16. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts +14 -2
  17. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts.map +1 -1
  18. package/dest/contract_function_simulator/oracle/utility_execution_oracle.js +22 -0
  19. package/dest/contract_sync/contract_sync_service.d.ts +5 -3
  20. package/dest/contract_sync/contract_sync_service.d.ts.map +1 -1
  21. package/dest/contract_sync/contract_sync_service.js +47 -30
  22. package/dest/oracle_version.d.ts +2 -2
  23. package/dest/oracle_version.js +2 -2
  24. package/dest/pxe.d.ts +5 -3
  25. package/dest/pxe.d.ts.map +1 -1
  26. package/dest/pxe.js +19 -10
  27. package/package.json +16 -16
  28. package/src/contract_function_simulator/contract_function_simulator.ts +30 -4
  29. package/src/contract_function_simulator/oracle/interfaces.ts +2 -1
  30. package/src/contract_function_simulator/oracle/legacy_oracle_mappings.ts +103 -45
  31. package/src/contract_function_simulator/oracle/oracle.ts +25 -4
  32. package/src/contract_function_simulator/oracle/private_execution.ts +1 -1
  33. package/src/contract_function_simulator/oracle/private_execution_oracle.ts +0 -17
  34. package/src/contract_function_simulator/oracle/utility_execution_oracle.ts +27 -1
  35. package/src/contract_sync/contract_sync_service.ts +67 -38
  36. package/src/oracle_version.ts +2 -2
  37. package/src/pxe.ts +39 -12
@@ -17,6 +17,7 @@ import {
17
17
  MAX_NULLIFIERS_PER_TX,
18
18
  MAX_NULLIFIER_READ_REQUESTS_PER_TX,
19
19
  MAX_PRIVATE_LOGS_PER_TX,
20
+ MAX_TX_LIFETIME,
20
21
  PRIVATE_TX_L2_GAS_OVERHEAD,
21
22
  PUBLIC_TX_L2_GAS_OVERHEAD,
22
23
  TX_DA_GAS_OVERHEAD,
@@ -79,6 +80,7 @@ import {
79
80
  BlockHeader,
80
81
  CallContext,
81
82
  HashedValues,
83
+ type OffchainEffect,
82
84
  PrivateExecutionResult,
83
85
  TxConstantData,
84
86
  TxExecutionRequest,
@@ -319,7 +321,7 @@ export class ContractFunctionSimulator {
319
321
  anchorBlockHeader: BlockHeader,
320
322
  scopes: AccessScopes,
321
323
  jobId: string,
322
- ): Promise<Fr[]> {
324
+ ): Promise<{ result: Fr[]; offchainEffects: OffchainEffect[] }> {
323
325
  const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(call.to, call.selector);
324
326
 
325
327
  if (entryPointArtifact.functionType !== FunctionType.UTILITY) {
@@ -341,6 +343,7 @@ export class ContractFunctionSimulator {
341
343
  capsuleStore: this.capsuleStore,
342
344
  privateEventStore: this.privateEventStore,
343
345
  messageContextService: this.messageContextService,
346
+ contractSyncService: this.contractSyncService,
344
347
  jobId,
345
348
  scopes,
346
349
  });
@@ -368,7 +371,10 @@ export class ContractFunctionSimulator {
368
371
  });
369
372
 
370
373
  this.log.verbose(`Utility execution for ${call.to}.${call.selector} completed`);
371
- return witnessMapToFields(acirExecutionResult.returnWitness);
374
+ return {
375
+ result: witnessMapToFields(acirExecutionResult.returnWitness),
376
+ offchainEffects: oracle.getOffchainEffects(),
377
+ };
372
378
  } catch (err) {
373
379
  throw createSimulationError(err instanceof Error ? err : new Error('Unknown error during private execution'));
374
380
  }
@@ -438,14 +444,34 @@ export async function generateSimulatedProvingResult(
438
444
 
439
445
  let publicTeardownCallRequest;
440
446
 
447
+ // We set expiration timestamp to anchor_block_timestamp + MAX_TX_LIFETIME (24h) just like kernels do
448
+ let expirationTimestamp =
449
+ privateExecutionResult.entrypoint.publicInputs.anchorBlockHeader.globalVariables.timestamp +
450
+ BigInt(MAX_TX_LIFETIME);
451
+
452
+ let feePayer = AztecAddress.zero();
453
+
441
454
  const executions = [privateExecutionResult.entrypoint];
442
455
 
443
456
  while (executions.length !== 0) {
444
457
  const execution = executions.shift()!;
445
458
  executions.unshift(...execution!.nestedExecutionResults);
446
459
 
460
+ // Just like kernels we overwrite the default value if the call sets it.
461
+ const callExpirationTimestamp = execution.publicInputs.expirationTimestamp;
462
+ if (callExpirationTimestamp !== 0n && callExpirationTimestamp < expirationTimestamp) {
463
+ expirationTimestamp = callExpirationTimestamp;
464
+ }
465
+
447
466
  const { contractAddress } = execution.publicInputs.callContext;
448
467
 
468
+ if (execution.publicInputs.isFeePayer) {
469
+ if (!feePayer.isZero()) {
470
+ throw new Error('Multiple fee payers found in private execution result');
471
+ }
472
+ feePayer = contractAddress;
473
+ }
474
+
449
475
  scopedNoteHashes.push(
450
476
  ...execution.publicInputs.noteHashes
451
477
  .getActiveItems()
@@ -666,8 +692,8 @@ export async function generateSimulatedProvingResult(
666
692
  daGas: TX_DA_GAS_OVERHEAD,
667
693
  }),
668
694
  ),
669
- /*feePayer=*/ AztecAddress.zero(),
670
- /*expirationTimestamp=*/ 0n,
695
+ /*feePayer=*/ feePayer,
696
+ /*expirationTimestamp=*/ expirationTimestamp,
671
697
  hasPublicCalls ? inputsForPublic : undefined,
672
698
  !hasPublicCalls ? inputsForRollup : undefined,
673
699
  );
@@ -143,6 +143,8 @@ export interface IUtilityExecutionOracle {
143
143
  copyCapsule(contractAddress: AztecAddress, srcKey: Fr, dstKey: Fr, numEntries: number): Promise<void>;
144
144
  aes128Decrypt(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise<Buffer>;
145
145
  getSharedSecret(address: AztecAddress, ephPk: Point): Promise<Point>;
146
+ invalidateContractSyncCache(contractAddress: AztecAddress, scopes: AztecAddress[]): void;
147
+ emitOffchainEffect(data: Fr[]): Promise<void>;
146
148
  }
147
149
 
148
150
  /**
@@ -180,5 +182,4 @@ export interface IPrivateExecutionOracle {
180
182
  getSenderForTags(): Promise<AztecAddress | undefined>;
181
183
  setSenderForTags(senderForTags: AztecAddress): Promise<void>;
182
184
  getNextAppTagAsSender(sender: AztecAddress, recipient: AztecAddress): Promise<Tag>;
183
- emitOffchainEffect(data: Fr[]): Promise<void>;
184
185
  }
@@ -11,39 +11,101 @@ import type { Oracle } from './oracle.js';
11
11
  export function buildLegacyOracleCallbacks(oracle: Oracle): ACIRCallback {
12
12
  return {
13
13
  // Simple prefix renames (privateXxx/utilityXxx → aztec_prv_/aztec_utl_)
14
- utilityLog: (...args: ACVMField[][]) => oracle.aztec_utl_log(args[0], args[1], args[2], args[3]),
15
- utilityAssertCompatibleOracleVersion: (...args: ACVMField[][]) =>
16
- oracle.aztec_utl_assertCompatibleOracleVersion(args[0]),
17
- utilityLoadCapsule: (...args: ACVMField[][]) => oracle.aztec_utl_loadCapsule(args[0], args[1], args[2]),
18
- privateStoreInExecutionCache: (...args: ACVMField[][]) => oracle.aztec_prv_storeInExecutionCache(args[0], args[1]),
19
- privateLoadFromExecutionCache: (...args: ACVMField[][]) => oracle.aztec_prv_loadFromExecutionCache(args[0]),
20
- privateCallPrivateFunction: (...args: ACVMField[][]) =>
21
- oracle.aztec_prv_callPrivateFunction(args[0], args[1], args[2], args[3], args[4]),
22
- privateIsNullifierPending: (...args: ACVMField[][]) => oracle.aztec_prv_isNullifierPending(args[0], args[1]),
23
- privateNotifyCreatedNullifier: (...args: ACVMField[][]) => oracle.aztec_prv_notifyCreatedNullifier(args[0]),
24
- privateNotifyCreatedContractClassLog: (...args: ACVMField[][]) =>
25
- oracle.aztec_prv_notifyCreatedContractClassLog(args[0], args[1], args[2], args[3]),
26
- privateGetNextAppTagAsSender: (...args: ACVMField[][]) => oracle.aztec_prv_getNextAppTagAsSender(args[0], args[1]),
27
- privateGetSenderForTags: () => oracle.aztec_prv_getSenderForTags(),
28
- privateSetSenderForTags: (...args: ACVMField[][]) => oracle.aztec_prv_setSenderForTags(args[0]),
29
- utilityGetUtilityContext: () => oracle.aztec_utl_getUtilityContext(),
30
- utilityStorageRead: (...args: ACVMField[][]) => oracle.aztec_utl_storageRead(args[0], args[1], args[2], args[3]),
31
- utilityStoreCapsule: (...args: ACVMField[][]) => oracle.aztec_utl_storeCapsule(args[0], args[1], args[2]),
32
- utilityCopyCapsule: (...args: ACVMField[][]) => oracle.aztec_utl_copyCapsule(args[0], args[1], args[2], args[3]),
33
- utilityDeleteCapsule: (...args: ACVMField[][]) => oracle.aztec_utl_deleteCapsule(args[0], args[1]),
34
- utilityAes128Decrypt: (...args: ACVMField[][]) =>
35
- oracle.aztec_utl_aes128Decrypt(args[0], args[1], args[2], args[3]),
36
- utilityGetSharedSecret: (...args: ACVMField[][]) =>
37
- oracle.aztec_utl_getSharedSecret(args[0], args[1], args[2], args[3]),
38
- utilityFetchTaggedLogs: (...args: ACVMField[][]) => oracle.aztec_utl_fetchTaggedLogs(args[0]),
39
- utilityBulkRetrieveLogs: (...args: ACVMField[][]) => oracle.aztec_utl_bulkRetrieveLogs(args[0], args[1], args[2]),
14
+ utilityLog: (
15
+ level: ACVMField[],
16
+ message: ACVMField[],
17
+ _ignoredFieldsSize: ACVMField[],
18
+ fields: ACVMField[],
19
+ ): Promise<ACVMField[]> => oracle.aztec_utl_log(level, message, _ignoredFieldsSize, fields),
20
+ utilityAssertCompatibleOracleVersion: (version: ACVMField[]): Promise<ACVMField[]> =>
21
+ oracle.aztec_utl_assertCompatibleOracleVersion(version),
22
+ utilityLoadCapsule: (
23
+ contractAddress: ACVMField[],
24
+ slot: ACVMField[],
25
+ tSize: ACVMField[],
26
+ ): Promise<(ACVMField | ACVMField[])[]> => oracle.aztec_utl_loadCapsule(contractAddress, slot, tSize),
27
+ privateStoreInExecutionCache: (values: ACVMField[], hash: ACVMField[]): Promise<ACVMField[]> =>
28
+ oracle.aztec_prv_storeInExecutionCache(values, hash),
29
+ privateLoadFromExecutionCache: (returnsHash: ACVMField[]): Promise<ACVMField[][]> =>
30
+ oracle.aztec_prv_loadFromExecutionCache(returnsHash),
31
+ privateCallPrivateFunction: (
32
+ contractAddress: ACVMField[],
33
+ functionSelector: ACVMField[],
34
+ argsHash: ACVMField[],
35
+ sideEffectCounter: ACVMField[],
36
+ isStaticCall: ACVMField[],
37
+ ): Promise<ACVMField[][]> =>
38
+ oracle.aztec_prv_callPrivateFunction(
39
+ contractAddress,
40
+ functionSelector,
41
+ argsHash,
42
+ sideEffectCounter,
43
+ isStaticCall,
44
+ ),
45
+ privateIsNullifierPending: (innerNullifier: ACVMField[], contractAddress: ACVMField[]): Promise<ACVMField[]> =>
46
+ oracle.aztec_prv_isNullifierPending(innerNullifier, contractAddress),
47
+ privateNotifyCreatedNullifier: (innerNullifier: ACVMField[]): Promise<ACVMField[]> =>
48
+ oracle.aztec_prv_notifyCreatedNullifier(innerNullifier),
49
+ privateNotifyCreatedContractClassLog: (
50
+ contractAddress: ACVMField[],
51
+ message: ACVMField[],
52
+ length: ACVMField[],
53
+ counter: ACVMField[],
54
+ ): Promise<ACVMField[]> =>
55
+ oracle.aztec_prv_notifyCreatedContractClassLog(contractAddress, message, length, counter),
56
+ utilityGetUtilityContext: (): Promise<(ACVMField | ACVMField[])[]> => oracle.aztec_utl_getUtilityContext(),
57
+ utilityStorageRead: (
58
+ blockHash: ACVMField[],
59
+ contractAddress: ACVMField[],
60
+ startStorageSlot: ACVMField[],
61
+ numberOfElements: ACVMField[],
62
+ ): Promise<ACVMField[][]> =>
63
+ oracle.aztec_utl_storageRead(blockHash, contractAddress, startStorageSlot, numberOfElements),
64
+ utilityStoreCapsule: (
65
+ contractAddress: ACVMField[],
66
+ slot: ACVMField[],
67
+ capsule: ACVMField[],
68
+ ): Promise<ACVMField[]> => oracle.aztec_utl_storeCapsule(contractAddress, slot, capsule),
69
+ utilityCopyCapsule: (
70
+ contractAddress: ACVMField[],
71
+ srcSlot: ACVMField[],
72
+ dstSlot: ACVMField[],
73
+ numEntries: ACVMField[],
74
+ ): Promise<ACVMField[]> => oracle.aztec_utl_copyCapsule(contractAddress, srcSlot, dstSlot, numEntries),
75
+ utilityDeleteCapsule: (contractAddress: ACVMField[], slot: ACVMField[]): Promise<ACVMField[]> =>
76
+ oracle.aztec_utl_deleteCapsule(contractAddress, slot),
77
+ utilityGetSharedSecret: (
78
+ address: ACVMField[],
79
+ ephPKField0: ACVMField[],
80
+ ephPKField1: ACVMField[],
81
+ ephPKField2: ACVMField[],
82
+ ): Promise<ACVMField[]> => oracle.aztec_utl_getSharedSecret(address, ephPKField0, ephPKField1, ephPKField2),
83
+ utilityFetchTaggedLogs: (pendingTaggedLogArrayBaseSlot: ACVMField[]): Promise<ACVMField[]> =>
84
+ oracle.aztec_utl_fetchTaggedLogs(pendingTaggedLogArrayBaseSlot),
85
+ utilityBulkRetrieveLogs: (
86
+ contractAddress: ACVMField[],
87
+ logRetrievalRequestsArrayBaseSlot: ACVMField[],
88
+ logRetrievalResponsesArrayBaseSlot: ACVMField[],
89
+ ): Promise<ACVMField[]> =>
90
+ oracle.aztec_utl_bulkRetrieveLogs(
91
+ contractAddress,
92
+ logRetrievalRequestsArrayBaseSlot,
93
+ logRetrievalResponsesArrayBaseSlot,
94
+ ),
95
+ utilityGetL1ToL2MembershipWitness: (
96
+ contractAddress: ACVMField[],
97
+ messageHash: ACVMField[],
98
+ secret: ACVMField[],
99
+ ): Promise<(ACVMField | ACVMField[])[]> =>
100
+ oracle.aztec_utl_getL1ToL2MembershipWitness(contractAddress, messageHash, secret),
101
+ utilityEmitOffchainEffect: (data: ACVMField[]): Promise<ACVMField[]> => oracle.aztec_utl_emitOffchainEffect(data),
40
102
  // Adapter: old 3-param signature → new 5-param with injected constants.
41
103
  // Values derived from: MAX_MESSAGE_CONTENT_LEN(11) - RESERVED_FIELDS (3 for notes, 1 for events).
42
104
  utilityValidateAndStoreEnqueuedNotesAndEvents: (
43
105
  contractAddress: ACVMField[],
44
106
  noteValidationRequestsArrayBaseSlot: ACVMField[],
45
107
  eventValidationRequestsArrayBaseSlot: ACVMField[],
46
- ) =>
108
+ ): Promise<ACVMField[]> =>
47
109
  oracle.aztec_utl_validateAndStoreEnqueuedNotesAndEvents(
48
110
  contractAddress,
49
111
  noteValidationRequestsArrayBaseSlot,
@@ -51,27 +113,23 @@ export function buildLegacyOracleCallbacks(oracle: Oracle): ACIRCallback {
51
113
  [new Fr(8).toString()],
52
114
  [new Fr(10).toString()],
53
115
  ),
54
- utilityGetL1ToL2MembershipWitness: (...args: ACVMField[][]) =>
55
- oracle.aztec_utl_getL1ToL2MembershipWitness(args[0], args[1], args[2]),
56
- utilityCheckNullifierExists: (...args: ACVMField[][]) => oracle.aztec_utl_checkNullifierExists(args[0]),
57
- utilityGetRandomField: () => oracle.aztec_utl_getRandomField(),
58
- utilityEmitOffchainEffect: (...args: ACVMField[][]) => oracle.aztec_utl_emitOffchainEffect(args[0]),
59
116
  // Renames (same signature, different oracle name)
60
- privateNotifySetMinRevertibleSideEffectCounter: (...args: ACVMField[][]) =>
61
- oracle.aztec_prv_notifyRevertiblePhaseStart(args[0]),
62
- privateIsSideEffectCounterRevertible: (...args: ACVMField[][]) => oracle.aztec_prv_inRevertiblePhase(args[0]),
117
+ privateNotifySetMinRevertibleSideEffectCounter: (counter: ACVMField[]): Promise<ACVMField[]> =>
118
+ oracle.aztec_prv_notifyRevertiblePhaseStart(counter),
119
+ privateIsSideEffectCounterRevertible: (sideEffectCounter: ACVMField[]): Promise<ACVMField[]> =>
120
+ oracle.aztec_prv_inRevertiblePhase(sideEffectCounter),
63
121
  // Signature changes: old 4-param oracles → new 1-param validatePublicCalldata
64
122
  privateNotifyEnqueuedPublicFunctionCall: (
65
- [_contractAddress]: ACVMField[],
66
- [calldataHash]: ACVMField[],
67
- [_sideEffectCounter]: ACVMField[],
68
- [_isStaticCall]: ACVMField[],
69
- ) => oracle.aztec_prv_validatePublicCalldata([calldataHash]),
123
+ _contractAddress: ACVMField[],
124
+ calldataHash: ACVMField[],
125
+ _sideEffectCounter: ACVMField[],
126
+ _isStaticCall: ACVMField[],
127
+ ): Promise<ACVMField[]> => oracle.aztec_prv_validatePublicCalldata(calldataHash),
70
128
  privateNotifySetPublicTeardownFunctionCall: (
71
- [_contractAddress]: ACVMField[],
72
- [calldataHash]: ACVMField[],
73
- [_sideEffectCounter]: ACVMField[],
74
- [_isStaticCall]: ACVMField[],
75
- ) => oracle.aztec_prv_validatePublicCalldata([calldataHash]),
129
+ _contractAddress: ACVMField[],
130
+ calldataHash: ACVMField[],
131
+ _sideEffectCounter: ACVMField[],
132
+ _isStaticCall: ACVMField[],
133
+ ): Promise<ACVMField[]> => oracle.aztec_prv_validatePublicCalldata(calldataHash),
76
134
  };
77
135
  }
@@ -605,7 +605,7 @@ export class Oracle {
605
605
  }
606
606
 
607
607
  // eslint-disable-next-line camelcase
608
- async aztec_utl_aes128Decrypt(
608
+ async aztec_utl_tryAes128Decrypt(
609
609
  ciphertextBVecStorage: ACVMField[],
610
610
  [ciphertextLength]: ACVMField[],
611
611
  iv: ACVMField[],
@@ -615,8 +615,15 @@ export class Oracle {
615
615
  const ivBuffer = fromUintArray(iv, 8);
616
616
  const symKeyBuffer = fromUintArray(symKey, 8);
617
617
 
618
- const plaintext = await this.handlerAsUtility().aes128Decrypt(ciphertext, ivBuffer, symKeyBuffer);
619
- return bufferToBoundedVec(plaintext, ciphertextBVecStorage.length);
618
+ // Noir Option<BoundedVec> is encoded as [is_some: Field, storage: Field[], length: Field].
619
+ try {
620
+ const plaintext = await this.handlerAsUtility().aes128Decrypt(ciphertext, ivBuffer, symKeyBuffer);
621
+ const [storage, length] = bufferToBoundedVec(plaintext, ciphertextBVecStorage.length);
622
+ return [toACVMField(1), storage, length];
623
+ } catch {
624
+ const zeroStorage = Array(ciphertextBVecStorage.length).fill(toACVMField(0));
625
+ return [toACVMField(0), zeroStorage, toACVMField(0)];
626
+ }
620
627
  }
621
628
 
622
629
  // eslint-disable-next-line camelcase
@@ -633,9 +640,23 @@ export class Oracle {
633
640
  return secret.toFields().map(toACVMField);
634
641
  }
635
642
 
643
+ // eslint-disable-next-line camelcase
644
+ aztec_utl_invalidateContractSyncCache(
645
+ [contractAddress]: ACVMField[],
646
+ scopes: ACVMField[],
647
+ [scopeCount]: ACVMField[],
648
+ ): Promise<ACVMField[]> {
649
+ const scopeAddresses = scopes.slice(0, +scopeCount).map(s => AztecAddress.fromField(Fr.fromString(s)));
650
+ this.handlerAsUtility().invalidateContractSyncCache(
651
+ AztecAddress.fromField(Fr.fromString(contractAddress)),
652
+ scopeAddresses,
653
+ );
654
+ return Promise.resolve([]);
655
+ }
656
+
636
657
  // eslint-disable-next-line camelcase
637
658
  async aztec_utl_emitOffchainEffect(data: ACVMField[]) {
638
- await this.handlerAsPrivate().emitOffchainEffect(data.map(Fr.fromString));
659
+ await this.handlerAsUtility().emitOffchainEffect(data.map(Fr.fromString));
639
660
  return [];
640
661
  }
641
662
 
@@ -103,7 +103,7 @@ export async function executePrivateFunction(
103
103
  newNotes,
104
104
  noteHashNullifierCounterMap,
105
105
  rawReturnValues,
106
- offchainEffects,
106
+ offchainEffects.map(e => ({ data: e.data })),
107
107
  taggingIndexRanges,
108
108
  nestedExecutionResults,
109
109
  contractClassLogs,
@@ -26,7 +26,6 @@ import {
26
26
  } from '@aztec/stdlib/tx';
27
27
 
28
28
  import type { AccessScopes } from '../../access_scopes.js';
29
- import type { ContractSyncService } from '../../contract_sync/contract_sync_service.js';
30
29
  import { NoteService } from '../../notes/note_service.js';
31
30
  import type { SenderTaggingStore } from '../../storage/tagging_store/sender_tagging_store.js';
32
31
  import { syncSenderTaggingIndexes } from '../../tagging/index.js';
@@ -49,7 +48,6 @@ export type PrivateExecutionOracleArgs = Omit<UtilityExecutionOracleArgs, 'contr
49
48
  noteCache: ExecutionNoteCache;
50
49
  taggingIndexCache: ExecutionTaggingIndexCache;
51
50
  senderTaggingStore: SenderTaggingStore;
52
- contractSyncService: ContractSyncService;
53
51
  totalPublicCalldataCount?: number;
54
52
  sideEffectCounter?: number;
55
53
  senderForTags?: AztecAddress;
@@ -73,7 +71,6 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
73
71
  private newNotes: NoteAndSlot[] = [];
74
72
  private noteHashNullifierCounterMap: Map<number, number> = new Map();
75
73
  private contractClassLogs: CountedContractClassLog[] = [];
76
- private offchainEffects: { data: Fr[] }[] = [];
77
74
  private nestedExecutionResults: PrivateCallExecutionResult[] = [];
78
75
 
79
76
  private readonly argsHash: Fr;
@@ -84,7 +81,6 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
84
81
  private readonly noteCache: ExecutionNoteCache;
85
82
  private readonly taggingIndexCache: ExecutionTaggingIndexCache;
86
83
  private readonly senderTaggingStore: SenderTaggingStore;
87
- private readonly contractSyncService: ContractSyncService;
88
84
  private totalPublicCalldataCount: number;
89
85
  protected sideEffectCounter: number;
90
86
  private senderForTags?: AztecAddress;
@@ -104,7 +100,6 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
104
100
  this.noteCache = args.noteCache;
105
101
  this.taggingIndexCache = args.taggingIndexCache;
106
102
  this.senderTaggingStore = args.senderTaggingStore;
107
- this.contractSyncService = args.contractSyncService;
108
103
  this.totalPublicCalldataCount = args.totalPublicCalldataCount ?? 0;
109
104
  this.sideEffectCounter = args.sideEffectCounter ?? 0;
110
105
  this.senderForTags = args.senderForTags;
@@ -158,13 +153,6 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
158
153
  return this.contractClassLogs;
159
154
  }
160
155
 
161
- /**
162
- * Return the offchain effects emitted during this execution.
163
- */
164
- public getOffchainEffects() {
165
- return this.offchainEffects;
166
- }
167
-
168
156
  /**
169
157
  * Returns the tagging index ranges that were used in this execution (and that need to be stored in the db).
170
158
  */
@@ -654,9 +642,4 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
654
642
  public getDebugFunctionName() {
655
643
  return this.contractStore.getDebugFunctionName(this.contractAddress, this.callContext.functionSelector);
656
644
  }
657
-
658
- public emitOffchainEffect(data: Fr[]): Promise<void> {
659
- this.offchainEffects.push({ data });
660
- return Promise.resolve();
661
- }
662
645
  }
@@ -19,10 +19,11 @@ import { deriveEcdhSharedSecret } from '@aztec/stdlib/logs';
19
19
  import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging';
20
20
  import type { NoteStatus } from '@aztec/stdlib/note';
21
21
  import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
22
- import type { BlockHeader, Capsule } from '@aztec/stdlib/tx';
22
+ import type { BlockHeader, Capsule, OffchainEffect } from '@aztec/stdlib/tx';
23
23
 
24
24
  import type { AccessScopes } from '../../access_scopes.js';
25
25
  import { createContractLogger, logContractMessage } from '../../contract_logging.js';
26
+ import type { ContractSyncService } from '../../contract_sync/contract_sync_service.js';
26
27
  import { EventService } from '../../events/event_service.js';
27
28
  import { LogService } from '../../logs/log_service.js';
28
29
  import { MessageContextService } from '../../messages/message_context_service.js';
@@ -62,6 +63,7 @@ export type UtilityExecutionOracleArgs = {
62
63
  capsuleStore: CapsuleStore;
63
64
  privateEventStore: PrivateEventStore;
64
65
  messageContextService: MessageContextService;
66
+ contractSyncService: ContractSyncService;
65
67
  jobId: string;
66
68
  log?: ReturnType<typeof createLogger>;
67
69
  scopes: AccessScopes;
@@ -75,6 +77,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
75
77
  isUtility = true as const;
76
78
 
77
79
  private contractLogger: Logger | undefined;
80
+ private offchainEffects: OffchainEffect[] = [];
78
81
 
79
82
  protected readonly contractAddress: AztecAddress;
80
83
  protected readonly authWitnesses: AuthWitness[];
@@ -90,6 +93,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
90
93
  protected readonly capsuleStore: CapsuleStore;
91
94
  protected readonly privateEventStore: PrivateEventStore;
92
95
  protected readonly messageContextService: MessageContextService;
96
+ protected readonly contractSyncService: ContractSyncService;
93
97
  protected readonly jobId: string;
94
98
  protected logger: ReturnType<typeof createLogger>;
95
99
  protected readonly scopes: AccessScopes;
@@ -109,6 +113,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
109
113
  this.capsuleStore = args.capsuleStore;
110
114
  this.privateEventStore = args.privateEventStore;
111
115
  this.messageContextService = args.messageContextService;
116
+ this.contractSyncService = args.contractSyncService;
112
117
  this.jobId = args.jobId;
113
118
  this.logger = args.log ?? createLogger('simulator:client_view_context');
114
119
  this.scopes = args.scopes;
@@ -650,6 +655,17 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
650
655
  return this.capsuleStore.copyCapsule(this.contractAddress, srcSlot, dstSlot, numEntries, this.jobId);
651
656
  }
652
657
 
658
+ /**
659
+ * Clears cached sync state for a contract for a set of scopes, forcing re-sync on the next query so that newly
660
+ * stored notes or events are discovered.
661
+ */
662
+ public invalidateContractSyncCache(contractAddress: AztecAddress, scopes: AztecAddress[]): void {
663
+ if (!contractAddress.equals(this.contractAddress)) {
664
+ throw new Error(`Contract ${this.contractAddress} cannot invalidate sync cache of ${contractAddress}`);
665
+ }
666
+ this.contractSyncService.invalidateContractForScopes(contractAddress, scopes);
667
+ }
668
+
653
669
  // TODO(#11849): consider replacing this oracle with a pure Noir implementation of aes decryption.
654
670
  public aes128Decrypt(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise<Buffer> {
655
671
  const aes128 = new Aes128();
@@ -671,4 +687,14 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
671
687
  const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM);
672
688
  return deriveEcdhSharedSecret(addressSecret, ephPk);
673
689
  }
690
+
691
+ public emitOffchainEffect(data: Fr[]): Promise<void> {
692
+ this.offchainEffects.push({ data, contractAddress: this.contractAddress });
693
+ return Promise.resolve();
694
+ }
695
+
696
+ /** Returns offchain effects collected during execution. */
697
+ public getOffchainEffects(): OffchainEffect[] {
698
+ return this.offchainEffects;
699
+ }
674
700
  }
@@ -20,12 +20,12 @@ export class ContractSyncService implements StagedStore {
20
20
  readonly storeName = 'contract_sync';
21
21
 
22
22
  // Tracks contracts synced since last wipe. The cache is keyed per individual scope address
23
- // (`contractAddress:scopeAddress`), or `contractAddress:*` for undefined scopes (all accounts).
23
+ // (`contractAddress:scopeAddress`), or `contractAddress:*` for all scopes (all accounts).
24
24
  // The value is a promise that resolves when the contract is synced.
25
25
  private syncedContracts: Map<string, Promise<void>> = new Map();
26
26
 
27
- // Per-job overridden contract addresses - these contracts should not be synced.
28
- private overriddenContracts: Map<string, Set<string>> = new Map();
27
+ // Per-job excluded contract addresses - these contracts should not be synced.
28
+ private excludedFromSync: Map<string, Set<string>> = new Map();
29
29
 
30
30
  constructor(
31
31
  private aztecNode: AztecNode,
@@ -35,8 +35,8 @@ export class ContractSyncService implements StagedStore {
35
35
  ) {}
36
36
 
37
37
  /** Sets contracts that should be skipped during sync for a specific job. */
38
- setOverriddenContracts(jobId: string, addresses: Set<string>): void {
39
- this.overriddenContracts.set(jobId, addresses);
38
+ setExcludedFromSync(jobId: string, addresses: Set<string>): void {
39
+ this.excludedFromSync.set(jobId, addresses);
40
40
  }
41
41
 
42
42
  /**
@@ -56,47 +56,34 @@ export class ContractSyncService implements StagedStore {
56
56
  jobId: string,
57
57
  scopes: AccessScopes,
58
58
  ): Promise<void> {
59
- // Skip sync if this contract has an override for this job (overrides are keyed by contract address only)
60
- const overrides = this.overriddenContracts.get(jobId);
61
- if (overrides?.has(contractAddress.toString())) {
59
+ if (this.#shouldSkipSync(jobId, contractAddress)) {
62
60
  return;
63
61
  }
64
62
 
65
- // Skip sync if we already synced for "all scopes", or if we have an empty list of scopes
66
- const allScopesKey = toKey(contractAddress, 'ALL_SCOPES');
67
- const allScopesExisting = this.syncedContracts.get(allScopesKey);
68
- if (allScopesExisting || (scopes !== 'ALL_SCOPES' && scopes.length == 0)) {
69
- return;
70
- }
71
-
72
- const unsyncedScopes =
73
- scopes === 'ALL_SCOPES'
74
- ? scopes
75
- : scopes.filter(scope => !this.syncedContracts.has(toKey(contractAddress, scope)));
76
- const unsyncedScopesKeys = toKeys(contractAddress, unsyncedScopes);
77
-
78
- if (unsyncedScopesKeys.length > 0) {
79
- // Start sync and store the promise for all unsynced scopes
80
- const promise = this.#doSync(
63
+ this.#startSyncIfNeeded(contractAddress, scopes, scopesToSync =>
64
+ this.#syncContract(
81
65
  contractAddress,
82
66
  functionToInvokeAfterSync,
83
67
  utilityExecutor,
84
68
  anchorBlockHeader,
85
69
  jobId,
86
- unsyncedScopes,
87
- ).catch(err => {
88
- // There was an error syncing the contract, so we remove it from the cache so that it can be retried.
89
- unsyncedScopesKeys.forEach(key => this.syncedContracts.delete(key));
90
- throw err;
91
- });
92
- unsyncedScopesKeys.forEach(key => this.syncedContracts.set(key, promise));
93
- }
70
+ scopesToSync,
71
+ ),
72
+ );
94
73
 
95
- const promises = toKeys(contractAddress, scopes).map(key => this.syncedContracts.get(key)!);
96
- await Promise.all(promises);
74
+ await this.#awaitSync(contractAddress, scopes);
97
75
  }
98
76
 
99
- async #doSync(
77
+ /** Clears sync cache entries for the given scopes of a contract. Also clears the ALL_SCOPES entry. */
78
+ invalidateContractForScopes(contractAddress: AztecAddress, scopes: AztecAddress[]): void {
79
+ if (scopes.length === 0) {
80
+ return;
81
+ }
82
+ scopes.forEach(scope => this.syncedContracts.delete(toKey(contractAddress, scope)));
83
+ this.syncedContracts.delete(toKey(contractAddress, 'ALL_SCOPES'));
84
+ }
85
+
86
+ async #syncContract(
100
87
  contractAddress: AztecAddress,
101
88
  functionToInvokeAfterSync: FunctionSelector | null,
102
89
  utilityExecutor: (call: FunctionCall, scopes: AccessScopes) => Promise<any>,
@@ -129,8 +116,8 @@ export class ContractSyncService implements StagedStore {
129
116
  }
130
117
 
131
118
  commit(jobId: string): Promise<void> {
132
- // Clear overridden contracts for this job
133
- this.overriddenContracts.delete(jobId);
119
+ // Clear excluded contracts for this job
120
+ this.excludedFromSync.delete(jobId);
134
121
  return Promise.resolve();
135
122
  }
136
123
 
@@ -138,9 +125,51 @@ export class ContractSyncService implements StagedStore {
138
125
  // We clear the synced contracts cache here because, when the job is discarded, any associated database writes from
139
126
  // the sync are also undone.
140
127
  this.syncedContracts.clear();
141
- this.overriddenContracts.delete(jobId);
128
+ this.excludedFromSync.delete(jobId);
142
129
  return Promise.resolve();
143
130
  }
131
+ /** Returns true if sync should be skipped for this contract */
132
+ #shouldSkipSync(jobId: string, contractAddress: AztecAddress): boolean {
133
+ return !!this.excludedFromSync.get(jobId)?.has(contractAddress.toString());
134
+ }
135
+
136
+ /** If there are unsynced scopes, starts sync and stores the promise in cache with error cleanup. */
137
+ #startSyncIfNeeded(
138
+ contractAddress: AztecAddress,
139
+ scopes: AccessScopes,
140
+ syncFn: (scopesToSync: AccessScopes) => Promise<void>,
141
+ ): void {
142
+ const scopesToSync = this.#getScopesToSync(contractAddress, scopes);
143
+ const keys = toKeys(contractAddress, scopesToSync);
144
+ if (keys.length === 0) {
145
+ return;
146
+ }
147
+ const promise = syncFn(scopesToSync).catch(err => {
148
+ keys.forEach(key => this.syncedContracts.delete(key));
149
+ throw err;
150
+ });
151
+ keys.forEach(key => this.syncedContracts.set(key, promise));
152
+ }
153
+
154
+ /** Filters out scopes that are already cached, returning only those that still need syncing. */
155
+ #getScopesToSync(contractAddress: AztecAddress, scopes: AccessScopes): AccessScopes {
156
+ if (this.syncedContracts.has(toKey(contractAddress, 'ALL_SCOPES'))) {
157
+ // If we are already syncing all scopes, then return an empty list
158
+ return [];
159
+ }
160
+ if (scopes === 'ALL_SCOPES') {
161
+ return 'ALL_SCOPES';
162
+ }
163
+ return scopes.filter(scope => !this.syncedContracts.has(toKey(contractAddress, scope)));
164
+ }
165
+
166
+ /** Collects all relevant scope promises (including in-flight ones from concurrent calls) and awaits them. */
167
+ async #awaitSync(contractAddress: AztecAddress, scopes: AccessScopes): Promise<void> {
168
+ const promises = toKeys(contractAddress, scopes)
169
+ .map(key => this.syncedContracts.get(key))
170
+ .filter(p => p !== undefined);
171
+ await Promise.all(promises);
172
+ }
144
173
  }
145
174
 
146
175
  function toKeys(contract: AztecAddress, scopes: AccessScopes) {
@@ -4,9 +4,9 @@
4
4
  ///
5
5
  /// @dev Whenever a contract function or Noir test is run, the `aztec_utl_assertCompatibleOracleVersion` oracle is called
6
6
  /// and if the oracle version is incompatible an error is thrown.
7
- export const ORACLE_VERSION = 16;
7
+ export const ORACLE_VERSION = 18;
8
8
 
9
9
  /// This hash is computed as by hashing the Oracle interface and it is used to detect when the Oracle interface changes,
10
10
  /// which in turn implies that you need to update the ORACLE_VERSION constant in this file and in
11
11
  /// `noir-projects/aztec-nr/aztec/src/oracle/version.nr`.
12
- export const ORACLE_INTERFACE_HASH = '73ccb2a24bc9fe7514108be9ff98d7ca8734bc316fb7c1ec4329d1d32f412a55';
12
+ export const ORACLE_INTERFACE_HASH = '57e5b07c6d55fb167ef90f8d0f410f9bdb5b154a31159c624a061be40b02a2c2';