@aztec/pxe 0.56.0 → 0.58.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/bin/index.js +0 -0
- package/dest/contract_data_oracle/index.d.ts +1 -2
- package/dest/contract_data_oracle/index.d.ts.map +1 -1
- package/dest/contract_data_oracle/index.js +1 -1
- package/dest/contract_data_oracle/private_functions_tree.d.ts +1 -2
- package/dest/contract_data_oracle/private_functions_tree.d.ts.map +1 -1
- package/dest/contract_data_oracle/private_functions_tree.js +1 -1
- package/dest/database/contracts/contract_instance_db.d.ts +1 -2
- package/dest/database/contracts/contract_instance_db.d.ts.map +1 -1
- package/dest/database/deferred_note_dao.d.ts +6 -2
- package/dest/database/deferred_note_dao.d.ts.map +1 -1
- package/dest/database/deferred_note_dao.js +8 -5
- package/dest/database/incoming_note_dao.d.ts +3 -1
- package/dest/database/incoming_note_dao.d.ts.map +1 -1
- package/dest/database/incoming_note_dao.js +5 -1
- package/dest/database/kv_pxe_database.d.ts +1 -2
- package/dest/database/kv_pxe_database.d.ts.map +1 -1
- package/dest/database/kv_pxe_database.js +4 -4
- package/dest/database/outgoing_note_dao.d.ts +3 -1
- package/dest/database/outgoing_note_dao.d.ts.map +1 -1
- package/dest/database/outgoing_note_dao.js +5 -1
- package/dest/database/pxe_database.d.ts +1 -2
- 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 +2 -3
- package/dest/kernel_oracle/index.d.ts +8 -4
- package/dest/kernel_oracle/index.d.ts.map +1 -1
- package/dest/kernel_oracle/index.js +6 -5
- package/dest/kernel_prover/hints/build_private_kernel_reset_private_inputs.d.ts +28 -0
- package/dest/kernel_prover/hints/build_private_kernel_reset_private_inputs.d.ts.map +1 -0
- package/dest/kernel_prover/hints/build_private_kernel_reset_private_inputs.js +260 -0
- package/dest/kernel_prover/hints/index.d.ts +1 -2
- package/dest/kernel_prover/hints/index.d.ts.map +1 -1
- package/dest/kernel_prover/hints/index.js +2 -3
- package/dest/kernel_prover/kernel_prover.d.ts +2 -4
- package/dest/kernel_prover/kernel_prover.d.ts.map +1 -1
- package/dest/kernel_prover/kernel_prover.js +37 -30
- package/dest/kernel_prover/proving_data_oracle.d.ts +2 -2
- package/dest/kernel_prover/proving_data_oracle.d.ts.map +1 -1
- package/dest/kernel_prover/test/test_circuit_prover.d.ts +2 -3
- package/dest/kernel_prover/test/test_circuit_prover.d.ts.map +1 -1
- package/dest/kernel_prover/test/test_circuit_prover.js +10 -13
- package/dest/note_processor/note_processor.d.ts +3 -3
- package/dest/note_processor/note_processor.d.ts.map +1 -1
- package/dest/note_processor/note_processor.js +16 -18
- package/dest/note_processor/utils/add_nullable_field_to_payload.d.ts +12 -0
- package/dest/note_processor/utils/add_nullable_field_to_payload.d.ts.map +1 -0
- package/dest/note_processor/utils/add_nullable_field_to_payload.js +46 -0
- package/dest/note_processor/utils/brute_force_note_info.d.ts +26 -0
- package/dest/note_processor/utils/brute_force_note_info.d.ts.map +1 -0
- package/dest/note_processor/utils/brute_force_note_info.js +52 -0
- package/dest/note_processor/utils/index.d.ts +3 -0
- package/dest/note_processor/utils/index.d.ts.map +1 -0
- package/dest/note_processor/utils/index.js +2 -0
- package/dest/note_processor/{produce_note_dao.d.ts → utils/produce_note_daos.d.ts} +12 -8
- package/dest/note_processor/utils/produce_note_daos.d.ts.map +1 -0
- package/dest/note_processor/utils/produce_note_daos.js +53 -0
- package/dest/note_processor/utils/produce_note_daos_for_key.d.ts +9 -0
- package/dest/note_processor/utils/produce_note_daos_for_key.d.ts.map +1 -0
- package/dest/note_processor/utils/produce_note_daos_for_key.js +80 -0
- package/dest/pxe_http/pxe_http_server.d.ts.map +1 -1
- package/dest/pxe_http/pxe_http_server.js +13 -4
- package/dest/pxe_service/create_pxe_service.d.ts.map +1 -1
- package/dest/pxe_service/create_pxe_service.js +2 -18
- package/dest/pxe_service/pxe_service.d.ts +4 -6
- package/dest/pxe_service/pxe_service.d.ts.map +1 -1
- package/dest/pxe_service/pxe_service.js +55 -49
- package/dest/simulator_oracle/index.d.ts +1 -2
- package/dest/simulator_oracle/index.d.ts.map +1 -1
- package/dest/simulator_oracle/index.js +1 -1
- package/dest/synchronizer/synchronizer.d.ts +2 -2
- package/dest/synchronizer/synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/synchronizer.js +6 -10
- package/package.json +17 -14
- package/src/contract_data_oracle/index.ts +1 -2
- package/src/contract_data_oracle/private_functions_tree.ts +1 -1
- package/src/database/contracts/contract_instance_db.ts +1 -2
- package/src/database/deferred_note_dao.ts +5 -1
- package/src/database/incoming_note_dao.ts +24 -1
- package/src/database/kv_pxe_database.ts +10 -3
- package/src/database/outgoing_note_dao.ts +23 -1
- package/src/database/pxe_database.ts +6 -2
- package/src/database/pxe_database_test_suite.ts +7 -2
- package/src/kernel_oracle/index.ts +5 -4
- package/src/kernel_prover/hints/build_private_kernel_reset_private_inputs.ts +467 -0
- package/src/kernel_prover/hints/index.ts +1 -2
- package/src/kernel_prover/kernel_prover.ts +74 -79
- package/src/kernel_prover/proving_data_oracle.ts +2 -1
- package/src/kernel_prover/test/test_circuit_prover.ts +13 -16
- package/src/note_processor/note_processor.ts +36 -27
- package/src/note_processor/utils/add_nullable_field_to_payload.ts +67 -0
- package/src/note_processor/utils/brute_force_note_info.ts +82 -0
- package/src/note_processor/utils/index.ts +2 -0
- package/src/note_processor/utils/produce_note_daos.ts +114 -0
- package/src/note_processor/utils/produce_note_daos_for_key.ts +157 -0
- package/src/pxe_http/pxe_http_server.ts +19 -3
- package/src/pxe_service/create_pxe_service.ts +1 -18
- package/src/pxe_service/pxe_service.ts +81 -83
- package/src/simulator_oracle/index.ts +1 -1
- package/src/synchronizer/synchronizer.ts +12 -10
- package/dest/kernel_prover/hints/build_private_kernel_reset_hints.d.ts +0 -5
- package/dest/kernel_prover/hints/build_private_kernel_reset_hints.d.ts.map +0 -1
- package/dest/kernel_prover/hints/build_private_kernel_reset_hints.js +0 -90
- package/dest/kernel_prover/hints/needs_reset.d.ts +0 -5
- package/dest/kernel_prover/hints/needs_reset.d.ts.map +0 -1
- package/dest/kernel_prover/hints/needs_reset.js +0 -38
- package/dest/note_processor/produce_note_dao.d.ts.map +0 -1
- package/dest/note_processor/produce_note_dao.js +0 -131
- package/src/kernel_prover/hints/build_private_kernel_reset_hints.ts +0 -249
- package/src/kernel_prover/hints/needs_reset.ts +0 -54
- package/src/note_processor/produce_note_dao.ts +0 -235
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { type L1NotePayload, type PublicKey, type TxHash, type UnencryptedTxL2Logs } from '@aztec/circuit-types';
|
|
2
|
+
import { type Fr } from '@aztec/foundation/fields';
|
|
3
|
+
import { type Logger } from '@aztec/foundation/log';
|
|
4
|
+
import { type AcirSimulator } from '@aztec/simulator';
|
|
5
|
+
|
|
6
|
+
import { type DeferredNoteDao } from '../../database/deferred_note_dao.js';
|
|
7
|
+
import { IncomingNoteDao } from '../../database/incoming_note_dao.js';
|
|
8
|
+
import { OutgoingNoteDao } from '../../database/outgoing_note_dao.js';
|
|
9
|
+
import { type PxeDatabase } from '../../database/pxe_database.js';
|
|
10
|
+
import { produceNoteDaosForKey } from './produce_note_daos_for_key.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Decodes a note from a transaction that we know was intended for us.
|
|
14
|
+
* Throws if we do not yet have the contract corresponding to the note in our database.
|
|
15
|
+
* Accepts a set of excluded indices, which are indices that have been assigned a note in the same tx.
|
|
16
|
+
* Inserts the index of the note into the excludedIndices set if the note is successfully decoded.
|
|
17
|
+
*
|
|
18
|
+
* @param simulator - An instance of AcirSimulator.
|
|
19
|
+
* @param db - An instance of PxeDatabase.
|
|
20
|
+
* @param ivpkM - The public counterpart to the secret key to be used in the decryption of incoming note logs.
|
|
21
|
+
* @param ovpkM - The public counterpart to the secret key to be used in the decryption of outgoing note logs.
|
|
22
|
+
* @param payload - An instance of l1NotePayload.
|
|
23
|
+
* @param txHash - The hash of the transaction that created the note. Equivalent to the first nullifier of the transaction.
|
|
24
|
+
* @param noteHashes - New note hashes in this transaction, one of which belongs to this note.
|
|
25
|
+
* @param dataStartIndexForTx - The next available leaf index for the note hash tree for this transaction.
|
|
26
|
+
* @param excludedIndices - Indices that have been assigned a note in the same tx. Notes in a tx can have the same l1NotePayload, we need to find a different index for each replicate.
|
|
27
|
+
* @param logger - An instance of Logger.
|
|
28
|
+
* @param unencryptedLogs - Unencrypted logs for the transaction (used to complete partial notes).
|
|
29
|
+
* @returns An object containing the incoming, outgoing, and deferred notes.
|
|
30
|
+
*/
|
|
31
|
+
export async function produceNoteDaos(
|
|
32
|
+
simulator: AcirSimulator,
|
|
33
|
+
db: PxeDatabase,
|
|
34
|
+
ivpkM: PublicKey | undefined,
|
|
35
|
+
ovpkM: PublicKey | undefined,
|
|
36
|
+
payload: L1NotePayload,
|
|
37
|
+
txHash: TxHash,
|
|
38
|
+
noteHashes: Fr[],
|
|
39
|
+
dataStartIndexForTx: number,
|
|
40
|
+
excludedIndices: Set<number>,
|
|
41
|
+
logger: Logger,
|
|
42
|
+
unencryptedLogs: UnencryptedTxL2Logs,
|
|
43
|
+
): Promise<{
|
|
44
|
+
incomingNote: IncomingNoteDao | undefined;
|
|
45
|
+
outgoingNote: OutgoingNoteDao | undefined;
|
|
46
|
+
incomingDeferredNote: DeferredNoteDao | undefined;
|
|
47
|
+
outgoingDeferredNote: DeferredNoteDao | undefined;
|
|
48
|
+
}> {
|
|
49
|
+
// WARNING: This code is full of tech debt and will be refactored once we have final design of partial notes
|
|
50
|
+
// delivery.
|
|
51
|
+
if (!ivpkM && !ovpkM) {
|
|
52
|
+
throw new Error('Both ivpkM and ovpkM are undefined. Cannot create note.');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let incomingNote: IncomingNoteDao | undefined;
|
|
56
|
+
let outgoingNote: OutgoingNoteDao | undefined;
|
|
57
|
+
let incomingDeferredNote: DeferredNoteDao | undefined;
|
|
58
|
+
let outgoingDeferredNote: DeferredNoteDao | undefined;
|
|
59
|
+
|
|
60
|
+
if (ivpkM) {
|
|
61
|
+
[incomingNote, incomingDeferredNote] = await produceNoteDaosForKey(
|
|
62
|
+
simulator,
|
|
63
|
+
db,
|
|
64
|
+
ivpkM,
|
|
65
|
+
payload,
|
|
66
|
+
txHash,
|
|
67
|
+
noteHashes,
|
|
68
|
+
dataStartIndexForTx,
|
|
69
|
+
excludedIndices,
|
|
70
|
+
logger,
|
|
71
|
+
unencryptedLogs,
|
|
72
|
+
IncomingNoteDao.fromPayloadAndNoteInfo,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (ovpkM) {
|
|
77
|
+
if (incomingNote) {
|
|
78
|
+
// Incoming note is defined meaning that this PXE has both the incoming and outgoing keys. We can skip computing
|
|
79
|
+
// note hash and note index since we already have them in the incoming note.
|
|
80
|
+
outgoingNote = new OutgoingNoteDao(
|
|
81
|
+
payload.note,
|
|
82
|
+
payload.contractAddress,
|
|
83
|
+
payload.storageSlot,
|
|
84
|
+
payload.noteTypeId,
|
|
85
|
+
txHash,
|
|
86
|
+
incomingNote.nonce,
|
|
87
|
+
incomingNote.noteHash,
|
|
88
|
+
incomingNote.index,
|
|
89
|
+
ovpkM,
|
|
90
|
+
);
|
|
91
|
+
} else {
|
|
92
|
+
[outgoingNote, outgoingDeferredNote] = await produceNoteDaosForKey(
|
|
93
|
+
simulator,
|
|
94
|
+
db,
|
|
95
|
+
ovpkM,
|
|
96
|
+
payload,
|
|
97
|
+
txHash,
|
|
98
|
+
noteHashes,
|
|
99
|
+
dataStartIndexForTx,
|
|
100
|
+
excludedIndices,
|
|
101
|
+
logger,
|
|
102
|
+
unencryptedLogs,
|
|
103
|
+
OutgoingNoteDao.fromPayloadAndNoteInfo,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
incomingNote,
|
|
110
|
+
outgoingNote,
|
|
111
|
+
incomingDeferredNote,
|
|
112
|
+
outgoingDeferredNote,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { type L1NotePayload, type TxHash, UnencryptedTxL2Logs } from '@aztec/circuit-types';
|
|
2
|
+
import { Fr, type PublicKey } from '@aztec/circuits.js';
|
|
3
|
+
import { type Logger } from '@aztec/foundation/log';
|
|
4
|
+
import { type AcirSimulator, ContractNotFoundError } from '@aztec/simulator';
|
|
5
|
+
|
|
6
|
+
import { DeferredNoteDao } from '../../database/deferred_note_dao.js';
|
|
7
|
+
import { type PxeDatabase } from '../../database/pxe_database.js';
|
|
8
|
+
import { addNullableFieldsToPayload } from './add_nullable_field_to_payload.js';
|
|
9
|
+
import { type NoteInfo, bruteForceNoteInfo } from './brute_force_note_info.js';
|
|
10
|
+
|
|
11
|
+
export async function produceNoteDaosForKey<T>(
|
|
12
|
+
simulator: AcirSimulator,
|
|
13
|
+
db: PxeDatabase,
|
|
14
|
+
pkM: PublicKey,
|
|
15
|
+
payload: L1NotePayload,
|
|
16
|
+
txHash: TxHash,
|
|
17
|
+
noteHashes: Fr[],
|
|
18
|
+
dataStartIndexForTx: number,
|
|
19
|
+
excludedIndices: Set<number>,
|
|
20
|
+
logger: Logger,
|
|
21
|
+
unencryptedLogs: UnencryptedTxL2Logs,
|
|
22
|
+
daoConstructor: (payload: L1NotePayload, noteInfo: NoteInfo, dataStartIndexForTx: number, pkM: PublicKey) => T,
|
|
23
|
+
): Promise<[T | undefined, DeferredNoteDao | undefined]> {
|
|
24
|
+
let noteDao: T | undefined;
|
|
25
|
+
let deferredNoteDao: DeferredNoteDao | undefined;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const noteInfo = await bruteForceNoteInfo(
|
|
29
|
+
simulator,
|
|
30
|
+
noteHashes,
|
|
31
|
+
txHash,
|
|
32
|
+
payload,
|
|
33
|
+
excludedIndices,
|
|
34
|
+
true, // For incoming we compute a nullifier (recipient of incoming is the party that nullifies).
|
|
35
|
+
);
|
|
36
|
+
excludedIndices?.add(noteInfo.noteHashIndex);
|
|
37
|
+
|
|
38
|
+
noteDao = daoConstructor(payload, noteInfo, dataStartIndexForTx, pkM);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
if (e instanceof ContractNotFoundError) {
|
|
41
|
+
logger.warn(e.message);
|
|
42
|
+
|
|
43
|
+
deferredNoteDao = new DeferredNoteDao(
|
|
44
|
+
pkM,
|
|
45
|
+
payload.note,
|
|
46
|
+
payload.contractAddress,
|
|
47
|
+
payload.storageSlot,
|
|
48
|
+
payload.noteTypeId,
|
|
49
|
+
txHash,
|
|
50
|
+
noteHashes,
|
|
51
|
+
dataStartIndexForTx,
|
|
52
|
+
unencryptedLogs,
|
|
53
|
+
);
|
|
54
|
+
} else if (
|
|
55
|
+
(e as any).message.includes('failed to solve blackbox function: embedded_curve_add') ||
|
|
56
|
+
(e as any).message.includes('Could not find key prefix.')
|
|
57
|
+
) {
|
|
58
|
+
// TODO(#8769): This branch is a temporary partial notes delivery solution that should be eventually replaced.
|
|
59
|
+
// Both error messages above occur only when we are dealing with a partial note and are thrown when calling
|
|
60
|
+
// `note.compute_note_hash()` or `note.compute_nullifier_without_context()`
|
|
61
|
+
// in `compute_note_hash_and_optionally_a_nullifier` function. It occurs with partial notes because in the
|
|
62
|
+
// partial flow we receive a note log of a note that is missing some fields here and then we try to compute
|
|
63
|
+
// the note hash with MSM while some of the fields are zeroed out (or get a nsk for zero npk_m_hash).
|
|
64
|
+
noteDao = await handlePartialNote(
|
|
65
|
+
simulator,
|
|
66
|
+
db,
|
|
67
|
+
pkM,
|
|
68
|
+
payload,
|
|
69
|
+
txHash,
|
|
70
|
+
noteHashes,
|
|
71
|
+
dataStartIndexForTx,
|
|
72
|
+
excludedIndices,
|
|
73
|
+
logger,
|
|
74
|
+
unencryptedLogs,
|
|
75
|
+
daoConstructor,
|
|
76
|
+
);
|
|
77
|
+
} else {
|
|
78
|
+
logger.error(`Could not process note because of "${e}". Discarding note...`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return [noteDao, deferredNoteDao];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function handlePartialNote<T>(
|
|
86
|
+
simulator: AcirSimulator,
|
|
87
|
+
db: PxeDatabase,
|
|
88
|
+
pkM: PublicKey,
|
|
89
|
+
payload: L1NotePayload,
|
|
90
|
+
txHash: TxHash,
|
|
91
|
+
noteHashes: Fr[],
|
|
92
|
+
dataStartIndexForTx: number,
|
|
93
|
+
excludedIndices: Set<number>,
|
|
94
|
+
logger: Logger,
|
|
95
|
+
unencryptedLogs: UnencryptedTxL2Logs,
|
|
96
|
+
daoConstructor: (payload: L1NotePayload, noteInfo: NoteInfo, dataStartIndexForTx: number, pkM: PublicKey) => T,
|
|
97
|
+
): Promise<T | undefined> {
|
|
98
|
+
let noteDao: T | undefined;
|
|
99
|
+
|
|
100
|
+
for (const functionLogs of unencryptedLogs.functionLogs) {
|
|
101
|
+
for (const log of functionLogs.logs) {
|
|
102
|
+
const { data } = log;
|
|
103
|
+
// It is the expectation that partial notes will have the corresponding unencrypted log be multiple
|
|
104
|
+
// of Fr.SIZE_IN_BYTES as the nullable fields should be simply concatenated.
|
|
105
|
+
if (data.length % Fr.SIZE_IN_BYTES === 0) {
|
|
106
|
+
const nullableFields = [];
|
|
107
|
+
for (let i = 0; i < data.length; i += Fr.SIZE_IN_BYTES) {
|
|
108
|
+
const chunk = data.subarray(i, i + Fr.SIZE_IN_BYTES);
|
|
109
|
+
nullableFields.push(Fr.fromBuffer(chunk));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// We insert the nullable fields into the note and then we try to produce the note dao again
|
|
113
|
+
const payloadWithNullableFields = await addNullableFieldsToPayload(db, payload, nullableFields);
|
|
114
|
+
|
|
115
|
+
let deferredNoteDao: DeferredNoteDao | undefined;
|
|
116
|
+
try {
|
|
117
|
+
[noteDao, deferredNoteDao] = await produceNoteDaosForKey(
|
|
118
|
+
simulator,
|
|
119
|
+
db,
|
|
120
|
+
pkM,
|
|
121
|
+
payloadWithNullableFields,
|
|
122
|
+
txHash,
|
|
123
|
+
noteHashes,
|
|
124
|
+
dataStartIndexForTx,
|
|
125
|
+
excludedIndices,
|
|
126
|
+
logger,
|
|
127
|
+
UnencryptedTxL2Logs.empty(), // We set unencrypted logs to empty to prevent infinite recursion.
|
|
128
|
+
daoConstructor,
|
|
129
|
+
);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
// We ignore the key prefix error because that is expected to be triggered when an incorrect value
|
|
132
|
+
// is inserted at the position of `npk_m_hash`. This happens commonly because we are brute forcing
|
|
133
|
+
// the unencrypted logs.
|
|
134
|
+
if (!(e as any).message.includes('Could not find key prefix.')) {
|
|
135
|
+
throw e;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (deferredNoteDao) {
|
|
140
|
+
// This should not happen as we should first get contract not found error before the blackbox func error.
|
|
141
|
+
throw new Error('Partial notes should never be deferred.');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (noteDao) {
|
|
145
|
+
// We managed to complete the partial note so we terminate the search.
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!noteDao) {
|
|
153
|
+
logger.error(`Partial note note found. Discarding note...`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return noteDao;
|
|
157
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AuthWitness,
|
|
3
3
|
CompleteAddress,
|
|
4
|
+
CountedNoteLog,
|
|
5
|
+
CountedPublicExecutionRequest,
|
|
6
|
+
EncryptedL2Log,
|
|
7
|
+
EncryptedL2NoteLog,
|
|
4
8
|
EncryptedNoteL2BlockL2Logs,
|
|
5
9
|
ExtendedNote,
|
|
6
10
|
ExtendedUnencryptedL2Log,
|
|
@@ -9,17 +13,20 @@ import {
|
|
|
9
13
|
Note,
|
|
10
14
|
NullifierMembershipWitness,
|
|
11
15
|
type PXE,
|
|
16
|
+
PrivateExecutionResult,
|
|
12
17
|
SiblingPath,
|
|
13
|
-
SimulatedTx,
|
|
14
18
|
Tx,
|
|
15
19
|
TxEffect,
|
|
16
20
|
TxExecutionRequest,
|
|
17
21
|
TxHash,
|
|
22
|
+
TxProvingResult,
|
|
18
23
|
TxReceipt,
|
|
24
|
+
TxSimulationResult,
|
|
19
25
|
UnencryptedL2BlockL2Logs,
|
|
26
|
+
UnencryptedL2Log,
|
|
20
27
|
UniqueNote,
|
|
21
28
|
} from '@aztec/circuit-types';
|
|
22
|
-
import { FunctionSelector } from '@aztec/circuits.js';
|
|
29
|
+
import { FunctionSelector, PrivateCallStackItem, PublicKeys } from '@aztec/circuits.js';
|
|
23
30
|
import { NoteSelector } from '@aztec/foundation/abi';
|
|
24
31
|
import { AztecAddress } from '@aztec/foundation/aztec-address';
|
|
25
32
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
@@ -50,6 +57,7 @@ export function createPXERpcServer(pxeService: PXE): JsonRpcServer {
|
|
|
50
57
|
GrumpkinScalar,
|
|
51
58
|
Note,
|
|
52
59
|
ExtendedNote,
|
|
60
|
+
PublicKeys,
|
|
53
61
|
UniqueNote,
|
|
54
62
|
SiblingPath,
|
|
55
63
|
AuthWitness,
|
|
@@ -59,9 +67,17 @@ export function createPXERpcServer(pxeService: PXE): JsonRpcServer {
|
|
|
59
67
|
},
|
|
60
68
|
{
|
|
61
69
|
EncryptedNoteL2BlockL2Logs,
|
|
70
|
+
EncryptedL2NoteLog,
|
|
71
|
+
EncryptedL2Log,
|
|
72
|
+
UnencryptedL2Log,
|
|
62
73
|
NoteSelector,
|
|
63
74
|
NullifierMembershipWitness,
|
|
64
|
-
|
|
75
|
+
TxSimulationResult,
|
|
76
|
+
TxProvingResult,
|
|
77
|
+
PrivateExecutionResult,
|
|
78
|
+
PrivateCallStackItem,
|
|
79
|
+
CountedPublicExecutionRequest,
|
|
80
|
+
CountedNoteLog,
|
|
65
81
|
Tx,
|
|
66
82
|
TxReceipt,
|
|
67
83
|
UnencryptedL2BlockL2Logs,
|
|
@@ -4,12 +4,6 @@ import { randomBytes } from '@aztec/foundation/crypto';
|
|
|
4
4
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
5
5
|
import { KeyStore } from '@aztec/key-store';
|
|
6
6
|
import { createStore } from '@aztec/kv-store/utils';
|
|
7
|
-
import { getCanonicalAuthRegistry } from '@aztec/protocol-contracts/auth-registry';
|
|
8
|
-
import { getCanonicalClassRegisterer } from '@aztec/protocol-contracts/class-registerer';
|
|
9
|
-
import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice';
|
|
10
|
-
import { getCanonicalInstanceDeployer } from '@aztec/protocol-contracts/instance-deployer';
|
|
11
|
-
import { getCanonicalMultiCallEntrypointContract } from '@aztec/protocol-contracts/multi-call-entrypoint';
|
|
12
|
-
import { getCanonicalRouter } from '@aztec/protocol-contracts/router';
|
|
13
7
|
|
|
14
8
|
import { type PXEServiceConfig } from '../config/index.js';
|
|
15
9
|
import { KVPxeDatabase } from '../database/kv_pxe_database.js';
|
|
@@ -45,17 +39,6 @@ export async function createPXEService(
|
|
|
45
39
|
|
|
46
40
|
const prover = proofCreator ?? (await createProver(config, logSuffix));
|
|
47
41
|
const server = new PXEService(keyStore, aztecNode, db, prover, config, logSuffix);
|
|
48
|
-
for (const contract of [
|
|
49
|
-
getCanonicalClassRegisterer(),
|
|
50
|
-
getCanonicalInstanceDeployer(),
|
|
51
|
-
getCanonicalMultiCallEntrypointContract(),
|
|
52
|
-
getCanonicalFeeJuice(),
|
|
53
|
-
getCanonicalAuthRegistry(),
|
|
54
|
-
getCanonicalRouter(),
|
|
55
|
-
]) {
|
|
56
|
-
await server.registerContract(contract);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
42
|
await server.start();
|
|
60
43
|
return server;
|
|
61
44
|
}
|
|
@@ -71,5 +54,5 @@ function createProver(config: PXEServiceConfig, logSuffix?: string) {
|
|
|
71
54
|
}
|
|
72
55
|
const bbConfig = config as Required<Pick<PXEServiceConfig, 'bbBinaryPath' | 'bbWorkingDirectory'>> & PXEServiceConfig;
|
|
73
56
|
const log = createDebugLogger('aztec:pxe:bb-native-prover' + (logSuffix ? `:${logSuffix}` : ''));
|
|
74
|
-
return BBNativePrivateKernelProver.new(bbConfig, log);
|
|
57
|
+
return BBNativePrivateKernelProver.new({ bbSkipCleanup: false, ...bbConfig }, log);
|
|
75
58
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type AuthWitness,
|
|
3
3
|
type AztecNode,
|
|
4
|
-
EncryptedNoteTxL2Logs,
|
|
5
|
-
EncryptedTxL2Logs,
|
|
6
4
|
type EventMetadata,
|
|
7
5
|
EventType,
|
|
8
6
|
type ExtendedNote,
|
|
@@ -16,17 +14,20 @@ import {
|
|
|
16
14
|
type OutgoingNotesFilter,
|
|
17
15
|
type PXE,
|
|
18
16
|
type PXEInfo,
|
|
17
|
+
type PrivateExecutionResult,
|
|
19
18
|
type PrivateKernelProver,
|
|
19
|
+
type PrivateKernelSimulateOutput,
|
|
20
|
+
PrivateSimulationResult,
|
|
21
|
+
type PublicSimulationOutput,
|
|
20
22
|
type SiblingPath,
|
|
21
|
-
SimulatedTx,
|
|
22
23
|
SimulationError,
|
|
23
|
-
|
|
24
|
-
Tx,
|
|
24
|
+
type Tx,
|
|
25
25
|
type TxEffect,
|
|
26
26
|
type TxExecutionRequest,
|
|
27
27
|
type TxHash,
|
|
28
|
+
TxProvingResult,
|
|
28
29
|
type TxReceipt,
|
|
29
|
-
|
|
30
|
+
TxSimulationResult,
|
|
30
31
|
UniqueNote,
|
|
31
32
|
getNonNullifiedL1ToL2MessageWitness,
|
|
32
33
|
isNoirCallStackUnresolved,
|
|
@@ -35,8 +36,13 @@ import { type NoteProcessorStats } from '@aztec/circuit-types/stats';
|
|
|
35
36
|
import {
|
|
36
37
|
AztecAddress,
|
|
37
38
|
type CompleteAddress,
|
|
39
|
+
type ContractClassWithId,
|
|
40
|
+
type ContractInstanceWithAddress,
|
|
38
41
|
type L1_TO_L2_MSG_TREE_HEIGHT,
|
|
42
|
+
type NodeInfo,
|
|
43
|
+
PUBLIC_DISPATCH_SELECTOR,
|
|
39
44
|
type PartialAddress,
|
|
45
|
+
type PrivateKernelTailCircuitPublicInputs,
|
|
40
46
|
computeContractAddressFromInstance,
|
|
41
47
|
computeContractClassId,
|
|
42
48
|
getContractClassFromArtifact,
|
|
@@ -53,24 +59,12 @@ import { Fr, type Point } from '@aztec/foundation/fields';
|
|
|
53
59
|
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
|
|
54
60
|
import { SerialQueue } from '@aztec/foundation/queue';
|
|
55
61
|
import { type KeyStore } from '@aztec/key-store';
|
|
56
|
-
import { ClassRegistererAddress } from '@aztec/protocol-contracts/class-registerer';
|
|
57
|
-
import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice';
|
|
58
|
-
import { getCanonicalInstanceDeployer } from '@aztec/protocol-contracts/instance-deployer';
|
|
59
|
-
import { getCanonicalMultiCallEntrypointAddress } from '@aztec/protocol-contracts/multi-call-entrypoint';
|
|
60
62
|
import {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
collectSortedEncryptedLogs,
|
|
67
|
-
collectSortedNoteEncryptedLogs,
|
|
68
|
-
collectSortedUnencryptedLogs,
|
|
69
|
-
resolveAssertionMessage,
|
|
70
|
-
resolveOpcodeLocations,
|
|
71
|
-
} from '@aztec/simulator';
|
|
72
|
-
import { type ContractClassWithId, type ContractInstanceWithAddress } from '@aztec/types/contracts';
|
|
73
|
-
import { type NodeInfo } from '@aztec/types/interfaces';
|
|
63
|
+
ProtocolContractAddress,
|
|
64
|
+
getCanonicalProtocolContract,
|
|
65
|
+
protocolContractNames,
|
|
66
|
+
} from '@aztec/protocol-contracts';
|
|
67
|
+
import { type AcirSimulator, resolveAssertionMessage, resolveOpcodeLocations } from '@aztec/simulator';
|
|
74
68
|
|
|
75
69
|
import { type PXEServiceConfig, getPackageInfo } from '../config/index.js';
|
|
76
70
|
import { ContractDataOracle } from '../contract_data_oracle/index.js';
|
|
@@ -123,6 +117,7 @@ export class PXEService implements PXE {
|
|
|
123
117
|
const { l2BlockPollingIntervalMS } = this.config;
|
|
124
118
|
await this.synchronizer.start(1, l2BlockPollingIntervalMS);
|
|
125
119
|
await this.restoreNoteProcessors();
|
|
120
|
+
await this.#registerProtocolContracts();
|
|
126
121
|
const info = await this.getNodeInfo();
|
|
127
122
|
this.log.info(`Started PXE connected to chain ${info.l1ChainId} version ${info.protocolVersion}`);
|
|
128
123
|
}
|
|
@@ -141,7 +136,7 @@ export class PXEService implements PXE {
|
|
|
141
136
|
}
|
|
142
137
|
|
|
143
138
|
count++;
|
|
144
|
-
await this.synchronizer.addAccount(address
|
|
139
|
+
await this.synchronizer.addAccount(address, this.keyStore, this.config.l2StartingBlock);
|
|
145
140
|
}
|
|
146
141
|
|
|
147
142
|
if (count > 0) {
|
|
@@ -200,7 +195,7 @@ export class PXEService implements PXE {
|
|
|
200
195
|
this.log.info(`Account:\n "${accountCompleteAddress.address.toString()}"\n already registered.`);
|
|
201
196
|
return accountCompleteAddress;
|
|
202
197
|
} else {
|
|
203
|
-
await this.synchronizer.addAccount(accountCompleteAddress
|
|
198
|
+
await this.synchronizer.addAccount(accountCompleteAddress, this.keyStore, this.config.l2StartingBlock);
|
|
204
199
|
this.log.info(`Registered account ${accountCompleteAddress.address.toString()}`);
|
|
205
200
|
this.log.debug(`Registered account\n ${accountCompleteAddress.toReadableString()}`);
|
|
206
201
|
}
|
|
@@ -510,13 +505,21 @@ export class PXEService implements PXE {
|
|
|
510
505
|
return await this.node.getBlock(blockNumber);
|
|
511
506
|
}
|
|
512
507
|
|
|
513
|
-
|
|
508
|
+
async #simulateKernels(
|
|
509
|
+
txRequest: TxExecutionRequest,
|
|
510
|
+
privateExecutionResult: PrivateExecutionResult,
|
|
511
|
+
): Promise<PrivateKernelTailCircuitPublicInputs> {
|
|
512
|
+
const result = await this.#prove(txRequest, new TestPrivateKernelProver(), privateExecutionResult);
|
|
513
|
+
return result.publicInputs;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
public proveTx(
|
|
517
|
+
txRequest: TxExecutionRequest,
|
|
518
|
+
privateExecutionResult: PrivateExecutionResult,
|
|
519
|
+
): Promise<TxProvingResult> {
|
|
514
520
|
return this.jobQueue.put(async () => {
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
simulatedTx.publicOutput = await this.#simulatePublicCalls(simulatedTx.tx);
|
|
518
|
-
}
|
|
519
|
-
return simulatedTx.tx;
|
|
521
|
+
const { publicInputs, clientIvcProof } = await this.#prove(txRequest, this.proofCreator, privateExecutionResult);
|
|
522
|
+
return new TxProvingResult(privateExecutionResult, publicInputs, clientIvcProof!);
|
|
520
523
|
});
|
|
521
524
|
}
|
|
522
525
|
|
|
@@ -527,15 +530,19 @@ export class PXEService implements PXE {
|
|
|
527
530
|
msgSender: AztecAddress | undefined = undefined,
|
|
528
531
|
skipTxValidation: boolean = false,
|
|
529
532
|
scopes?: AztecAddress[],
|
|
530
|
-
): Promise<
|
|
533
|
+
): Promise<TxSimulationResult> {
|
|
531
534
|
return await this.jobQueue.put(async () => {
|
|
532
|
-
const
|
|
535
|
+
const privateExecutionResult = await this.#executePrivate(txRequest, msgSender, scopes);
|
|
536
|
+
const publicInputs = await this.#simulateKernels(txRequest, privateExecutionResult);
|
|
537
|
+
const privateSimulationResult = new PrivateSimulationResult(privateExecutionResult, publicInputs);
|
|
538
|
+
const simulatedTx = privateSimulationResult.toSimulatedTx();
|
|
539
|
+
let publicOutput: PublicSimulationOutput | undefined;
|
|
533
540
|
if (simulatePublic) {
|
|
534
|
-
|
|
541
|
+
publicOutput = await this.#simulatePublicCalls(simulatedTx);
|
|
535
542
|
}
|
|
536
543
|
|
|
537
544
|
if (!skipTxValidation) {
|
|
538
|
-
if (!(await this.node.isValidTx(simulatedTx
|
|
545
|
+
if (!(await this.node.isValidTx(simulatedTx, true))) {
|
|
539
546
|
throw new Error('The simulated transaction is unable to be added to state and is invalid.');
|
|
540
547
|
}
|
|
541
548
|
}
|
|
@@ -545,9 +552,9 @@ export class PXEService implements PXE {
|
|
|
545
552
|
// Meaning that it will not necessarily have produced a nullifier (and thus have no TxHash)
|
|
546
553
|
// If we log, the `getTxHash` function will throw.
|
|
547
554
|
if (!msgSender) {
|
|
548
|
-
this.log.info(`Executed local simulation for ${simulatedTx.
|
|
555
|
+
this.log.info(`Executed local simulation for ${simulatedTx.getTxHash()}`);
|
|
549
556
|
}
|
|
550
|
-
return
|
|
557
|
+
return TxSimulationResult.fromPrivateSimulationResultAndPublicOutput(privateSimulationResult, publicOutput);
|
|
551
558
|
});
|
|
552
559
|
}
|
|
553
560
|
|
|
@@ -656,14 +663,24 @@ export class PXEService implements PXE {
|
|
|
656
663
|
return Promise.resolve({
|
|
657
664
|
pxeVersion: this.packageVersion,
|
|
658
665
|
protocolContractAddresses: {
|
|
659
|
-
classRegisterer:
|
|
660
|
-
feeJuice:
|
|
661
|
-
instanceDeployer:
|
|
662
|
-
multiCallEntrypoint:
|
|
666
|
+
classRegisterer: ProtocolContractAddress.ContractClassRegisterer,
|
|
667
|
+
feeJuice: ProtocolContractAddress.FeeJuice,
|
|
668
|
+
instanceDeployer: ProtocolContractAddress.ContractInstanceDeployer,
|
|
669
|
+
multiCallEntrypoint: ProtocolContractAddress.MultiCallEntrypoint,
|
|
663
670
|
},
|
|
664
671
|
});
|
|
665
672
|
}
|
|
666
673
|
|
|
674
|
+
async #registerProtocolContracts() {
|
|
675
|
+
for (const name of protocolContractNames) {
|
|
676
|
+
const { address, contractClass, instance, artifact } = getCanonicalProtocolContract(name);
|
|
677
|
+
await this.db.addContractArtifact(contractClass.id, artifact);
|
|
678
|
+
await this.db.addContractInstance(instance);
|
|
679
|
+
await this.synchronizer.reprocessDeferredNotesForContract(address);
|
|
680
|
+
this.log.info(`Added protocol contract ${name} at ${address.toString()}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
667
684
|
/**
|
|
668
685
|
* Retrieves the simulation parameters required to run an ACIR simulation.
|
|
669
686
|
* This includes the contract address, function artifact, and historical tree roots.
|
|
@@ -687,11 +704,11 @@ export class PXEService implements PXE {
|
|
|
687
704
|
};
|
|
688
705
|
}
|
|
689
706
|
|
|
690
|
-
async #
|
|
707
|
+
async #executePrivate(
|
|
691
708
|
txRequest: TxExecutionRequest,
|
|
692
709
|
msgSender?: AztecAddress,
|
|
693
710
|
scopes?: AztecAddress[],
|
|
694
|
-
): Promise<
|
|
711
|
+
): Promise<PrivateExecutionResult> {
|
|
695
712
|
// TODO - Pause syncing while simulating.
|
|
696
713
|
|
|
697
714
|
const { contractAddress, functionArtifact } = await this.#getSimulationParameters(txRequest);
|
|
@@ -749,9 +766,13 @@ export class PXEService implements PXE {
|
|
|
749
766
|
if (err instanceof SimulationError) {
|
|
750
767
|
const callStack = err.getCallStack();
|
|
751
768
|
const originalFailingFunction = callStack[callStack.length - 1];
|
|
769
|
+
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/8985): Properly fix this.
|
|
770
|
+
// To be able to resolve the assertion message, we need to use the information from the public dispatch function,
|
|
771
|
+
// no matter what the call stack selector points to (since we've modified it to point to the target function).
|
|
772
|
+
// We should remove this because the AVM (or public protocol) shouldn't be aware of the public dispatch calling convention.
|
|
752
773
|
const debugInfo = await this.contractDataOracle.getFunctionDebugMetadata(
|
|
753
774
|
originalFailingFunction.contractAddress,
|
|
754
|
-
|
|
775
|
+
FunctionSelector.fromField(new Fr(PUBLIC_DISPATCH_SELECTOR)),
|
|
755
776
|
);
|
|
756
777
|
const noirCallStack = err.getNoirCallStack();
|
|
757
778
|
if (debugInfo) {
|
|
@@ -794,40 +815,18 @@ export class PXEService implements PXE {
|
|
|
794
815
|
* A private transaction object containing the proof, public inputs, and encrypted logs.
|
|
795
816
|
* The return values of the private execution
|
|
796
817
|
*/
|
|
797
|
-
async #
|
|
818
|
+
async #prove(
|
|
798
819
|
txExecutionRequest: TxExecutionRequest,
|
|
799
820
|
proofCreator: PrivateKernelProver,
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
const kernelOracle = new KernelOracle(this.contractDataOracle, this.keyStore, this.node);
|
|
821
|
+
privateExecutionResult: PrivateExecutionResult,
|
|
822
|
+
): Promise<PrivateKernelSimulateOutput<PrivateKernelTailCircuitPublicInputs>> {
|
|
823
|
+
// use the block the tx was simulated against
|
|
824
|
+
const block =
|
|
825
|
+
privateExecutionResult.callStackItem.publicInputs.historicalHeader.globalVariables.blockNumber.toNumber();
|
|
826
|
+
const kernelOracle = new KernelOracle(this.contractDataOracle, this.keyStore, this.node, block);
|
|
807
827
|
const kernelProver = new KernelProver(kernelOracle, proofCreator);
|
|
808
828
|
this.log.debug(`Executing kernel prover...`);
|
|
809
|
-
|
|
810
|
-
txExecutionRequest.toTxRequest(),
|
|
811
|
-
executionResult,
|
|
812
|
-
);
|
|
813
|
-
|
|
814
|
-
const noteEncryptedLogs = new EncryptedNoteTxL2Logs([collectSortedNoteEncryptedLogs(executionResult)]);
|
|
815
|
-
const unencryptedLogs = new UnencryptedTxL2Logs([collectSortedUnencryptedLogs(executionResult)]);
|
|
816
|
-
const encryptedLogs = new EncryptedTxL2Logs([collectSortedEncryptedLogs(executionResult)]);
|
|
817
|
-
const enqueuedPublicFunctions = collectEnqueuedPublicFunctionCalls(executionResult);
|
|
818
|
-
const teardownPublicFunction = collectPublicTeardownFunctionCall(executionResult);
|
|
819
|
-
|
|
820
|
-
const tx = new Tx(
|
|
821
|
-
publicInputs,
|
|
822
|
-
clientIvcProof!,
|
|
823
|
-
noteEncryptedLogs,
|
|
824
|
-
encryptedLogs,
|
|
825
|
-
unencryptedLogs,
|
|
826
|
-
enqueuedPublicFunctions,
|
|
827
|
-
teardownPublicFunction,
|
|
828
|
-
);
|
|
829
|
-
|
|
830
|
-
return new SimulatedTx(tx, accumulateReturnValues(executionResult));
|
|
829
|
+
return await kernelProver.prove(txExecutionRequest.toTxRequest(), privateExecutionResult);
|
|
831
830
|
}
|
|
832
831
|
|
|
833
832
|
/**
|
|
@@ -944,11 +943,10 @@ export class PXEService implements PXE {
|
|
|
944
943
|
|
|
945
944
|
const visibleEvents = encryptedLogs.flatMap(encryptedLog => {
|
|
946
945
|
for (const sk of vsks) {
|
|
947
|
-
const
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
return [decryptedLog];
|
|
946
|
+
const decryptedEvent =
|
|
947
|
+
L1EventPayload.decryptAsIncoming(encryptedLog, sk) ?? L1EventPayload.decryptAsOutgoing(encryptedLog, sk);
|
|
948
|
+
if (decryptedEvent !== undefined) {
|
|
949
|
+
return [decryptedEvent];
|
|
952
950
|
}
|
|
953
951
|
}
|
|
954
952
|
|
|
@@ -957,19 +955,19 @@ export class PXEService implements PXE {
|
|
|
957
955
|
|
|
958
956
|
const decodedEvents = visibleEvents
|
|
959
957
|
.map(visibleEvent => {
|
|
960
|
-
if (visibleEvent
|
|
958
|
+
if (visibleEvent === undefined) {
|
|
961
959
|
return undefined;
|
|
962
960
|
}
|
|
963
|
-
if (!visibleEvent.
|
|
961
|
+
if (!visibleEvent.eventTypeId.equals(eventMetadata.eventSelector)) {
|
|
964
962
|
return undefined;
|
|
965
963
|
}
|
|
966
|
-
if (visibleEvent.
|
|
964
|
+
if (visibleEvent.event.items.length !== eventMetadata.fieldNames.length) {
|
|
967
965
|
throw new Error(
|
|
968
966
|
'Something is weird here, we have matching EventSelectors, but the actual payload has mismatched length',
|
|
969
967
|
);
|
|
970
968
|
}
|
|
971
969
|
|
|
972
|
-
return eventMetadata.decode(visibleEvent
|
|
970
|
+
return eventMetadata.decode(visibleEvent);
|
|
973
971
|
})
|
|
974
972
|
.filter(visibleEvent => visibleEvent !== undefined) as T[];
|
|
975
973
|
|