@aztec/txe 0.70.0 → 0.72.1

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.
@@ -2,9 +2,11 @@ import { createLogger } from '@aztec/aztec.js';
2
2
  import {
3
3
  type AztecNode,
4
4
  type EpochProofQuote,
5
- type GetUnencryptedLogsResponse,
5
+ type GetContractClassLogsResponse,
6
+ type GetPublicLogsResponse,
6
7
  type InBlock,
7
8
  type L2Block,
9
+ L2BlockHash,
8
10
  type L2BlockNumber,
9
11
  type L2Tips,
10
12
  type LogFilter,
@@ -18,10 +20,9 @@ import {
18
20
  type Tx,
19
21
  type TxEffect,
20
22
  TxHash,
21
- type TxReceipt,
23
+ TxReceipt,
22
24
  TxScopedL2Log,
23
25
  type TxValidationResult,
24
- type UnencryptedL2Log,
25
26
  } from '@aztec/circuit-types';
26
27
  import {
27
28
  type ARCHIVE_HEIGHT,
@@ -35,31 +36,38 @@ import {
35
36
  type NULLIFIER_TREE_HEIGHT,
36
37
  type NodeInfo,
37
38
  type PUBLIC_DATA_TREE_HEIGHT,
39
+ PUBLIC_LOG_DATA_SIZE_IN_FIELDS,
38
40
  type PrivateLog,
39
41
  type ProtocolContractAddresses,
42
+ type PublicLog,
40
43
  } from '@aztec/circuits.js';
41
44
  import { type L1ContractAddresses } from '@aztec/ethereum';
45
+ import { poseidon2Hash } from '@aztec/foundation/crypto';
42
46
  import { Fr } from '@aztec/foundation/fields';
47
+ import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state';
43
48
 
44
49
  export class TXENode implements AztecNode {
45
50
  #logsByTags = new Map<string, TxScopedL2Log[]>();
46
- #txEffectsByTxHash = new Map<string, InBlock<TxEffect> | undefined>();
51
+ #txEffectsByTxHash = new Map<string, InBlock<TxEffect>>();
52
+ #txReceiptsByTxHash = new Map<string, TxReceipt>();
47
53
  #blockNumberToNullifiers = new Map<number, Fr[]>();
48
54
  #noteIndex = 0;
49
55
 
50
- #blockNumber: number;
51
56
  #logger = createLogger('aztec:txe_node');
52
57
 
53
- constructor(blockNumber: number) {
54
- this.#blockNumber = blockNumber;
55
- }
58
+ constructor(
59
+ private blockNumber: number,
60
+ private version: number,
61
+ private chainId: number,
62
+ private trees: MerkleTrees,
63
+ ) {}
56
64
 
57
65
  /**
58
66
  * Fetches the current block number.
59
67
  * @returns The block number.
60
68
  */
61
69
  getBlockNumber(): Promise<number> {
62
- return Promise.resolve(this.#blockNumber);
70
+ return Promise.resolve(this.blockNumber);
63
71
  }
64
72
 
65
73
  /**
@@ -67,7 +75,7 @@ export class TXENode implements AztecNode {
67
75
  * @param - The block number to set.
68
76
  */
69
77
  setBlockNumber(blockNumber: number) {
70
- this.#blockNumber = blockNumber;
78
+ this.blockNumber = blockNumber;
71
79
  }
72
80
 
73
81
  /**
@@ -76,23 +84,42 @@ export class TXENode implements AztecNode {
76
84
  * @returns The requested tx effect.
77
85
  */
78
86
  getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined> {
79
- const txEffect = this.#txEffectsByTxHash.get(new Fr(txHash.toBuffer()).toString());
87
+ const txEffect = this.#txEffectsByTxHash.get(txHash.toString());
80
88
 
81
89
  return Promise.resolve(txEffect);
82
90
  }
83
91
 
84
92
  /**
85
- * Sets a tx effect for a given block number.
93
+ * Sets a tx effect and receipt for a given block number.
86
94
  * @param blockNumber - The block number that this tx effect resides.
87
95
  * @param txHash - The transaction hash of the transaction.
88
96
  * @param effect - The tx effect to set.
89
97
  */
90
98
  setTxEffect(blockNumber: number, txHash: TxHash, effect: TxEffect) {
91
- this.#txEffectsByTxHash.set(new Fr(txHash.toBuffer()).toString(), {
92
- l2BlockHash: blockNumber.toString(),
99
+ // We are not creating real blocks on which membership proofs can be constructed - we instead define its hash as
100
+ // simply the hash of the block number.
101
+ const blockHash = poseidon2Hash([blockNumber]);
102
+
103
+ this.#txEffectsByTxHash.set(txHash.toString(), {
104
+ l2BlockHash: blockHash.toString(),
93
105
  l2BlockNumber: blockNumber,
94
106
  data: effect,
95
107
  });
108
+
109
+ // We also set the receipt since we want to be able to serve `getTxReceipt` - we don't care about most values here,
110
+ // but we do need to be able to retrieve the block number of a given txHash.
111
+ this.#txReceiptsByTxHash.set(
112
+ txHash.toString(),
113
+ new TxReceipt(
114
+ txHash,
115
+ TxReceipt.statusFromRevertCode(effect.revertCode),
116
+ '',
117
+ undefined,
118
+ new L2BlockHash(blockHash.toBuffer()),
119
+ blockNumber,
120
+ undefined,
121
+ ),
122
+ );
96
123
  }
97
124
 
98
125
  /**
@@ -167,45 +194,48 @@ export class TXENode implements AztecNode {
167
194
  /**
168
195
  * Adds public logs to the txe node, given a block
169
196
  * @param blockNumber - The block number at which to add the public logs.
170
- * @param privateLogs - The unencrypted logs to be added.
171
- */
172
- addPublicLogsByTags(blockNumber: number, unencryptedLogs: UnencryptedL2Log[]) {
173
- unencryptedLogs.forEach(log => {
174
- if (log.data.length < 32 * 33) {
175
- // TODO remove when #9835 and #9836 are fixed
176
- this.#logger.warn(`Skipping unencrypted log with insufficient data length: ${log.data.length}`);
197
+ * @param publicLogs - The public logs to be added.
198
+ */
199
+ addPublicLogsByTags(blockNumber: number, publicLogs: PublicLog[]) {
200
+ publicLogs.forEach(log => {
201
+ // Check that each log stores 3 lengths in its first field. If not, it's not a tagged log:
202
+ const firstFieldBuf = log.log[0].toBuffer();
203
+ if (
204
+ !firstFieldBuf.subarray(0, 24).equals(Buffer.alloc(24)) ||
205
+ firstFieldBuf[26] !== 0 ||
206
+ firstFieldBuf[29] !== 0
207
+ ) {
208
+ // See parseLogFromPublic - the first field of a tagged log is 8 bytes structured:
209
+ // [ publicLen[0], publicLen[1], 0, privateLen[0], privateLen[1], 0, ciphertextLen[0], ciphertextLen[1]]
210
+ this.#logger.warn(`Skipping public log with invalid first field: ${log.log[0]}`);
177
211
  return;
178
212
  }
179
- try {
180
- // TODO remove when #9835 and #9836 are fixed. The partial note logs are emitted as bytes, but encoded as Fields.
181
- // This means that for every 32 bytes of payload, we only have 1 byte of data.
182
- // Also, the tag is not stored in the first 32 bytes of the log, (that's the length of public fields now) but in the next 32.
183
- const correctedBuffer = Buffer.alloc(32);
184
- const initialOffset = 32;
185
- for (let i = 0; i < 32; i++) {
186
- const byte = Fr.fromBuffer(log.data.subarray(i * 32 + initialOffset, i * 32 + 32 + initialOffset)).toNumber();
187
- correctedBuffer.writeUInt8(byte, i);
188
- }
189
- const tag = new Fr(correctedBuffer);
190
-
191
- this.#logger.verbose(
192
- `Found tagged unencrypted log with tag ${tag.toString()} in block ${this.getBlockNumber()}`,
193
- );
194
-
195
- const currentLogs = this.#logsByTags.get(tag.toString()) ?? [];
196
- const scopedLog = new TxScopedL2Log(
197
- new TxHash(new Fr(blockNumber)),
198
- this.#noteIndex,
199
- blockNumber,
200
- true,
201
- log.toBuffer(),
202
- );
203
-
204
- currentLogs.push(scopedLog);
205
- this.#logsByTags.set(tag.toString(), currentLogs);
206
- } catch (err) {
207
- this.#logger.warn(`Failed to add tagged log to store: ${err}`);
213
+ // Check that the length values line up with the log contents
214
+ const publicValuesLength = firstFieldBuf.subarray(-8).readUint16BE();
215
+ const privateValuesLength = firstFieldBuf.subarray(-8).readUint16BE(3);
216
+ // Add 1 for the first field holding lengths
217
+ const totalLogLength = 1 + publicValuesLength + privateValuesLength;
218
+ // Note that zeroes can be valid log values, so we can only assert that we do not go over the given length
219
+ if (totalLogLength > PUBLIC_LOG_DATA_SIZE_IN_FIELDS || log.log.slice(totalLogLength).find(f => !f.isZero())) {
220
+ this.#logger.warn(`Skipping invalid tagged public log with first field: ${log.log[0]}`);
221
+ return;
208
222
  }
223
+ // The first elt stores lengths => tag is in fields[1]
224
+ const tag = log.log[1];
225
+
226
+ this.#logger.verbose(`Found tagged public log with tag ${tag.toString()} in block ${this.getBlockNumber()}`);
227
+
228
+ const currentLogs = this.#logsByTags.get(tag.toString()) ?? [];
229
+ const scopedLog = new TxScopedL2Log(
230
+ new TxHash(new Fr(blockNumber)),
231
+ this.#noteIndex,
232
+ blockNumber,
233
+ true,
234
+ log.toBuffer(),
235
+ );
236
+
237
+ currentLogs.push(scopedLog);
238
+ this.#logsByTags.set(tag.toString(), currentLogs);
209
239
  });
210
240
  }
211
241
  /**
@@ -234,12 +264,29 @@ export class TXENode implements AztecNode {
234
264
  * @param leafValue - The values to search for
235
265
  * @returns The indexes of the given leaves in the given tree or undefined if not found.
236
266
  */
237
- findLeavesIndexes(
238
- _blockNumber: L2BlockNumber,
239
- _treeId: MerkleTreeId,
240
- _leafValues: Fr[],
267
+ async findLeavesIndexes(
268
+ blockNumber: L2BlockNumber,
269
+ treeId: MerkleTreeId,
270
+ leafValues: Fr[],
241
271
  ): Promise<(bigint | undefined)[]> {
242
- throw new Error('TXE Node method findLeavesIndexes not implemented');
272
+ // Temporary workaround to be able to respond this query: the trees are currently stored in the TXE oracle, but we
273
+ // hold a reference to them.
274
+ // We should likely migrate this so that the trees are owned by the node.
275
+
276
+ // TODO: blockNumber is being passed as undefined, figure out why
277
+ if (blockNumber === 'latest' || blockNumber === undefined) {
278
+ blockNumber = await this.getBlockNumber();
279
+ }
280
+
281
+ const db =
282
+ blockNumber === (await this.getBlockNumber())
283
+ ? await this.trees.getLatest()
284
+ : new MerkleTreeSnapshotOperationsFacade(this.trees, blockNumber);
285
+
286
+ return await db.findLeafIndices(
287
+ treeId,
288
+ leafValues.map(x => x.toBuffer()),
289
+ );
243
290
  }
244
291
 
245
292
  /**
@@ -420,7 +467,7 @@ export class TXENode implements AztecNode {
420
467
  * @returns The rollup version.
421
468
  */
422
469
  getVersion(): Promise<number> {
423
- throw new Error('TXE Node method getVersion not implemented');
470
+ return Promise.resolve(this.version);
424
471
  }
425
472
 
426
473
  /**
@@ -428,7 +475,7 @@ export class TXENode implements AztecNode {
428
475
  * @returns The chain id.
429
476
  */
430
477
  getChainId(): Promise<number> {
431
- throw new Error('TXE Node method getChainId not implemented');
478
+ return Promise.resolve(this.chainId);
432
479
  }
433
480
 
434
481
  /**
@@ -456,12 +503,12 @@ export class TXENode implements AztecNode {
456
503
  }
457
504
 
458
505
  /**
459
- * Gets unencrypted logs based on the provided filter.
506
+ * Gets public logs based on the provided filter.
460
507
  * @param filter - The filter to apply to the logs.
461
508
  * @returns The requested logs.
462
509
  */
463
- getUnencryptedLogs(_filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
464
- throw new Error('TXE Node method getUnencryptedLogs not implemented');
510
+ getPublicLogs(_filter: LogFilter): Promise<GetPublicLogsResponse> {
511
+ throw new Error('TXE Node method getPublicLogs not implemented');
465
512
  }
466
513
 
467
514
  /**
@@ -469,7 +516,7 @@ export class TXENode implements AztecNode {
469
516
  * @param filter - The filter to apply to the logs.
470
517
  * @returns The requested logs.
471
518
  */
472
- getContractClassLogs(_filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
519
+ getContractClassLogs(_filter: LogFilter): Promise<GetContractClassLogsResponse> {
473
520
  throw new Error('TXE Node method getContractClassLogs not implemented');
474
521
  }
475
522
 
@@ -490,8 +537,13 @@ export class TXENode implements AztecNode {
490
537
  * @param txHash - The transaction hash.
491
538
  * @returns A receipt of the transaction.
492
539
  */
493
- getTxReceipt(_txHash: TxHash): Promise<TxReceipt> {
494
- throw new Error('TXE Node method getTxReceipt not implemented');
540
+ getTxReceipt(txHash: TxHash): Promise<TxReceipt> {
541
+ const txEffect = this.#txReceiptsByTxHash.get(txHash.toString());
542
+ if (!txEffect) {
543
+ throw new Error('Unknown txHash');
544
+ }
545
+
546
+ return Promise.resolve(txEffect);
495
547
  }
496
548
 
497
549
  /**
@@ -35,6 +35,7 @@ import {
35
35
  PublicDataTreeLeaf,
36
36
  type PublicDataTreeLeafPreimage,
37
37
  type PublicDataWrite,
38
+ type PublicLog,
38
39
  computeContractClassId,
39
40
  computeTaggingSecretPoint,
40
41
  deriveKeys,
@@ -84,7 +85,6 @@ import {
84
85
  createSimulationError,
85
86
  resolveAssertionMessageFromError,
86
87
  } from '@aztec/simulator/server';
87
- import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
88
88
  import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state';
89
89
 
90
90
  import { TXENode } from '../node/txe_node.js';
@@ -95,7 +95,6 @@ import { TXEWorldStateDB } from '../util/txe_world_state_db.js';
95
95
  export class TXE implements TypedOracle {
96
96
  private blockNumber = 0;
97
97
  private sideEffectCounter = 0;
98
- private contractAddress: AztecAddress;
99
98
  private msgSender: AztecAddress;
100
99
  private functionSelector = FunctionSelector.fromField(new Fr(0));
101
100
  private isStaticCall = false;
@@ -105,17 +104,17 @@ export class TXE implements TypedOracle {
105
104
  private contractDataOracle: ContractDataOracle;
106
105
  private simulatorOracle: SimulatorOracle;
107
106
 
108
- private version: Fr = Fr.ONE;
109
- private chainId: Fr = Fr.ONE;
110
-
111
107
  private uniqueNoteHashesFromPublic: Fr[] = [];
112
108
  private siloedNullifiersFromPublic: Fr[] = [];
113
109
  private privateLogs: PrivateLog[] = [];
114
- private publicLogs: UnencryptedL2Log[] = [];
110
+ private publicLogs: PublicLog[] = [];
115
111
 
116
112
  private committedBlocks = new Set<number>();
117
113
 
118
- private node = new TXENode(this.blockNumber);
114
+ private VERSION = 1;
115
+ private CHAIN_ID = 1;
116
+
117
+ private node: TXENode;
119
118
 
120
119
  private simulationProvider = new WASMSimulator();
121
120
 
@@ -123,16 +122,19 @@ export class TXE implements TypedOracle {
123
122
 
124
123
  debug: LogFn;
125
124
 
126
- constructor(
125
+ private constructor(
127
126
  private logger: Logger,
128
127
  private trees: MerkleTrees,
129
128
  private executionCache: HashedValuesCache,
130
129
  private keyStore: KeyStore,
131
130
  private txeDatabase: TXEDatabase,
131
+ private contractAddress: AztecAddress,
132
132
  ) {
133
133
  this.noteCache = new ExecutionNoteCache(this.getTxRequestHash());
134
134
  this.contractDataOracle = new ContractDataOracle(txeDatabase);
135
- this.contractAddress = AztecAddress.random();
135
+
136
+ this.node = new TXENode(this.blockNumber, this.VERSION, this.CHAIN_ID, this.trees);
137
+
136
138
  // Default msg_sender (for entrypoints) is now Fr.max_value rather than 0 addr (see #7190 & #7404)
137
139
  this.msgSender = AztecAddress.fromField(Fr.MAX_FIELD_VALUE);
138
140
  this.simulatorOracle = new SimulatorOracle(
@@ -146,6 +148,16 @@ export class TXE implements TypedOracle {
146
148
  this.debug = createDebugOnlyLogger('aztec:kv-pxe-database');
147
149
  }
148
150
 
151
+ static async create(
152
+ logger: Logger,
153
+ trees: MerkleTrees,
154
+ executionCache: HashedValuesCache,
155
+ keyStore: KeyStore,
156
+ txeDatabase: TXEDatabase,
157
+ ) {
158
+ return new TXE(logger, trees, executionCache, keyStore, txeDatabase, await AztecAddress.random());
159
+ }
160
+
149
161
  // Utils
150
162
 
151
163
  async #getTreesAt(blockNumber: number) {
@@ -156,12 +168,12 @@ export class TXE implements TypedOracle {
156
168
  return db;
157
169
  }
158
170
 
159
- getChainId() {
160
- return Promise.resolve(this.chainId);
171
+ getChainId(): Promise<Fr> {
172
+ return Promise.resolve(this.node.getChainId().then(id => new Fr(id)));
161
173
  }
162
174
 
163
- getVersion() {
164
- return Promise.resolve(this.version);
175
+ getVersion(): Promise<Fr> {
176
+ return Promise.resolve(this.node.getVersion().then(v => new Fr(v)));
165
177
  }
166
178
 
167
179
  getMsgSender() {
@@ -232,8 +244,8 @@ export class TXE implements TypedOracle {
232
244
 
233
245
  const stateReference = await db.getStateReference();
234
246
  const inputs = PrivateContextInputs.empty();
235
- inputs.txContext.chainId = this.chainId;
236
- inputs.txContext.version = this.version;
247
+ inputs.txContext.chainId = new Fr(await this.node.getChainId());
248
+ inputs.txContext.version = new Fr(await this.node.getVersion());
237
249
  inputs.historicalHeader.globalVariables.blockNumber = new Fr(blockNumber);
238
250
  inputs.historicalHeader.state = stateReference;
239
251
  inputs.historicalHeader.lastArchive.root = Fr.fromBuffer(
@@ -252,8 +264,8 @@ export class TXE implements TypedOracle {
252
264
  const account = await this.txeDatabase.getAccount(address);
253
265
  const privateKey = await this.keyStore.getMasterSecretKey(account.publicKeys.masterIncomingViewingPublicKey);
254
266
  const schnorr = new Schnorr();
255
- const signature = schnorr.constructSignature(messageHash.toBuffer(), privateKey).toBuffer();
256
- const authWitness = new AuthWitness(messageHash, [...signature]);
267
+ const signature = await schnorr.constructSignature(messageHash.toBuffer(), privateKey);
268
+ const authWitness = new AuthWitness(messageHash, [...signature.toBuffer()]);
257
269
  return this.txeDatabase.addAuthWitness(authWitness.requestHash, authWitness.witness);
258
270
  }
259
271
 
@@ -322,26 +334,13 @@ export class TXE implements TypedOracle {
322
334
  this.privateLogs.push(...privateLogs);
323
335
  }
324
336
 
325
- addPublicLogs(logs: UnencryptedL2Log[]) {
337
+ addPublicLogs(logs: PublicLog[]) {
326
338
  logs.forEach(log => {
327
- if (log.data.length < 32 * 33) {
328
- // TODO remove when #9835 and #9836 are fixed
329
- this.logger.warn(`Skipping unencrypted log with insufficient data length: ${log.data.length}`);
330
- return;
331
- }
332
339
  try {
333
- // TODO remove when #9835 and #9836 are fixed. The partial note logs are emitted as bytes, but encoded as Fields.
334
- // This means that for every 32 bytes of payload, we only have 1 byte of data.
335
- // Also, the tag is not stored in the first 32 bytes of the log, (that's the length of public fields now) but in the next 32.
336
- const correctedBuffer = Buffer.alloc(32);
337
- const initialOffset = 32;
338
- for (let i = 0; i < 32; i++) {
339
- const byte = Fr.fromBuffer(log.data.subarray(i * 32 + initialOffset, i * 32 + 32 + initialOffset)).toNumber();
340
- correctedBuffer.writeUInt8(byte, i);
341
- }
342
- const tag = new Fr(correctedBuffer);
343
-
344
- this.logger.verbose(`Found tagged unencrypted log with tag ${tag.toString()} in block ${this.blockNumber}`);
340
+ // The first elt stores lengths => tag is in fields[1]
341
+ const tag = log.log[1];
342
+
343
+ this.logger.verbose(`Found tagged public log with tag ${tag.toString()} in block ${this.blockNumber}`);
345
344
  this.publicLogs.push(log);
346
345
  } catch (err) {
347
346
  this.logger.warn(`Failed to add tagged log to store: ${err}`);
@@ -371,10 +370,6 @@ export class TXE implements TypedOracle {
371
370
  return Fr.random();
372
371
  }
373
372
 
374
- storeArrayInExecutionCache(values: Fr[]) {
375
- return Promise.resolve(this.executionCache.store(values));
376
- }
377
-
378
373
  storeInExecutionCache(values: Fr[]) {
379
374
  return Promise.resolve(this.executionCache.store(values));
380
375
  }
@@ -406,11 +401,11 @@ export class TXE implements TypedOracle {
406
401
  return [new Fr(index), ...siblingPath.toFields()];
407
402
  }
408
403
 
409
- async getSiblingPath(blockNumber: number, treeId: MerkleTreeId, leafIndex: Fr) {
410
- const committedDb = new MerkleTreeSnapshotOperationsFacade(this.trees, blockNumber);
411
- const result = await committedDb.getSiblingPath(treeId, leafIndex.toBigInt());
412
- return result.toFields();
413
- }
404
+ // async getSiblingPath(blockNumber: number, treeId: MerkleTreeId, leafIndex: Fr) {
405
+ // const committedDb = new MerkleTreeSnapshotOperationsFacade(this.trees, blockNumber);
406
+ // const result = await committedDb.getSiblingPath(treeId, leafIndex.toBigInt());
407
+ // return result.toFields();
408
+ // }
414
409
 
415
410
  async getNullifierMembershipWitness(
416
411
  blockNumber: number,
@@ -810,8 +805,8 @@ export class TXE implements TypedOracle {
810
805
  const worldStateDb = new TXEWorldStateDB(db, new TXEPublicContractDataSource(this));
811
806
 
812
807
  const globalVariables = GlobalVariables.empty();
813
- globalVariables.chainId = this.chainId;
814
- globalVariables.version = this.version;
808
+ globalVariables.chainId = new Fr(await this.node.getChainId());
809
+ globalVariables.version = new Fr(await this.node.getVersion());
815
810
  globalVariables.blockNumber = new Fr(this.blockNumber);
816
811
  globalVariables.gasFees = new GasFees(1, 1);
817
812
 
@@ -830,23 +825,33 @@ export class TXE implements TypedOracle {
830
825
  const simulator = new PublicTxSimulator(
831
826
  db,
832
827
  new TXEWorldStateDB(db, new TXEPublicContractDataSource(this)),
833
- new NoopTelemetryClient(),
834
828
  globalVariables,
835
829
  );
836
830
 
831
+ const { usedTxRequestHashForNonces } = this.noteCache.finish();
832
+ const firstNullifier = usedTxRequestHashForNonces ? this.getTxRequestHash() : this.noteCache.getAllNullifiers()[0];
833
+
837
834
  // When setting up a teardown call, we tell it that
838
835
  // private execution used Gas(1, 1) so it can compute a tx fee.
839
836
  const gasUsedByPrivate = isTeardown ? new Gas(1, 1) : Gas.empty();
840
837
  const tx = createTxForPublicCalls(
841
838
  /*setupExecutionRequests=*/ [],
842
839
  /*appExecutionRequests=*/ isTeardown ? [] : [executionRequest],
840
+ firstNullifier,
843
841
  /*teardownExecutionRequests=*/ isTeardown ? executionRequest : undefined,
844
842
  gasUsedByPrivate,
845
843
  );
846
844
 
847
845
  const result = await simulator.simulate(tx);
846
+ const noteHashes = result.avmProvingRequest.inputs.output.accumulatedData.noteHashes.filter(s => !s.isEmpty());
848
847
 
849
- this.addPublicLogs(tx.unencryptedLogs.unrollLogs());
848
+ await this.addUniqueNoteHashesFromPublic(noteHashes);
849
+
850
+ this.addPublicLogs(
851
+ result.avmProvingRequest.inputs.output.accumulatedData.publicLogs.filter(
852
+ log => !log.contractAddress.equals(AztecAddress.ZERO),
853
+ ),
854
+ );
850
855
 
851
856
  return Promise.resolve(result);
852
857
  }
@@ -898,7 +903,11 @@ export class TXE implements TypedOracle {
898
903
  const sideEffects = executionResult.avmProvingRequest.inputs.output.accumulatedData;
899
904
  const publicDataWrites = sideEffects.publicDataWrites.filter(s => !s.isEmpty());
900
905
  const noteHashes = sideEffects.noteHashes.filter(s => !s.isEmpty());
901
- const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty());
906
+
907
+ const { usedTxRequestHashForNonces } = this.noteCache.finish();
908
+ const firstNullifier = usedTxRequestHashForNonces ? this.getTxRequestHash() : this.noteCache.getAllNullifiers()[0];
909
+ const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty()).filter(s => !s.equals(firstNullifier));
910
+
902
911
  await this.addPublicDataWrites(publicDataWrites);
903
912
  await this.addUniqueNoteHashesFromPublic(noteHashes);
904
913
  await this.addSiloedNullifiers(nullifiers);
@@ -952,7 +961,7 @@ export class TXE implements TypedOracle {
952
961
  async #calculateAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) {
953
962
  const senderCompleteAddress = await this.getCompleteAddress(sender);
954
963
  const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender);
955
- const secretPoint = computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient);
964
+ const secretPoint = await computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient);
956
965
  // Silo the secret to the app so it can't be used to track other app's notes
957
966
  const appSecret = poseidon2Hash([secretPoint.x, secretPoint.y, contractAddress]);
958
967
  return appSecret;
@@ -974,6 +983,19 @@ export class TXE implements TypedOracle {
974
983
  return Promise.resolve();
975
984
  }
976
985
 
986
+ deliverNote(
987
+ _contractAddress: AztecAddress,
988
+ _storageSlot: Fr,
989
+ _nonce: Fr,
990
+ _content: Fr[],
991
+ _noteHash: Fr,
992
+ _nullifier: Fr,
993
+ _txHash: Fr,
994
+ _recipient: AztecAddress,
995
+ ): Promise<void> {
996
+ throw new Error('deliverNote');
997
+ }
998
+
977
999
  // AVM oracles
978
1000
 
979
1001
  async avmOpcodeCall(targetContractAddress: AztecAddress, args: Fr[], isStaticCall: boolean): Promise<PublicTxResult> {
@@ -999,7 +1021,11 @@ export class TXE implements TypedOracle {
999
1021
  const sideEffects = executionResult.avmProvingRequest.inputs.output.accumulatedData;
1000
1022
  const publicDataWrites = sideEffects.publicDataWrites.filter(s => !s.isEmpty());
1001
1023
  const noteHashes = sideEffects.noteHashes.filter(s => !s.isEmpty());
1002
- const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty());
1024
+ const { usedTxRequestHashForNonces } = this.noteCache.finish();
1025
+ const firstNullifier = usedTxRequestHashForNonces
1026
+ ? this.getTxRequestHash()
1027
+ : this.noteCache.getAllNullifiers()[0];
1028
+ const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty()).filter(s => !s.equals(firstNullifier));
1003
1029
  await this.addPublicDataWrites(publicDataWrites);
1004
1030
  await this.addUniqueNoteHashes(noteHashes);
1005
1031
  await this.addSiloedNullifiers(nullifiers);
@@ -1058,36 +1084,35 @@ export class TXE implements TypedOracle {
1058
1084
  return preimage.value;
1059
1085
  }
1060
1086
 
1061
- /**
1062
- * Used by contracts during execution to store arbitrary data in the local PXE database. The data is siloed/scoped
1063
- * to a specific `contract`.
1064
- * @param contract - The contract address to store the data under.
1065
- * @param key - A field element representing the key to store the data under.
1066
- * @param values - An array of field elements representing the data to store.
1067
- */
1068
- store(contract: AztecAddress, key: Fr, values: Fr[]): Promise<void> {
1069
- if (!contract.equals(this.contractAddress)) {
1070
- // TODO(#10727): instead of this check check that this.contractAddress is allowed to process notes for contract
1071
- throw new Error(
1072
- `Contract address ${contract} does not match the oracle's contract address ${this.contractAddress}`,
1073
- );
1087
+ dbStore(contractAddress: AztecAddress, slot: Fr, values: Fr[]): Promise<void> {
1088
+ if (!contractAddress.equals(this.contractAddress)) {
1089
+ // TODO(#10727): instead of this check that this.contractAddress is allowed to access the external DB
1090
+ throw new Error(`Contract ${contractAddress} is not allowed to access ${this.contractAddress}'s PXE DB`);
1074
1091
  }
1075
- return this.txeDatabase.store(this.contractAddress, key, values);
1076
- }
1077
-
1078
- /**
1079
- * Used by contracts during execution to load arbitrary data from the local PXE database. The data is siloed/scoped
1080
- * to a specific `contract`.
1081
- * @param contract - The contract address to load the data from.
1082
- * @param key - A field element representing the key under which to load the data..
1083
- * @returns An array of field elements representing the stored data or `null` if no data is stored under the key.
1084
- */
1085
- load(contract: AztecAddress, key: Fr): Promise<Fr[] | null> {
1086
- if (!contract.equals(this.contractAddress)) {
1087
- // TODO(#10727): instead of this check check that this.contractAddress is allowed to process notes for contract
1088
- this.debug(`Data not found for contract ${contract.toString()} and key ${key.toString()}`);
1089
- return Promise.resolve(null);
1092
+ return this.txeDatabase.dbStore(this.contractAddress, slot, values);
1093
+ }
1094
+
1095
+ dbLoad(contractAddress: AztecAddress, slot: Fr): Promise<Fr[] | null> {
1096
+ if (!contractAddress.equals(this.contractAddress)) {
1097
+ // TODO(#10727): instead of this check that this.contractAddress is allowed to access the external DB
1098
+ throw new Error(`Contract ${contractAddress} is not allowed to access ${this.contractAddress}'s PXE DB`);
1099
+ }
1100
+ return this.txeDatabase.dbLoad(this.contractAddress, slot);
1101
+ }
1102
+
1103
+ dbDelete(contractAddress: AztecAddress, slot: Fr): Promise<void> {
1104
+ if (!contractAddress.equals(this.contractAddress)) {
1105
+ // TODO(#10727): instead of this check that this.contractAddress is allowed to access the external DB
1106
+ throw new Error(`Contract ${contractAddress} is not allowed to access ${this.contractAddress}'s PXE DB`);
1107
+ }
1108
+ return this.txeDatabase.dbDelete(this.contractAddress, slot);
1109
+ }
1110
+
1111
+ dbCopy(contractAddress: AztecAddress, srcSlot: Fr, dstSlot: Fr, numEntries: number): Promise<void> {
1112
+ if (!contractAddress.equals(this.contractAddress)) {
1113
+ // TODO(#10727): instead of this check that this.contractAddress is allowed to access the external DB
1114
+ throw new Error(`Contract ${contractAddress} is not allowed to access ${this.contractAddress}'s PXE DB`);
1090
1115
  }
1091
- return this.txeDatabase.load(this.contractAddress, key);
1116
+ return this.txeDatabase.dbCopy(this.contractAddress, srcSlot, dstSlot, numEntries);
1092
1117
  }
1093
1118
  }