@aztec/pxe 0.0.1-commit.e61ad554 → 0.0.1-commit.ee80a48
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/block_synchronizer/block_synchronizer.d.ts +3 -3
- package/dest/block_synchronizer/block_synchronizer.d.ts.map +1 -1
- package/dest/block_synchronizer/block_synchronizer.js +5 -5
- package/dest/contract_function_simulator/contract_function_simulator.d.ts +2 -4
- package/dest/contract_function_simulator/contract_function_simulator.d.ts.map +1 -1
- package/dest/contract_function_simulator/contract_function_simulator.js +7 -9
- package/dest/contract_function_simulator/oracle/interfaces.d.ts +8 -8
- package/dest/contract_function_simulator/oracle/interfaces.d.ts.map +1 -1
- package/dest/contract_function_simulator/oracle/oracle.d.ts +3 -3
- package/dest/contract_function_simulator/oracle/oracle.d.ts.map +1 -1
- package/dest/contract_function_simulator/oracle/oracle.js +15 -15
- package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +2 -3
- package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
- package/dest/contract_function_simulator/oracle/private_execution_oracle.js +5 -5
- package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts +20 -16
- package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts.map +1 -1
- package/dest/contract_function_simulator/oracle/utility_execution_oracle.js +23 -27
- package/dest/contract_sync/index.d.ts +4 -3
- package/dest/contract_sync/index.d.ts.map +1 -1
- package/dest/contract_sync/index.js +11 -4
- package/dest/debug/pxe_debug_utils.d.ts +16 -6
- package/dest/debug/pxe_debug_utils.d.ts.map +1 -1
- package/dest/debug/pxe_debug_utils.js +17 -8
- package/dest/entrypoints/client/bundle/utils.d.ts +1 -1
- package/dest/entrypoints/client/bundle/utils.d.ts.map +1 -1
- package/dest/entrypoints/client/bundle/utils.js +12 -6
- package/dest/entrypoints/client/lazy/utils.d.ts +2 -2
- package/dest/entrypoints/client/lazy/utils.d.ts.map +1 -1
- package/dest/entrypoints/client/lazy/utils.js +13 -7
- package/dest/entrypoints/pxe_creation_options.d.ts +3 -2
- package/dest/entrypoints/pxe_creation_options.d.ts.map +1 -1
- package/dest/entrypoints/server/utils.d.ts +1 -1
- package/dest/entrypoints/server/utils.d.ts.map +1 -1
- package/dest/entrypoints/server/utils.js +19 -8
- package/dest/events/event_service.d.ts +4 -5
- package/dest/events/event_service.d.ts.map +1 -1
- package/dest/events/event_service.js +5 -6
- package/dest/job_coordinator/job_coordinator.d.ts +3 -2
- package/dest/job_coordinator/job_coordinator.d.ts.map +1 -1
- package/dest/job_coordinator/job_coordinator.js +3 -2
- package/dest/logs/log_service.d.ts +6 -5
- package/dest/logs/log_service.d.ts.map +1 -1
- package/dest/logs/log_service.js +10 -15
- package/dest/notes/note_service.d.ts +4 -5
- package/dest/notes/note_service.d.ts.map +1 -1
- package/dest/notes/note_service.js +6 -7
- package/dest/oracle_version.d.ts +3 -3
- package/dest/oracle_version.d.ts.map +1 -1
- package/dest/oracle_version.js +2 -2
- package/dest/private_kernel/private_kernel_execution_prover.d.ts +3 -2
- package/dest/private_kernel/private_kernel_execution_prover.d.ts.map +1 -1
- package/dest/private_kernel/private_kernel_execution_prover.js +2 -2
- package/dest/private_kernel/private_kernel_oracle.d.ts +3 -3
- package/dest/private_kernel/private_kernel_oracle.d.ts.map +1 -1
- package/dest/pxe.d.ts +1 -1
- package/dest/pxe.d.ts.map +1 -1
- package/dest/pxe.js +11 -9
- package/dest/storage/address_store/address_store.d.ts +1 -1
- package/dest/storage/address_store/address_store.d.ts.map +1 -1
- package/dest/storage/address_store/address_store.js +12 -11
- package/dest/storage/anchor_block_store/anchor_block_store.d.ts +9 -1
- package/dest/storage/anchor_block_store/anchor_block_store.d.ts.map +1 -1
- package/dest/storage/anchor_block_store/anchor_block_store.js +8 -1
- package/dest/storage/capsule_store/capsule_store.js +6 -8
- package/dest/storage/contract_store/contract_store.d.ts +1 -1
- package/dest/storage/contract_store/contract_store.d.ts.map +1 -1
- package/dest/storage/contract_store/contract_store.js +22 -13
- package/dest/storage/metadata.d.ts +1 -1
- package/dest/storage/metadata.js +1 -1
- package/dest/storage/note_store/note_store.d.ts +11 -1
- package/dest/storage/note_store/note_store.d.ts.map +1 -1
- package/dest/storage/note_store/note_store.js +143 -105
- package/dest/storage/private_event_store/private_event_store.d.ts +1 -1
- package/dest/storage/private_event_store/private_event_store.d.ts.map +1 -1
- package/dest/storage/private_event_store/private_event_store.js +84 -61
- package/dest/storage/private_event_store/stored_private_event.d.ts +4 -4
- package/dest/storage/private_event_store/stored_private_event.d.ts.map +1 -1
- package/dest/storage/private_event_store/stored_private_event.js +2 -2
- package/dest/storage/tagging_store/recipient_tagging_store.d.ts +1 -1
- package/dest/storage/tagging_store/recipient_tagging_store.d.ts.map +1 -1
- package/dest/storage/tagging_store/recipient_tagging_store.js +31 -19
- package/dest/storage/tagging_store/sender_address_book_store.d.ts +1 -1
- package/dest/storage/tagging_store/sender_address_book_store.d.ts.map +1 -1
- package/dest/storage/tagging_store/sender_address_book_store.js +20 -14
- package/dest/storage/tagging_store/sender_tagging_store.d.ts +1 -1
- package/dest/storage/tagging_store/sender_tagging_store.d.ts.map +1 -1
- package/dest/storage/tagging_store/sender_tagging_store.js +183 -113
- package/dest/tagging/get_all_logs_by_tags.d.ts +4 -4
- package/dest/tagging/get_all_logs_by_tags.d.ts.map +1 -1
- package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.d.ts +3 -3
- package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.d.ts.map +1 -1
- package/dest/tagging/recipient_sync/utils/load_logs_for_range.d.ts +3 -3
- package/dest/tagging/recipient_sync/utils/load_logs_for_range.d.ts.map +1 -1
- package/dest/tagging/sender_sync/sync_sender_tagging_indexes.d.ts +3 -3
- package/dest/tagging/sender_sync/sync_sender_tagging_indexes.d.ts.map +1 -1
- package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.d.ts +3 -3
- package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.d.ts.map +1 -1
- package/package.json +16 -16
- package/src/block_synchronizer/block_synchronizer.ts +17 -19
- package/src/contract_function_simulator/contract_function_simulator.ts +6 -6
- package/src/contract_function_simulator/oracle/interfaces.ts +10 -10
- package/src/contract_function_simulator/oracle/oracle.ts +27 -17
- package/src/contract_function_simulator/oracle/private_execution_oracle.ts +3 -5
- package/src/contract_function_simulator/oracle/utility_execution_oracle.ts +32 -35
- package/src/contract_sync/index.ts +23 -2
- package/src/debug/pxe_debug_utils.ts +23 -9
- package/src/entrypoints/client/bundle/utils.ts +7 -14
- package/src/entrypoints/client/lazy/utils.ts +8 -14
- package/src/entrypoints/pxe_creation_options.ts +2 -1
- package/src/entrypoints/server/utils.ts +15 -19
- package/src/events/event_service.ts +4 -6
- package/src/job_coordinator/job_coordinator.ts +4 -3
- package/src/logs/log_service.ts +14 -14
- package/src/notes/note_service.ts +5 -7
- package/src/oracle_version.ts +2 -2
- package/src/private_kernel/private_kernel_execution_prover.ts +6 -3
- package/src/private_kernel/private_kernel_oracle.ts +2 -2
- package/src/pxe.ts +21 -7
- package/src/storage/address_store/address_store.ts +15 -15
- package/src/storage/anchor_block_store/anchor_block_store.ts +8 -0
- package/src/storage/capsule_store/capsule_store.ts +8 -8
- package/src/storage/contract_store/contract_store.ts +22 -11
- package/src/storage/metadata.ts +1 -1
- package/src/storage/note_store/note_store.ts +159 -129
- package/src/storage/private_event_store/private_event_store.ts +102 -81
- package/src/storage/private_event_store/stored_private_event.ts +3 -3
- package/src/storage/tagging_store/recipient_tagging_store.ts +31 -21
- package/src/storage/tagging_store/sender_address_book_store.ts +20 -14
- package/src/storage/tagging_store/sender_tagging_store.ts +210 -126
- package/src/tagging/get_all_logs_by_tags.ts +3 -3
- package/src/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.ts +2 -2
- package/src/tagging/recipient_sync/utils/load_logs_for_range.ts +2 -2
- package/src/tagging/sender_sync/sync_sender_tagging_indexes.ts +2 -2
- package/src/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.ts +3 -3
package/src/pxe.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PrivateEventFilter } from '@aztec/aztec.js/wallet';
|
|
2
2
|
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
3
3
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
|
-
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
5
5
|
import { SerialQueue } from '@aztec/foundation/queue';
|
|
6
6
|
import { Timer } from '@aztec/foundation/timer';
|
|
7
7
|
import { KeyStore } from '@aztec/key-store';
|
|
@@ -128,6 +128,10 @@ export class PXE {
|
|
|
128
128
|
config: PXEConfig,
|
|
129
129
|
loggerOrSuffix?: string | Logger,
|
|
130
130
|
) {
|
|
131
|
+
// Extract bindings from the logger, or use empty bindings if a string suffix is provided.
|
|
132
|
+
const bindings: LoggerBindings | undefined =
|
|
133
|
+
loggerOrSuffix && typeof loggerOrSuffix !== 'string' ? loggerOrSuffix.getBindings() : undefined;
|
|
134
|
+
|
|
131
135
|
const log =
|
|
132
136
|
!loggerOrSuffix || typeof loggerOrSuffix === 'string'
|
|
133
137
|
? createLogger(loggerOrSuffix ? `pxe:service:${loggerOrSuffix}` : `pxe:service`)
|
|
@@ -153,10 +157,10 @@ export class PXE {
|
|
|
153
157
|
privateEventStore,
|
|
154
158
|
tipsStore,
|
|
155
159
|
config,
|
|
156
|
-
|
|
160
|
+
bindings,
|
|
157
161
|
);
|
|
158
162
|
|
|
159
|
-
const jobCoordinator = new JobCoordinator(store);
|
|
163
|
+
const jobCoordinator = new JobCoordinator(store, bindings);
|
|
160
164
|
jobCoordinator.registerStores([
|
|
161
165
|
capsuleStore,
|
|
162
166
|
senderTaggingStore,
|
|
@@ -165,7 +169,7 @@ export class PXE {
|
|
|
165
169
|
noteStore,
|
|
166
170
|
]);
|
|
167
171
|
|
|
168
|
-
const debugUtils = new PXEDebugUtils(contractStore, noteStore);
|
|
172
|
+
const debugUtils = new PXEDebugUtils(contractStore, noteStore, synchronizer, anchorBlockStore);
|
|
169
173
|
|
|
170
174
|
const jobQueue = new SerialQueue();
|
|
171
175
|
|
|
@@ -192,7 +196,7 @@ export class PXE {
|
|
|
192
196
|
debugUtils,
|
|
193
197
|
);
|
|
194
198
|
|
|
195
|
-
debugUtils.setPXE(pxe);
|
|
199
|
+
debugUtils.setPXE(pxe, pxe.#putInJobQueue.bind(pxe));
|
|
196
200
|
|
|
197
201
|
pxe.jobQueue.start();
|
|
198
202
|
|
|
@@ -213,7 +217,6 @@ export class PXE {
|
|
|
213
217
|
this.keyStore,
|
|
214
218
|
this.addressStore,
|
|
215
219
|
BenchmarkedNodeFactory.create(this.node),
|
|
216
|
-
this.anchorBlockStore,
|
|
217
220
|
this.senderTaggingStore,
|
|
218
221
|
this.recipientTaggingStore,
|
|
219
222
|
this.senderAddressBookStore,
|
|
@@ -300,7 +303,9 @@ export class PXE {
|
|
|
300
303
|
privateSyncCall => this.#simulateUtility(contractFunctionSimulator, privateSyncCall, [], undefined, jobId),
|
|
301
304
|
this.node,
|
|
302
305
|
this.contractStore,
|
|
306
|
+
this.noteStore,
|
|
303
307
|
anchorBlockHeader,
|
|
308
|
+
jobId,
|
|
304
309
|
);
|
|
305
310
|
|
|
306
311
|
const result = await contractFunctionSimulator.run(
|
|
@@ -400,7 +405,12 @@ export class PXE {
|
|
|
400
405
|
const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
|
|
401
406
|
const anchorBlockHash = await anchorBlockHeader.hash();
|
|
402
407
|
const kernelOracle = new PrivateKernelOracle(this.contractStore, this.keyStore, this.node, anchorBlockHash);
|
|
403
|
-
const kernelTraceProver = new PrivateKernelExecutionProver(
|
|
408
|
+
const kernelTraceProver = new PrivateKernelExecutionProver(
|
|
409
|
+
kernelOracle,
|
|
410
|
+
proofCreator,
|
|
411
|
+
!this.proverEnabled,
|
|
412
|
+
this.log.getBindings(),
|
|
413
|
+
);
|
|
404
414
|
this.log.debug(`Executing kernel trace prover (${JSON.stringify(config)})...`);
|
|
405
415
|
return await kernelTraceProver.proveWithKernels(txExecutionRequest.toTxRequest(), privateExecutionResult, config);
|
|
406
416
|
}
|
|
@@ -968,7 +978,9 @@ export class PXE {
|
|
|
968
978
|
privateSyncCall => this.#simulateUtility(contractFunctionSimulator, privateSyncCall, [], undefined, jobId),
|
|
969
979
|
this.node,
|
|
970
980
|
this.contractStore,
|
|
981
|
+
this.noteStore,
|
|
971
982
|
anchorBlockHeader,
|
|
983
|
+
jobId,
|
|
972
984
|
);
|
|
973
985
|
|
|
974
986
|
const executionResult = await this.#simulateUtility(
|
|
@@ -1039,7 +1051,9 @@ export class PXE {
|
|
|
1039
1051
|
await this.#simulateUtility(contractFunctionSimulator, privateSyncCall, [], undefined, jobId),
|
|
1040
1052
|
this.node,
|
|
1041
1053
|
this.contractStore,
|
|
1054
|
+
this.noteStore,
|
|
1042
1055
|
anchorBlockHeader,
|
|
1056
|
+
jobId,
|
|
1043
1057
|
);
|
|
1044
1058
|
});
|
|
1045
1059
|
|
|
@@ -43,23 +43,23 @@ export class AddressStore {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
async #getCompleteAddress(address: AztecAddress): Promise<CompleteAddress | undefined> {
|
|
47
|
-
const index = await this.#completeAddressIndex.getAsync(address.toString());
|
|
48
|
-
if (index === undefined) {
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const value = await this.#completeAddresses.atAsync(index);
|
|
53
|
-
return value ? await CompleteAddress.fromBuffer(value) : undefined;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
46
|
getCompleteAddress(account: AztecAddress): Promise<CompleteAddress | undefined> {
|
|
57
|
-
return this.#
|
|
47
|
+
return this.#store.transactionAsync(async () => {
|
|
48
|
+
const index = await this.#completeAddressIndex.getAsync(account.toString());
|
|
49
|
+
if (index === undefined) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const value = await this.#completeAddresses.atAsync(index);
|
|
54
|
+
return value ? await CompleteAddress.fromBuffer(value) : undefined;
|
|
55
|
+
});
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
getCompleteAddresses(): Promise<CompleteAddress[]> {
|
|
59
|
+
return this.#store.transactionAsync(async () => {
|
|
60
|
+
return await Promise.all(
|
|
61
|
+
(await toArray(this.#completeAddresses.valuesAsync())).map(v => CompleteAddress.fromBuffer(v)),
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -10,6 +10,14 @@ export class AnchorBlockStore {
|
|
|
10
10
|
this.#synchronizedHeader = this.#store.openSingleton('header');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Sets the currently synchronized block
|
|
15
|
+
*
|
|
16
|
+
* Important: this method is only called from BlockSynchronizer, and since we need it to run atomically with other
|
|
17
|
+
* stores in the case of a reorg, it MUST NOT be wrapped in a `transactionAsync` call. Doing so would result in a
|
|
18
|
+
* deadlock when the backend is IndexedDB, because `transactionAsync` is not designed to support reentrancy.
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
13
21
|
async setHeader(header: BlockHeader): Promise<void> {
|
|
14
22
|
await this.#synchronizedHeader.set(header.toBuffer());
|
|
15
23
|
}
|
|
@@ -57,14 +57,14 @@ export class CapsuleStore implements StagedStore {
|
|
|
57
57
|
*/
|
|
58
58
|
async #getFromStage(jobId: string, dbSlotKey: string): Promise<Buffer | null | undefined> {
|
|
59
59
|
const jobStagedCapsules = this.#getJobStagedCapsules(jobId);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return staged;
|
|
60
|
+
const staged: Buffer | null | undefined = jobStagedCapsules.get(dbSlotKey);
|
|
61
|
+
|
|
62
|
+
// Always issue DB read to keep IndexedDB transaction alive, even if the value is in the job staged data. This
|
|
63
|
+
// keeps IndexedDB transactions alive (they auto-commit when a new micro-task starts and there are no pending read
|
|
64
|
+
// requests). The staged value still takes precedence if it exists (including null for deletions).
|
|
65
|
+
const dbValue = await this.#loadCapsuleFromDb(dbSlotKey);
|
|
66
|
+
|
|
67
|
+
return staged !== undefined ? staged : dbValue;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
@@ -42,10 +42,12 @@ export class ContractStore {
|
|
|
42
42
|
/** Map from contract address to contract class id */
|
|
43
43
|
#contractClassIdMap: Map<string, Fr> = new Map();
|
|
44
44
|
|
|
45
|
+
#store: AztecAsyncKVStore;
|
|
45
46
|
#contractArtifacts: AztecAsyncMap<string, Buffer>;
|
|
46
47
|
#contractInstances: AztecAsyncMap<string, Buffer>;
|
|
47
48
|
|
|
48
49
|
constructor(store: AztecAsyncKVStore) {
|
|
50
|
+
this.#store = store;
|
|
49
51
|
this.#contractArtifacts = store.openMap('contract_artifacts');
|
|
50
52
|
this.#contractInstances = store.openMap('contracts_instances');
|
|
51
53
|
}
|
|
@@ -53,6 +55,7 @@ export class ContractStore {
|
|
|
53
55
|
// Setters
|
|
54
56
|
|
|
55
57
|
public async addContractArtifact(id: Fr, contract: ContractArtifact): Promise<void> {
|
|
58
|
+
// Validation outside transactionAsync - these are not DB operations
|
|
56
59
|
const privateFunctions = contract.functions.filter(
|
|
57
60
|
functionArtifact => functionArtifact.functionType === FunctionType.PRIVATE,
|
|
58
61
|
);
|
|
@@ -69,7 +72,9 @@ export class ContractStore {
|
|
|
69
72
|
throw new Error('Repeated function selectors of private functions');
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
await this.#
|
|
75
|
+
await this.#store.transactionAsync(() =>
|
|
76
|
+
this.#contractArtifacts.set(id.toString(), contractArtifactToBuffer(contract)),
|
|
77
|
+
);
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
async addContractInstance(contract: ContractInstanceWithAddress): Promise<void> {
|
|
@@ -123,21 +128,27 @@ export class ContractStore {
|
|
|
123
128
|
|
|
124
129
|
// Public getters
|
|
125
130
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
getContractsAddresses(): Promise<AztecAddress[]> {
|
|
132
|
+
return this.#store.transactionAsync(async () => {
|
|
133
|
+
const keys = await toArray(this.#contractInstances.keysAsync());
|
|
134
|
+
return keys.map(AztecAddress.fromString);
|
|
135
|
+
});
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
/** Returns a contract instance for a given address. Throws if not found. */
|
|
132
|
-
public
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
public getContractInstance(contractAddress: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
|
|
140
|
+
return this.#store.transactionAsync(async () => {
|
|
141
|
+
const contract = await this.#contractInstances.getAsync(contractAddress.toString());
|
|
142
|
+
return contract && SerializableContractInstance.fromBuffer(contract).withAddress(contractAddress);
|
|
143
|
+
});
|
|
135
144
|
}
|
|
136
145
|
|
|
137
|
-
public
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
146
|
+
public getContractArtifact(contractClassId: Fr): Promise<ContractArtifact | undefined> {
|
|
147
|
+
return this.#store.transactionAsync(async () => {
|
|
148
|
+
const contract = await this.#contractArtifacts.getAsync(contractClassId.toString());
|
|
149
|
+
// TODO(@spalladino): AztecAsyncMap lies and returns Uint8Arrays instead of Buffers, hence the extra Buffer.from.
|
|
150
|
+
return contract && contractArtifactFromBuffer(Buffer.from(contract));
|
|
151
|
+
});
|
|
141
152
|
}
|
|
142
153
|
|
|
143
154
|
/** Returns a contract class for a given class id. Throws if not found. */
|
package/src/storage/metadata.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PXE_DATA_SCHEMA_VERSION =
|
|
1
|
+
export const PXE_DATA_SCHEMA_VERSION = 3;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { toArray } from '@aztec/foundation/iterable';
|
|
2
1
|
import { Semaphore } from '@aztec/foundation/queue';
|
|
3
2
|
import type { Fr } from '@aztec/foundation/schemas';
|
|
4
3
|
import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap } from '@aztec/kv-store';
|
|
@@ -68,32 +67,26 @@ export class NoteStore implements StagedStore {
|
|
|
68
67
|
* @param jobId - The job context for staged writes
|
|
69
68
|
*/
|
|
70
69
|
public addNotes(notes: NoteDao[], scope: AztecAddress, jobId: string): Promise<void[]> {
|
|
71
|
-
return this.#withJobLock(jobId, () =>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
return this.#withJobLock(jobId, () =>
|
|
71
|
+
this.#store.transactionAsync(() =>
|
|
72
|
+
Promise.all(
|
|
73
|
+
notes.map(async note => {
|
|
74
|
+
const noteForJob =
|
|
75
|
+
(await this.#readNote(note.siloedNullifier.toString(), jobId)) ?? new StoredNote(note, new Set());
|
|
76
|
+
noteForJob.addScope(scope.toString());
|
|
77
|
+
this.#writeNote(noteForJob, jobId);
|
|
78
|
+
}),
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
async #readNote(nullifier: string, jobId: string): Promise<StoredNote | undefined> {
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
if (noteForJob) {
|
|
87
|
-
return noteForJob;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Then check persistent storage
|
|
85
|
+
// Always issue DB read to keep IndexedDB transaction alive (they auto-commit when a new micro-task starts and there
|
|
86
|
+
// are no pending read requests). The staged value still takes precedence if it exists.
|
|
91
87
|
const noteBuffer = await this.#notes.getAsync(nullifier);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return undefined;
|
|
88
|
+
const noteForJob = this.#getNotesForJob(jobId).get(nullifier);
|
|
89
|
+
return noteForJob ?? (noteBuffer ? StoredNote.fromBuffer(noteBuffer) : undefined);
|
|
97
90
|
}
|
|
98
91
|
|
|
99
92
|
#writeNote(note: StoredNote, jobId: string) {
|
|
@@ -112,57 +105,98 @@ export class NoteStore implements StagedStore {
|
|
|
112
105
|
* returned once if this is the case)
|
|
113
106
|
* @throws If filtering by an empty scopes array. Scopes have to be set to undefined or to a non-empty array.
|
|
114
107
|
*/
|
|
115
|
-
|
|
108
|
+
getNotes(filter: NotesFilter, jobId: string): Promise<NoteDao[]> {
|
|
116
109
|
if (filter.scopes !== undefined && filter.scopes.length === 0) {
|
|
117
|
-
|
|
110
|
+
return Promise.reject(new Error('Trying to get notes with an empty scopes array'));
|
|
118
111
|
}
|
|
119
112
|
|
|
120
|
-
|
|
113
|
+
return this.#store.transactionAsync(async () => {
|
|
114
|
+
const targetStatus = filter.status ?? NoteStatus.ACTIVE;
|
|
115
|
+
|
|
116
|
+
// The code below might read a bit unnatural, the reason is that we need to be careful in how we use `await` inside
|
|
117
|
+
// `transactionAsync`, otherwise browsers might choose to auto-commit the IndexedDB transaction forcing us to
|
|
118
|
+
// explicitly handle that condition. The rule we need to honor is: do not await unless you generate a database
|
|
119
|
+
// read or write or you're done using the DB for the remainder of the transaction. The following sequence is
|
|
120
|
+
// unsafe in IndexedDB:
|
|
121
|
+
//
|
|
122
|
+
// 1. start transactionAsync()
|
|
123
|
+
// 2. await readDb() <-- OK, transaction alive because we issued DB ops
|
|
124
|
+
// 3. run a bunch of computations (no await involved) <-- OK, tx alive because we are in the same microtask
|
|
125
|
+
// 4. await doSthNotInDb() <-- no DB ops issued in this task, browser's free to decide to commit the tx
|
|
126
|
+
// 5. await readDb() <-- BOOM, TransactionInactiveError
|
|
127
|
+
//
|
|
128
|
+
// Note that the real issue is in step number 5: we try to continue using a transaction that the browser might
|
|
129
|
+
// have already committed.
|
|
130
|
+
//
|
|
131
|
+
// We need to read candidate notes which are either indexed by contract address in the DB (in
|
|
132
|
+
// #nullifiersByContractAddress), or lie in memory for the not yet committed `jobId`.
|
|
133
|
+
// So we collect promises based on both sources without awaiting for them.
|
|
134
|
+
const noteReadPromises: Map<string, Promise<StoredNote | undefined>> = new Map();
|
|
135
|
+
|
|
136
|
+
// Awaiting the getValuesAsync iterator is fine because it's reading from the DB
|
|
137
|
+
for await (const nullifier of this.#nullifiersByContractAddress.getValuesAsync(
|
|
138
|
+
filter.contractAddress.toString(),
|
|
139
|
+
)) {
|
|
140
|
+
// Each #readNote will perform a DB read
|
|
141
|
+
noteReadPromises.set(nullifier, this.#readNote(nullifier, jobId));
|
|
142
|
+
}
|
|
121
143
|
|
|
122
|
-
|
|
144
|
+
// Add staged nullifiers from job, no awaits involved, so we are fine
|
|
145
|
+
for (const storedNote of this.#getNotesForJob(jobId).values()) {
|
|
146
|
+
if (storedNote.noteDao.contractAddress.equals(filter.contractAddress)) {
|
|
147
|
+
const nullifier = storedNote.noteDao.siloedNullifier.toString();
|
|
148
|
+
if (!noteReadPromises.has(nullifier)) {
|
|
149
|
+
noteReadPromises.set(nullifier, Promise.resolve(storedNote));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
123
153
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const note = await this.#readNote(nullifier, jobId);
|
|
154
|
+
// By now we have pending DB requests from all the #readNote calls. Await them all together.
|
|
155
|
+
const notes = await Promise.all(noteReadPromises.values());
|
|
127
156
|
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
throw new Error('PXE note database is corrupted.');
|
|
131
|
-
}
|
|
157
|
+
// The rest of the function is await-free, and just deals with filtering and sorting our findings.
|
|
158
|
+
const foundNotes: Map<string, NoteDao> = new Map();
|
|
132
159
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
160
|
+
for (const note of notes) {
|
|
161
|
+
// Defensive: hitting this case means we're mishandling contract indices or in-memory job data
|
|
162
|
+
if (!note) {
|
|
163
|
+
throw new Error('PXE note database is corrupted.');
|
|
164
|
+
}
|
|
137
165
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
166
|
+
// Apply filters
|
|
167
|
+
if (targetStatus === NoteStatus.ACTIVE && note.isNullified()) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
141
170
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
171
|
+
if (filter.owner && !note.noteDao.owner.equals(filter.owner)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
145
174
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
175
|
+
if (filter.storageSlot && !note.noteDao.storageSlot.equals(filter.storageSlot)) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
149
178
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
179
|
+
if (filter.siloedNullifier && !note.noteDao.siloedNullifier.equals(filter.siloedNullifier)) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
153
182
|
|
|
154
|
-
|
|
155
|
-
|
|
183
|
+
if (filter.scopes && note.scopes.intersection(new Set(filter.scopes.map(s => s.toString()))).size === 0) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
156
186
|
|
|
157
|
-
|
|
158
|
-
return [...foundNotes.values()].sort((a, b) => {
|
|
159
|
-
if (a.l2BlockNumber !== b.l2BlockNumber) {
|
|
160
|
-
return a.l2BlockNumber - b.l2BlockNumber;
|
|
161
|
-
}
|
|
162
|
-
if (a.txIndexInBlock !== b.txIndexInBlock) {
|
|
163
|
-
return a.txIndexInBlock - b.txIndexInBlock;
|
|
187
|
+
foundNotes.set(note.noteDao.siloedNullifier.toString(), note.noteDao);
|
|
164
188
|
}
|
|
165
|
-
|
|
189
|
+
|
|
190
|
+
// Sort by block number, then by tx index within block, then by note index within tx
|
|
191
|
+
return [...foundNotes.values()].sort((a, b) => {
|
|
192
|
+
if (a.l2BlockNumber !== b.l2BlockNumber) {
|
|
193
|
+
return a.l2BlockNumber - b.l2BlockNumber;
|
|
194
|
+
}
|
|
195
|
+
if (a.txIndexInBlock !== b.txIndexInBlock) {
|
|
196
|
+
return a.txIndexInBlock - b.txIndexInBlock;
|
|
197
|
+
}
|
|
198
|
+
return a.noteIndexInTx - b.noteIndexInTx;
|
|
199
|
+
});
|
|
166
200
|
});
|
|
167
201
|
}
|
|
168
202
|
|
|
@@ -182,12 +216,12 @@ export class NoteStore implements StagedStore {
|
|
|
182
216
|
* @throws Error if any nullifier is not found in this notes store
|
|
183
217
|
*/
|
|
184
218
|
applyNullifiers(nullifiers: DataInBlock<Fr>[], jobId: string): Promise<NoteDao[]> {
|
|
219
|
+
if (nullifiers.length === 0) {
|
|
220
|
+
return Promise.resolve([]);
|
|
221
|
+
}
|
|
222
|
+
|
|
185
223
|
return this.#withJobLock(jobId, () =>
|
|
186
224
|
this.#store.transactionAsync(async () => {
|
|
187
|
-
if (nullifiers.length === 0) {
|
|
188
|
-
return [];
|
|
189
|
-
}
|
|
190
|
-
|
|
191
225
|
const notesToNullify = await Promise.all(
|
|
192
226
|
nullifiers.map(async nullifierInBlock => {
|
|
193
227
|
const nullifier = nullifierInBlock.data.toString();
|
|
@@ -197,14 +231,13 @@ export class NoteStore implements StagedStore {
|
|
|
197
231
|
throw new Error(`Attempted to mark a note as nullified which does not exist in PXE DB`);
|
|
198
232
|
}
|
|
199
233
|
|
|
200
|
-
return { storedNote
|
|
234
|
+
return { storedNote, blockNumber: nullifierInBlock.l2BlockNumber };
|
|
201
235
|
}),
|
|
202
236
|
);
|
|
203
237
|
|
|
204
238
|
const notesNullifiedInThisCall: Map<string, NoteDao> = new Map();
|
|
205
239
|
for (const noteToNullify of notesToNullify) {
|
|
206
|
-
|
|
207
|
-
const note = noteToNullify.storedNote!;
|
|
240
|
+
const note = noteToNullify.storedNote;
|
|
208
241
|
|
|
209
242
|
// Skip already nullified notes
|
|
210
243
|
if (note.isNullified()) {
|
|
@@ -249,18 +282,23 @@ export class NoteStore implements StagedStore {
|
|
|
249
282
|
* @param blockNumber - Notes created after this block number will be deleted
|
|
250
283
|
*/
|
|
251
284
|
async #deleteActiveNotesAfterBlock(blockNumber: number): Promise<void> {
|
|
252
|
-
|
|
253
|
-
|
|
285
|
+
// Collect notes to delete during iteration to keep IndexedDB transaction alive.
|
|
286
|
+
const notesToDelete: { nullifier: string; contractAddress: string }[] = [];
|
|
287
|
+
for await (const noteBuffer of this.#notes.valuesAsync()) {
|
|
254
288
|
const storedNote = StoredNote.fromBuffer(noteBuffer);
|
|
255
289
|
if (storedNote.noteDao.l2BlockNumber > blockNumber) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
noteNullifier,
|
|
261
|
-
);
|
|
290
|
+
notesToDelete.push({
|
|
291
|
+
nullifier: storedNote.noteDao.siloedNullifier.toString(),
|
|
292
|
+
contractAddress: storedNote.noteDao.contractAddress.toString(),
|
|
293
|
+
});
|
|
262
294
|
}
|
|
263
295
|
}
|
|
296
|
+
|
|
297
|
+
// Delete all collected notes. Each delete is a DB operation that keeps the transaction alive.
|
|
298
|
+
for (const { nullifier, contractAddress } of notesToDelete) {
|
|
299
|
+
await this.#notes.delete(nullifier);
|
|
300
|
+
await this.#nullifiersByContractAddress.deleteValue(contractAddress, nullifier);
|
|
301
|
+
}
|
|
264
302
|
}
|
|
265
303
|
|
|
266
304
|
/**
|
|
@@ -273,62 +311,69 @@ export class NoteStore implements StagedStore {
|
|
|
273
311
|
* @param anchorBlockNumber - Upper bound for the block range to process
|
|
274
312
|
*/
|
|
275
313
|
async #rewindNullifiedNotesAfterBlock(blockNumber: number, anchorBlockNumber: number): Promise<void> {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return note;
|
|
291
|
-
}),
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
const storedNotes = nullifiedNoteBuffers.map(buffer => StoredNote.fromBuffer(buffer));
|
|
314
|
+
// First pass: collect all nullifiers for all blocks, starting reads during iteration to keep tx alive.
|
|
315
|
+
const nullifiersByBlock: Map<number, { nullifier: string; noteReadPromise: Promise<Buffer | undefined> }[]> =
|
|
316
|
+
new Map();
|
|
317
|
+
|
|
318
|
+
for (let i = blockNumber + 1; i <= anchorBlockNumber; i++) {
|
|
319
|
+
const blockNullifiers: { nullifier: string; noteReadPromise: Promise<Buffer | undefined> }[] = [];
|
|
320
|
+
for await (const nullifier of this.#nullifiersByNullificationBlockNumber.getValuesAsync(i)) {
|
|
321
|
+
// Start read immediately during iteration to keep IndexedDB transaction alive
|
|
322
|
+
blockNullifiers.push({ nullifier, noteReadPromise: this.#notes.getAsync(nullifier) });
|
|
323
|
+
}
|
|
324
|
+
if (blockNullifiers.length > 0) {
|
|
325
|
+
nullifiersByBlock.set(i, blockNullifiers);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
295
328
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
329
|
+
// Second pass: await reads and perform writes
|
|
330
|
+
for (const [block, nullifiers] of nullifiersByBlock) {
|
|
331
|
+
for (const { nullifier, noteReadPromise } of nullifiers) {
|
|
332
|
+
const noteBuffer = await noteReadPromise;
|
|
333
|
+
if (!noteBuffer) {
|
|
334
|
+
throw new Error(`PXE DB integrity error: no note found with nullifier ${nullifier}`);
|
|
335
|
+
}
|
|
299
336
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
throw new Error(`No scopes found for nullified note with nullifier ${noteNullifier}`);
|
|
337
|
+
const storedNote = StoredNote.fromBuffer(noteBuffer);
|
|
338
|
+
if (storedNote.scopes.size === 0) {
|
|
339
|
+
throw new Error(`No scopes found for nullified note with nullifier ${nullifier}`);
|
|
304
340
|
}
|
|
305
341
|
|
|
306
342
|
storedNote.markAsActive();
|
|
307
343
|
|
|
308
344
|
await Promise.all([
|
|
309
|
-
this.#notes.set(
|
|
310
|
-
this.#nullifiersByNullificationBlockNumber.deleteValue(
|
|
345
|
+
this.#notes.set(nullifier, storedNote.toBuffer()),
|
|
346
|
+
this.#nullifiersByNullificationBlockNumber.deleteValue(block, nullifier),
|
|
311
347
|
]);
|
|
312
348
|
}
|
|
313
349
|
}
|
|
314
350
|
}
|
|
315
351
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
352
|
+
/**
|
|
353
|
+
* Commits in memory job data to persistent storage.
|
|
354
|
+
*
|
|
355
|
+
* Called by JobCoordinator when a job completes successfully.
|
|
356
|
+
*
|
|
357
|
+
* Note: JobCoordinator wraps all commits in a single transaction, so we don't need our own transactionAsync here
|
|
358
|
+
* (and using one would throw on IndexedDB as it does not support nested txs).
|
|
359
|
+
*
|
|
360
|
+
* @param jobId - The jobId identifying which staged data to commit
|
|
361
|
+
*/
|
|
362
|
+
async commit(jobId: string): Promise<void> {
|
|
363
|
+
for (const [nullifier, storedNote] of this.#getNotesForJob(jobId)) {
|
|
364
|
+
await this.#notes.set(nullifier, storedNote.toBuffer());
|
|
365
|
+
await this.#nullifiersByContractAddress.set(storedNote.noteDao.contractAddress.toString(), nullifier);
|
|
366
|
+
if (storedNote.nullifiedAt !== undefined) {
|
|
367
|
+
await this.#nullifiersByNullificationBlockNumber.set(storedNote.nullifiedAt, nullifier);
|
|
324
368
|
}
|
|
369
|
+
}
|
|
325
370
|
|
|
326
|
-
|
|
327
|
-
});
|
|
371
|
+
this.#clearJobData(jobId);
|
|
328
372
|
}
|
|
329
373
|
|
|
330
374
|
discardStaged(jobId: string): Promise<void> {
|
|
331
|
-
|
|
375
|
+
this.#clearJobData(jobId);
|
|
376
|
+
return Promise.resolve();
|
|
332
377
|
}
|
|
333
378
|
|
|
334
379
|
#clearJobData(jobId: string) {
|
|
@@ -363,19 +408,4 @@ export class NoteStore implements StagedStore {
|
|
|
363
408
|
}
|
|
364
409
|
return notesForJob;
|
|
365
410
|
}
|
|
366
|
-
|
|
367
|
-
async #nullifiersOfContract(contractAddress: AztecAddress, jobId: string): Promise<Set<string>> {
|
|
368
|
-
// Collect persisted nullifiers for this contract
|
|
369
|
-
const persistedNullifiers: string[] = await toArray(
|
|
370
|
-
this.#nullifiersByContractAddress.getValuesAsync(contractAddress.toString()),
|
|
371
|
-
);
|
|
372
|
-
|
|
373
|
-
// Collect staged nullifiers from the job where the note's contract matches
|
|
374
|
-
const stagedNullifiers = this.#getNotesForJob(jobId)
|
|
375
|
-
.values()
|
|
376
|
-
.filter(storedNote => storedNote.noteDao.contractAddress.equals(contractAddress))
|
|
377
|
-
.map(storedNote => storedNote.noteDao.siloedNullifier.toString());
|
|
378
|
-
|
|
379
|
-
return new Set([...persistedNullifiers, ...stagedNullifiers]);
|
|
380
|
-
}
|
|
381
411
|
}
|