@aztec/pxe 0.67.1-devnet → 0.68.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/pxe",
3
- "version": "0.67.1-devnet",
3
+ "version": "0.68.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -23,7 +23,7 @@
23
23
  "clean": "rm -rf ./dest .tsbuildinfo ./src/config/package_info.ts",
24
24
  "formatting": "run -T prettier --check ./src && run -T eslint ./src",
25
25
  "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src",
26
- "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests",
26
+ "test": "HARDWARE_CONCURRENCY=16 RAYON_NUM_THREADS=4 NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=8",
27
27
  "start": "LOG_LEVEL=${LOG_LEVEL:-debug} && node ./dest/bin/index.js",
28
28
  "generate": "node ./scripts/generate_package_info.js"
29
29
  },
@@ -59,9 +59,9 @@
59
59
  ],
60
60
  "reporters": [
61
61
  [
62
- "default",
62
+ "jest-silent-reporter",
63
63
  {
64
- "summaryThreshold": 9999
64
+ "useDots": true
65
65
  }
66
66
  ]
67
67
  ],
@@ -71,19 +71,19 @@
71
71
  ]
72
72
  },
73
73
  "dependencies": {
74
- "@aztec/bb-prover": "0.67.1-devnet",
75
- "@aztec/bb.js": "0.67.1-devnet",
76
- "@aztec/builder": "0.67.1-devnet",
77
- "@aztec/circuit-types": "0.67.1-devnet",
78
- "@aztec/circuits.js": "0.67.1-devnet",
79
- "@aztec/ethereum": "0.67.1-devnet",
80
- "@aztec/foundation": "0.67.1-devnet",
81
- "@aztec/key-store": "0.67.1-devnet",
82
- "@aztec/kv-store": "0.67.1-devnet",
83
- "@aztec/noir-protocol-circuits-types": "0.67.1-devnet",
84
- "@aztec/protocol-contracts": "0.67.1-devnet",
85
- "@aztec/simulator": "0.67.1-devnet",
86
- "@aztec/types": "0.67.1-devnet",
74
+ "@aztec/bb-prover": "0.68.0",
75
+ "@aztec/bb.js": "0.68.0",
76
+ "@aztec/builder": "0.68.0",
77
+ "@aztec/circuit-types": "0.68.0",
78
+ "@aztec/circuits.js": "0.68.0",
79
+ "@aztec/ethereum": "0.68.0",
80
+ "@aztec/foundation": "0.68.0",
81
+ "@aztec/key-store": "0.68.0",
82
+ "@aztec/kv-store": "0.68.0",
83
+ "@aztec/noir-protocol-circuits-types": "0.68.0",
84
+ "@aztec/protocol-contracts": "0.68.0",
85
+ "@aztec/simulator": "0.68.0",
86
+ "@aztec/types": "0.68.0",
87
87
  "@msgpack/msgpack": "^3.0.0-beta2",
88
88
  "@noir-lang/noirc_abi": "portal:../../noir/packages/noirc_abi",
89
89
  "@noir-lang/types": "workspace:*",
@@ -538,7 +538,7 @@ export class KVPxeDatabase implements PxeDatabase {
538
538
  return (await toArray(this.#completeAddresses.valuesAsync())).map(v => CompleteAddress.fromBuffer(v));
539
539
  }
540
540
 
541
- async addContactAddress(address: AztecAddress): Promise<boolean> {
541
+ async addSenderAddress(address: AztecAddress): Promise<boolean> {
542
542
  if (await this.#addressBook.hasAsync(address.toString())) {
543
543
  return false;
544
544
  }
@@ -548,11 +548,11 @@ export class KVPxeDatabase implements PxeDatabase {
548
548
  return true;
549
549
  }
550
550
 
551
- async getContactAddresses(): Promise<AztecAddress[]> {
551
+ async getSenderAddresses(): Promise<AztecAddress[]> {
552
552
  return (await toArray(this.#addressBook.entriesAsync())).map(AztecAddress.fromString);
553
553
  }
554
554
 
555
- async removeContactAddress(address: AztecAddress): Promise<boolean> {
555
+ async removeSenderAddress(address: AztecAddress): Promise<boolean> {
556
556
  if (!this.#addressBook.hasAsync(address.toString())) {
557
557
  return false;
558
558
  }
@@ -119,24 +119,24 @@ export interface PxeDatabase extends ContractArtifactDatabase, ContractInstanceD
119
119
  setHeader(header: BlockHeader): Promise<void>;
120
120
 
121
121
  /**
122
- * Adds contact address to the database.
122
+ * Adds sender address to the database.
123
123
  * @param address - The address to add to the address book.
124
124
  * @returns A promise resolving to true if the address was added, false if it already exists.
125
125
  */
126
- addContactAddress(address: AztecAddress): Promise<boolean>;
126
+ addSenderAddress(address: AztecAddress): Promise<boolean>;
127
127
 
128
128
  /**
129
- * Retrieves the list of contact addresses in the address book.
129
+ * Retrieves the list of sender addresses in the address book.
130
130
  * @returns An array of Aztec addresses.
131
131
  */
132
- getContactAddresses(): Promise<AztecAddress[]>;
132
+ getSenderAddresses(): Promise<AztecAddress[]>;
133
133
 
134
134
  /**
135
- * Removes a contact address from the database.
135
+ * Removes a sender address from the database.
136
136
  * @param address - The address to remove from the address book.
137
137
  * @returns A promise resolving to true if the address was removed, false if it does not exist.
138
138
  */
139
- removeContactAddress(address: AztecAddress): Promise<boolean>;
139
+ removeSenderAddress(address: AztecAddress): Promise<boolean>;
140
140
 
141
141
  /**
142
142
  * Adds complete address to the database.
@@ -1,5 +1,8 @@
1
1
  import { type SimulationError, isNoirCallStackUnresolved } from '@aztec/circuit-types';
2
- import { AztecAddress, Fr, FunctionSelector, PUBLIC_DISPATCH_SELECTOR } from '@aztec/circuits.js';
2
+ import { PUBLIC_DISPATCH_SELECTOR } from '@aztec/circuits.js/constants';
3
+ import { FunctionSelector } from '@aztec/foundation/abi';
4
+ import { AztecAddress } from '@aztec/foundation/aztec-address';
5
+ import { Fr } from '@aztec/foundation/fields';
3
6
  import { type Logger } from '@aztec/foundation/log';
4
7
  import { resolveAssertionMessageFromRevertData, resolveOpcodeLocations } from '@aztec/simulator/errors';
5
8
 
@@ -39,7 +42,7 @@ export async function enrichSimulationError(err: SimulationError, db: PxeDatabas
39
42
  );
40
43
  } else {
41
44
  logger.warn(
42
- `Could not function artifact in contract ${contract.name} for function '${fnName}' when enriching error callstack`,
45
+ `Could not find function artifact in contract ${contract.name} for function '${fnName}' when enriching error callstack`,
43
46
  );
44
47
  }
45
48
  });
@@ -31,29 +31,32 @@ import {
31
31
  UniqueNote,
32
32
  getNonNullifiedL1ToL2MessageWitness,
33
33
  } from '@aztec/circuit-types';
34
+ import type {
35
+ CompleteAddress,
36
+ ContractClassWithId,
37
+ ContractInstanceWithAddress,
38
+ GasFees,
39
+ L1_TO_L2_MSG_TREE_HEIGHT,
40
+ NodeInfo,
41
+ PartialAddress,
42
+ PrivateKernelTailCircuitPublicInputs,
43
+ } from '@aztec/circuits.js';
34
44
  import {
35
- type AztecAddress,
36
- type CompleteAddress,
37
- type ContractClassWithId,
38
- type ContractInstanceWithAddress,
39
- type GasFees,
40
- type L1_TO_L2_MSG_TREE_HEIGHT,
41
- type NodeInfo,
42
- type PartialAddress,
43
- type PrivateKernelTailCircuitPublicInputs,
44
- computeAddressSecret,
45
45
  computeContractAddressFromInstance,
46
46
  computeContractClassId,
47
47
  getContractClassFromArtifact,
48
- } from '@aztec/circuits.js';
48
+ } from '@aztec/circuits.js/contract';
49
49
  import { computeNoteHashNonce, siloNullifier } from '@aztec/circuits.js/hash';
50
+ import { computeAddressSecret } from '@aztec/circuits.js/keys';
50
51
  import {
51
52
  type AbiDecoded,
52
53
  type ContractArtifact,
53
54
  EventSelector,
54
55
  FunctionSelector,
56
+ FunctionType,
55
57
  encodeArguments,
56
58
  } from '@aztec/foundation/abi';
59
+ import { type AztecAddress } from '@aztec/foundation/aztec-address';
57
60
  import { Fr, type Point } from '@aztec/foundation/fields';
58
61
  import { type Logger, createLogger } from '@aztec/foundation/log';
59
62
  import { Timer } from '@aztec/foundation/timer';
@@ -163,37 +166,37 @@ export class PXEService implements PXE {
163
166
  return accountCompleteAddress;
164
167
  }
165
168
 
166
- public async registerContact(address: AztecAddress): Promise<AztecAddress> {
169
+ public async registerSender(address: AztecAddress): Promise<AztecAddress> {
167
170
  const accounts = await this.keyStore.getAccounts();
168
171
  if (accounts.includes(address)) {
169
- this.log.info(`Account:\n "${address.toString()}"\n already registered.`);
172
+ this.log.info(`Sender:\n "${address.toString()}"\n already registered.`);
170
173
  return address;
171
174
  }
172
175
 
173
- const wasAdded = await this.db.addContactAddress(address);
176
+ const wasAdded = await this.db.addSenderAddress(address);
174
177
 
175
178
  if (wasAdded) {
176
- this.log.info(`Added contact:\n ${address.toString()}`);
179
+ this.log.info(`Added sender:\n ${address.toString()}`);
177
180
  } else {
178
- this.log.info(`Contact:\n "${address.toString()}"\n already registered.`);
181
+ this.log.info(`Sender:\n "${address.toString()}"\n already registered.`);
179
182
  }
180
183
 
181
184
  return address;
182
185
  }
183
186
 
184
- public getContacts(): Promise<AztecAddress[]> {
185
- const contacts = this.db.getContactAddresses();
187
+ public getSenders(): Promise<AztecAddress[]> {
188
+ const senders = this.db.getSenderAddresses();
186
189
 
187
- return Promise.resolve(contacts);
190
+ return Promise.resolve(senders);
188
191
  }
189
192
 
190
- public async removeContact(address: AztecAddress): Promise<void> {
191
- const wasRemoved = await this.db.removeContactAddress(address);
193
+ public async removeSender(address: AztecAddress): Promise<void> {
194
+ const wasRemoved = await this.db.removeSenderAddress(address);
192
195
 
193
196
  if (wasRemoved) {
194
- this.log.info(`Removed contact:\n ${address.toString()}`);
197
+ this.log.info(`Removed sender:\n ${address.toString()}`);
195
198
  } else {
196
- this.log.info(`Contact:\n "${address.toString()}"\n not in address book.`);
199
+ this.log.info(`Sender:\n "${address.toString()}"\n not in address book.`);
197
200
  }
198
201
 
199
202
  return Promise.resolve();
@@ -240,8 +243,14 @@ export class PXEService implements PXE {
240
243
 
241
244
  await this.db.addContractArtifact(contractClassId, artifact);
242
245
 
243
- // TODO: PXE may not want to broadcast the artifact to the network
244
- await this.node.addContractArtifact(instance.address, artifact);
246
+ const functionNames: Record<string, string> = {};
247
+ for (const fn of artifact.functions) {
248
+ if (fn.functionType === FunctionType.PUBLIC) {
249
+ functionNames[FunctionSelector.fromNameAndParameters(fn.name, fn.parameters).toString()] = fn.name;
250
+ }
251
+ }
252
+
253
+ await this.node.registerContractFunctionNames(instance.address, functionNames);
245
254
 
246
255
  // TODO(#10007): Node should get public contract class from the registration event, not from PXE registration
247
256
  await this.node.addContractClass({ ...contractClass, privateFunctions: [], unconstrainedFunctions: [] });
@@ -479,6 +488,7 @@ export class PXEService implements PXE {
479
488
  simulatePublic: boolean,
480
489
  msgSender: AztecAddress | undefined = undefined,
481
490
  skipTxValidation: boolean = false,
491
+ enforceFeePayment: boolean = true,
482
492
  profile: boolean = false,
483
493
  scopes?: AztecAddress[],
484
494
  ): Promise<TxSimulationResult> {
@@ -516,7 +526,7 @@ export class PXEService implements PXE {
516
526
  const simulatedTx = privateSimulationResult.toSimulatedTx();
517
527
  let publicOutput: PublicSimulationOutput | undefined;
518
528
  if (simulatePublic) {
519
- publicOutput = await this.#simulatePublicCalls(simulatedTx);
529
+ publicOutput = await this.#simulatePublicCalls(simulatedTx, enforceFeePayment);
520
530
  }
521
531
 
522
532
  if (!skipTxValidation) {
@@ -773,11 +783,11 @@ export class PXEService implements PXE {
773
783
  * It can also be used for estimating gas in the future.
774
784
  * @param tx - The transaction to be simulated.
775
785
  */
776
- async #simulatePublicCalls(tx: Tx) {
786
+ async #simulatePublicCalls(tx: Tx, enforceFeePayment: boolean) {
777
787
  // Simulating public calls can throw if the TX fails in a phase that doesn't allow reverts (setup)
778
788
  // Or return as reverted if it fails in a phase that allows reverts (app logic, teardown)
779
789
  try {
780
- const result = await this.node.simulatePublicCalls(tx);
790
+ const result = await this.node.simulatePublicCalls(tx, enforceFeePayment);
781
791
  if (result.revertReason) {
782
792
  throw result.revertReason;
783
793
  }
@@ -38,7 +38,7 @@ import { type IncomingNoteDao } from '../database/incoming_note_dao.js';
38
38
  import { type PxeDatabase } from '../database/index.js';
39
39
  import { produceNoteDaos } from '../note_decryption_utils/produce_note_daos.js';
40
40
  import { getAcirSimulator } from '../simulator/index.js';
41
- import { getIndexedTaggingSecretsForTheWindow, getInitialIndexesMap } from './tagging_utils.js';
41
+ import { WINDOW_HALF_SIZE, getIndexedTaggingSecretsForTheWindow, getInitialIndexesMap } from './tagging_utils.js';
42
42
 
43
43
  /**
44
44
  * A data oracle that provides information needed for simulating a transaction.
@@ -253,8 +253,8 @@ export class SimulatorOracle implements DBOracle {
253
253
  * finally the index specified tag. We will then query the node with this tag for each address in the address book.
254
254
  * @returns The full list of the users contact addresses.
255
255
  */
256
- public getContacts(): Promise<AztecAddress[]> {
257
- return this.db.getContactAddresses();
256
+ public getSenders(): Promise<AztecAddress[]> {
257
+ return this.db.getSenderAddresses();
258
258
  }
259
259
 
260
260
  /**
@@ -321,18 +321,19 @@ export class SimulatorOracle implements DBOracle {
321
321
  * @param recipient - The address receiving the notes
322
322
  * @returns A list of indexed tagging secrets
323
323
  */
324
- async #getIndexedTaggingSecretsForContacts(
324
+ async #getIndexedTaggingSecretsForSenders(
325
325
  contractAddress: AztecAddress,
326
326
  recipient: AztecAddress,
327
327
  ): Promise<IndexedTaggingSecret[]> {
328
328
  const recipientCompleteAddress = await this.getCompleteAddress(recipient);
329
329
  const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient);
330
330
 
331
- // We implicitly add all PXE accounts as contacts, this helps us decrypt tags on notes that we send to ourselves (recipient = us, sender = us)
332
- const contacts = [...(await this.db.getContactAddresses()), ...(await this.keyStore.getAccounts())].filter(
331
+ // We implicitly add all PXE accounts as senders, this helps us decrypt tags on notes that we send to ourselves
332
+ // (recipient = us, sender = us)
333
+ const senders = [...(await this.db.getSenderAddresses()), ...(await this.keyStore.getAccounts())].filter(
333
334
  (address, index, self) => index === self.findIndex(otherAddress => otherAddress.equals(address)),
334
335
  );
335
- const appTaggingSecrets = contacts.map(contact => {
336
+ const appTaggingSecrets = senders.map(contact => {
336
337
  const sharedSecret = computeTaggingSecretPoint(recipientCompleteAddress, recipientIvsk, contact);
337
338
  return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]);
338
339
  });
@@ -353,59 +354,56 @@ export class SimulatorOracle implements DBOracle {
353
354
  recipient: AztecAddress,
354
355
  ): Promise<void> {
355
356
  const appTaggingSecret = await this.#calculateAppTaggingSecret(contractAddress, sender, recipient);
356
- let [currentIndex] = await this.db.getTaggingSecretsIndexesAsSender([appTaggingSecret]);
357
-
358
- const INDEX_OFFSET = 10;
359
-
360
- let previousEmptyBack = 0;
361
- let currentEmptyBack = 0;
362
- let currentEmptyFront: number;
363
-
364
- // The below code is trying to find the index of the start of the first window in which for all elements of window, we do not see logs.
365
- // We take our window size, and fetch the node for these logs. We store both the amount of empty consecutive slots from the front and the back.
366
- // We use our current empty consecutive slots from the front, as well as the previous consecutive empty slots from the back to see if we ever hit a time where there
367
- // is a window in which we see the combination of them to be greater than the window's size. If true, we rewind current index to the start of said window and use it.
368
- // Assuming two windows of 5:
369
- // [0, 1, 0, 1, 0], [0, 0, 0, 0, 0]
370
- // We can see that when processing the second window, the previous amount of empty slots from the back of the window (1), added with the empty elements from the front of the window (5)
371
- // is greater than 5 (6) and therefore we have found a window to use.
372
- // We simply need to take the number of elements (10) - the size of the window (5) - the number of consecutive empty elements from the back of the last window (1) = 4;
373
- // This is the first index of our desired window.
374
- // Note that if we ever see a situation like so:
375
- // [0, 1, 0, 1, 0], [0, 0, 0, 0, 1]
376
- // This also returns the correct index (4), but this is indicative of a problem / desync. i.e. we should never have a window that has a log that exists after the window.
357
+ const [oldIndex] = await this.db.getTaggingSecretsIndexesAsSender([appTaggingSecret]);
377
358
 
359
+ // This algorithm works such that:
360
+ // 1. If we find minimum consecutive empty logs in a window of logs we set the index to the index of the last log
361
+ // we found and quit.
362
+ // 2. If we don't find minimum consecutive empty logs in a window of logs we slide the window to latest log index
363
+ // and repeat the process.
364
+ const MIN_CONSECUTIVE_EMPTY_LOGS = 10;
365
+ const WINDOW_SIZE = MIN_CONSECUTIVE_EMPTY_LOGS * 2;
366
+
367
+ let [numConsecutiveEmptyLogs, currentIndex] = [0, oldIndex];
378
368
  do {
379
- const currentTags = [...new Array(INDEX_OFFSET)].map((_, i) => {
369
+ // We compute the tags for the current window of indexes
370
+ const currentTags = [...new Array(WINDOW_SIZE)].map((_, i) => {
380
371
  const indexedAppTaggingSecret = new IndexedTaggingSecret(appTaggingSecret, currentIndex + i);
381
372
  return indexedAppTaggingSecret.computeSiloedTag(recipient, contractAddress);
382
373
  });
383
- previousEmptyBack = currentEmptyBack;
384
374
 
375
+ // We fetch the logs for the tags
385
376
  const possibleLogs = await this.aztecNode.getLogsByTags(currentTags);
386
377
 
387
- const indexOfFirstLog = possibleLogs.findIndex(possibleLog => possibleLog.length !== 0);
388
- currentEmptyFront = indexOfFirstLog === -1 ? INDEX_OFFSET : indexOfFirstLog;
389
-
378
+ // We find the index of the last log in the window that is not empty
390
379
  const indexOfLastLog = possibleLogs.findLastIndex(possibleLog => possibleLog.length !== 0);
391
- currentEmptyBack = indexOfLastLog === -1 ? INDEX_OFFSET : INDEX_OFFSET - 1 - indexOfLastLog;
392
380
 
393
- currentIndex += INDEX_OFFSET;
394
- } while (currentEmptyFront + previousEmptyBack < INDEX_OFFSET);
381
+ if (indexOfLastLog === -1) {
382
+ // We haven't found any logs in the current window so we stop looking
383
+ break;
384
+ }
395
385
 
396
- // We unwind the entire current window and the amount of consecutive empty slots from the previous window
397
- const newIndex = currentIndex - (INDEX_OFFSET + previousEmptyBack);
386
+ // We move the current index to that of the last log we found
387
+ currentIndex += indexOfLastLog + 1;
398
388
 
399
- await this.db.setTaggingSecretsIndexesAsSender([new IndexedTaggingSecret(appTaggingSecret, newIndex)]);
389
+ // We compute the number of consecutive empty logs we found and repeat the process if we haven't found enough.
390
+ numConsecutiveEmptyLogs = WINDOW_SIZE - indexOfLastLog - 1;
391
+ } while (numConsecutiveEmptyLogs < MIN_CONSECUTIVE_EMPTY_LOGS);
400
392
 
401
393
  const contractName = await this.contractDataOracle.getDebugContractName(contractAddress);
402
- this.log.debug(`Syncing logs for sender ${sender} at contract ${contractName}(${contractAddress})`, {
403
- sender,
404
- secret: appTaggingSecret,
405
- index: currentIndex,
406
- contractName,
407
- contractAddress,
408
- });
394
+ if (currentIndex !== oldIndex) {
395
+ await this.db.setTaggingSecretsIndexesAsSender([new IndexedTaggingSecret(appTaggingSecret, currentIndex)]);
396
+
397
+ this.log.debug(`Syncing logs for sender ${sender} at contract ${contractName}(${contractAddress})`, {
398
+ sender,
399
+ secret: appTaggingSecret,
400
+ index: currentIndex,
401
+ contractName,
402
+ contractAddress,
403
+ });
404
+ } else {
405
+ this.log.debug(`No new logs found for sender ${sender} at contract ${contractName}(${contractAddress})`);
406
+ }
409
407
  }
410
408
 
411
409
  /**
@@ -421,9 +419,6 @@ export class SimulatorOracle implements DBOracle {
421
419
  maxBlockNumber: number,
422
420
  scopes?: AztecAddress[],
423
421
  ): Promise<Map<string, TxScopedL2Log[]>> {
424
- // Half the size of the window we slide over the tagging secret indexes.
425
- const WINDOW_HALF_SIZE = 10;
426
-
427
422
  // Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles.
428
423
  // However it is impossible at the moment due to the language not supporting nested slices.
429
424
  // This nesting is necessary because for a given set of tags we don't
@@ -440,7 +435,7 @@ export class SimulatorOracle implements DBOracle {
440
435
  const logsForRecipient: TxScopedL2Log[] = [];
441
436
 
442
437
  // Get all the secrets for the recipient and sender pairs (#9365)
443
- const secrets = await this.#getIndexedTaggingSecretsForContacts(contractAddress, recipient);
438
+ const secrets = await this.#getIndexedTaggingSecretsForSenders(contractAddress, recipient);
444
439
 
445
440
  // We fetch logs for a window of indexes in a range:
446
441
  // <latest_log_index - WINDOW_HALF_SIZE, latest_log_index + WINDOW_HALF_SIZE>.
@@ -1,5 +1,8 @@
1
1
  import { type Fr, IndexedTaggingSecret } from '@aztec/circuits.js';
2
2
 
3
+ // Half the size of the window we slide over the tagging secret indexes.
4
+ export const WINDOW_HALF_SIZE = 10;
5
+
3
6
  export function getIndexedTaggingSecretsForTheWindow(
4
7
  secretsAndWindows: { appTaggingSecret: Fr; leftMostIndex: number; rightMostIndex: number }[],
5
8
  ): IndexedTaggingSecret[] {