@aztec/txe 0.0.1-commit.5daedc8 → 0.0.1-commit.6201a7b05

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 (78) hide show
  1. package/dest/constants.d.ts +3 -0
  2. package/dest/constants.d.ts.map +1 -0
  3. package/dest/constants.js +2 -0
  4. package/dest/index.d.ts +1 -1
  5. package/dest/index.d.ts.map +1 -1
  6. package/dest/index.js +91 -56
  7. package/dest/oracle/interfaces.d.ts +36 -29
  8. package/dest/oracle/interfaces.d.ts.map +1 -1
  9. package/dest/oracle/txe_oracle_public_context.d.ts +16 -16
  10. package/dest/oracle/txe_oracle_public_context.d.ts.map +1 -1
  11. package/dest/oracle/txe_oracle_public_context.js +20 -22
  12. package/dest/oracle/txe_oracle_top_level_context.d.ts +44 -27
  13. package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
  14. package/dest/oracle/txe_oracle_top_level_context.js +245 -109
  15. package/dest/rpc_translator.d.ts +136 -78
  16. package/dest/rpc_translator.d.ts.map +1 -1
  17. package/dest/rpc_translator.js +545 -178
  18. package/dest/state_machine/archiver.d.ts +21 -52
  19. package/dest/state_machine/archiver.d.ts.map +1 -1
  20. package/dest/state_machine/archiver.js +67 -99
  21. package/dest/state_machine/dummy_p2p_client.d.ts +21 -15
  22. package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
  23. package/dest/state_machine/dummy_p2p_client.js +45 -25
  24. package/dest/state_machine/global_variable_builder.d.ts +11 -5
  25. package/dest/state_machine/global_variable_builder.d.ts.map +1 -1
  26. package/dest/state_machine/global_variable_builder.js +21 -3
  27. package/dest/state_machine/index.d.ts +9 -7
  28. package/dest/state_machine/index.d.ts.map +1 -1
  29. package/dest/state_machine/index.js +45 -24
  30. package/dest/state_machine/mock_epoch_cache.d.ts +26 -8
  31. package/dest/state_machine/mock_epoch_cache.d.ts.map +1 -1
  32. package/dest/state_machine/mock_epoch_cache.js +49 -9
  33. package/dest/state_machine/synchronizer.d.ts +6 -5
  34. package/dest/state_machine/synchronizer.d.ts.map +1 -1
  35. package/dest/state_machine/synchronizer.js +8 -7
  36. package/dest/txe_session.d.ts +71 -15
  37. package/dest/txe_session.d.ts.map +1 -1
  38. package/dest/txe_session.js +228 -57
  39. package/dest/util/encoding.d.ts +688 -19
  40. package/dest/util/encoding.d.ts.map +1 -1
  41. package/dest/util/encoding.js +5 -1
  42. package/dest/util/txe_account_store.d.ts +10 -0
  43. package/dest/util/txe_account_store.d.ts.map +1 -0
  44. package/dest/util/{txe_account_data_provider.js → txe_account_store.js} +1 -1
  45. package/dest/util/txe_public_contract_data_source.d.ts +8 -8
  46. package/dest/util/txe_public_contract_data_source.d.ts.map +1 -1
  47. package/dest/util/txe_public_contract_data_source.js +13 -32
  48. package/dest/utils/block_creation.d.ts +21 -6
  49. package/dest/utils/block_creation.d.ts.map +1 -1
  50. package/dest/utils/block_creation.js +38 -4
  51. package/dest/utils/tx_effect_creation.d.ts +3 -3
  52. package/dest/utils/tx_effect_creation.d.ts.map +1 -1
  53. package/dest/utils/tx_effect_creation.js +4 -7
  54. package/package.json +18 -18
  55. package/src/constants.ts +3 -0
  56. package/src/index.ts +103 -63
  57. package/src/oracle/interfaces.ts +37 -33
  58. package/src/oracle/txe_oracle_public_context.ts +21 -28
  59. package/src/oracle/txe_oracle_top_level_context.ts +291 -151
  60. package/src/rpc_translator.ts +645 -206
  61. package/src/state_machine/archiver.ts +63 -117
  62. package/src/state_machine/dummy_p2p_client.ts +62 -33
  63. package/src/state_machine/global_variable_builder.ts +38 -6
  64. package/src/state_machine/index.ts +68 -22
  65. package/src/state_machine/mock_epoch_cache.ts +61 -14
  66. package/src/state_machine/synchronizer.ts +9 -8
  67. package/src/txe_session.ts +351 -105
  68. package/src/util/encoding.ts +6 -1
  69. package/src/util/{txe_account_data_provider.ts → txe_account_store.ts} +1 -1
  70. package/src/util/txe_public_contract_data_source.ts +20 -47
  71. package/src/utils/block_creation.ts +49 -15
  72. package/src/utils/tx_effect_creation.ts +5 -12
  73. package/dest/util/txe_account_data_provider.d.ts +0 -10
  74. package/dest/util/txe_account_data_provider.d.ts.map +0 -1
  75. package/dest/util/txe_contract_data_provider.d.ts +0 -12
  76. package/dest/util/txe_contract_data_provider.d.ts.map +0 -1
  77. package/dest/util/txe_contract_data_provider.js +0 -22
  78. package/src/util/txe_contract_data_provider.ts +0 -36
@@ -1,15 +1,21 @@
1
- import { Fr } from '@aztec/foundation/fields';
1
+ import { BlockNumber } from '@aztec/foundation/branded-types';
2
+ import { Fr } from '@aztec/foundation/curves/bn254';
2
3
  import { type Logger, createLogger } from '@aztec/foundation/log';
3
4
  import { KeyStore } from '@aztec/key-store';
4
5
  import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
5
- import type { ProtocolContract } from '@aztec/protocol-contracts';
6
6
  import {
7
- AddressDataProvider,
8
- CapsuleDataProvider,
9
- NoteDataProvider,
10
- PXEOracleInterface,
11
- PrivateEventDataProvider,
12
- TaggingDataProvider,
7
+ AddressStore,
8
+ AnchorBlockStore,
9
+ CapsuleService,
10
+ CapsuleStore,
11
+ ContractStore,
12
+ JobCoordinator,
13
+ NoteService,
14
+ NoteStore,
15
+ PrivateEventStore,
16
+ RecipientTaggingStore,
17
+ SenderAddressBookStore,
18
+ SenderTaggingStore,
13
19
  } from '@aztec/pxe/server';
14
20
  import {
15
21
  ExecutionNoteCache,
@@ -17,35 +23,39 @@ import {
17
23
  HashedValuesCache,
18
24
  type IPrivateExecutionOracle,
19
25
  type IUtilityExecutionOracle,
26
+ Oracle,
20
27
  PrivateExecutionOracle,
21
28
  UtilityExecutionOracle,
22
29
  } from '@aztec/pxe/simulator';
23
- import { FunctionSelector } from '@aztec/stdlib/abi';
30
+ import {
31
+ ExecutionError,
32
+ WASMSimulator,
33
+ createSimulationError,
34
+ extractCallStack,
35
+ resolveAssertionMessageFromError,
36
+ toACVMWitness,
37
+ } from '@aztec/simulator/client';
38
+ import { FunctionCall, FunctionSelector, FunctionType } from '@aztec/stdlib/abi';
24
39
  import type { AuthWitness } from '@aztec/stdlib/auth-witness';
25
40
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
26
- import { Body, L2Block } from '@aztec/stdlib/block';
27
41
  import { GasSettings } from '@aztec/stdlib/gas';
28
42
  import { computeProtocolNullifier } from '@aztec/stdlib/hash';
29
43
  import { PrivateContextInputs } from '@aztec/stdlib/kernel';
30
- import { makeAppendOnlyTreeSnapshot, makeGlobalVariables } from '@aztec/stdlib/testing';
31
- import { CallContext, GlobalVariables, TxContext } from '@aztec/stdlib/tx';
32
- import type { UInt32 } from '@aztec/stdlib/types';
44
+ import { makeGlobalVariables } from '@aztec/stdlib/testing';
45
+ import { CallContext, GlobalVariables, OFFCHAIN_MESSAGE_IDENTIFIER, TxContext } from '@aztec/stdlib/tx';
33
46
 
34
47
  import { z } from 'zod';
35
48
 
49
+ import { DEFAULT_ADDRESS } from './constants.js';
36
50
  import type { IAvmExecutionOracle, ITxeExecutionOracle } from './oracle/interfaces.js';
37
51
  import { TXEOraclePublicContext } from './oracle/txe_oracle_public_context.js';
38
52
  import { TXEOracleTopLevelContext } from './oracle/txe_oracle_top_level_context.js';
39
53
  import { RPCTranslator } from './rpc_translator.js';
54
+ import { TXEArchiver } from './state_machine/archiver.js';
40
55
  import { TXEStateMachine } from './state_machine/index.js';
41
56
  import type { ForeignCallArgs, ForeignCallResult } from './util/encoding.js';
42
- import { TXEAccountDataProvider } from './util/txe_account_data_provider.js';
43
- import { TXEContractDataProvider } from './util/txe_contract_data_provider.js';
44
- import {
45
- getSingleTxBlockRequestHash,
46
- insertTxEffectIntoWorldTrees,
47
- makeTXEBlockHeader,
48
- } from './utils/block_creation.js';
57
+ import { TXEAccountStore } from './util/txe_account_store.js';
58
+ import { getSingleTxBlockRequestHash, insertTxEffectIntoWorldTrees, makeTXEBlock } from './utils/block_creation.js';
49
59
  import { makeTxEffect } from './utils/tx_effect_creation.js';
50
60
 
51
61
  /**
@@ -67,7 +77,6 @@ type SessionState =
67
77
  | {
68
78
  name: 'PRIVATE';
69
79
  nextBlockGlobalVariables: GlobalVariables;
70
- protocolNullifier: Fr;
71
80
  noteCache: ExecutionNoteCache;
72
81
  taggingIndexCache: ExecutionTaggingIndexCache;
73
82
  }
@@ -102,11 +111,72 @@ export type TXEOracleFunctionName = Exclude<
102
111
  export interface TXESessionStateHandler {
103
112
  enterTopLevelState(): Promise<void>;
104
113
  enterPublicState(contractAddress?: AztecAddress): Promise<void>;
105
- enterPrivateState(contractAddress?: AztecAddress, anchorBlockNumber?: UInt32): Promise<PrivateContextInputs>;
114
+ enterPrivateState(contractAddress?: AztecAddress, anchorBlockNumber?: BlockNumber): Promise<PrivateContextInputs>;
106
115
  enterUtilityState(contractAddress?: AztecAddress): Promise<void>;
116
+
117
+ // TODO(F-335): Exposing the job info is abstraction breakage - drop the following 2 functions.
118
+ cycleJob(): Promise<string>;
119
+ getCurrentJob(): string;
120
+
121
+ /**
122
+ * Runs an executor-style top-level call (private/public call, utility execution) with last-call tracking.
123
+ */
124
+ withTopLevelCallTracking<T>(work: () => Promise<{ result: T; txHash?: Fr }>): Promise<T>;
125
+
126
+ /**
127
+ * Captures a raw offchain effect payload for consumption from test environment. Called by the `emit_offchain_effect`
128
+ * oracle handler whenever a contract function emits an offchain message, at any call depth.
129
+ */
130
+ recordOffchainEffect(data: Fr[]): void;
131
+
132
+ /**
133
+ * Returns the raw offchain effect payloads emitted by the last top-level call. Each payload follows the protocol
134
+ * convention documented on `OFFCHAIN_MESSAGE_IDENTIFIER`, i.e. `[identifier, recipient, ...ciphertext]`. Decoding into
135
+ * `OffchainMessage` structs happens on the Noir side of the test helper. Marks the buffer as queried so the
136
+ * unqueried-messages warning doesn't fire on the next reset.
137
+ */
138
+ getLastCallOffchainEffects(): { effects: Fr[][] };
139
+
140
+ /**
141
+ * Returns the context of the last top-level call: its tx hash (`Fr.ZERO` if the call was tx-less) and the anchor
142
+ * block timestamp captured at the start of the call. Does *not* mark the buffer as queried — context reads are
143
+ * metadata, not effect consumption.
144
+ */
145
+ getLastCallContext(): { txHash: Fr; anchorBlockTimestamp: bigint };
107
146
  }
108
147
 
109
- const DEFAULT_ADDRESS = AztecAddress.fromNumber(42);
148
+ /**
149
+ * Session state tracking the most recently completed top-level call: the offchain effect buffer it produced, and the
150
+ * call's context (tx hash + anchor block timestamp). The context is refreshed on every top-level call, independently
151
+ * of whether the call produced offchain effects.
152
+ */
153
+ interface LastCallState {
154
+ /**
155
+ * Raw offchain effect payloads emitted by the currently-executing (or most recently completed) top-level call. Wiped
156
+ * at the start of every top-level entry point, appended to on every `emit_offchain_effect` oracle invocation.
157
+ */
158
+ offchainEffects: Fr[][];
159
+ /**
160
+ * Tracks whether the test has queried `effects` since the last reset. If a new top-level call clobbers the buffer
161
+ * without it being queried first, any accumulated messages are lost and we emit a warning so tests don't silently
162
+ * drop delivery.
163
+ */
164
+ queried: boolean;
165
+ /**
166
+ * Tx hash of the most recently completed top-level call, or `Fr.ZERO` if the call was tx-less (context setters,
167
+ * utility execution). Populated by call executor handlers after execution completes.
168
+ */
169
+ txHash: Fr;
170
+ /**
171
+ * Anchor block timestamp of the most recently completed top-level call, captured from the anchor block header that
172
+ * was active when the call started. Populated by call executor handlers after execution completes.
173
+ */
174
+ anchorBlockTimestamp: bigint;
175
+ }
176
+
177
+ function emptyLastCallState(): LastCallState {
178
+ return { offchainEffects: [], queried: false, txHash: Fr.ZERO, anchorBlockTimestamp: 0n };
179
+ }
110
180
 
111
181
  /**
112
182
  * A `TXESession` corresponds to a Noir `#[test]` function, and handles all of its oracle calls, stores test-specific
@@ -115,6 +185,7 @@ const DEFAULT_ADDRESS = AztecAddress.fromNumber(42);
115
185
  export class TXESession implements TXESessionStateHandler {
116
186
  private state: SessionState = { name: 'TOP_LEVEL' };
117
187
  private authwits: Map<string, AuthWitness> = new Map();
188
+ private lastCallInfo: LastCallState = emptyLastCallState();
118
189
 
119
190
  constructor(
120
191
  private logger: Logger,
@@ -124,78 +195,96 @@ export class TXESession implements TXESessionStateHandler {
124
195
  | IPrivateExecutionOracle
125
196
  | IAvmExecutionOracle
126
197
  | ITxeExecutionOracle,
127
- private contractDataProvider: TXEContractDataProvider,
198
+ private contractStore: ContractStore,
199
+ private noteStore: NoteStore,
128
200
  private keyStore: KeyStore,
129
- private addressDataProvider: AddressDataProvider,
130
- private accountDataProvider: TXEAccountDataProvider,
201
+ private addressStore: AddressStore,
202
+ private accountStore: TXEAccountStore,
203
+ private senderTaggingStore: SenderTaggingStore,
204
+ private recipientTaggingStore: RecipientTaggingStore,
205
+ private senderAddressBookStore: SenderAddressBookStore,
206
+ private capsuleStore: CapsuleStore,
207
+ private privateEventStore: PrivateEventStore,
208
+ private jobCoordinator: JobCoordinator,
209
+ private currentJobId: string,
131
210
  private chainId: Fr,
132
211
  private version: Fr,
133
212
  private nextBlockTimestamp: bigint,
134
- private pxeOracleInterface: PXEOracleInterface,
135
213
  ) {}
136
214
 
137
- static async init(protocolContracts: ProtocolContract[]) {
215
+ static async init(contractStore: ContractStore) {
138
216
  const store = await openTmpStore('txe-session');
139
217
 
140
- const addressDataProvider = new AddressDataProvider(store);
141
- const privateEventDataProvider = new PrivateEventDataProvider(store);
142
- const contractDataProvider = new TXEContractDataProvider(store);
143
- const noteDataProvider = await NoteDataProvider.create(store);
144
- const taggingDataProvider = new TaggingDataProvider(store);
145
- const capsuleDataProvider = new CapsuleDataProvider(store);
218
+ const addressStore = new AddressStore(store);
219
+ const privateEventStore = new PrivateEventStore(store);
220
+ const noteStore = new NoteStore(store);
221
+ const senderTaggingStore = new SenderTaggingStore(store);
222
+ const recipientTaggingStore = new RecipientTaggingStore(store);
223
+ const senderAddressBookStore = new SenderAddressBookStore(store);
224
+ const capsuleStore = new CapsuleStore(store);
146
225
  const keyStore = new KeyStore(store);
147
- const accountDataProvider = new TXEAccountDataProvider(store);
148
-
149
- // Register protocol contracts.
150
- for (const { contractClass, instance, artifact } of protocolContracts) {
151
- await contractDataProvider.addContractArtifact(contractClass.id, artifact);
152
- await contractDataProvider.addContractInstance(instance);
153
- }
154
-
155
- const stateMachine = await TXEStateMachine.create(store);
226
+ const accountStore = new TXEAccountStore(store);
227
+
228
+ // Create job coordinator and register staged stores
229
+ const jobCoordinator = new JobCoordinator(store);
230
+ jobCoordinator.registerStores([
231
+ capsuleStore,
232
+ senderTaggingStore,
233
+ recipientTaggingStore,
234
+ privateEventStore,
235
+ noteStore,
236
+ ]);
237
+
238
+ const archiver = new TXEArchiver(store);
239
+ const anchorBlockStore = new AnchorBlockStore(store);
240
+ const stateMachine = await TXEStateMachine.create(archiver, anchorBlockStore, contractStore, noteStore);
156
241
 
157
242
  const nextBlockTimestamp = BigInt(Math.floor(new Date().getTime() / 1000));
158
243
  const version = new Fr(await stateMachine.node.getVersion());
159
244
  const chainId = new Fr(await stateMachine.node.getChainId());
160
245
 
161
- const pxeOracleInterface = new PXEOracleInterface(
162
- stateMachine.node,
163
- keyStore,
164
- contractDataProvider,
165
- noteDataProvider,
166
- capsuleDataProvider,
167
- stateMachine.syncDataProvider,
168
- taggingDataProvider,
169
- addressDataProvider,
170
- privateEventDataProvider,
171
- );
246
+ const initialJobId = jobCoordinator.beginJob();
247
+
248
+ const logger = createLogger('txe:session');
172
249
 
173
250
  const topLevelOracleHandler = new TXEOracleTopLevelContext(
174
251
  stateMachine,
175
- contractDataProvider,
252
+ contractStore,
253
+ noteStore,
176
254
  keyStore,
177
- addressDataProvider,
178
- accountDataProvider,
179
- pxeOracleInterface,
255
+ addressStore,
256
+ accountStore,
257
+ senderTaggingStore,
258
+ recipientTaggingStore,
259
+ senderAddressBookStore,
260
+ capsuleStore,
261
+ privateEventStore,
180
262
  nextBlockTimestamp,
181
263
  version,
182
264
  chainId,
183
265
  new Map(),
184
266
  );
185
- await topLevelOracleHandler.txeAdvanceBlocksBy(1);
267
+ await topLevelOracleHandler.advanceBlocksBy(1);
186
268
 
187
269
  return new TXESession(
188
- createLogger('txe:session'),
270
+ logger,
189
271
  stateMachine,
190
272
  topLevelOracleHandler,
191
- contractDataProvider,
273
+ contractStore,
274
+ noteStore,
192
275
  keyStore,
193
- addressDataProvider,
194
- accountDataProvider,
276
+ addressStore,
277
+ accountStore,
278
+ senderTaggingStore,
279
+ recipientTaggingStore,
280
+ senderAddressBookStore,
281
+ capsuleStore,
282
+ privateEventStore,
283
+ jobCoordinator,
284
+ initialJobId,
195
285
  version,
196
286
  chainId,
197
287
  nextBlockTimestamp,
198
- pxeOracleInterface,
199
288
  );
200
289
  }
201
290
 
@@ -230,6 +319,61 @@ export class TXESession implements TXESessionStateHandler {
230
319
  }
231
320
  }
232
321
 
322
+ getCurrentJob(): string {
323
+ return this.currentJobId;
324
+ }
325
+
326
+ /** Commits the current job and begins a new one. Returns the new job ID. */
327
+ async cycleJob(): Promise<string> {
328
+ await this.jobCoordinator.commitJob(this.currentJobId);
329
+ this.currentJobId = this.jobCoordinator.beginJob();
330
+ return this.currentJobId;
331
+ }
332
+
333
+ private resetLastCall(): void {
334
+ const notQueriedMessageCount = this.lastCallInfo.queried
335
+ ? 0
336
+ : this.lastCallInfo.offchainEffects.filter(payload => payload[0]?.equals(OFFCHAIN_MESSAGE_IDENTIFIER)).length;
337
+ if (notQueriedMessageCount > 0) {
338
+ this.logger.warn(
339
+ `Dropping ${notQueriedMessageCount} unqueried offchain message(s) from the previous top-level call. ` +
340
+ `To deliver them, call \`env.offchain_messages()\` and forward the result to the recipient contract's ` +
341
+ `\`offchain_receive\` utility before issuing another top-level call. To intentionally discard, assign ` +
342
+ `to \`let _ = env.offchain_messages()\` to silence this warning.`,
343
+ );
344
+ }
345
+ this.lastCallInfo = emptyLastCallState();
346
+ }
347
+
348
+ recordOffchainEffect(data: Fr[]): void {
349
+ this.lastCallInfo.offchainEffects.push(data);
350
+ }
351
+
352
+ private setLastCallContext(txHash: Fr, anchorBlockTimestamp: bigint): void {
353
+ this.lastCallInfo.txHash = txHash;
354
+ this.lastCallInfo.anchorBlockTimestamp = anchorBlockTimestamp;
355
+ }
356
+
357
+ async withTopLevelCallTracking<T>(work: () => Promise<{ result: T; txHash?: Fr }>): Promise<T> {
358
+ this.resetLastCall();
359
+ // Capture the anchor *before* `work` runs: private/public executor calls mine a new block as a
360
+ // side effect, and that block's timestamp should not be attributed to this call's anchor.
361
+ const anchorBlockTimestamp = (await this.stateMachine.node.getBlockHeader('latest'))!.globalVariables.timestamp;
362
+ const { result, txHash } = await work();
363
+ this.setLastCallContext(txHash ?? Fr.ZERO, anchorBlockTimestamp);
364
+ return result;
365
+ }
366
+
367
+ getLastCallOffchainEffects(): { effects: Fr[][] } {
368
+ this.lastCallInfo.queried = true;
369
+ return { effects: this.lastCallInfo.offchainEffects };
370
+ }
371
+
372
+ getLastCallContext(): { txHash: Fr; anchorBlockTimestamp: bigint } {
373
+ const { txHash, anchorBlockTimestamp } = this.lastCallInfo;
374
+ return { txHash, anchorBlockTimestamp };
375
+ }
376
+
233
377
  async enterTopLevelState() {
234
378
  switch (this.state.name) {
235
379
  case 'PRIVATE': {
@@ -252,13 +396,21 @@ export class TXESession implements TXESessionStateHandler {
252
396
  }
253
397
  }
254
398
 
399
+ // Commit all staged stores from the job that was just completed, then begin a new job
400
+ await this.cycleJob();
401
+
255
402
  this.oracleHandler = new TXEOracleTopLevelContext(
256
403
  this.stateMachine,
257
- this.contractDataProvider,
404
+ this.contractStore,
405
+ this.noteStore,
258
406
  this.keyStore,
259
- this.addressDataProvider,
260
- this.accountDataProvider,
261
- this.pxeOracleInterface,
407
+ this.addressStore,
408
+ this.accountStore,
409
+ this.senderTaggingStore,
410
+ this.recipientTaggingStore,
411
+ this.senderAddressBookStore,
412
+ this.capsuleStore,
413
+ this.privateEventStore,
262
414
  this.nextBlockTimestamp,
263
415
  this.version,
264
416
  this.chainId,
@@ -271,25 +423,24 @@ export class TXESession implements TXESessionStateHandler {
271
423
 
272
424
  async enterPrivateState(
273
425
  contractAddress: AztecAddress = DEFAULT_ADDRESS,
274
- anchorBlockNumber?: UInt32,
426
+ anchorBlockNumber?: BlockNumber,
275
427
  ): Promise<PrivateContextInputs> {
276
428
  this.exitTopLevelState();
277
-
278
- // There is no automatic message discovery and contract-driven syncing process in inlined private or utility
279
- // contexts, which means that known nullifiers are also not searched for, since it is during the tagging sync that
280
- // we perform this. We therefore search for known nullifiers now, as otherwise notes that were nullified would not
281
- // be removed from the database.
282
- // TODO(#12553): make the synchronizer sync here instead and remove this
283
- await this.pxeOracleInterface.syncNoteNullifiers(contractAddress);
429
+ this.resetLastCall();
284
430
 
285
431
  // Private execution has two associated block numbers: the anchor block (i.e. the historical block that is used to
286
432
  // build the proof), and the *next* block, i.e. the one we'll create once the execution ends, and which will contain
287
433
  // a single transaction with the effects of what was done in the test.
288
434
  const anchorBlock = await this.stateMachine.node.getBlockHeader(anchorBlockNumber ?? 'latest');
435
+
436
+ await new NoteService(this.noteStore, this.stateMachine.node, anchorBlock!, this.currentJobId).syncNoteNullifiers(
437
+ contractAddress,
438
+ await this.keyStore.getAccounts(),
439
+ );
289
440
  const latestBlock = await this.stateMachine.node.getBlockHeader('latest');
290
441
 
291
442
  const nextBlockGlobalVariables = makeGlobalVariables(undefined, {
292
- blockNumber: latestBlock!.globalVariables.blockNumber + 1,
443
+ blockNumber: BlockNumber(latestBlock!.globalVariables.blockNumber + 1),
293
444
  timestamp: this.nextBlockTimestamp,
294
445
  version: this.version,
295
446
  chainId: this.chainId,
@@ -300,38 +451,59 @@ export class TXESession implements TXESessionStateHandler {
300
451
  const noteCache = new ExecutionNoteCache(protocolNullifier);
301
452
  const taggingIndexCache = new ExecutionTaggingIndexCache();
302
453
 
303
- this.oracleHandler = new PrivateExecutionOracle(
304
- Fr.ZERO,
305
- new TxContext(this.chainId, this.version, GasSettings.empty()),
306
- new CallContext(AztecAddress.ZERO, contractAddress, FunctionSelector.empty(), false),
307
- anchorBlock!,
308
- [],
309
- [],
310
- new HashedValuesCache(),
454
+ const utilityExecutor = this.utilityExecutorForContractSync(anchorBlock);
455
+ this.oracleHandler = new PrivateExecutionOracle({
456
+ argsHash: Fr.ZERO,
457
+ txContext: new TxContext(this.chainId, this.version, GasSettings.empty()),
458
+ callContext: new CallContext(AztecAddress.ZERO, contractAddress, FunctionSelector.empty(), false),
459
+ anchorBlockHeader: anchorBlock!,
460
+ utilityExecutor,
461
+ authWitnesses: [],
462
+ capsules: [],
463
+ executionCache: new HashedValuesCache(),
311
464
  noteCache,
312
465
  taggingIndexCache,
313
- this.pxeOracleInterface,
314
- );
466
+ contractStore: this.contractStore,
467
+ noteStore: this.noteStore,
468
+ keyStore: this.keyStore,
469
+ addressStore: this.addressStore,
470
+ aztecNode: this.stateMachine.node,
471
+ senderTaggingStore: this.senderTaggingStore,
472
+ recipientTaggingStore: this.recipientTaggingStore,
473
+ senderAddressBookStore: this.senderAddressBookStore,
474
+ capsuleService: new CapsuleService(this.capsuleStore, await this.keyStore.getAccounts()),
475
+ privateEventStore: this.privateEventStore,
476
+ contractSyncService: this.stateMachine.contractSyncService,
477
+ l2TipsStore: this.stateMachine.node,
478
+ jobId: this.currentJobId,
479
+ scopes: await this.keyStore.getAccounts(),
480
+ messageContextService: this.stateMachine.messageContextService,
481
+ });
315
482
 
316
483
  // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary
317
484
  // data) in order to refer to it later, mimicking the way this object is used by the ContractFunctionSimulator. The
318
485
  // difference resides in that the simulator has all information needed in order to run the simulation, while ours
319
486
  // will be ongoing as the different oracles will be invoked from the Noir test, until eventually the private
320
487
  // execution finishes.
321
- this.state = { name: 'PRIVATE', nextBlockGlobalVariables, protocolNullifier, noteCache, taggingIndexCache };
488
+ this.state = { name: 'PRIVATE', nextBlockGlobalVariables, noteCache, taggingIndexCache };
322
489
  this.logger.debug(`Entered state ${this.state.name}`);
323
490
 
491
+ // Record the *resolved* anchor's timestamp — if the caller pinned the anchor to a past block
492
+ // via `anchorBlockNumber`, "latest" would be the wrong anchor for offchain-message semantics.
493
+ this.setLastCallContext(Fr.ZERO, anchorBlock!.globalVariables.timestamp);
494
+
324
495
  return (this.oracleHandler as PrivateExecutionOracle).getPrivateContextInputs();
325
496
  }
326
497
 
327
498
  async enterPublicState(contractAddress?: AztecAddress) {
328
499
  this.exitTopLevelState();
500
+ this.resetLastCall();
329
501
 
330
502
  // The PublicContext will create a block with a single transaction in it, containing the effects of what was done in
331
503
  // the test. The block therefore gets the *next* block number and timestamp.
332
- const latestBlockNumber = (await this.stateMachine.node.getBlockHeader('latest'))!.globalVariables.blockNumber;
504
+ const latestHeader = (await this.stateMachine.node.getBlockHeader('latest'))!;
333
505
  const globalVariables = makeGlobalVariables(undefined, {
334
- blockNumber: latestBlockNumber + 1,
506
+ blockNumber: BlockNumber(latestHeader.globalVariables.blockNumber + 1),
335
507
  timestamp: this.nextBlockTimestamp,
336
508
  version: this.version,
337
509
  chainId: this.chainId,
@@ -346,22 +518,55 @@ export class TXESession implements TXESessionStateHandler {
346
518
 
347
519
  this.state = { name: 'PUBLIC' };
348
520
  this.logger.debug(`Entered state ${this.state.name}`);
521
+
522
+ // Public state is anchored at the latest block.
523
+ this.setLastCallContext(Fr.ZERO, latestHeader.globalVariables.timestamp);
349
524
  }
350
525
 
351
526
  async enterUtilityState(contractAddress: AztecAddress = DEFAULT_ADDRESS) {
352
527
  this.exitTopLevelState();
528
+ this.resetLastCall();
529
+
530
+ const anchorBlockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
353
531
 
354
532
  // There is no automatic message discovery and contract-driven syncing process in inlined private or utility
355
533
  // contexts, which means that known nullifiers are also not searched for, since it is during the tagging sync that
356
534
  // we perform this. We therefore search for known nullifiers now, as otherwise notes that were nullified would not
357
535
  // be removed from the database.
358
536
  // TODO(#12553): make the synchronizer sync here instead and remove this
359
- await this.pxeOracleInterface.syncNoteNullifiers(contractAddress);
360
-
361
- this.oracleHandler = new UtilityExecutionOracle(contractAddress, [], [], this.pxeOracleInterface);
537
+ await new NoteService(
538
+ this.noteStore,
539
+ this.stateMachine.node,
540
+ anchorBlockHeader,
541
+ this.currentJobId,
542
+ ).syncNoteNullifiers(contractAddress, await this.keyStore.getAccounts());
543
+
544
+ this.oracleHandler = new UtilityExecutionOracle({
545
+ contractAddress,
546
+ authWitnesses: [],
547
+ capsules: [],
548
+ anchorBlockHeader,
549
+ contractStore: this.contractStore,
550
+ noteStore: this.noteStore,
551
+ keyStore: this.keyStore,
552
+ addressStore: this.addressStore,
553
+ aztecNode: this.stateMachine.node,
554
+ recipientTaggingStore: this.recipientTaggingStore,
555
+ senderAddressBookStore: this.senderAddressBookStore,
556
+ capsuleService: new CapsuleService(this.capsuleStore, await this.keyStore.getAccounts()),
557
+ privateEventStore: this.privateEventStore,
558
+ messageContextService: this.stateMachine.messageContextService,
559
+ contractSyncService: this.stateMachine.contractSyncService,
560
+ l2TipsStore: this.stateMachine.node,
561
+ jobId: this.currentJobId,
562
+ scopes: await this.keyStore.getAccounts(),
563
+ });
362
564
 
363
565
  this.state = { name: 'UTILITY' };
364
566
  this.logger.debug(`Entered state ${this.state.name}`);
567
+
568
+ // Utility state anchors at whatever the anchor block store is pointing to (tracked as latest).
569
+ this.setLastCallContext(Fr.ZERO, anchorBlockHeader.globalVariables.timestamp);
365
570
  }
366
571
 
367
572
  private exitTopLevelState() {
@@ -371,8 +576,8 @@ export class TXESession implements TXESessionStateHandler {
371
576
 
372
577
  // Note that while all public and private contexts do is build a single block that we then process when exiting
373
578
  // those, the top level context performs a large number of actions not captured in the following 'close' call. Among
374
- // others, it will create empty blocks (via `txeAdvanceBlocksBy` and `deploy`), create blocks with transactions via
375
- // `txePrivateCallNewFlow` and `txePublicCallNewFlow`, add accounts to PXE via `txeAddAccount`, etc. This is a
579
+ // others, it will create empty blocks (via `advanceBlocksBy` and `deploy`), create blocks with transactions via
580
+ // `privateCallNewFlow` and `publicCallNewFlow`, add accounts to PXE via `addAccount`, etc. This is a
376
581
  // slight inconsistency in the working model of this class, but is not too bad.
377
582
  // TODO: it's quite unfortunate that we need to capture the authwits created to later pass them again when the top
378
583
  // level context is re-created. This is because authwits create a temporary utility context that'd otherwise reset
@@ -392,21 +597,14 @@ export class TXESession implements TXESessionStateHandler {
392
597
 
393
598
  // We rely on the note cache to determine the effects of the transaction. This is incomplete as it doesn't private
394
599
  // logs (other effects like enqueued public calls don't need to be considered since those are not allowed).
395
- const txEffect = await makeTxEffect(
396
- this.state.noteCache,
397
- this.state.protocolNullifier,
398
- this.state.nextBlockGlobalVariables.blockNumber,
399
- );
600
+
601
+ const txEffect = await makeTxEffect(this.state.noteCache, this.state.nextBlockGlobalVariables.blockNumber);
400
602
 
401
603
  // We build a block holding just this transaction
402
604
  const forkedWorldTrees = await this.stateMachine.synchronizer.nativeWorldStateService.fork();
403
605
  await insertTxEffectIntoWorldTrees(txEffect, forkedWorldTrees);
404
606
 
405
- const block = new L2Block(
406
- makeAppendOnlyTreeSnapshot(),
407
- await makeTXEBlockHeader(forkedWorldTrees, this.state.nextBlockGlobalVariables),
408
- new Body([txEffect]),
409
- );
607
+ const block = await makeTXEBlock(forkedWorldTrees, this.state.nextBlockGlobalVariables, [txEffect]);
410
608
  await this.stateMachine.handleL2Block(block);
411
609
 
412
610
  await forkedWorldTrees.close();
@@ -431,4 +629,52 @@ export class TXESession implements TXESessionStateHandler {
431
629
  throw new Error(`Expected to be in state 'UTILITY', but got '${this.state.name}' instead`);
432
630
  }
433
631
  }
632
+
633
+ private utilityExecutorForContractSync(anchorBlock: any) {
634
+ return async (call: FunctionCall, scopes: AztecAddress[]) => {
635
+ const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(call.to, call.selector);
636
+ if (entryPointArtifact.functionType !== FunctionType.UTILITY) {
637
+ throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`);
638
+ }
639
+
640
+ try {
641
+ const oracle = new UtilityExecutionOracle({
642
+ contractAddress: call.to,
643
+ authWitnesses: [],
644
+ capsules: [],
645
+ anchorBlockHeader: anchorBlock!,
646
+ contractStore: this.contractStore,
647
+ noteStore: this.noteStore,
648
+ keyStore: this.keyStore,
649
+ addressStore: this.addressStore,
650
+ aztecNode: this.stateMachine.node,
651
+ recipientTaggingStore: this.recipientTaggingStore,
652
+ senderAddressBookStore: this.senderAddressBookStore,
653
+ capsuleService: new CapsuleService(this.capsuleStore, scopes),
654
+ privateEventStore: this.privateEventStore,
655
+ messageContextService: this.stateMachine.messageContextService,
656
+ contractSyncService: this.stateMachine.contractSyncService,
657
+ l2TipsStore: this.stateMachine.node,
658
+ jobId: this.currentJobId,
659
+ scopes,
660
+ });
661
+ await new WASMSimulator()
662
+ .executeUserCircuit(toACVMWitness(0, call.args), entryPointArtifact, new Oracle(oracle).toACIRCallback())
663
+ .catch((err: Error) => {
664
+ err.message = resolveAssertionMessageFromError(err, entryPointArtifact);
665
+ throw new ExecutionError(
666
+ err.message,
667
+ {
668
+ contractAddress: call.to,
669
+ functionSelector: call.selector,
670
+ },
671
+ extractCallStack(err, entryPointArtifact.debug),
672
+ { cause: err },
673
+ );
674
+ });
675
+ } catch (err) {
676
+ throw createSimulationError(err instanceof Error ? err : new Error('Unknown error contract data sync'));
677
+ }
678
+ };
679
+ }
434
680
  }
@@ -1,8 +1,9 @@
1
+ import { Fr } from '@aztec/foundation/curves/bn254';
1
2
  import type { EthAddress } from '@aztec/foundation/eth-address';
2
- import { Fr } from '@aztec/foundation/fields';
3
3
  import { hexToBuffer } from '@aztec/foundation/string';
4
4
  import { type ContractArtifact, ContractArtifactSchema } from '@aztec/stdlib/abi';
5
5
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
6
+ import { BlockHash } from '@aztec/stdlib/block';
6
7
  import { type ContractInstanceWithAddress, ContractInstanceWithAddressSchema } from '@aztec/stdlib/contract';
7
8
 
8
9
  import { z } from 'zod';
@@ -25,6 +26,10 @@ export function addressFromSingle(obj: ForeignCallSingle) {
25
26
  return new AztecAddress(fromSingle(obj));
26
27
  }
27
28
 
29
+ export function blockHashFromSingle(obj: ForeignCallSingle) {
30
+ return new BlockHash(fromSingle(obj));
31
+ }
32
+
28
33
  export function fromArray(obj: ForeignCallArray) {
29
34
  return obj.map(str => Fr.fromBuffer(hexToBuffer(str)));
30
35
  }