@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.
- package/dest/database/deferred_note_dao.d.ts +5 -4
- package/dest/database/deferred_note_dao.d.ts.map +1 -1
- package/dest/database/deferred_note_dao.js +4 -3
- package/dest/database/incoming_note_dao.d.ts +74 -0
- package/dest/database/incoming_note_dao.d.ts.map +1 -0
- package/dest/database/incoming_note_dao.js +93 -0
- package/dest/database/index.d.ts +1 -0
- package/dest/database/index.d.ts.map +1 -1
- package/dest/database/index.js +2 -1
- package/dest/database/kv_pxe_database.d.ts +10 -7
- package/dest/database/kv_pxe_database.d.ts.map +1 -1
- package/dest/database/kv_pxe_database.js +149 -78
- package/dest/database/{note_dao.d.ts → outgoing_note_dao.d.ts} +10 -14
- package/dest/database/outgoing_note_dao.d.ts.map +1 -0
- package/dest/database/outgoing_note_dao.js +84 -0
- package/dest/database/pxe_database.d.ts +21 -9
- package/dest/database/pxe_database.d.ts.map +1 -1
- package/dest/database/pxe_database_test_suite.d.ts.map +1 -1
- package/dest/database/pxe_database_test_suite.js +71 -24
- package/dest/index.d.ts +3 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +4 -1
- package/dest/kernel_oracle/index.d.ts +1 -1
- package/dest/note_processor/note_processor.d.ts +23 -20
- package/dest/note_processor/note_processor.d.ts.map +1 -1
- package/dest/note_processor/note_processor.js +116 -76
- package/dest/note_processor/produce_note_dao.d.ts +13 -4
- package/dest/note_processor/produce_note_dao.d.ts.map +1 -1
- package/dest/note_processor/produce_note_dao.js +88 -31
- package/dest/pxe_http/pxe_http_server.d.ts.map +1 -1
- package/dest/pxe_http/pxe_http_server.js +3 -1
- package/dest/pxe_service/create_pxe_service.d.ts.map +1 -1
- package/dest/pxe_service/create_pxe_service.js +3 -1
- package/dest/pxe_service/pxe_service.d.ts +9 -4
- package/dest/pxe_service/pxe_service.d.ts.map +1 -1
- package/dest/pxe_service/pxe_service.js +106 -28
- package/dest/simulator_oracle/index.js +2 -2
- package/dest/synchronizer/synchronizer.d.ts +2 -2
- package/dest/synchronizer/synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/synchronizer.js +37 -36
- package/package.json +23 -15
- package/src/database/deferred_note_dao.ts +4 -3
- package/src/database/{note_dao.ts → incoming_note_dao.ts} +14 -10
- package/src/database/index.ts +1 -0
- package/src/database/kv_pxe_database.ts +127 -29
- package/src/database/outgoing_note_dao.ts +91 -0
- package/src/database/pxe_database.ts +23 -9
- package/src/database/pxe_database_test_suite.ts +93 -29
- package/src/index.ts +3 -0
- package/src/note_processor/note_processor.ts +190 -121
- package/src/note_processor/produce_note_dao.ts +164 -50
- package/src/pxe_http/pxe_http_server.ts +2 -0
- package/src/pxe_service/create_pxe_service.ts +2 -0
- package/src/pxe_service/pxe_service.ts +170 -42
- package/src/simulator_oracle/index.ts +1 -1
- package/src/synchronizer/synchronizer.ts +48 -52
- package/dest/database/note_dao.d.ts.map +0 -1
- package/dest/database/note_dao.js +0 -89
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
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
|
|
10
|
-
import {
|
|
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:
|
|
77
|
+
let notes: IncomingNoteDao[];
|
|
76
78
|
|
|
77
|
-
const filteringTests: [() =>
|
|
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.
|
|
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
|
-
|
|
118
|
+
randomIncomingNoteDao({
|
|
117
119
|
contractAddress: contractAddresses[i % contractAddresses.length],
|
|
118
120
|
storageSlot: storageSlots[i % storageSlots.length],
|
|
119
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
158
|
-
|
|
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.
|
|
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].
|
|
169
|
+
await expect(database.removeNullifiedNotes(nullifiers, notesToNullify[0].ivpkM)).resolves.toEqual(
|
|
170
170
|
notesToNullify,
|
|
171
171
|
);
|
|
172
172
|
|
|
173
|
-
const actualNotesWithDefault = await database.
|
|
174
|
-
const actualNotesWithActive = await database.
|
|
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.
|
|
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].
|
|
187
|
+
await expect(database.removeNullifiedNotes(nullifiers, notesToNullify[0].ivpkM)).resolves.toEqual(
|
|
188
188
|
notesToNullify,
|
|
189
189
|
);
|
|
190
190
|
|
|
191
|
-
const result = await database.
|
|
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 {
|
|
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 {
|
|
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
|
|
18
|
+
import { type OutgoingNoteDao } from '../database/outgoing_note_dao.js';
|
|
19
19
|
import { getAcirSimulator } from '../simulator/index.js';
|
|
20
|
-
import {
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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 = {
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
public
|
|
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
|
|
56
|
-
private simulator
|
|
57
|
-
private log
|
|
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.
|
|
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
|
-
* @
|
|
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(
|
|
92
|
-
if (
|
|
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
|
|
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 (
|
|
126
|
+
for (const block of blocks) {
|
|
107
127
|
this.stats.blocks++;
|
|
108
|
-
const { txLogs } =
|
|
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
|
|
117
|
-
const
|
|
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
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
|
178
|
-
|
|
207
|
+
block,
|
|
208
|
+
incomingNotes,
|
|
209
|
+
outgoingNotes,
|
|
179
210
|
});
|
|
180
211
|
}
|
|
181
212
|
|
|
182
213
|
await this.processBlocksAndNotes(blocksAndNotes);
|
|
183
|
-
await this.processDeferredNotes(
|
|
214
|
+
await this.processDeferredNotes(deferredIncomingNotes, deferredOutgoingNotes);
|
|
184
215
|
|
|
185
|
-
const syncedToBlock =
|
|
186
|
-
await this.db.setSynchedBlockNumberForPublicKey(this.
|
|
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
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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.
|
|
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
|
|
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(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
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<
|
|
299
|
+
public async decodeDeferredNotes(deferredNoteDaos: DeferredNoteDao[]): Promise<{
|
|
300
|
+
incomingNotes: IncomingNoteDao[];
|
|
301
|
+
outgoingNotes: OutgoingNoteDao[];
|
|
302
|
+
}> {
|
|
254
303
|
const excludedIndices: Set<number> = new Set();
|
|
255
|
-
const
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
this.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
348
|
+
return { incomingNotes, outgoingNotes };
|
|
280
349
|
}
|
|
281
350
|
}
|