@aztec/txe 0.0.1-commit.2ed92850 → 0.0.1-commit.43597cc1

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 (40) hide show
  1. package/dest/oracle/interfaces.d.ts +2 -2
  2. package/dest/oracle/interfaces.d.ts.map +1 -1
  3. package/dest/oracle/txe_oracle_public_context.d.ts +2 -2
  4. package/dest/oracle/txe_oracle_public_context.d.ts.map +1 -1
  5. package/dest/oracle/txe_oracle_public_context.js +3 -4
  6. package/dest/oracle/txe_oracle_top_level_context.d.ts +2 -2
  7. package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
  8. package/dest/oracle/txe_oracle_top_level_context.js +34 -15
  9. package/dest/rpc_translator.d.ts +7 -7
  10. package/dest/rpc_translator.d.ts.map +1 -1
  11. package/dest/rpc_translator.js +40 -29
  12. package/dest/state_machine/archiver.d.ts +1 -1
  13. package/dest/state_machine/archiver.d.ts.map +1 -1
  14. package/dest/state_machine/archiver.js +2 -0
  15. package/dest/state_machine/dummy_p2p_client.d.ts +4 -3
  16. package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
  17. package/dest/state_machine/dummy_p2p_client.js +5 -2
  18. package/dest/state_machine/index.d.ts +5 -5
  19. package/dest/state_machine/index.d.ts.map +1 -1
  20. package/dest/state_machine/index.js +14 -9
  21. package/dest/state_machine/mock_epoch_cache.d.ts +3 -1
  22. package/dest/state_machine/mock_epoch_cache.d.ts.map +1 -1
  23. package/dest/state_machine/mock_epoch_cache.js +4 -0
  24. package/dest/txe_session.d.ts +1 -1
  25. package/dest/txe_session.d.ts.map +1 -1
  26. package/dest/txe_session.js +11 -8
  27. package/dest/utils/block_creation.d.ts +1 -1
  28. package/dest/utils/block_creation.d.ts.map +1 -1
  29. package/dest/utils/block_creation.js +3 -1
  30. package/package.json +15 -15
  31. package/src/oracle/interfaces.ts +1 -1
  32. package/src/oracle/txe_oracle_public_context.ts +3 -5
  33. package/src/oracle/txe_oracle_top_level_context.ts +63 -27
  34. package/src/rpc_translator.ts +42 -24
  35. package/src/state_machine/archiver.ts +2 -0
  36. package/src/state_machine/dummy_p2p_client.ts +7 -2
  37. package/src/state_machine/index.ts +24 -9
  38. package/src/state_machine/mock_epoch_cache.ts +5 -0
  39. package/src/txe_session.ts +13 -14
  40. package/src/utils/block_creation.ts +3 -1
@@ -22,7 +22,6 @@ import {
22
22
  SenderAddressBookStore,
23
23
  SenderTaggingStore,
24
24
  enrichPublicSimulationError,
25
- syncState,
26
25
  } from '@aztec/pxe/server';
27
26
  import {
28
27
  ExecutionNoteCache,
@@ -132,13 +131,14 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
132
131
  }
133
132
 
134
133
  // We instruct users to debug contracts via this oracle, so it makes sense that they'd expect it to also work in tests
135
- utilityDebugLog(level: number, message: string, fields: Fr[]): void {
134
+ utilityDebugLog(level: number, message: string, fields: Fr[]): Promise<void> {
136
135
  if (!LogLevels[level]) {
137
136
  throw new Error(`Invalid debug log level: ${level}`);
138
137
  }
139
138
  const levelName = LogLevels[level];
140
139
 
141
140
  this.logger[levelName](`${applyStringFormatting(message, fields)}`, { module: `${this.logger.module}:debug_log` });
141
+ return Promise.resolve();
142
142
  }
143
143
 
144
144
  txeGetDefaultAddress(): AztecAddress {
@@ -297,12 +297,24 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
297
297
  throw new Error(message);
298
298
  }
299
299
 
300
+ // When `from` is the zero address (used when creating a new contract account for example),
301
+ // we disable scope filtering by setting effectiveScopes to undefined. This allows these operations
302
+ // to proceed without requiring keys registered for the zero address.
303
+ const effectiveScopes = from.isZero() ? undefined : [from];
304
+
300
305
  // Sync notes before executing private function to discover notes from previous transactions
301
306
  const utilityExecutor = async (call: FunctionCall) => {
302
- await this.executeUtilityCall(call);
307
+ await this.executeUtilityCall(call, effectiveScopes);
303
308
  };
304
309
 
305
- await syncState(targetContractAddress, this.contractStore, functionSelector, utilityExecutor);
310
+ const blockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
311
+ await this.stateMachine.contractSyncService.ensureContractSynced(
312
+ targetContractAddress,
313
+ functionSelector,
314
+ utilityExecutor,
315
+ blockHeader,
316
+ this.jobId,
317
+ );
306
318
 
307
319
  const blockNumber = await this.txeGetNextBlockNumber();
308
320
 
@@ -314,8 +326,6 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
314
326
 
315
327
  const txContext = new TxContext(this.chainId, this.version, gasSettings);
316
328
 
317
- const blockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
318
-
319
329
  const protocolNullifier = await computeProtocolNullifier(getSingleTxBlockRequestHash(blockNumber));
320
330
  const noteCache = new ExecutionNoteCache(protocolNullifier);
321
331
  // In production, the account contract sets the min revertible counter before calling the app function.
@@ -346,17 +356,17 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
346
356
  this.keyStore,
347
357
  this.addressStore,
348
358
  this.stateMachine.node,
349
- this.stateMachine.anchorBlockStore,
350
359
  this.senderTaggingStore,
351
360
  this.recipientTaggingStore,
352
361
  this.senderAddressBookStore,
353
362
  this.capsuleStore,
354
363
  this.privateEventStore,
364
+ this.stateMachine.contractSyncService,
355
365
  this.jobId,
356
366
  0, // totalPublicArgsCount
357
367
  minRevertibleSideEffectCounter, // (start) sideEffectCounter
358
368
  undefined, // log
359
- undefined, // scopes
369
+ effectiveScopes, // scopes
360
370
  /**
361
371
  * In TXE, the typical transaction entrypoint is skipped, so we need to simulate the actions that such a
362
372
  * contract would perform, including setting senderForTags.
@@ -402,7 +412,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
402
412
  // We pass the non-zero minRevertibleSideEffectCounter to make sure the side effects are split correctly.
403
413
  const { publicInputs } = await generateSimulatedProvingResult(
404
414
  result,
405
- this.contractStore,
415
+ (addr, sel) => this.contractStore.getDebugFunctionName(addr, sel),
406
416
  minRevertibleSideEffectCounter,
407
417
  );
408
418
 
@@ -415,7 +425,11 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
415
425
 
416
426
  const forkedWorldTrees = await this.stateMachine.synchronizer.nativeWorldStateService.fork();
417
427
 
418
- const contractsDB = new PublicContractsDB(new TXEPublicContractDataSource(blockNumber, this.contractStore));
428
+ const bindings = this.logger.getBindings();
429
+ const contractsDB = new PublicContractsDB(
430
+ new TXEPublicContractDataSource(blockNumber, this.contractStore),
431
+ bindings,
432
+ );
419
433
  const guardedMerkleTrees = new GuardedMerkleTreeOperations(forkedWorldTrees);
420
434
  const config = PublicSimulatorConfig.from({
421
435
  skipFeeEnforcement: true,
@@ -428,8 +442,10 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
428
442
  globals,
429
443
  guardedMerkleTrees,
430
444
  contractsDB,
431
- new CppPublicTxSimulator(guardedMerkleTrees, contractsDB, globals, config),
445
+ new CppPublicTxSimulator(guardedMerkleTrees, contractsDB, globals, config, bindings),
432
446
  new TestDateProvider(),
447
+ undefined,
448
+ createLogger('simulator:public-processor', bindings),
433
449
  );
434
450
 
435
451
  const tx = await Tx.create({
@@ -526,7 +542,11 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
526
542
 
527
543
  const forkedWorldTrees = await this.stateMachine.synchronizer.nativeWorldStateService.fork();
528
544
 
529
- const contractsDB = new PublicContractsDB(new TXEPublicContractDataSource(blockNumber, this.contractStore));
545
+ const bindings2 = this.logger.getBindings();
546
+ const contractsDB = new PublicContractsDB(
547
+ new TXEPublicContractDataSource(blockNumber, this.contractStore),
548
+ bindings2,
549
+ );
530
550
  const guardedMerkleTrees = new GuardedMerkleTreeOperations(forkedWorldTrees);
531
551
  const config = PublicSimulatorConfig.from({
532
552
  skipFeeEnforcement: true,
@@ -535,8 +555,16 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
535
555
  collectStatistics: false,
536
556
  collectCallMetadata: true,
537
557
  });
538
- const simulator = new CppPublicTxSimulator(guardedMerkleTrees, contractsDB, globals, config);
539
- const processor = new PublicProcessor(globals, guardedMerkleTrees, contractsDB, simulator, new TestDateProvider());
558
+ const simulator = new CppPublicTxSimulator(guardedMerkleTrees, contractsDB, globals, config, bindings2);
559
+ const processor = new PublicProcessor(
560
+ globals,
561
+ guardedMerkleTrees,
562
+ contractsDB,
563
+ simulator,
564
+ new TestDateProvider(),
565
+ undefined,
566
+ createLogger('simulator:public-processor', bindings2),
567
+ );
540
568
 
541
569
  // We're simulating a scenario in which private execution immediately enqueues a public call and halts. The private
542
570
  // kernel init would in this case inject a nullifier with the transaction request hash as a non-revertible
@@ -646,25 +674,32 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
646
674
  }
647
675
 
648
676
  // Sync notes before executing utility function to discover notes from previous transactions
649
- await syncState(targetContractAddress, this.contractStore, functionSelector, async call => {
650
- await this.executeUtilityCall(call);
651
- });
652
-
653
- const call = new FunctionCall(
654
- artifact.name,
677
+ const blockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
678
+ await this.stateMachine.contractSyncService.ensureContractSynced(
655
679
  targetContractAddress,
656
680
  functionSelector,
657
- FunctionType.UTILITY,
658
- false,
659
- false,
660
- args,
661
- [],
681
+ async call => {
682
+ await this.executeUtilityCall(call);
683
+ },
684
+ blockHeader,
685
+ this.jobId,
662
686
  );
663
687
 
688
+ const call = FunctionCall.from({
689
+ name: artifact.name,
690
+ to: targetContractAddress,
691
+ selector: functionSelector,
692
+ type: FunctionType.UTILITY,
693
+ hideMsgSender: false,
694
+ isStatic: false,
695
+ args,
696
+ returnTypes: [],
697
+ });
698
+
664
699
  return this.executeUtilityCall(call);
665
700
  }
666
701
 
667
- private async executeUtilityCall(call: FunctionCall): Promise<Fr[]> {
702
+ private async executeUtilityCall(call: FunctionCall, scopes?: AztecAddress[]): Promise<Fr[]> {
668
703
  const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(call.to, call.selector);
669
704
  if (entryPointArtifact.functionType !== FunctionType.UTILITY) {
670
705
  throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`);
@@ -687,12 +722,13 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl
687
722
  this.keyStore,
688
723
  this.addressStore,
689
724
  this.stateMachine.node,
690
- this.stateMachine.anchorBlockStore,
691
725
  this.recipientTaggingStore,
692
726
  this.senderAddressBookStore,
693
727
  this.capsuleStore,
694
728
  this.privateEventStore,
695
729
  this.jobId,
730
+ undefined, // log
731
+ scopes, // scopes - used to filter notes by account
696
732
  );
697
733
  const acirExecutionResult = await new WASMSimulator()
698
734
  .executeUserCircuit(toACVMWitness(0, call.args), entryPointArtifact, new Oracle(oracle).toACIRCallback())
@@ -10,7 +10,7 @@ import {
10
10
  } from '@aztec/pxe/simulator';
11
11
  import { type ContractArtifact, EventSelector, FunctionSelector, NoteSelector } from '@aztec/stdlib/abi';
12
12
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
13
- import { L2BlockHash } from '@aztec/stdlib/block';
13
+ import { BlockHash } from '@aztec/stdlib/block';
14
14
 
15
15
  import type { IAvmExecutionOracle, ITxeExecutionOracle } from './oracle/interfaces.js';
16
16
  import type { TXESessionStateHandler } from './txe_session.js';
@@ -328,7 +328,7 @@ export class RPCTranslator {
328
328
 
329
329
  // When the argument is a slice, noir automatically adds a length field to oracle call.
330
330
  // When the argument is an array, we add the field length manually to the signature.
331
- utilityDebugLog(
331
+ async utilityDebugLog(
332
332
  foreignLevel: ForeignCallSingle,
333
333
  foreignMessage: ForeignCallArray,
334
334
  _foreignLength: ForeignCallSingle,
@@ -340,7 +340,7 @@ export class RPCTranslator {
340
340
  .join('');
341
341
  const fields = fromArray(foreignFields);
342
342
 
343
- this.handlerAsMisc().utilityDebugLog(level, message, fields);
343
+ await this.handlerAsMisc().utilityDebugLog(level, message, fields);
344
344
 
345
345
  return toForeignCallResult([]);
346
346
  }
@@ -351,7 +351,7 @@ export class RPCTranslator {
351
351
  foreignStartStorageSlot: ForeignCallSingle,
352
352
  foreignNumberOfElements: ForeignCallSingle,
353
353
  ) {
354
- const blockHash = L2BlockHash.fromString(foreignBlockHash);
354
+ const blockHash = new BlockHash(fromSingle(foreignBlockHash));
355
355
  const contractAddress = addressFromSingle(foreignContractAddress);
356
356
  const startStorageSlot = fromSingle(foreignStartStorageSlot);
357
357
  const numberOfElements = fromSingle(foreignNumberOfElements).toNumber();
@@ -367,7 +367,7 @@ export class RPCTranslator {
367
367
  }
368
368
 
369
369
  async utilityGetPublicDataWitness(foreignBlockHash: ForeignCallSingle, foreignLeafSlot: ForeignCallSingle) {
370
- const blockHash = L2BlockHash.fromString(foreignBlockHash);
370
+ const blockHash = new BlockHash(fromSingle(foreignBlockHash));
371
371
  const leafSlot = fromSingle(foreignLeafSlot);
372
372
 
373
373
  const witness = await this.handlerAsUtility().utilityGetPublicDataWitness(blockHash, leafSlot);
@@ -545,12 +545,23 @@ export class RPCTranslator {
545
545
  );
546
546
  }
547
547
 
548
- async utilityGetPublicKeysAndPartialAddress(foreignAddress: ForeignCallSingle) {
548
+ async utilityTryGetPublicKeysAndPartialAddress(foreignAddress: ForeignCallSingle) {
549
549
  const address = addressFromSingle(foreignAddress);
550
550
 
551
- const { publicKeys, partialAddress } = await this.handlerAsUtility().utilityGetPublicKeysAndPartialAddress(address);
551
+ const result = await this.handlerAsUtility().utilityTryGetPublicKeysAndPartialAddress(address);
552
552
 
553
- return toForeignCallResult([toArray([...publicKeys.toFields(), partialAddress])]);
553
+ // We are going to return a Noir Option struct to represent the possibility of null values. Options are a struct
554
+ // with two fields: `some` (a boolean) and `value` (a field array in this case).
555
+ if (result === undefined) {
556
+ // No data was found so we set `some` to 0 and pad `value` with zeros get the correct return size.
557
+ return toForeignCallResult([toSingle(new Fr(0)), toArray(Array(13).fill(new Fr(0)))]);
558
+ } else {
559
+ // Data was found so we set `some` to 1 and return it along with `value`.
560
+ return toForeignCallResult([
561
+ toSingle(new Fr(1)),
562
+ toArray([...result.publicKeys.toFields(), result.partialAddress]),
563
+ ]);
564
+ }
554
565
  }
555
566
 
556
567
  async utilityGetKeyValidationRequest(foreignPkMHash: ForeignCallSingle) {
@@ -574,7 +585,7 @@ export class RPCTranslator {
574
585
  }
575
586
 
576
587
  async utilityGetNullifierMembershipWitness(foreignBlockHash: ForeignCallSingle, foreignNullifier: ForeignCallSingle) {
577
- const blockHash = L2BlockHash.fromString(foreignBlockHash);
588
+ const blockHash = new BlockHash(fromSingle(foreignBlockHash));
578
589
  const nullifier = fromSingle(foreignNullifier);
579
590
 
580
591
  const witness = await this.handlerAsUtility().utilityGetNullifierMembershipWitness(blockHash, nullifier);
@@ -641,26 +652,34 @@ export class RPCTranslator {
641
652
  return toForeignCallResult(header.toFields().map(toSingle));
642
653
  }
643
654
 
644
- async utilityGetNoteHashMembershipWitness(foreignBlockHash: ForeignCallSingle, foreignLeafValue: ForeignCallSingle) {
645
- const blockHash = L2BlockHash.fromString(foreignBlockHash);
646
- const leafValue = fromSingle(foreignLeafValue);
655
+ async utilityGetNoteHashMembershipWitness(
656
+ foreignAnchorBlockHash: ForeignCallSingle,
657
+ foreignNoteHash: ForeignCallSingle,
658
+ ) {
659
+ const blockHash = new BlockHash(fromSingle(foreignAnchorBlockHash));
660
+ const noteHash = fromSingle(foreignNoteHash);
647
661
 
648
- const witness = await this.handlerAsUtility().utilityGetNoteHashMembershipWitness(blockHash, leafValue);
662
+ const witness = await this.handlerAsUtility().utilityGetNoteHashMembershipWitness(blockHash, noteHash);
649
663
 
650
664
  if (!witness) {
651
- throw new Error(`Note hash ${leafValue} not found in the note hash tree at block ${blockHash.toString()}.`);
665
+ throw new Error(`Note hash ${noteHash} not found in the note hash tree at block ${blockHash.toString()}.`);
652
666
  }
653
667
  return toForeignCallResult(witness.toNoirRepresentation());
654
668
  }
655
669
 
656
- async utilityGetArchiveMembershipWitness(foreignBlockHash: ForeignCallSingle, foreignLeafValue: ForeignCallSingle) {
657
- const blockHash = L2BlockHash.fromString(foreignBlockHash);
658
- const leafValue = fromSingle(foreignLeafValue);
670
+ async utilityGetBlockHashMembershipWitness(
671
+ foreignAnchorBlockHash: ForeignCallSingle,
672
+ foreignBlockHash: ForeignCallSingle,
673
+ ) {
674
+ const anchorBlockHash = new BlockHash(fromSingle(foreignAnchorBlockHash));
675
+ const blockHash = new BlockHash(fromSingle(foreignBlockHash));
659
676
 
660
- const witness = await this.handlerAsUtility().utilityGetArchiveMembershipWitness(blockHash, leafValue);
677
+ const witness = await this.handlerAsUtility().utilityGetBlockHashMembershipWitness(anchorBlockHash, blockHash);
661
678
 
662
679
  if (!witness) {
663
- throw new Error(`Block hash ${leafValue} not found in the archive tree at block ${blockHash.toString()}.`);
680
+ throw new Error(
681
+ `Block hash ${blockHash.toString()} not found in the archive tree at anchor block ${anchorBlockHash.toString()}.`,
682
+ );
664
683
  }
665
684
  return toForeignCallResult(witness.toNoirRepresentation());
666
685
  }
@@ -669,7 +688,7 @@ export class RPCTranslator {
669
688
  foreignBlockHash: ForeignCallSingle,
670
689
  foreignNullifier: ForeignCallSingle,
671
690
  ) {
672
- const blockHash = L2BlockHash.fromString(foreignBlockHash);
691
+ const blockHash = new BlockHash(fromSingle(foreignBlockHash));
673
692
  const nullifier = fromSingle(foreignNullifier);
674
693
 
675
694
  const witness = await this.handlerAsUtility().utilityGetLowNullifierMembershipWitness(blockHash, nullifier);
@@ -911,11 +930,10 @@ export class RPCTranslator {
911
930
  return toForeignCallResult([]);
912
931
  }
913
932
 
914
- async avmOpcodeNullifierExists(foreignInnerNullifier: ForeignCallSingle, foreignTargetAddress: ForeignCallSingle) {
915
- const innerNullifier = fromSingle(foreignInnerNullifier);
916
- const targetAddress = AztecAddress.fromField(fromSingle(foreignTargetAddress));
933
+ async avmOpcodeNullifierExists(foreignSiloedNullifier: ForeignCallSingle) {
934
+ const siloedNullifier = fromSingle(foreignSiloedNullifier);
917
935
 
918
- const exists = await this.handlerAsAvm().avmOpcodeNullifierExists(innerNullifier, targetAddress);
936
+ const exists = await this.handlerAsAvm().avmOpcodeNullifierExists(siloedNullifier);
919
937
 
920
938
  return toForeignCallResult([toSingle(new Fr(exists))]);
921
939
  }
@@ -59,6 +59,8 @@ export class TXEArchiver extends ArchiverDataSourceBase {
59
59
  if (!checkpointedBlock) {
60
60
  throw new Error(`L2Tips requested from TXE Archiver but no checkpointed block found for block number ${number}`);
61
61
  }
62
+ // TXE uses 1-block-per-checkpoint for testing simplicity, so we can use block number as checkpoint number.
63
+ // This uses the deprecated fromBlockNumber method intentionally for the TXE testing environment.
62
64
  const checkpoint = await this.store.getRangeOfCheckpoints(CheckpointNumber.fromBlockNumber(number), 1);
63
65
  if (checkpoint.length === 0) {
64
66
  throw new Error(`L2Tips requested from TXE Archiver but no checkpoint found for block number ${number}`);
@@ -6,6 +6,7 @@ import type {
6
6
  P2PBlockReceivedCallback,
7
7
  P2PCheckpointReceivedCallback,
8
8
  P2PConfig,
9
+ P2PDuplicateProposalCallback,
9
10
  P2PSyncState,
10
11
  PeerId,
11
12
  ReqRespSubProtocol,
@@ -133,8 +134,8 @@ export class DummyP2P implements P2P {
133
134
  throw new Error('DummyP2P does not implement "getCheckpointAttestationsForSlot"');
134
135
  }
135
136
 
136
- public addCheckpointAttestations(_attestations: CheckpointAttestation[]): Promise<void> {
137
- throw new Error('DummyP2P does not implement "addCheckpointAttestations"');
137
+ public addOwnCheckpointAttestations(_attestations: CheckpointAttestation[]): Promise<void> {
138
+ throw new Error('DummyP2P does not implement "addOwnCheckpointAttestations"');
138
139
  }
139
140
 
140
141
  public getL2BlockHash(_number: number): Promise<string | undefined> {
@@ -206,4 +207,8 @@ export class DummyP2P implements P2P {
206
207
 
207
208
  //This is no-op
208
209
  public registerThisValidatorAddresses(_address: EthAddress[]): void {}
210
+
211
+ public registerDuplicateProposalCallback(_callback: P2PDuplicateProposalCallback): void {
212
+ throw new Error('DummyP2P does not implement "registerDuplicateProposalCallback"');
213
+ }
209
214
  }
@@ -3,8 +3,7 @@ import { TestCircuitVerifier } from '@aztec/bb-prover/test';
3
3
  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
- import type { AztecAsyncKVStore } from '@aztec/kv-store';
7
- import { AnchorBlockStore } from '@aztec/pxe/server';
6
+ import { type AnchorBlockStore, type ContractStore, ContractSyncService, type NoteStore } from '@aztec/pxe/server';
8
7
  import { L2Block } from '@aztec/stdlib/block';
9
8
  import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
10
9
  import type { AztecNode } from '@aztec/stdlib/interfaces/client';
@@ -26,13 +25,16 @@ export class TXEStateMachine {
26
25
  public synchronizer: TXESynchronizer,
27
26
  public archiver: TXEArchiver,
28
27
  public anchorBlockStore: AnchorBlockStore,
28
+ public contractSyncService: ContractSyncService,
29
29
  ) {}
30
30
 
31
- public static async create(db: AztecAsyncKVStore) {
32
- const archiver = new TXEArchiver(db);
31
+ public static async create(
32
+ archiver: TXEArchiver,
33
+ anchorBlockStore: AnchorBlockStore,
34
+ contractStore: ContractStore,
35
+ noteStore: NoteStore,
36
+ ) {
33
37
  const synchronizer = await TXESynchronizer.create();
34
- const anchorBlockStore = new AnchorBlockStore(db);
35
-
36
38
  const aztecNodeConfig = {} as AztecNodeConfig;
37
39
 
38
40
  const log = createLogger('txe_node');
@@ -58,11 +60,21 @@ export class TXEStateMachine {
58
60
  log,
59
61
  );
60
62
 
61
- return new this(node, synchronizer, archiver, anchorBlockStore);
63
+ const contractSyncService = new ContractSyncService(
64
+ node,
65
+ contractStore,
66
+ noteStore,
67
+ createLogger('txe:contract_sync'),
68
+ );
69
+
70
+ return new this(node, synchronizer, archiver, anchorBlockStore, contractSyncService);
62
71
  }
63
72
 
64
73
  public async handleL2Block(block: L2Block) {
65
- // Create a checkpoint from the block manually
74
+ // Create a checkpoint from the block manually.
75
+ // TXE uses 1-block-per-checkpoint for testing simplicity, so we can use block number as checkpoint number.
76
+ // This uses the deprecated fromBlockNumber method intentionally for the TXE testing environment.
77
+ const checkpointNumber = CheckpointNumber.fromBlockNumber(block.number);
66
78
  const checkpoint = new Checkpoint(
67
79
  block.archive,
68
80
  CheckpointHeader.from({
@@ -79,7 +91,7 @@ export class TXEStateMachine {
79
91
  totalManaUsed: block.header.totalManaUsed,
80
92
  }),
81
93
  [block],
82
- CheckpointNumber.fromBlockNumber(block.number),
94
+ checkpointNumber,
83
95
  );
84
96
 
85
97
  const publishedCheckpoint = new PublishedCheckpoint(
@@ -91,6 +103,9 @@ export class TXEStateMachine {
91
103
  ),
92
104
  [],
93
105
  );
106
+ // Wipe contract sync cache when anchor block changes (mirrors BlockSynchronizer behavior)
107
+ this.contractSyncService.wipe();
108
+
94
109
  await Promise.all([
95
110
  this.synchronizer.handleL2Block(block),
96
111
  this.archiver.addCheckpoints([publishedCheckpoint], undefined),
@@ -1,6 +1,7 @@
1
1
  import type { EpochAndSlot, EpochCacheInterface, EpochCommitteeInfo, SlotTag } from '@aztec/epoch-cache';
2
2
  import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
3
3
  import { EthAddress } from '@aztec/foundation/eth-address';
4
+ import { EmptyL1RollupConstants, type L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
4
5
 
5
6
  /**
6
7
  * Mock implementation of the EpochCacheInterface used to satisfy dependencies of AztecNodeService.
@@ -64,4 +65,8 @@ export class MockEpochCache implements EpochCacheInterface {
64
65
  filterInCommittee(_slot: SlotTag, _validators: EthAddress[]): Promise<EthAddress[]> {
65
66
  return Promise.resolve([]);
66
67
  }
68
+
69
+ getL1Constants(): L1RollupConstants {
70
+ return EmptyL1RollupConstants;
71
+ }
67
72
  }
@@ -6,6 +6,7 @@ import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
6
6
  import type { ProtocolContract } from '@aztec/protocol-contracts';
7
7
  import {
8
8
  AddressStore,
9
+ AnchorBlockStore,
9
10
  CapsuleStore,
10
11
  JobCoordinator,
11
12
  NoteService,
@@ -49,6 +50,7 @@ import type { IAvmExecutionOracle, ITxeExecutionOracle } from './oracle/interfac
49
50
  import { TXEOraclePublicContext } from './oracle/txe_oracle_public_context.js';
50
51
  import { TXEOracleTopLevelContext } from './oracle/txe_oracle_top_level_context.js';
51
52
  import { RPCTranslator } from './rpc_translator.js';
53
+ import { TXEArchiver } from './state_machine/archiver.js';
52
54
  import { TXEStateMachine } from './state_machine/index.js';
53
55
  import type { ForeignCallArgs, ForeignCallResult } from './util/encoding.js';
54
56
  import { TXEAccountStore } from './util/txe_account_store.js';
@@ -176,7 +178,9 @@ export class TXESession implements TXESessionStateHandler {
176
178
  await contractStore.addContractInstance(instance);
177
179
  }
178
180
 
179
- const stateMachine = await TXEStateMachine.create(store);
181
+ const archiver = new TXEArchiver(store);
182
+ const anchorBlockStore = new AnchorBlockStore(store);
183
+ const stateMachine = await TXEStateMachine.create(archiver, anchorBlockStore, contractStore, noteStore);
180
184
 
181
185
  const nextBlockTimestamp = BigInt(Math.floor(new Date().getTime() / 1000));
182
186
  const version = new Fr(await stateMachine.node.getVersion());
@@ -312,17 +316,14 @@ export class TXESession implements TXESessionStateHandler {
312
316
  ): Promise<PrivateContextInputs> {
313
317
  this.exitTopLevelState();
314
318
 
315
- await new NoteService(
316
- this.noteStore,
317
- this.stateMachine.node,
318
- this.stateMachine.anchorBlockStore,
319
- this.currentJobId,
320
- ).syncNoteNullifiers(contractAddress);
321
-
322
319
  // Private execution has two associated block numbers: the anchor block (i.e. the historical block that is used to
323
320
  // build the proof), and the *next* block, i.e. the one we'll create once the execution ends, and which will contain
324
321
  // a single transaction with the effects of what was done in the test.
325
322
  const anchorBlock = await this.stateMachine.node.getBlockHeader(anchorBlockNumber ?? 'latest');
323
+
324
+ await new NoteService(this.noteStore, this.stateMachine.node, anchorBlock!, this.currentJobId).syncNoteNullifiers(
325
+ contractAddress,
326
+ );
326
327
  const latestBlock = await this.stateMachine.node.getBlockHeader('latest');
327
328
 
328
329
  const nextBlockGlobalVariables = makeGlobalVariables(undefined, {
@@ -354,12 +355,12 @@ export class TXESession implements TXESessionStateHandler {
354
355
  this.keyStore,
355
356
  this.addressStore,
356
357
  this.stateMachine.node,
357
- this.stateMachine.anchorBlockStore,
358
358
  this.senderTaggingStore,
359
359
  this.recipientTaggingStore,
360
360
  this.senderAddressBookStore,
361
361
  this.capsuleStore,
362
362
  this.privateEventStore,
363
+ this.stateMachine.contractSyncService,
363
364
  this.currentJobId,
364
365
  );
365
366
 
@@ -401,6 +402,8 @@ export class TXESession implements TXESessionStateHandler {
401
402
  async enterUtilityState(contractAddress: AztecAddress = DEFAULT_ADDRESS) {
402
403
  this.exitTopLevelState();
403
404
 
405
+ const anchorBlockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
406
+
404
407
  // There is no automatic message discovery and contract-driven syncing process in inlined private or utility
405
408
  // contexts, which means that known nullifiers are also not searched for, since it is during the tagging sync that
406
409
  // we perform this. We therefore search for known nullifiers now, as otherwise notes that were nullified would not
@@ -409,12 +412,10 @@ export class TXESession implements TXESessionStateHandler {
409
412
  await new NoteService(
410
413
  this.noteStore,
411
414
  this.stateMachine.node,
412
- this.stateMachine.anchorBlockStore,
415
+ anchorBlockHeader,
413
416
  this.currentJobId,
414
417
  ).syncNoteNullifiers(contractAddress);
415
418
 
416
- const anchorBlockHeader = await this.stateMachine.anchorBlockStore.getBlockHeader();
417
-
418
419
  this.oracleHandler = new UtilityExecutionOracle(
419
420
  contractAddress,
420
421
  [],
@@ -425,7 +426,6 @@ export class TXESession implements TXESessionStateHandler {
425
426
  this.keyStore,
426
427
  this.addressStore,
427
428
  this.stateMachine.node,
428
- this.stateMachine.anchorBlockStore,
429
429
  this.recipientTaggingStore,
430
430
  this.senderAddressBookStore,
431
431
  this.capsuleStore,
@@ -516,7 +516,6 @@ export class TXESession implements TXESessionStateHandler {
516
516
  this.keyStore,
517
517
  this.addressStore,
518
518
  this.stateMachine.node,
519
- this.stateMachine.anchorBlockStore,
520
519
  this.recipientTaggingStore,
521
520
  this.senderAddressBookStore,
522
521
  this.capsuleStore,
@@ -87,7 +87,9 @@ export async function makeTXEBlock(
87
87
  const newArchiveInfo = await worldTrees.getTreeInfo(MerkleTreeId.ARCHIVE);
88
88
  const newArchive = new AppendOnlyTreeSnapshot(new Fr(newArchiveInfo.root), Number(newArchiveInfo.size));
89
89
 
90
- // L2Block requires checkpointNumber and indexWithinCheckpoint
90
+ // L2Block requires checkpointNumber and indexWithinCheckpoint.
91
+ // TXE uses 1-block-per-checkpoint for testing simplicity, so we can use block number as checkpoint number.
92
+ // This uses the deprecated fromBlockNumber method intentionally for the TXE testing environment.
91
93
  const checkpointNumber = CheckpointNumber.fromBlockNumber(globalVariables.blockNumber);
92
94
  const indexWithinCheckpoint = IndexWithinCheckpoint(0);
93
95