@aztec/pxe 0.42.0 → 0.44.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.
Files changed (58) hide show
  1. package/dest/database/deferred_note_dao.d.ts +5 -4
  2. package/dest/database/deferred_note_dao.d.ts.map +1 -1
  3. package/dest/database/deferred_note_dao.js +4 -3
  4. package/dest/database/incoming_note_dao.d.ts +74 -0
  5. package/dest/database/incoming_note_dao.d.ts.map +1 -0
  6. package/dest/database/incoming_note_dao.js +93 -0
  7. package/dest/database/index.d.ts +1 -0
  8. package/dest/database/index.d.ts.map +1 -1
  9. package/dest/database/index.js +2 -1
  10. package/dest/database/kv_pxe_database.d.ts +10 -7
  11. package/dest/database/kv_pxe_database.d.ts.map +1 -1
  12. package/dest/database/kv_pxe_database.js +149 -78
  13. package/dest/database/{note_dao.d.ts → outgoing_note_dao.d.ts} +10 -14
  14. package/dest/database/outgoing_note_dao.d.ts.map +1 -0
  15. package/dest/database/outgoing_note_dao.js +84 -0
  16. package/dest/database/pxe_database.d.ts +21 -9
  17. package/dest/database/pxe_database.d.ts.map +1 -1
  18. package/dest/database/pxe_database_test_suite.d.ts.map +1 -1
  19. package/dest/database/pxe_database_test_suite.js +71 -24
  20. package/dest/index.d.ts +3 -0
  21. package/dest/index.d.ts.map +1 -1
  22. package/dest/index.js +4 -1
  23. package/dest/kernel_oracle/index.d.ts +1 -1
  24. package/dest/note_processor/note_processor.d.ts +23 -20
  25. package/dest/note_processor/note_processor.d.ts.map +1 -1
  26. package/dest/note_processor/note_processor.js +116 -76
  27. package/dest/note_processor/produce_note_dao.d.ts +13 -4
  28. package/dest/note_processor/produce_note_dao.d.ts.map +1 -1
  29. package/dest/note_processor/produce_note_dao.js +88 -31
  30. package/dest/pxe_http/pxe_http_server.d.ts.map +1 -1
  31. package/dest/pxe_http/pxe_http_server.js +3 -1
  32. package/dest/pxe_service/create_pxe_service.d.ts.map +1 -1
  33. package/dest/pxe_service/create_pxe_service.js +3 -1
  34. package/dest/pxe_service/pxe_service.d.ts +9 -4
  35. package/dest/pxe_service/pxe_service.d.ts.map +1 -1
  36. package/dest/pxe_service/pxe_service.js +106 -28
  37. package/dest/simulator_oracle/index.js +2 -2
  38. package/dest/synchronizer/synchronizer.d.ts +2 -2
  39. package/dest/synchronizer/synchronizer.d.ts.map +1 -1
  40. package/dest/synchronizer/synchronizer.js +37 -36
  41. package/package.json +23 -15
  42. package/src/database/deferred_note_dao.ts +4 -3
  43. package/src/database/{note_dao.ts → incoming_note_dao.ts} +14 -10
  44. package/src/database/index.ts +1 -0
  45. package/src/database/kv_pxe_database.ts +127 -29
  46. package/src/database/outgoing_note_dao.ts +91 -0
  47. package/src/database/pxe_database.ts +23 -9
  48. package/src/database/pxe_database_test_suite.ts +93 -29
  49. package/src/index.ts +3 -0
  50. package/src/note_processor/note_processor.ts +190 -121
  51. package/src/note_processor/produce_note_dao.ts +164 -50
  52. package/src/pxe_http/pxe_http_server.ts +2 -0
  53. package/src/pxe_service/create_pxe_service.ts +2 -0
  54. package/src/pxe_service/pxe_service.ts +170 -42
  55. package/src/simulator_oracle/index.ts +1 -1
  56. package/src/synchronizer/synchronizer.ts +48 -52
  57. package/dest/database/note_dao.d.ts.map +0 -1
  58. package/dest/database/note_dao.js +0 -89
@@ -1,4 +1,4 @@
1
- import { type NoteFilter, NoteStatus, randomTxHash } from '@aztec/circuit-types';
1
+ import { type IncomingNotesFilter, NoteStatus, type OutgoingNotesFilter, randomTxHash } from '@aztec/circuit-types';
2
2
  import { AztecAddress, CompleteAddress, INITIAL_L2_BLOCK_NUM, PublicKeys } from '@aztec/circuits.js';
3
3
  import { makeHeader } from '@aztec/circuits.js/testing';
4
4
  import { randomInt } from '@aztec/foundation/crypto';
@@ -6,8 +6,10 @@ import { Fr, Point } from '@aztec/foundation/fields';
6
6
  import { BenchmarkingContractArtifact } from '@aztec/noir-contracts.js/Benchmarking';
7
7
  import { SerializableContractInstance } from '@aztec/types/contracts';
8
8
 
9
- import { type NoteDao } from './note_dao.js';
10
- import { randomNoteDao } from './note_dao.test.js';
9
+ import { type IncomingNoteDao } from './incoming_note_dao.js';
10
+ import { randomIncomingNoteDao } from './incoming_note_dao.test.js';
11
+ import { type OutgoingNoteDao } from './outgoing_note_dao.js';
12
+ import { randomOutgoingNoteDao } from './outgoing_note_dao.test.js';
11
13
  import { type PxeDatabase } from './pxe_database.js';
12
14
 
13
15
  /**
@@ -68,13 +70,13 @@ export function describePxeDatabase(getDatabase: () => PxeDatabase) {
68
70
  });
69
71
  });
70
72
 
71
- describe('notes', () => {
73
+ describe('incoming notes', () => {
72
74
  let owners: CompleteAddress[];
73
75
  let contractAddresses: AztecAddress[];
74
76
  let storageSlots: Fr[];
75
- let notes: NoteDao[];
77
+ let notes: IncomingNoteDao[];
76
78
 
77
- const filteringTests: [() => NoteFilter, () => NoteDao[]][] = [
79
+ const filteringTests: [() => IncomingNotesFilter, () => IncomingNoteDao[]][] = [
78
80
  [() => ({}), () => notes],
79
81
 
80
82
  [
@@ -94,7 +96,7 @@ export function describePxeDatabase(getDatabase: () => PxeDatabase) {
94
96
 
95
97
  [
96
98
  () => ({ owner: owners[0].address }),
97
- () => notes.filter(note => note.publicKey.equals(owners[0].publicKeys.masterIncomingViewingPublicKey)),
99
+ () => notes.filter(note => note.ivpkM.equals(owners[0].publicKeys.masterIncomingViewingPublicKey)),
98
100
  ],
99
101
 
100
102
  [
@@ -107,46 +109,44 @@ export function describePxeDatabase(getDatabase: () => PxeDatabase) {
107
109
  [() => ({ contractAddress: contractAddresses[0], storageSlot: storageSlots[1] }), () => []],
108
110
  ];
109
111
 
110
- beforeEach(() => {
112
+ beforeEach(async () => {
111
113
  owners = Array.from({ length: 2 }).map(() => CompleteAddress.random());
112
114
  contractAddresses = Array.from({ length: 2 }).map(() => AztecAddress.random());
113
115
  storageSlots = Array.from({ length: 2 }).map(() => Fr.random());
114
116
 
115
117
  notes = Array.from({ length: 10 }).map((_, i) =>
116
- randomNoteDao({
118
+ randomIncomingNoteDao({
117
119
  contractAddress: contractAddresses[i % contractAddresses.length],
118
120
  storageSlot: storageSlots[i % storageSlots.length],
119
- publicKey: owners[i % owners.length].publicKeys.masterIncomingViewingPublicKey,
121
+ ivpkM: owners[i % owners.length].publicKeys.masterIncomingViewingPublicKey,
120
122
  index: BigInt(i),
121
123
  }),
122
124
  );
123
- });
124
125
 
125
- beforeEach(async () => {
126
126
  for (const owner of owners) {
127
127
  await database.addCompleteAddress(owner);
128
128
  }
129
129
  });
130
130
 
131
131
  it.each(filteringTests)('stores notes in bulk and retrieves notes', async (getFilter, getExpected) => {
132
- await database.addNotes(notes);
133
- await expect(database.getNotes(getFilter())).resolves.toEqual(getExpected());
132
+ await database.addNotes(notes, []);
133
+ await expect(database.getIncomingNotes(getFilter())).resolves.toEqual(getExpected());
134
134
  });
135
135
 
136
136
  it.each(filteringTests)('stores notes one by one and retrieves notes', async (getFilter, getExpected) => {
137
137
  for (const note of notes) {
138
138
  await database.addNote(note);
139
139
  }
140
- await expect(database.getNotes(getFilter())).resolves.toEqual(getExpected());
140
+ await expect(database.getIncomingNotes(getFilter())).resolves.toEqual(getExpected());
141
141
  });
142
142
 
143
143
  it.each(filteringTests)('retrieves nullified notes', async (getFilter, getExpected) => {
144
- await database.addNotes(notes);
144
+ await database.addNotes(notes, []);
145
145
 
146
146
  // Nullify all notes and use the same filter as other test cases
147
147
  for (const owner of owners) {
148
148
  const notesToNullify = notes.filter(note =>
149
- note.publicKey.equals(owner.publicKeys.masterIncomingViewingPublicKey),
149
+ note.ivpkM.equals(owner.publicKeys.masterIncomingViewingPublicKey),
150
150
  );
151
151
  const nullifiers = notesToNullify.map(note => note.siloedNullifier);
152
152
  await expect(
@@ -154,41 +154,41 @@ export function describePxeDatabase(getDatabase: () => PxeDatabase) {
154
154
  ).resolves.toEqual(notesToNullify);
155
155
  }
156
156
 
157
- await expect(database.getNotes({ ...getFilter(), status: NoteStatus.ACTIVE_OR_NULLIFIED })).resolves.toEqual(
158
- getExpected(),
159
- );
157
+ await expect(
158
+ database.getIncomingNotes({ ...getFilter(), status: NoteStatus.ACTIVE_OR_NULLIFIED }),
159
+ ).resolves.toEqual(getExpected());
160
160
  });
161
161
 
162
162
  it('skips nullified notes by default or when requesting active', async () => {
163
- await database.addNotes(notes);
163
+ await database.addNotes(notes, []);
164
164
 
165
165
  const notesToNullify = notes.filter(note =>
166
- note.publicKey.equals(owners[0].publicKeys.masterIncomingViewingPublicKey),
166
+ note.ivpkM.equals(owners[0].publicKeys.masterIncomingViewingPublicKey),
167
167
  );
168
168
  const nullifiers = notesToNullify.map(note => note.siloedNullifier);
169
- await expect(database.removeNullifiedNotes(nullifiers, notesToNullify[0].publicKey)).resolves.toEqual(
169
+ await expect(database.removeNullifiedNotes(nullifiers, notesToNullify[0].ivpkM)).resolves.toEqual(
170
170
  notesToNullify,
171
171
  );
172
172
 
173
- const actualNotesWithDefault = await database.getNotes({});
174
- const actualNotesWithActive = await database.getNotes({ status: NoteStatus.ACTIVE });
173
+ const actualNotesWithDefault = await database.getIncomingNotes({});
174
+ const actualNotesWithActive = await database.getIncomingNotes({ status: NoteStatus.ACTIVE });
175
175
 
176
176
  expect(actualNotesWithDefault).toEqual(actualNotesWithActive);
177
177
  expect(actualNotesWithActive).toEqual(notes.filter(note => !notesToNullify.includes(note)));
178
178
  });
179
179
 
180
180
  it('returns active and nullified notes when requesting either', async () => {
181
- await database.addNotes(notes);
181
+ await database.addNotes(notes, []);
182
182
 
183
183
  const notesToNullify = notes.filter(note =>
184
- note.publicKey.equals(owners[0].publicKeys.masterIncomingViewingPublicKey),
184
+ note.ivpkM.equals(owners[0].publicKeys.masterIncomingViewingPublicKey),
185
185
  );
186
186
  const nullifiers = notesToNullify.map(note => note.siloedNullifier);
187
- await expect(database.removeNullifiedNotes(nullifiers, notesToNullify[0].publicKey)).resolves.toEqual(
187
+ await expect(database.removeNullifiedNotes(nullifiers, notesToNullify[0].ivpkM)).resolves.toEqual(
188
188
  notesToNullify,
189
189
  );
190
190
 
191
- const result = await database.getNotes({
191
+ const result = await database.getIncomingNotes({
192
192
  status: NoteStatus.ACTIVE_OR_NULLIFIED,
193
193
  });
194
194
 
@@ -198,6 +198,70 @@ export function describePxeDatabase(getDatabase: () => PxeDatabase) {
198
198
  });
199
199
  });
200
200
 
201
+ describe('outgoing notes', () => {
202
+ let owners: CompleteAddress[];
203
+ let contractAddresses: AztecAddress[];
204
+ let storageSlots: Fr[];
205
+ let notes: OutgoingNoteDao[];
206
+
207
+ const filteringTests: [() => OutgoingNotesFilter, () => OutgoingNoteDao[]][] = [
208
+ [() => ({}), () => notes],
209
+
210
+ [
211
+ () => ({ contractAddress: contractAddresses[0] }),
212
+ () => notes.filter(note => note.contractAddress.equals(contractAddresses[0])),
213
+ ],
214
+ [() => ({ contractAddress: AztecAddress.random() }), () => []],
215
+
216
+ [
217
+ () => ({ storageSlot: storageSlots[0] }),
218
+ () => notes.filter(note => note.storageSlot.equals(storageSlots[0])),
219
+ ],
220
+ [() => ({ storageSlot: Fr.random() }), () => []],
221
+
222
+ [() => ({ txHash: notes[0].txHash }), () => [notes[0]]],
223
+ [() => ({ txHash: randomTxHash() }), () => []],
224
+
225
+ [
226
+ () => ({ owner: owners[0].address }),
227
+ () => notes.filter(note => note.ovpkM.equals(owners[0].publicKeys.masterOutgoingViewingPublicKey)),
228
+ ],
229
+
230
+ [
231
+ () => ({ contractAddress: contractAddresses[0], storageSlot: storageSlots[0] }),
232
+ () =>
233
+ notes.filter(
234
+ note => note.contractAddress.equals(contractAddresses[0]) && note.storageSlot.equals(storageSlots[0]),
235
+ ),
236
+ ],
237
+ [() => ({ contractAddress: contractAddresses[0], storageSlot: storageSlots[1] }), () => []],
238
+ ];
239
+
240
+ beforeEach(async () => {
241
+ owners = Array.from({ length: 2 }).map(() => CompleteAddress.random());
242
+ contractAddresses = Array.from({ length: 2 }).map(() => AztecAddress.random());
243
+ storageSlots = Array.from({ length: 2 }).map(() => Fr.random());
244
+
245
+ notes = Array.from({ length: 10 }).map((_, i) =>
246
+ randomOutgoingNoteDao({
247
+ contractAddress: contractAddresses[i % contractAddresses.length],
248
+ storageSlot: storageSlots[i % storageSlots.length],
249
+ ovpkM: owners[i % owners.length].publicKeys.masterOutgoingViewingPublicKey,
250
+ index: BigInt(i),
251
+ }),
252
+ );
253
+
254
+ for (const owner of owners) {
255
+ await database.addCompleteAddress(owner);
256
+ }
257
+ });
258
+
259
+ it.each(filteringTests)('stores notes in bulk and retrieves notes', async (getFilter, getExpected) => {
260
+ await database.addNotes([], notes);
261
+ await expect(database.getOutgoingNotes(getFilter())).resolves.toEqual(getExpected());
262
+ });
263
+ });
264
+
201
265
  describe('block header', () => {
202
266
  it('stores and retrieves the block header', async () => {
203
267
  const header = makeHeader(randomInt(1000), INITIAL_L2_BLOCK_NUM);
package/src/index.ts CHANGED
@@ -9,3 +9,6 @@ export * from '@aztec/foundation/fields';
9
9
  export * from '@aztec/foundation/eth-address';
10
10
  export * from '@aztec/foundation/aztec-address';
11
11
  export * from '@aztec/key-store';
12
+ export * from './database/index.js';
13
+ export { ContractDataOracle } from './contract_data_oracle/index.js';
14
+ export { PrivateFunctionsTree } from './contract_data_oracle/private_functions_tree.js';
@@ -1,36 +1,34 @@
1
- import {
2
- type AztecNode,
3
- type EncryptedNoteL2BlockL2Logs,
4
- L1NotePayload,
5
- type L2Block,
6
- TaggedNote,
7
- } from '@aztec/circuit-types';
1
+ import { type AztecNode, L1NotePayload, type L2Block, TaggedLog } from '@aztec/circuit-types';
8
2
  import { type NoteProcessorStats } from '@aztec/circuit-types/stats';
9
- import { INITIAL_L2_BLOCK_NUM, MAX_NEW_NOTE_HASHES_PER_TX, type PublicKey } from '@aztec/circuits.js';
3
+ import {
4
+ type AztecAddress,
5
+ INITIAL_L2_BLOCK_NUM,
6
+ MAX_NEW_NOTE_HASHES_PER_TX,
7
+ type PublicKey,
8
+ } from '@aztec/circuits.js';
10
9
  import { type Fr } from '@aztec/foundation/fields';
11
- import { createDebugLogger } from '@aztec/foundation/log';
10
+ import { type Logger, createDebugLogger } from '@aztec/foundation/log';
12
11
  import { Timer } from '@aztec/foundation/timer';
13
12
  import { type KeyStore } from '@aztec/key-store';
14
- import { ContractNotFoundError } from '@aztec/simulator';
13
+ import { type AcirSimulator } from '@aztec/simulator';
15
14
 
16
- import { DeferredNoteDao } from '../database/deferred_note_dao.js';
15
+ import { type DeferredNoteDao } from '../database/deferred_note_dao.js';
16
+ import { type IncomingNoteDao } from '../database/incoming_note_dao.js';
17
17
  import { type PxeDatabase } from '../database/index.js';
18
- import { type NoteDao } from '../database/note_dao.js';
18
+ import { type OutgoingNoteDao } from '../database/outgoing_note_dao.js';
19
19
  import { getAcirSimulator } from '../simulator/index.js';
20
- import { produceNoteDao } from './produce_note_dao.js';
20
+ import { produceNoteDaos } from './produce_note_dao.js';
21
21
 
22
22
  /**
23
23
  * Contains all the decrypted data in this array so that we can later batch insert it all into the database.
24
24
  */
25
25
  interface ProcessedData {
26
- /**
27
- * Holds L2 block.
28
- */
26
+ /** Holds L2 block. */
29
27
  block: L2Block;
30
- /**
31
- * DAOs of processed notes.
32
- */
33
- noteDaos: NoteDao[];
28
+ /** DAOs of processed incoming notes. */
29
+ incomingNotes: IncomingNoteDao[];
30
+ /** DAOs of processed outgoing notes. */
31
+ outgoingNotes: OutgoingNoteDao[];
34
32
  }
35
33
 
36
34
  /**
@@ -42,21 +40,46 @@ export class NoteProcessor {
42
40
  public readonly timer: Timer = new Timer();
43
41
 
44
42
  /** Stats accumulated for this processor. */
45
- public readonly stats: NoteProcessorStats = { seen: 0, decrypted: 0, deferred: 0, failed: 0, blocks: 0, txs: 0 };
43
+ public readonly stats: NoteProcessorStats = {
44
+ seen: 0,
45
+ decryptedIncoming: 0,
46
+ decryptedOutgoing: 0,
47
+ deferredIncoming: 0,
48
+ deferredOutgoing: 0,
49
+ failed: 0,
50
+ blocks: 0,
51
+ txs: 0,
52
+ };
46
53
 
47
- constructor(
48
- /**
49
- * The public counterpart to the private key to be used in note decryption.
50
- */
51
- public readonly masterIncomingViewingPublicKey: PublicKey,
54
+ private constructor(
55
+ public readonly account: AztecAddress,
56
+ /** The public counterpart to the secret key to be used in the decryption of incoming note logs. */
57
+ private readonly ivpkM: PublicKey,
58
+ /** The public counterpart to the secret key to be used in the decryption of outgoing note logs. */
59
+ private readonly ovpkM: PublicKey,
52
60
  private keyStore: KeyStore,
53
61
  private db: PxeDatabase,
54
62
  private node: AztecNode,
55
- private startingBlock: number = INITIAL_L2_BLOCK_NUM,
56
- private simulator = getAcirSimulator(db, node, keyStore),
57
- private log = createDebugLogger('aztec:note_processor'),
63
+ private startingBlock: number,
64
+ private simulator: AcirSimulator,
65
+ private log: Logger,
58
66
  ) {}
59
67
 
68
+ public static async create(
69
+ account: AztecAddress,
70
+ keyStore: KeyStore,
71
+ db: PxeDatabase,
72
+ node: AztecNode,
73
+ startingBlock: number = INITIAL_L2_BLOCK_NUM,
74
+ simulator = getAcirSimulator(db, node, keyStore),
75
+ log = createDebugLogger('aztec:note_processor'),
76
+ ) {
77
+ const ivpkM = await keyStore.getMasterIncomingViewingPublicKey(account);
78
+ const ovpkM = await keyStore.getMasterOutgoingViewingPublicKey(account);
79
+
80
+ return new NoteProcessor(account, ivpkM, ovpkM, keyStore, db, node, startingBlock, simulator, log);
81
+ }
82
+
60
83
  /**
61
84
  * Check if the NoteProcessor is synchronized with the remote block number.
62
85
  * The function queries the remote block number from the AztecNode and compares it with the syncedToBlock value in the NoteProcessor.
@@ -77,46 +100,40 @@ export class NoteProcessor {
77
100
  }
78
101
 
79
102
  private getSyncedToBlock(): number {
80
- return this.db.getSynchedBlockNumberForPublicKey(this.masterIncomingViewingPublicKey) ?? this.startingBlock - 1;
103
+ return this.db.getSynchedBlockNumberForPublicKey(this.ivpkM) ?? this.startingBlock - 1;
81
104
  }
82
105
 
83
106
  /**
84
107
  * Extracts new user-relevant notes from the information contained in the provided L2 blocks and encrypted logs.
85
108
  *
86
- * @throws If the number of blocks and encrypted logs do not match.
87
- * @param l2Blocks - L2 blocks to be processed.
88
- * @param encryptedL2BlockLogs - Encrypted logs associated with the L2 blocks.
109
+ * @param blocks - L2 blocks to be processed.
89
110
  * @returns A promise that resolves once the processing is completed.
90
111
  */
91
- public async process(l2Blocks: L2Block[], encryptedL2BlockLogs: EncryptedNoteL2BlockL2Logs[]): Promise<void> {
92
- if (l2Blocks.length !== encryptedL2BlockLogs.length) {
93
- throw new Error(
94
- `Number of blocks and EncryptedLogs is not equal. Received ${l2Blocks.length} blocks, ${encryptedL2BlockLogs.length} encrypted logs.`,
95
- );
96
- }
97
- if (l2Blocks.length === 0) {
112
+ public async process(blocks: L2Block[]): Promise<void> {
113
+ if (blocks.length === 0) {
98
114
  return;
99
115
  }
100
116
 
101
117
  const blocksAndNotes: ProcessedData[] = [];
102
118
  // Keep track of notes that we couldn't process because the contract was not found.
103
- const deferredNoteDaos: DeferredNoteDao[] = [];
119
+ const deferredIncomingNotes: DeferredNoteDao[] = [];
120
+ const deferredOutgoingNotes: DeferredNoteDao[] = [];
121
+
122
+ const ivskM = await this.keyStore.getMasterSecretKey(this.ivpkM);
123
+ const ovskM = await this.keyStore.getMasterSecretKey(this.ovpkM);
104
124
 
105
125
  // Iterate over both blocks and encrypted logs.
106
- for (let blockIndex = 0; blockIndex < encryptedL2BlockLogs.length; ++blockIndex) {
126
+ for (const block of blocks) {
107
127
  this.stats.blocks++;
108
- const { txLogs } = encryptedL2BlockLogs[blockIndex];
109
- const block = l2Blocks[blockIndex];
128
+ const { txLogs } = block.body.noteEncryptedLogs;
110
129
  const dataStartIndexForBlock =
111
130
  block.header.state.partial.noteHashTree.nextAvailableLeafIndex -
112
131
  block.body.numberOfTxsIncludingPadded * MAX_NEW_NOTE_HASHES_PER_TX;
113
132
 
114
133
  // We are using set for `userPertainingTxIndices` to avoid duplicates. This would happen in case there were
115
134
  // multiple encrypted logs in a tx pertaining to a user.
116
- const noteDaos: NoteDao[] = [];
117
- const secretKey = await this.keyStore.getMasterIncomingViewingSecretKeyForPublicKey(
118
- this.masterIncomingViewingPublicKey,
119
- );
135
+ const incomingNotes: IncomingNoteDao[] = [];
136
+ const outgoingNotes: OutgoingNoteDao[] = [];
120
137
 
121
138
  // Iterate over all the encrypted logs and try decrypting them. If successful, store the note.
122
139
  for (let indexOfTxInABlock = 0; indexOfTxInABlock < txLogs.length; ++indexOfTxInABlock) {
@@ -130,43 +147,56 @@ export class NoteProcessor {
130
147
  for (const functionLogs of txFunctionLogs) {
131
148
  for (const log of functionLogs.logs) {
132
149
  this.stats.seen++;
133
- // @todo Issue(#6410) We should also try decrypting as outgoing if this fails.
134
- const taggedNote = TaggedNote.decryptAsIncoming(log.data, secretKey);
135
- if (taggedNote?.notePayload) {
136
- const { notePayload: payload } = taggedNote;
137
- // We have successfully decrypted the data.
138
- const txHash = block.body.txEffects[indexOfTxInABlock].txHash;
139
- try {
140
- const noteDao = await produceNoteDao(
141
- this.simulator,
142
- this.masterIncomingViewingPublicKey,
143
- payload,
144
- txHash,
145
- newNoteHashes,
146
- dataStartIndexForTx,
147
- excludedIndices,
150
+ const incomingTaggedNote = TaggedLog.decryptAsIncoming(log.data, ivskM)!;
151
+ const outgoingTaggedNote = TaggedLog.decryptAsOutgoing(log.data, ovskM)!;
152
+
153
+ if (incomingTaggedNote || outgoingTaggedNote) {
154
+ if (
155
+ incomingTaggedNote &&
156
+ outgoingTaggedNote &&
157
+ !incomingTaggedNote.payload.equals(outgoingTaggedNote.payload)
158
+ ) {
159
+ throw new Error(
160
+ `Incoming and outgoing note payloads do not match. Incoming: ${JSON.stringify(
161
+ incomingTaggedNote.payload,
162
+ )}, Outgoing: ${JSON.stringify(outgoingTaggedNote.payload)}`,
148
163
  );
149
- noteDaos.push(noteDao);
150
- this.stats.decrypted++;
151
- } catch (e) {
152
- if (e instanceof ContractNotFoundError) {
153
- this.stats.deferred++;
154
- this.log.warn(e.message);
155
- const deferredNoteDao = new DeferredNoteDao(
156
- this.masterIncomingViewingPublicKey,
157
- payload.note,
158
- payload.contractAddress,
159
- payload.storageSlot,
160
- payload.noteTypeId,
161
- txHash,
162
- newNoteHashes,
163
- dataStartIndexForTx,
164
- );
165
- deferredNoteDaos.push(deferredNoteDao);
166
- } else {
167
- this.stats.failed++;
168
- this.log.error(`Could not process note because of "${e}". Discarding note...`);
169
- }
164
+ }
165
+
166
+ const payload = incomingTaggedNote?.payload || outgoingTaggedNote?.payload;
167
+
168
+ const txHash = block.body.txEffects[indexOfTxInABlock].txHash;
169
+ const { incomingNote, outgoingNote, incomingDeferredNote, outgoingDeferredNote } = await produceNoteDaos(
170
+ this.simulator,
171
+ incomingTaggedNote ? this.ivpkM : undefined,
172
+ outgoingTaggedNote ? this.ovpkM : undefined,
173
+ payload,
174
+ txHash,
175
+ newNoteHashes,
176
+ dataStartIndexForTx,
177
+ excludedIndices,
178
+ this.log,
179
+ );
180
+
181
+ if (incomingNote) {
182
+ incomingNotes.push(incomingNote);
183
+ this.stats.decryptedIncoming++;
184
+ }
185
+ if (outgoingNote) {
186
+ outgoingNotes.push(outgoingNote);
187
+ this.stats.decryptedOutgoing++;
188
+ }
189
+ if (incomingDeferredNote) {
190
+ deferredIncomingNotes.push(incomingDeferredNote);
191
+ this.stats.deferredIncoming++;
192
+ }
193
+ if (outgoingDeferredNote) {
194
+ deferredOutgoingNotes.push(outgoingDeferredNote);
195
+ this.stats.deferredOutgoing++;
196
+ }
197
+
198
+ if (incomingNote == undefined && outgoingNote == undefined && incomingDeferredNote == undefined) {
199
+ this.stats.failed++;
170
200
  }
171
201
  }
172
202
  }
@@ -174,16 +204,17 @@ export class NoteProcessor {
174
204
  }
175
205
 
176
206
  blocksAndNotes.push({
177
- block: l2Blocks[blockIndex],
178
- noteDaos,
207
+ block,
208
+ incomingNotes,
209
+ outgoingNotes,
179
210
  });
180
211
  }
181
212
 
182
213
  await this.processBlocksAndNotes(blocksAndNotes);
183
- await this.processDeferredNotes(deferredNoteDaos);
214
+ await this.processDeferredNotes(deferredIncomingNotes, deferredOutgoingNotes);
184
215
 
185
- const syncedToBlock = l2Blocks[l2Blocks.length - 1].number;
186
- await this.db.setSynchedBlockNumberForPublicKey(this.masterIncomingViewingPublicKey, syncedToBlock);
216
+ const syncedToBlock = blocks[blocks.length - 1].number;
217
+ await this.db.setSynchedBlockNumberForPublicKey(this.ivpkM, syncedToBlock);
187
218
 
188
219
  this.log.debug(`Synched block ${syncedToBlock}`);
189
220
  }
@@ -198,22 +229,26 @@ export class NoteProcessor {
198
229
  * @param blocksAndNotes - Array of objects containing L2 blocks, user-pertaining transaction indices, and NoteDaos.
199
230
  */
200
231
  private async processBlocksAndNotes(blocksAndNotes: ProcessedData[]) {
201
- const noteDaos = blocksAndNotes.flatMap(b => b.noteDaos);
202
- if (noteDaos.length) {
203
- await this.db.addNotes(noteDaos);
204
- noteDaos.forEach(noteDao => {
232
+ const incomingNotes = blocksAndNotes.flatMap(b => b.incomingNotes);
233
+ const outgoingNotes = blocksAndNotes.flatMap(b => b.outgoingNotes);
234
+ if (incomingNotes.length || outgoingNotes.length) {
235
+ await this.db.addNotes(incomingNotes, outgoingNotes);
236
+ incomingNotes.forEach(noteDao => {
205
237
  this.log.verbose(
206
- `Added note for contract ${noteDao.contractAddress} at slot ${
238
+ `Added incoming note for contract ${noteDao.contractAddress} at slot ${
207
239
  noteDao.storageSlot
208
240
  } with nullifier ${noteDao.siloedNullifier.toString()}`,
209
241
  );
210
242
  });
243
+ outgoingNotes.forEach(noteDao => {
244
+ this.log.verbose(`Added outgoing note for contract ${noteDao.contractAddress} at slot ${noteDao.storageSlot}`);
245
+ });
211
246
  }
212
247
 
213
248
  const newNullifiers: Fr[] = blocksAndNotes.flatMap(b =>
214
249
  b.block.body.txEffects.flatMap(txEffect => txEffect.nullifiers),
215
250
  );
216
- const removedNotes = await this.db.removeNullifiedNotes(newNullifiers, this.masterIncomingViewingPublicKey);
251
+ const removedNotes = await this.db.removeNullifiedNotes(newNullifiers, this.ivpkM);
217
252
  removedNotes.forEach(noteDao => {
218
253
  this.log.verbose(
219
254
  `Removed note for contract ${noteDao.contractAddress} at slot ${
@@ -226,14 +261,25 @@ export class NoteProcessor {
226
261
  /**
227
262
  * Store the given deferred notes in the database for later decoding.
228
263
  *
229
- * @param deferredNoteDaos - notes that are intended for us but we couldn't process because the contract was not found.
264
+ * @param deferredIncomingNotes - incoming notes that are intended for us but we couldn't process because the contract was not found.
265
+ * @param deferredOutgoingNotes - outgoing notes that we couldn't process because the contract was not found.
230
266
  */
231
- private async processDeferredNotes(deferredNoteDaos: DeferredNoteDao[]) {
232
- if (deferredNoteDaos.length) {
233
- await this.db.addDeferredNotes(deferredNoteDaos);
234
- deferredNoteDaos.forEach(noteDao => {
267
+ private async processDeferredNotes(
268
+ deferredIncomingNotes: DeferredNoteDao[],
269
+ deferredOutgoingNotes: DeferredNoteDao[],
270
+ ) {
271
+ if (deferredIncomingNotes.length || deferredOutgoingNotes.length) {
272
+ await this.db.addDeferredNotes([...deferredIncomingNotes, ...deferredOutgoingNotes]);
273
+ deferredIncomingNotes.forEach(noteDao => {
274
+ this.log.verbose(
275
+ `Deferred incoming note for contract ${noteDao.contractAddress} at slot ${
276
+ noteDao.storageSlot
277
+ } in tx ${noteDao.txHash.toString()}`,
278
+ );
279
+ });
280
+ deferredOutgoingNotes.forEach(noteDao => {
235
281
  this.log.verbose(
236
- `Deferred note for contract ${noteDao.contractAddress} at slot ${
282
+ `Deferred outgoing note for contract ${noteDao.contractAddress} at slot ${
237
283
  noteDao.storageSlot
238
284
  } in tx ${noteDao.txHash.toString()}`,
239
285
  );
@@ -245,37 +291,60 @@ export class NoteProcessor {
245
291
  * Retry decoding the given deferred notes because we now have the contract code.
246
292
  *
247
293
  * @param deferredNoteDaos - notes that we have previously deferred because the contract was not found
248
- * @returns An array of NoteDaos that were successfully decoded.
294
+ * @returns An object containing arrays of incoming and outgoing notes that were successfully decoded.
249
295
  *
250
296
  * @remarks Caller is responsible for making sure that we have the contract for the
251
297
  * deferred notes provided: we will not retry notes that fail again.
252
298
  */
253
- public async decodeDeferredNotes(deferredNoteDaos: DeferredNoteDao[]): Promise<NoteDao[]> {
299
+ public async decodeDeferredNotes(deferredNoteDaos: DeferredNoteDao[]): Promise<{
300
+ incomingNotes: IncomingNoteDao[];
301
+ outgoingNotes: OutgoingNoteDao[];
302
+ }> {
254
303
  const excludedIndices: Set<number> = new Set();
255
- const noteDaos: NoteDao[] = [];
304
+ const incomingNotes: IncomingNoteDao[] = [];
305
+ const outgoingNotes: OutgoingNoteDao[] = [];
306
+
256
307
  for (const deferredNote of deferredNoteDaos) {
257
- const { note, contractAddress, storageSlot, noteTypeId, txHash, newNoteHashes, dataStartIndexForTx } =
308
+ const { publicKey, note, contractAddress, storageSlot, noteTypeId, txHash, newNoteHashes, dataStartIndexForTx } =
258
309
  deferredNote;
259
310
  const payload = new L1NotePayload(note, contractAddress, storageSlot, noteTypeId);
260
311
 
261
- try {
262
- const noteDao = await produceNoteDao(
263
- this.simulator,
264
- this.masterIncomingViewingPublicKey,
265
- payload,
266
- txHash,
267
- newNoteHashes,
268
- dataStartIndexForTx,
269
- excludedIndices,
270
- );
271
- noteDaos.push(noteDao);
272
- this.stats.decrypted++;
273
- } catch (e) {
274
- this.stats.failed++;
275
- this.log.warn(`Could not process deferred note because of "${e}". Discarding note...`);
312
+ const isIncoming = publicKey.equals(this.ivpkM);
313
+ const isOutgoing = publicKey.equals(this.ovpkM);
314
+
315
+ if (!isIncoming && !isOutgoing) {
316
+ // The note does not belong to this note processor
317
+ continue;
318
+ }
319
+
320
+ const { incomingNote, outgoingNote } = await produceNoteDaos(
321
+ this.simulator,
322
+ isIncoming ? this.ivpkM : undefined,
323
+ isOutgoing ? this.ovpkM : undefined,
324
+ payload,
325
+ txHash,
326
+ newNoteHashes,
327
+ dataStartIndexForTx,
328
+ excludedIndices,
329
+ this.log,
330
+ );
331
+
332
+ if (isIncoming) {
333
+ if (!incomingNote) {
334
+ throw new Error('Deferred incoming note could not be decoded');
335
+ }
336
+ incomingNotes.push(incomingNote);
337
+ this.stats.decryptedIncoming++;
338
+ }
339
+ if (outgoingNote) {
340
+ if (!outgoingNote) {
341
+ throw new Error('Deferred outgoing note could not be decoded');
342
+ }
343
+ outgoingNotes.push(outgoingNote);
344
+ this.stats.decryptedOutgoing++;
276
345
  }
277
346
  }
278
347
 
279
- return noteDaos;
348
+ return { incomingNotes, outgoingNotes };
280
349
  }
281
350
  }