@aztec/txe 0.0.1-commit.934299a21 → 0.0.1-commit.949a33fd8

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 (56) hide show
  1. package/dest/index.d.ts +1 -1
  2. package/dest/index.d.ts.map +1 -1
  3. package/dest/index.js +8 -6
  4. package/dest/oracle/interfaces.d.ts +31 -28
  5. package/dest/oracle/interfaces.d.ts.map +1 -1
  6. package/dest/oracle/txe_oracle_public_context.d.ts +13 -13
  7. package/dest/oracle/txe_oracle_public_context.d.ts.map +1 -1
  8. package/dest/oracle/txe_oracle_public_context.js +12 -12
  9. package/dest/oracle/txe_oracle_top_level_context.d.ts +27 -19
  10. package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
  11. package/dest/oracle/txe_oracle_top_level_context.js +72 -42
  12. package/dest/rpc_translator.d.ts +128 -82
  13. package/dest/rpc_translator.d.ts.map +1 -1
  14. package/dest/rpc_translator.js +458 -170
  15. package/dest/state_machine/archiver.d.ts +3 -3
  16. package/dest/state_machine/archiver.d.ts.map +1 -1
  17. package/dest/state_machine/archiver.js +7 -8
  18. package/dest/state_machine/dummy_p2p_client.d.ts +3 -2
  19. package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
  20. package/dest/state_machine/dummy_p2p_client.js +5 -2
  21. package/dest/state_machine/global_variable_builder.d.ts +9 -4
  22. package/dest/state_machine/global_variable_builder.d.ts.map +1 -1
  23. package/dest/state_machine/global_variable_builder.js +9 -3
  24. package/dest/state_machine/index.d.ts +4 -2
  25. package/dest/state_machine/index.d.ts.map +1 -1
  26. package/dest/state_machine/index.js +8 -4
  27. package/dest/state_machine/mock_epoch_cache.d.ts +18 -3
  28. package/dest/state_machine/mock_epoch_cache.d.ts.map +1 -1
  29. package/dest/state_machine/mock_epoch_cache.js +35 -2
  30. package/dest/state_machine/synchronizer.d.ts +5 -5
  31. package/dest/state_machine/synchronizer.d.ts.map +1 -1
  32. package/dest/state_machine/synchronizer.js +3 -3
  33. package/dest/txe_session.d.ts +46 -1
  34. package/dest/txe_session.d.ts.map +1 -1
  35. package/dest/txe_session.js +86 -17
  36. package/dest/util/encoding.d.ts +71 -1
  37. package/dest/util/encoding.d.ts.map +1 -1
  38. package/dest/util/encoding.js +4 -0
  39. package/dest/util/txe_public_contract_data_source.d.ts +1 -1
  40. package/dest/util/txe_public_contract_data_source.d.ts.map +1 -1
  41. package/dest/util/txe_public_contract_data_source.js +1 -3
  42. package/package.json +15 -15
  43. package/src/index.ts +8 -5
  44. package/src/oracle/interfaces.ts +28 -32
  45. package/src/oracle/txe_oracle_public_context.ts +12 -12
  46. package/src/oracle/txe_oracle_top_level_context.ts +75 -46
  47. package/src/rpc_translator.ts +532 -204
  48. package/src/state_machine/archiver.ts +6 -5
  49. package/src/state_machine/dummy_p2p_client.ts +6 -2
  50. package/src/state_machine/global_variable_builder.ts +18 -3
  51. package/src/state_machine/index.ts +9 -2
  52. package/src/state_machine/mock_epoch_cache.ts +46 -3
  53. package/src/state_machine/synchronizer.ts +4 -4
  54. package/src/txe_session.ts +148 -17
  55. package/src/util/encoding.ts +5 -0
  56. package/src/util/txe_public_contract_data_source.ts +0 -2
@@ -17,7 +17,7 @@ export class TXEArchiver extends ArchiverDataSourceBase {
17
17
  private readonly updater = new ArchiverDataStoreUpdater(this.store);
18
18
 
19
19
  constructor(db: AztecAsyncKVStore) {
20
- const store = new KVArchiverDataStore(db, 9999, { epochDuration: 32 });
20
+ const store = new KVArchiverDataStore(db, 9999);
21
21
  super(store);
22
22
  }
23
23
 
@@ -76,15 +76,16 @@ export class TXEArchiver extends ArchiverDataSourceBase {
76
76
  proven: tipId,
77
77
  finalized: tipId,
78
78
  checkpointed: tipId,
79
+ proposedCheckpoint: tipId,
79
80
  };
80
81
  }
81
82
 
82
- public getL2SlotNumber(): Promise<SlotNumber | undefined> {
83
- throw new Error('TXE Archiver does not implement "getL2SlotNumber"');
83
+ public getSyncedL2SlotNumber(): Promise<SlotNumber | undefined> {
84
+ throw new Error('TXE Archiver does not implement "getSyncedL2SlotNumber"');
84
85
  }
85
86
 
86
- public getL2EpochNumber(): Promise<EpochNumber | undefined> {
87
- throw new Error('TXE Archiver does not implement "getL2EpochNumber"');
87
+ public getSyncedL2EpochNumber(): Promise<EpochNumber | undefined> {
88
+ throw new Error('TXE Archiver does not implement "getSyncedL2EpochNumber"');
88
89
  }
89
90
 
90
91
  public isEpochComplete(_epochNumber: EpochNumber): Promise<boolean> {
@@ -61,8 +61,12 @@ export class DummyP2P implements P2P {
61
61
  throw new Error('DummyP2P does not implement "registerBlockProposalHandler"');
62
62
  }
63
63
 
64
- public registerCheckpointProposalHandler(_handler: P2PCheckpointReceivedCallback): void {
65
- throw new Error('DummyP2P does not implement "registerCheckpointProposalHandler"');
64
+ public registerValidatorCheckpointProposalHandler(_handler: P2PCheckpointReceivedCallback): void {
65
+ throw new Error('DummyP2P does not implement "registerValidatorCheckpointProposalHandler"');
66
+ }
67
+
68
+ public registerAllNodesCheckpointProposalHandler(_handler: P2PCheckpointReceivedCallback): void {
69
+ throw new Error('DummyP2P does not implement "registerAllNodesCheckpointProposalHandler"');
66
70
  }
67
71
 
68
72
  public requestTxs(_txHashes: TxHash[]): Promise<(Tx | undefined)[]> {
@@ -1,15 +1,29 @@
1
+ import type { SimulationOverridesPlan } from '@aztec/ethereum/contracts';
1
2
  import { BlockNumber, type SlotNumber } from '@aztec/foundation/branded-types';
3
+ import { times } from '@aztec/foundation/collection';
2
4
  import type { EthAddress } from '@aztec/foundation/eth-address';
3
5
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
4
- import { GasFees } from '@aztec/stdlib/gas';
6
+ import { FEE_ORACLE_LAG, GasFees } from '@aztec/stdlib/gas';
5
7
  import { makeGlobalVariables } from '@aztec/stdlib/testing';
6
- import { type CheckpointGlobalVariables, type GlobalVariableBuilder, GlobalVariables } from '@aztec/stdlib/tx';
8
+ import {
9
+ type CheckpointGlobalVariables,
10
+ type FeeProvider,
11
+ type GlobalVariableBuilder,
12
+ GlobalVariables,
13
+ } from '@aztec/stdlib/tx';
7
14
 
8
- export class TXEGlobalVariablesBuilder implements GlobalVariableBuilder {
15
+ /** Simple FeeProvider for TXE that returns zero fees. */
16
+ export class TXEFeeProvider implements FeeProvider {
9
17
  public getCurrentMinFees(): Promise<GasFees> {
10
18
  return Promise.resolve(new GasFees(0, 0));
11
19
  }
12
20
 
21
+ public getPredictedMinFees(): Promise<GasFees[]> {
22
+ return Promise.resolve(times(FEE_ORACLE_LAG, () => new GasFees(0, 0)));
23
+ }
24
+ }
25
+
26
+ export class TXEGlobalVariablesBuilder implements GlobalVariableBuilder {
13
27
  public buildGlobalVariables(
14
28
  _blockNumber: BlockNumber,
15
29
  _coinbase: EthAddress,
@@ -23,6 +37,7 @@ export class TXEGlobalVariablesBuilder implements GlobalVariableBuilder {
23
37
  _coinbase: EthAddress,
24
38
  _feeRecipient: AztecAddress,
25
39
  _slotNumber: SlotNumber,
40
+ _simulationOverridesPlan?: SimulationOverridesPlan,
26
41
  ): Promise<CheckpointGlobalVariables> {
27
42
  const vars = makeGlobalVariables();
28
43
  return Promise.resolve({
@@ -4,6 +4,7 @@ import { CheckpointNumber } from '@aztec/foundation/branded-types';
4
4
  import { Fr } from '@aztec/foundation/curves/bn254';
5
5
  import { createLogger } from '@aztec/foundation/log';
6
6
  import { type AnchorBlockStore, type ContractStore, ContractSyncService, type NoteStore } from '@aztec/pxe/server';
7
+ import { MessageContextService } from '@aztec/pxe/simulator';
7
8
  import { L2Block } from '@aztec/stdlib/block';
8
9
  import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
9
10
  import type { AztecNode } from '@aztec/stdlib/interfaces/client';
@@ -12,7 +13,7 @@ import { getPackageVersion } from '@aztec/stdlib/update-checker';
12
13
 
13
14
  import { TXEArchiver } from './archiver.js';
14
15
  import { DummyP2P } from './dummy_p2p_client.js';
15
- import { TXEGlobalVariablesBuilder } from './global_variable_builder.js';
16
+ import { TXEFeeProvider, TXEGlobalVariablesBuilder } from './global_variable_builder.js';
16
17
  import { MockEpochCache } from './mock_epoch_cache.js';
17
18
  import { TXESynchronizer } from './synchronizer.js';
18
19
 
@@ -26,6 +27,7 @@ export class TXEStateMachine {
26
27
  public archiver: TXEArchiver,
27
28
  public anchorBlockStore: AnchorBlockStore,
28
29
  public contractSyncService: ContractSyncService,
30
+ public messageContextService: MessageContextService,
29
31
  ) {}
30
32
 
31
33
  public static async create(
@@ -51,12 +53,15 @@ export class TXEStateMachine {
51
53
  undefined,
52
54
  undefined,
53
55
  undefined,
56
+ undefined,
54
57
  VERSION,
55
58
  CHAIN_ID,
56
59
  new TXEGlobalVariablesBuilder(),
60
+ new TXEFeeProvider(),
57
61
  new MockEpochCache(),
58
62
  getPackageVersion() ?? '',
59
63
  new TestCircuitVerifier(),
64
+ new TestCircuitVerifier(),
60
65
  undefined,
61
66
  log,
62
67
  );
@@ -68,7 +73,9 @@ export class TXEStateMachine {
68
73
  createLogger('txe:contract_sync'),
69
74
  );
70
75
 
71
- return new this(node, synchronizer, archiver, anchorBlockStore, contractSyncService);
76
+ const messageContextService = new MessageContextService(node);
77
+
78
+ return new this(node, synchronizer, archiver, anchorBlockStore, contractSyncService, messageContextService);
72
79
  }
73
80
 
74
81
  public async handleL2Block(block: L2Block) {
@@ -8,7 +8,7 @@ import { EmptyL1RollupConstants, type L1RollupConstants } from '@aztec/stdlib/ep
8
8
  * Since in TXE we don't validate transactions, mock suffices here.
9
9
  */
10
10
  export class MockEpochCache implements EpochCacheInterface {
11
- getCommittee(): Promise<EpochCommitteeInfo> {
11
+ getCommittee(_slot: SlotTag = 'now'): Promise<EpochCommitteeInfo> {
12
12
  return Promise.resolve({
13
13
  committee: undefined,
14
14
  seed: 0n,
@@ -17,6 +17,22 @@ export class MockEpochCache implements EpochCacheInterface {
17
17
  });
18
18
  }
19
19
 
20
+ getSlotNow(): SlotNumber {
21
+ return SlotNumber(0);
22
+ }
23
+
24
+ getTargetSlot(): SlotNumber {
25
+ return SlotNumber(0);
26
+ }
27
+
28
+ getEpochNow(): EpochNumber {
29
+ return EpochNumber.ZERO;
30
+ }
31
+
32
+ getTargetEpoch(): EpochNumber {
33
+ return EpochNumber.ZERO;
34
+ }
35
+
20
36
  getEpochAndSlotNow(): EpochAndSlot & { nowMs: bigint } {
21
37
  return {
22
38
  epoch: EpochNumber.ZERO,
@@ -26,15 +42,27 @@ export class MockEpochCache implements EpochCacheInterface {
26
42
  };
27
43
  }
28
44
 
29
- getEpochAndSlotInNextL1Slot(): EpochAndSlot & { now: bigint } {
45
+ getEpochAndSlotInNextL1Slot(): EpochAndSlot & { nowSeconds: bigint } {
30
46
  return {
31
47
  epoch: EpochNumber.ZERO,
32
48
  slot: SlotNumber(0),
33
49
  ts: 0n,
34
- now: 0n,
50
+ nowSeconds: 0n,
35
51
  };
36
52
  }
37
53
 
54
+ getTargetEpochAndSlotInNextL1Slot(): EpochAndSlot & { nowSeconds: bigint } {
55
+ return this.getEpochAndSlotInNextL1Slot();
56
+ }
57
+
58
+ isProposerPipeliningEnabled(): boolean {
59
+ return false;
60
+ }
61
+
62
+ pipeliningOffset(): number {
63
+ return 0;
64
+ }
65
+
38
66
  getProposerIndexEncoding(_epoch: EpochNumber, _slot: SlotNumber, _seed: bigint): `0x${string}` {
39
67
  return '0x00';
40
68
  }
@@ -50,6 +78,13 @@ export class MockEpochCache implements EpochCacheInterface {
50
78
  };
51
79
  }
52
80
 
81
+ getTargetAndNextSlot(): { targetSlot: SlotNumber; nextSlot: SlotNumber } {
82
+ return {
83
+ targetSlot: SlotNumber(0),
84
+ nextSlot: SlotNumber(0),
85
+ };
86
+ }
87
+
53
88
  getProposerAttesterAddressInSlot(_slot: SlotNumber): Promise<EthAddress | undefined> {
54
89
  return Promise.resolve(undefined);
55
90
  }
@@ -66,6 +101,14 @@ export class MockEpochCache implements EpochCacheInterface {
66
101
  return Promise.resolve([]);
67
102
  }
68
103
 
104
+ isEscapeHatchOpen(_epoch: EpochNumber): Promise<boolean> {
105
+ return Promise.resolve(false);
106
+ }
107
+
108
+ isEscapeHatchOpenAtSlot(_slot: SlotTag): Promise<boolean> {
109
+ return Promise.resolve(false);
110
+ }
111
+
69
112
  getL1Constants(): L1RollupConstants {
70
113
  return EmptyL1RollupConstants;
71
114
  }
@@ -1,7 +1,7 @@
1
1
  import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants';
2
2
  import { BlockNumber } from '@aztec/foundation/branded-types';
3
3
  import { Fr } from '@aztec/foundation/curves/bn254';
4
- import type { L2Block } from '@aztec/stdlib/block';
4
+ import type { BlockHash, L2Block } from '@aztec/stdlib/block';
5
5
  import type {
6
6
  MerkleTreeReadOperations,
7
7
  MerkleTreeWriteOperations,
@@ -33,12 +33,12 @@ export class TXESynchronizer implements WorldStateSynchronizer {
33
33
  }
34
34
 
35
35
  /**
36
- * Forces an immediate sync to an optionally provided minimum block number
36
+ * Forces an immediate sync to an optionally provided minimum block number.
37
37
  * @param targetBlockNumber - The target block number that we must sync to. Will download unproven blocks if needed to reach it.
38
- * @param skipThrowIfTargetNotReached - Whether to skip throwing if the target block number is not reached.
38
+ * @param blockHash - If provided, verifies the block at targetBlockNumber matches this hash.
39
39
  * @returns A promise that resolves with the block number the world state was synced to
40
40
  */
41
- public syncImmediate(_minBlockNumber?: BlockNumber, _skipThrowIfTargetNotReached?: boolean): Promise<BlockNumber> {
41
+ public syncImmediate(_minBlockNumber?: BlockNumber, _blockHash?: BlockHash): Promise<BlockNumber> {
42
42
  return Promise.resolve(this.blockNumber);
43
43
  }
44
44
 
@@ -3,10 +3,10 @@ import { Fr } from '@aztec/foundation/curves/bn254';
3
3
  import { type Logger, createLogger } from '@aztec/foundation/log';
4
4
  import { KeyStore } from '@aztec/key-store';
5
5
  import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
6
- import type { AccessScopes } from '@aztec/pxe/client/lazy';
7
6
  import {
8
7
  AddressStore,
9
8
  AnchorBlockStore,
9
+ CapsuleService,
10
10
  CapsuleStore,
11
11
  ContractStore,
12
12
  JobCoordinator,
@@ -42,7 +42,7 @@ import { GasSettings } from '@aztec/stdlib/gas';
42
42
  import { computeProtocolNullifier } from '@aztec/stdlib/hash';
43
43
  import { PrivateContextInputs } from '@aztec/stdlib/kernel';
44
44
  import { makeGlobalVariables } from '@aztec/stdlib/testing';
45
- import { CallContext, GlobalVariables, TxContext } from '@aztec/stdlib/tx';
45
+ import { CallContext, GlobalVariables, OFFCHAIN_MESSAGE_IDENTIFIER, TxContext } from '@aztec/stdlib/tx';
46
46
 
47
47
  import { z } from 'zod';
48
48
 
@@ -117,6 +117,65 @@ export interface TXESessionStateHandler {
117
117
  // TODO(F-335): Exposing the job info is abstraction breakage - drop the following 2 functions.
118
118
  cycleJob(): Promise<string>;
119
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 };
146
+ }
147
+
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 };
120
179
  }
121
180
 
122
181
  /**
@@ -126,6 +185,7 @@ export interface TXESessionStateHandler {
126
185
  export class TXESession implements TXESessionStateHandler {
127
186
  private state: SessionState = { name: 'TOP_LEVEL' };
128
187
  private authwits: Map<string, AuthWitness> = new Map();
188
+ private lastCallInfo: LastCallState = emptyLastCallState();
129
189
 
130
190
  constructor(
131
191
  private logger: Logger,
@@ -185,6 +245,8 @@ export class TXESession implements TXESessionStateHandler {
185
245
 
186
246
  const initialJobId = jobCoordinator.beginJob();
187
247
 
248
+ const logger = createLogger('txe:session');
249
+
188
250
  const topLevelOracleHandler = new TXEOracleTopLevelContext(
189
251
  stateMachine,
190
252
  contractStore,
@@ -202,10 +264,10 @@ export class TXESession implements TXESessionStateHandler {
202
264
  chainId,
203
265
  new Map(),
204
266
  );
205
- await topLevelOracleHandler.txeAdvanceBlocksBy(1);
267
+ await topLevelOracleHandler.advanceBlocksBy(1);
206
268
 
207
269
  return new TXESession(
208
- createLogger('txe:session'),
270
+ logger,
209
271
  stateMachine,
210
272
  topLevelOracleHandler,
211
273
  contractStore,
@@ -268,6 +330,50 @@ export class TXESession implements TXESessionStateHandler {
268
330
  return this.currentJobId;
269
331
  }
270
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
+
271
377
  async enterTopLevelState() {
272
378
  switch (this.state.name) {
273
379
  case 'PRIVATE': {
@@ -320,6 +426,7 @@ export class TXESession implements TXESessionStateHandler {
320
426
  anchorBlockNumber?: BlockNumber,
321
427
  ): Promise<PrivateContextInputs> {
322
428
  this.exitTopLevelState();
429
+ this.resetLastCall();
323
430
 
324
431
  // Private execution has two associated block numbers: the anchor block (i.e. the historical block that is used to
325
432
  // build the proof), and the *next* block, i.e. the one we'll create once the execution ends, and which will contain
@@ -328,7 +435,7 @@ export class TXESession implements TXESessionStateHandler {
328
435
 
329
436
  await new NoteService(this.noteStore, this.stateMachine.node, anchorBlock!, this.currentJobId).syncNoteNullifiers(
330
437
  contractAddress,
331
- 'ALL_SCOPES',
438
+ await this.keyStore.getAccounts(),
332
439
  );
333
440
  const latestBlock = await this.stateMachine.node.getBlockHeader('latest');
334
441
 
@@ -364,11 +471,14 @@ export class TXESession implements TXESessionStateHandler {
364
471
  senderTaggingStore: this.senderTaggingStore,
365
472
  recipientTaggingStore: this.recipientTaggingStore,
366
473
  senderAddressBookStore: this.senderAddressBookStore,
367
- capsuleStore: this.capsuleStore,
474
+ capsuleService: new CapsuleService(this.capsuleStore, await this.keyStore.getAccounts()),
368
475
  privateEventStore: this.privateEventStore,
369
476
  contractSyncService: this.stateMachine.contractSyncService,
477
+ l2TipsStore: this.stateMachine.node,
370
478
  jobId: this.currentJobId,
371
- scopes: 'ALL_SCOPES',
479
+ scopes: await this.keyStore.getAccounts(),
480
+ messageContextService: this.stateMachine.messageContextService,
481
+ simulator: new WASMSimulator(),
372
482
  });
373
483
 
374
484
  // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary
@@ -379,17 +489,22 @@ export class TXESession implements TXESessionStateHandler {
379
489
  this.state = { name: 'PRIVATE', nextBlockGlobalVariables, noteCache, taggingIndexCache };
380
490
  this.logger.debug(`Entered state ${this.state.name}`);
381
491
 
492
+ // Record the *resolved* anchor's timestamp — if the caller pinned the anchor to a past block
493
+ // via `anchorBlockNumber`, "latest" would be the wrong anchor for offchain-message semantics.
494
+ this.setLastCallContext(Fr.ZERO, anchorBlock!.globalVariables.timestamp);
495
+
382
496
  return (this.oracleHandler as PrivateExecutionOracle).getPrivateContextInputs();
383
497
  }
384
498
 
385
499
  async enterPublicState(contractAddress?: AztecAddress) {
386
500
  this.exitTopLevelState();
501
+ this.resetLastCall();
387
502
 
388
503
  // The PublicContext will create a block with a single transaction in it, containing the effects of what was done in
389
504
  // the test. The block therefore gets the *next* block number and timestamp.
390
- const latestBlockNumber = (await this.stateMachine.node.getBlockHeader('latest'))!.globalVariables.blockNumber;
505
+ const latestHeader = (await this.stateMachine.node.getBlockHeader('latest'))!;
391
506
  const globalVariables = makeGlobalVariables(undefined, {
392
- blockNumber: BlockNumber(latestBlockNumber + 1),
507
+ blockNumber: BlockNumber(latestHeader.globalVariables.blockNumber + 1),
393
508
  timestamp: this.nextBlockTimestamp,
394
509
  version: this.version,
395
510
  chainId: this.chainId,
@@ -404,10 +519,14 @@ export class TXESession implements TXESessionStateHandler {
404
519
 
405
520
  this.state = { name: 'PUBLIC' };
406
521
  this.logger.debug(`Entered state ${this.state.name}`);
522
+
523
+ // Public state is anchored at the latest block.
524
+ this.setLastCallContext(Fr.ZERO, latestHeader.globalVariables.timestamp);
407
525
  }
408
526
 
409
527
  async enterUtilityState(contractAddress: AztecAddress = DEFAULT_ADDRESS) {
410
528
  this.exitTopLevelState();
529
+ this.resetLastCall();
411
530
 
412
531
  const anchorBlockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
413
532
 
@@ -421,7 +540,7 @@ export class TXESession implements TXESessionStateHandler {
421
540
  this.stateMachine.node,
422
541
  anchorBlockHeader,
423
542
  this.currentJobId,
424
- ).syncNoteNullifiers(contractAddress, 'ALL_SCOPES');
543
+ ).syncNoteNullifiers(contractAddress, await this.keyStore.getAccounts());
425
544
 
426
545
  this.oracleHandler = new UtilityExecutionOracle({
427
546
  contractAddress,
@@ -435,14 +554,21 @@ export class TXESession implements TXESessionStateHandler {
435
554
  aztecNode: this.stateMachine.node,
436
555
  recipientTaggingStore: this.recipientTaggingStore,
437
556
  senderAddressBookStore: this.senderAddressBookStore,
438
- capsuleStore: this.capsuleStore,
557
+ capsuleService: new CapsuleService(this.capsuleStore, await this.keyStore.getAccounts()),
439
558
  privateEventStore: this.privateEventStore,
559
+ messageContextService: this.stateMachine.messageContextService,
560
+ contractSyncService: this.stateMachine.contractSyncService,
561
+ l2TipsStore: this.stateMachine.node,
440
562
  jobId: this.currentJobId,
441
- scopes: 'ALL_SCOPES',
563
+ scopes: await this.keyStore.getAccounts(),
564
+ simulator: new WASMSimulator(),
442
565
  });
443
566
 
444
567
  this.state = { name: 'UTILITY' };
445
568
  this.logger.debug(`Entered state ${this.state.name}`);
569
+
570
+ // Utility state anchors at whatever the anchor block store is pointing to (tracked as latest).
571
+ this.setLastCallContext(Fr.ZERO, anchorBlockHeader.globalVariables.timestamp);
446
572
  }
447
573
 
448
574
  private exitTopLevelState() {
@@ -452,8 +578,8 @@ export class TXESession implements TXESessionStateHandler {
452
578
 
453
579
  // Note that while all public and private contexts do is build a single block that we then process when exiting
454
580
  // those, the top level context performs a large number of actions not captured in the following 'close' call. Among
455
- // others, it will create empty blocks (via `txeAdvanceBlocksBy` and `deploy`), create blocks with transactions via
456
- // `txePrivateCallNewFlow` and `txePublicCallNewFlow`, add accounts to PXE via `txeAddAccount`, etc. This is a
581
+ // others, it will create empty blocks (via `advanceBlocksBy` and `deploy`), create blocks with transactions via
582
+ // `privateCallNewFlow` and `publicCallNewFlow`, add accounts to PXE via `addAccount`, etc. This is a
457
583
  // slight inconsistency in the working model of this class, but is not too bad.
458
584
  // TODO: it's quite unfortunate that we need to capture the authwits created to later pass them again when the top
459
585
  // level context is re-created. This is because authwits create a temporary utility context that'd otherwise reset
@@ -507,13 +633,14 @@ export class TXESession implements TXESessionStateHandler {
507
633
  }
508
634
 
509
635
  private utilityExecutorForContractSync(anchorBlock: any) {
510
- return async (call: FunctionCall, scopes: AccessScopes) => {
636
+ return async (call: FunctionCall, scopes: AztecAddress[]) => {
511
637
  const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(call.to, call.selector);
512
638
  if (entryPointArtifact.functionType !== FunctionType.UTILITY) {
513
639
  throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`);
514
640
  }
515
641
 
516
642
  try {
643
+ const simulator = new WASMSimulator();
517
644
  const oracle = new UtilityExecutionOracle({
518
645
  contractAddress: call.to,
519
646
  authWitnesses: [],
@@ -526,12 +653,16 @@ export class TXESession implements TXESessionStateHandler {
526
653
  aztecNode: this.stateMachine.node,
527
654
  recipientTaggingStore: this.recipientTaggingStore,
528
655
  senderAddressBookStore: this.senderAddressBookStore,
529
- capsuleStore: this.capsuleStore,
656
+ capsuleService: new CapsuleService(this.capsuleStore, scopes),
530
657
  privateEventStore: this.privateEventStore,
658
+ messageContextService: this.stateMachine.messageContextService,
659
+ contractSyncService: this.stateMachine.contractSyncService,
660
+ l2TipsStore: this.stateMachine.node,
531
661
  jobId: this.currentJobId,
532
662
  scopes,
663
+ simulator,
533
664
  });
534
- await new WASMSimulator()
665
+ await simulator
535
666
  .executeUserCircuit(toACVMWitness(0, call.args), entryPointArtifact, new Oracle(oracle).toACIRCallback())
536
667
  .catch((err: Error) => {
537
668
  err.message = resolveAssertionMessageFromError(err, entryPointArtifact);
@@ -3,6 +3,7 @@ import type { EthAddress } from '@aztec/foundation/eth-address';
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
  }
@@ -26,8 +26,6 @@ export class TXEPublicContractDataSource implements ContractDataSource {
26
26
  packedBytecode: contractClass.packedBytecode,
27
27
  privateFunctionsRoot: contractClass.privateFunctionsRoot,
28
28
  version: contractClass.version,
29
- privateFunctions: [],
30
- utilityFunctions: [],
31
29
  };
32
30
  }
33
31