@aztec/txe 0.0.1-commit.e588bc7e5 → 0.0.1-commit.e5a3663dd

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 (36) hide show
  1. package/dest/oracle/interfaces.d.ts +7 -2
  2. package/dest/oracle/interfaces.d.ts.map +1 -1
  3. package/dest/oracle/txe_oracle_top_level_context.d.ts +14 -6
  4. package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
  5. package/dest/oracle/txe_oracle_top_level_context.js +46 -21
  6. package/dest/rpc_translator.d.ts +44 -3
  7. package/dest/rpc_translator.d.ts.map +1 -1
  8. package/dest/rpc_translator.js +207 -26
  9. package/dest/state_machine/archiver.d.ts +1 -1
  10. package/dest/state_machine/archiver.d.ts.map +1 -1
  11. package/dest/state_machine/archiver.js +6 -5
  12. package/dest/state_machine/global_variable_builder.d.ts +9 -4
  13. package/dest/state_machine/global_variable_builder.d.ts.map +1 -1
  14. package/dest/state_machine/global_variable_builder.js +9 -3
  15. package/dest/state_machine/index.d.ts +1 -1
  16. package/dest/state_machine/index.d.ts.map +1 -1
  17. package/dest/state_machine/index.js +2 -2
  18. package/dest/state_machine/mock_epoch_cache.d.ts +2 -1
  19. package/dest/state_machine/mock_epoch_cache.d.ts.map +1 -1
  20. package/dest/state_machine/mock_epoch_cache.js +3 -0
  21. package/dest/txe_session.d.ts +48 -4
  22. package/dest/txe_session.d.ts.map +1 -1
  23. package/dest/txe_session.js +77 -17
  24. package/dest/util/encoding.d.ts +3 -1
  25. package/dest/util/encoding.d.ts.map +1 -1
  26. package/dest/util/encoding.js +4 -0
  27. package/package.json +15 -15
  28. package/src/oracle/interfaces.ts +3 -1
  29. package/src/oracle/txe_oracle_top_level_context.ts +46 -21
  30. package/src/rpc_translator.ts +254 -37
  31. package/src/state_machine/archiver.ts +4 -5
  32. package/src/state_machine/global_variable_builder.ts +13 -4
  33. package/src/state_machine/index.ts +3 -1
  34. package/src/state_machine/mock_epoch_cache.ts +4 -0
  35. package/src/txe_session.ts +130 -12
  36. package/src/util/encoding.ts +5 -0
@@ -13,7 +13,7 @@ import { getPackageVersion } from '@aztec/stdlib/update-checker';
13
13
 
14
14
  import { TXEArchiver } from './archiver.js';
15
15
  import { DummyP2P } from './dummy_p2p_client.js';
16
- import { TXEGlobalVariablesBuilder } from './global_variable_builder.js';
16
+ import { TXEFeeProvider, TXEGlobalVariablesBuilder } from './global_variable_builder.js';
17
17
  import { MockEpochCache } from './mock_epoch_cache.js';
18
18
  import { TXESynchronizer } from './synchronizer.js';
19
19
 
@@ -53,9 +53,11 @@ export class TXEStateMachine {
53
53
  undefined,
54
54
  undefined,
55
55
  undefined,
56
+ undefined,
56
57
  VERSION,
57
58
  CHAIN_ID,
58
59
  new TXEGlobalVariablesBuilder(),
60
+ new TXEFeeProvider(),
59
61
  new MockEpochCache(),
60
62
  getPackageVersion() ?? '',
61
63
  new TestCircuitVerifier(),
@@ -59,6 +59,10 @@ export class MockEpochCache implements EpochCacheInterface {
59
59
  return false;
60
60
  }
61
61
 
62
+ pipeliningOffset(): number {
63
+ return 0;
64
+ }
65
+
62
66
  getProposerIndexEncoding(_epoch: EpochNumber, _slot: SlotNumber, _seed: bigint): `0x${string}` {
63
67
  return '0x00';
64
68
  }
@@ -9,7 +9,6 @@ import {
9
9
  CapsuleService,
10
10
  CapsuleStore,
11
11
  ContractStore,
12
- ContractSyncService,
13
12
  JobCoordinator,
14
13
  NoteService,
15
14
  NoteStore,
@@ -43,7 +42,7 @@ import { GasSettings } from '@aztec/stdlib/gas';
43
42
  import { computeProtocolNullifier } from '@aztec/stdlib/hash';
44
43
  import { PrivateContextInputs } from '@aztec/stdlib/kernel';
45
44
  import { makeGlobalVariables } from '@aztec/stdlib/testing';
46
- import { CallContext, GlobalVariables, TxContext } from '@aztec/stdlib/tx';
45
+ import { CallContext, GlobalVariables, OFFCHAIN_MESSAGE_IDENTIFIER, TxContext } from '@aztec/stdlib/tx';
47
46
 
48
47
  import { z } from 'zod';
49
48
 
@@ -118,6 +117,65 @@ export interface TXESessionStateHandler {
118
117
  // TODO(F-335): Exposing the job info is abstraction breakage - drop the following 2 functions.
119
118
  cycleJob(): Promise<string>;
120
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 };
121
179
  }
122
180
 
123
181
  /**
@@ -127,6 +185,7 @@ export interface TXESessionStateHandler {
127
185
  export class TXESession implements TXESessionStateHandler {
128
186
  private state: SessionState = { name: 'TOP_LEVEL' };
129
187
  private authwits: Map<string, AuthWitness> = new Map();
188
+ private lastCallInfo: LastCallState = emptyLastCallState();
130
189
 
131
190
  constructor(
132
191
  private logger: Logger,
@@ -151,7 +210,6 @@ export class TXESession implements TXESessionStateHandler {
151
210
  private chainId: Fr,
152
211
  private version: Fr,
153
212
  private nextBlockTimestamp: bigint,
154
- private contractSyncService: ContractSyncService,
155
213
  ) {}
156
214
 
157
215
  static async init(contractStore: ContractStore) {
@@ -188,7 +246,6 @@ export class TXESession implements TXESessionStateHandler {
188
246
  const initialJobId = jobCoordinator.beginJob();
189
247
 
190
248
  const logger = createLogger('txe:session');
191
- const contractSyncService = new ContractSyncService(stateMachine.node, contractStore, noteStore, logger);
192
249
 
193
250
  const topLevelOracleHandler = new TXEOracleTopLevelContext(
194
251
  stateMachine,
@@ -206,7 +263,6 @@ export class TXESession implements TXESessionStateHandler {
206
263
  version,
207
264
  chainId,
208
265
  new Map(),
209
- contractSyncService,
210
266
  );
211
267
  await topLevelOracleHandler.advanceBlocksBy(1);
212
268
 
@@ -229,7 +285,6 @@ export class TXESession implements TXESessionStateHandler {
229
285
  version,
230
286
  chainId,
231
287
  nextBlockTimestamp,
232
- contractSyncService,
233
288
  );
234
289
  }
235
290
 
@@ -275,6 +330,50 @@ export class TXESession implements TXESessionStateHandler {
275
330
  return this.currentJobId;
276
331
  }
277
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
+
278
377
  async enterTopLevelState() {
279
378
  switch (this.state.name) {
280
379
  case 'PRIVATE': {
@@ -316,7 +415,6 @@ export class TXESession implements TXESessionStateHandler {
316
415
  this.version,
317
416
  this.chainId,
318
417
  this.authwits,
319
- this.contractSyncService,
320
418
  );
321
419
 
322
420
  this.state = { name: 'TOP_LEVEL' };
@@ -328,6 +426,7 @@ export class TXESession implements TXESessionStateHandler {
328
426
  anchorBlockNumber?: BlockNumber,
329
427
  ): Promise<PrivateContextInputs> {
330
428
  this.exitTopLevelState();
429
+ this.resetLastCall();
331
430
 
332
431
  // Private execution has two associated block numbers: the anchor block (i.e. the historical block that is used to
333
432
  // build the proof), and the *next* block, i.e. the one we'll create once the execution ends, and which will contain
@@ -375,9 +474,11 @@ export class TXESession implements TXESessionStateHandler {
375
474
  capsuleService: new CapsuleService(this.capsuleStore, await this.keyStore.getAccounts()),
376
475
  privateEventStore: this.privateEventStore,
377
476
  contractSyncService: this.stateMachine.contractSyncService,
477
+ l2TipsStore: this.stateMachine.node,
378
478
  jobId: this.currentJobId,
379
479
  scopes: await this.keyStore.getAccounts(),
380
480
  messageContextService: this.stateMachine.messageContextService,
481
+ simulator: new WASMSimulator(),
381
482
  });
382
483
 
383
484
  // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary
@@ -388,17 +489,22 @@ export class TXESession implements TXESessionStateHandler {
388
489
  this.state = { name: 'PRIVATE', nextBlockGlobalVariables, noteCache, taggingIndexCache };
389
490
  this.logger.debug(`Entered state ${this.state.name}`);
390
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
+
391
496
  return (this.oracleHandler as PrivateExecutionOracle).getPrivateContextInputs();
392
497
  }
393
498
 
394
499
  async enterPublicState(contractAddress?: AztecAddress) {
395
500
  this.exitTopLevelState();
501
+ this.resetLastCall();
396
502
 
397
503
  // The PublicContext will create a block with a single transaction in it, containing the effects of what was done in
398
504
  // the test. The block therefore gets the *next* block number and timestamp.
399
- const latestBlockNumber = (await this.stateMachine.node.getBlockHeader('latest'))!.globalVariables.blockNumber;
505
+ const latestHeader = (await this.stateMachine.node.getBlockHeader('latest'))!;
400
506
  const globalVariables = makeGlobalVariables(undefined, {
401
- blockNumber: BlockNumber(latestBlockNumber + 1),
507
+ blockNumber: BlockNumber(latestHeader.globalVariables.blockNumber + 1),
402
508
  timestamp: this.nextBlockTimestamp,
403
509
  version: this.version,
404
510
  chainId: this.chainId,
@@ -413,10 +519,14 @@ export class TXESession implements TXESessionStateHandler {
413
519
 
414
520
  this.state = { name: 'PUBLIC' };
415
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);
416
525
  }
417
526
 
418
527
  async enterUtilityState(contractAddress: AztecAddress = DEFAULT_ADDRESS) {
419
528
  this.exitTopLevelState();
529
+ this.resetLastCall();
420
530
 
421
531
  const anchorBlockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
422
532
 
@@ -447,13 +557,18 @@ export class TXESession implements TXESessionStateHandler {
447
557
  capsuleService: new CapsuleService(this.capsuleStore, await this.keyStore.getAccounts()),
448
558
  privateEventStore: this.privateEventStore,
449
559
  messageContextService: this.stateMachine.messageContextService,
450
- contractSyncService: this.contractSyncService,
560
+ contractSyncService: this.stateMachine.contractSyncService,
561
+ l2TipsStore: this.stateMachine.node,
451
562
  jobId: this.currentJobId,
452
563
  scopes: await this.keyStore.getAccounts(),
564
+ simulator: new WASMSimulator(),
453
565
  });
454
566
 
455
567
  this.state = { name: 'UTILITY' };
456
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);
457
572
  }
458
573
 
459
574
  private exitTopLevelState() {
@@ -525,6 +640,7 @@ export class TXESession implements TXESessionStateHandler {
525
640
  }
526
641
 
527
642
  try {
643
+ const simulator = new WASMSimulator();
528
644
  const oracle = new UtilityExecutionOracle({
529
645
  contractAddress: call.to,
530
646
  authWitnesses: [],
@@ -540,11 +656,13 @@ export class TXESession implements TXESessionStateHandler {
540
656
  capsuleService: new CapsuleService(this.capsuleStore, scopes),
541
657
  privateEventStore: this.privateEventStore,
542
658
  messageContextService: this.stateMachine.messageContextService,
543
- contractSyncService: this.contractSyncService,
659
+ contractSyncService: this.stateMachine.contractSyncService,
660
+ l2TipsStore: this.stateMachine.node,
544
661
  jobId: this.currentJobId,
545
662
  scopes,
663
+ simulator,
546
664
  });
547
- await new WASMSimulator()
665
+ await simulator
548
666
  .executeUserCircuit(toACVMWitness(0, call.args), entryPointArtifact, new Oracle(oracle).toACIRCallback())
549
667
  .catch((err: Error) => {
550
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
  }