@aztec/txe 0.66.0 → 0.67.1-devnet
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 +3 -3
- package/dest/node/txe_node.d.ts +349 -0
- package/dest/node/txe_node.d.ts.map +1 -0
- package/dest/node/txe_node.js +521 -0
- package/dest/oracle/txe_oracle.d.ts +22 -8
- package/dest/oracle/txe_oracle.d.ts.map +1 -1
- package/dest/oracle/txe_oracle.js +186 -40
- package/dest/txe_service/txe_service.d.ts +5 -2
- package/dest/txe_service/txe_service.d.ts.map +1 -1
- package/dest/txe_service/txe_service.js +19 -9
- package/dest/util/txe_database.d.ts +3 -3
- package/dest/util/txe_database.d.ts.map +1 -1
- package/dest/util/txe_database.js +4 -3
- package/package.json +18 -14
- package/src/bin/index.ts +2 -2
- package/src/node/txe_node.ts +666 -0
- package/src/oracle/txe_oracle.ts +242 -44
- package/src/txe_service/txe_service.ts +22 -8
- package/src/util/txe_database.ts +6 -5
package/src/oracle/txe_oracle.ts
CHANGED
|
@@ -7,17 +7,20 @@ import {
|
|
|
7
7
|
PublicDataWitness,
|
|
8
8
|
PublicExecutionRequest,
|
|
9
9
|
SimulationError,
|
|
10
|
+
TxEffect,
|
|
11
|
+
TxHash,
|
|
10
12
|
type UnencryptedL2Log,
|
|
11
13
|
} from '@aztec/circuit-types';
|
|
12
14
|
import { type CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats';
|
|
13
15
|
import {
|
|
16
|
+
BlockHeader,
|
|
14
17
|
CallContext,
|
|
15
18
|
type ContractInstance,
|
|
16
19
|
type ContractInstanceWithAddress,
|
|
20
|
+
DEPLOYER_CONTRACT_ADDRESS,
|
|
17
21
|
Gas,
|
|
18
22
|
GasFees,
|
|
19
23
|
GlobalVariables,
|
|
20
|
-
Header,
|
|
21
24
|
IndexedTaggingSecret,
|
|
22
25
|
type KeyValidationRequest,
|
|
23
26
|
type L1_TO_L2_MSG_TREE_HEIGHT,
|
|
@@ -28,16 +31,23 @@ import {
|
|
|
28
31
|
type PUBLIC_DATA_TREE_HEIGHT,
|
|
29
32
|
PUBLIC_DISPATCH_SELECTOR,
|
|
30
33
|
PrivateContextInputs,
|
|
34
|
+
type PrivateLog,
|
|
31
35
|
PublicDataTreeLeaf,
|
|
32
36
|
type PublicDataTreeLeafPreimage,
|
|
33
37
|
type PublicDataWrite,
|
|
34
38
|
computeContractClassId,
|
|
35
|
-
|
|
39
|
+
computeTaggingSecretPoint,
|
|
36
40
|
deriveKeys,
|
|
37
41
|
getContractClassFromArtifact,
|
|
38
42
|
} from '@aztec/circuits.js';
|
|
39
43
|
import { Schnorr } from '@aztec/circuits.js/barretenberg';
|
|
40
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
computeNoteHashNonce,
|
|
46
|
+
computePublicDataTreeLeafSlot,
|
|
47
|
+
computeUniqueNoteHash,
|
|
48
|
+
siloNoteHash,
|
|
49
|
+
siloNullifier,
|
|
50
|
+
} from '@aztec/circuits.js/hash';
|
|
41
51
|
import {
|
|
42
52
|
type ContractArtifact,
|
|
43
53
|
type FunctionAbi,
|
|
@@ -51,10 +61,10 @@ import { Fr } from '@aztec/foundation/fields';
|
|
|
51
61
|
import { type Logger, applyStringFormatting } from '@aztec/foundation/log';
|
|
52
62
|
import { Timer } from '@aztec/foundation/timer';
|
|
53
63
|
import { type KeyStore } from '@aztec/key-store';
|
|
54
|
-
import { ContractDataOracle, enrichPublicSimulationError } from '@aztec/pxe';
|
|
64
|
+
import { ContractDataOracle, SimulatorOracle, enrichPublicSimulationError } from '@aztec/pxe';
|
|
55
65
|
import {
|
|
56
66
|
ExecutionError,
|
|
57
|
-
|
|
67
|
+
ExecutionNoteCache,
|
|
58
68
|
type MessageLoadOracleInputs,
|
|
59
69
|
type NoteData,
|
|
60
70
|
Oracle,
|
|
@@ -75,6 +85,7 @@ import { createTxForPublicCall } from '@aztec/simulator/public/fixtures';
|
|
|
75
85
|
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
|
|
76
86
|
import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state';
|
|
77
87
|
|
|
88
|
+
import { TXENode } from '../node/txe_node.js';
|
|
78
89
|
import { type TXEDatabase } from '../util/txe_database.js';
|
|
79
90
|
import { TXEPublicContractDataSource } from '../util/txe_public_contract_data_source.js';
|
|
80
91
|
import { TXEWorldStateDB } from '../util/txe_world_state_db.js';
|
|
@@ -90,10 +101,21 @@ export class TXE implements TypedOracle {
|
|
|
90
101
|
private nestedCallReturndata: Fr[] = [];
|
|
91
102
|
|
|
92
103
|
private contractDataOracle: ContractDataOracle;
|
|
104
|
+
private simulatorOracle: SimulatorOracle;
|
|
93
105
|
|
|
94
106
|
private version: Fr = Fr.ONE;
|
|
95
107
|
private chainId: Fr = Fr.ONE;
|
|
96
108
|
|
|
109
|
+
private uniqueNoteHashesFromPublic: Fr[] = [];
|
|
110
|
+
private siloedNullifiersFromPublic: Fr[] = [];
|
|
111
|
+
private siloedNullifiersFromPrivate: Set<string> = new Set();
|
|
112
|
+
private privateLogs: PrivateLog[] = [];
|
|
113
|
+
private publicLogs: UnencryptedL2Log[] = [];
|
|
114
|
+
|
|
115
|
+
private committedBlocks = new Set<number>();
|
|
116
|
+
|
|
117
|
+
private node = new TXENode(this.blockNumber);
|
|
118
|
+
|
|
97
119
|
constructor(
|
|
98
120
|
private logger: Logger,
|
|
99
121
|
private trees: MerkleTrees,
|
|
@@ -106,6 +128,7 @@ export class TXE implements TypedOracle {
|
|
|
106
128
|
this.contractAddress = AztecAddress.random();
|
|
107
129
|
// Default msg_sender (for entrypoints) is now Fr.max_value rather than 0 addr (see #7190 & #7404)
|
|
108
130
|
this.msgSender = AztecAddress.fromField(Fr.MAX_FIELD_VALUE);
|
|
131
|
+
this.simulatorOracle = new SimulatorOracle(this.contractDataOracle, txeDatabase, keyStore, this.node);
|
|
109
132
|
}
|
|
110
133
|
|
|
111
134
|
// Utils
|
|
@@ -156,6 +179,7 @@ export class TXE implements TypedOracle {
|
|
|
156
179
|
|
|
157
180
|
setBlockNumber(blockNumber: number) {
|
|
158
181
|
this.blockNumber = blockNumber;
|
|
182
|
+
this.node.setBlockNumber(blockNumber);
|
|
159
183
|
}
|
|
160
184
|
|
|
161
185
|
getTrees() {
|
|
@@ -210,7 +234,7 @@ export class TXE implements TypedOracle {
|
|
|
210
234
|
}
|
|
211
235
|
|
|
212
236
|
async addAuthWitness(address: AztecAddress, messageHash: Fr) {
|
|
213
|
-
const account = this.txeDatabase.getAccount(address);
|
|
237
|
+
const account = await this.txeDatabase.getAccount(address);
|
|
214
238
|
const privateKey = await this.keyStore.getMasterSecretKey(account.publicKeys.masterIncomingViewingPublicKey);
|
|
215
239
|
const schnorr = new Schnorr();
|
|
216
240
|
const signature = schnorr.constructSignature(messageHash.toBuffer(), privateKey).toBuffer();
|
|
@@ -236,18 +260,82 @@ export class TXE implements TypedOracle {
|
|
|
236
260
|
);
|
|
237
261
|
}
|
|
238
262
|
|
|
263
|
+
async addNullifiersFromPrivate(contractAddress: AztecAddress, nullifiers: Fr[]) {
|
|
264
|
+
const siloedNullifiers = nullifiers.map(nullifier => siloNullifier(contractAddress, nullifier));
|
|
265
|
+
const db = await this.trees.getLatest();
|
|
266
|
+
const nullifierIndexesInTree = await db.findLeafIndices(
|
|
267
|
+
MerkleTreeId.NULLIFIER_TREE,
|
|
268
|
+
siloedNullifiers.map(n => n.toBuffer()),
|
|
269
|
+
);
|
|
270
|
+
const notInTree = nullifierIndexesInTree.every(index => index === undefined);
|
|
271
|
+
const notInCache = siloedNullifiers.every(n => !this.siloedNullifiersFromPrivate.has(n.toString()));
|
|
272
|
+
if (notInTree && notInCache) {
|
|
273
|
+
siloedNullifiers.forEach(n => this.siloedNullifiersFromPrivate.add(n.toString()));
|
|
274
|
+
} else {
|
|
275
|
+
throw new Error(`Rejecting tx for emitting duplicate nullifiers`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async addSiloedNullifiersFromPublic(siloedNullifiers: Fr[]) {
|
|
280
|
+
this.siloedNullifiersFromPublic.push(...siloedNullifiers);
|
|
281
|
+
|
|
282
|
+
await this.addSiloedNullifiers(siloedNullifiers);
|
|
283
|
+
}
|
|
284
|
+
|
|
239
285
|
async addNullifiers(contractAddress: AztecAddress, nullifiers: Fr[]) {
|
|
240
286
|
const siloedNullifiers = nullifiers.map(nullifier => siloNullifier(contractAddress, nullifier));
|
|
241
287
|
await this.addSiloedNullifiers(siloedNullifiers);
|
|
242
288
|
}
|
|
243
289
|
|
|
244
|
-
async
|
|
290
|
+
async addUniqueNoteHashes(siloedNoteHashes: Fr[]) {
|
|
245
291
|
const db = await this.trees.getLatest();
|
|
246
292
|
await db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, siloedNoteHashes);
|
|
247
293
|
}
|
|
294
|
+
|
|
295
|
+
async addUniqueNoteHashesFromPublic(siloedNoteHashes: Fr[]) {
|
|
296
|
+
this.uniqueNoteHashesFromPublic.push(...siloedNoteHashes);
|
|
297
|
+
await this.addUniqueNoteHashes(siloedNoteHashes);
|
|
298
|
+
}
|
|
299
|
+
|
|
248
300
|
async addNoteHashes(contractAddress: AztecAddress, noteHashes: Fr[]) {
|
|
249
301
|
const siloedNoteHashes = noteHashes.map(noteHash => siloNoteHash(contractAddress, noteHash));
|
|
250
|
-
|
|
302
|
+
|
|
303
|
+
await this.addUniqueNoteHashes(siloedNoteHashes);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
addPrivateLogs(contractAddress: AztecAddress, privateLogs: PrivateLog[]) {
|
|
307
|
+
privateLogs.forEach(privateLog => {
|
|
308
|
+
privateLog.fields[0] = poseidon2Hash([contractAddress, privateLog.fields[0]]);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
this.privateLogs.push(...privateLogs);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
addPublicLogs(logs: UnencryptedL2Log[]) {
|
|
315
|
+
logs.forEach(log => {
|
|
316
|
+
if (log.data.length < 32 * 33) {
|
|
317
|
+
// TODO remove when #9835 and #9836 are fixed
|
|
318
|
+
this.logger.warn(`Skipping unencrypted log with insufficient data length: ${log.data.length}`);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
// TODO remove when #9835 and #9836 are fixed. The partial note logs are emitted as bytes, but encoded as Fields.
|
|
323
|
+
// This means that for every 32 bytes of payload, we only have 1 byte of data.
|
|
324
|
+
// Also, the tag is not stored in the first 32 bytes of the log, (that's the length of public fields now) but in the next 32.
|
|
325
|
+
const correctedBuffer = Buffer.alloc(32);
|
|
326
|
+
const initialOffset = 32;
|
|
327
|
+
for (let i = 0; i < 32; i++) {
|
|
328
|
+
const byte = Fr.fromBuffer(log.data.subarray(i * 32 + initialOffset, i * 32 + 32 + initialOffset)).toNumber();
|
|
329
|
+
correctedBuffer.writeUInt8(byte, i);
|
|
330
|
+
}
|
|
331
|
+
const tag = new Fr(correctedBuffer);
|
|
332
|
+
|
|
333
|
+
this.logger.verbose(`Found tagged unencrypted log with tag ${tag.toString()} in block ${this.blockNumber}`);
|
|
334
|
+
this.publicLogs.push(log);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
this.logger.warn(`Failed to add tagged log to store: ${err}`);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
251
339
|
}
|
|
252
340
|
|
|
253
341
|
// TypedOracle
|
|
@@ -298,11 +386,12 @@ export class TXE implements TypedOracle {
|
|
|
298
386
|
|
|
299
387
|
async getMembershipWitness(blockNumber: number, treeId: MerkleTreeId, leafValue: Fr): Promise<Fr[] | undefined> {
|
|
300
388
|
const db = await this.#getTreesAt(blockNumber);
|
|
301
|
-
const index = await db.
|
|
302
|
-
if (
|
|
389
|
+
const index = (await db.findLeafIndices(treeId, [leafValue.toBuffer()]))[0];
|
|
390
|
+
if (index === undefined) {
|
|
303
391
|
throw new Error(`Leaf value: ${leafValue} not found in ${MerkleTreeId[treeId]} at block ${blockNumber}`);
|
|
304
392
|
}
|
|
305
393
|
const siblingPath = await db.getSiblingPath(treeId, index);
|
|
394
|
+
|
|
306
395
|
return [new Fr(index), ...siblingPath.toFields()];
|
|
307
396
|
}
|
|
308
397
|
|
|
@@ -317,7 +406,7 @@ export class TXE implements TypedOracle {
|
|
|
317
406
|
nullifier: Fr,
|
|
318
407
|
): Promise<NullifierMembershipWitness | undefined> {
|
|
319
408
|
const db = await this.#getTreesAt(blockNumber);
|
|
320
|
-
const index = await db.
|
|
409
|
+
const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0];
|
|
321
410
|
if (!index) {
|
|
322
411
|
return undefined;
|
|
323
412
|
}
|
|
@@ -355,15 +444,30 @@ export class TXE implements TypedOracle {
|
|
|
355
444
|
}
|
|
356
445
|
}
|
|
357
446
|
|
|
358
|
-
getLowNullifierMembershipWitness(
|
|
359
|
-
|
|
360
|
-
|
|
447
|
+
async getLowNullifierMembershipWitness(
|
|
448
|
+
blockNumber: number,
|
|
449
|
+
nullifier: Fr,
|
|
361
450
|
): Promise<NullifierMembershipWitness | undefined> {
|
|
362
|
-
|
|
451
|
+
const committedDb = await this.#getTreesAt(blockNumber);
|
|
452
|
+
const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
|
|
453
|
+
if (!findResult) {
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
const { index, alreadyPresent } = findResult;
|
|
457
|
+
if (alreadyPresent) {
|
|
458
|
+
this.logger.warn(`Nullifier ${nullifier.toBigInt()} already exists in the tree`);
|
|
459
|
+
}
|
|
460
|
+
const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!;
|
|
461
|
+
|
|
462
|
+
const siblingPath = await committedDb.getSiblingPath<typeof NULLIFIER_TREE_HEIGHT>(
|
|
463
|
+
MerkleTreeId.NULLIFIER_TREE,
|
|
464
|
+
BigInt(index),
|
|
465
|
+
);
|
|
466
|
+
return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
|
|
363
467
|
}
|
|
364
468
|
|
|
365
|
-
async
|
|
366
|
-
const header =
|
|
469
|
+
async getBlockHeader(blockNumber: number): Promise<BlockHeader | undefined> {
|
|
470
|
+
const header = BlockHeader.empty();
|
|
367
471
|
const db = await this.#getTreesAt(blockNumber);
|
|
368
472
|
header.state = await db.getStateReference();
|
|
369
473
|
header.globalVariables.blockNumber = new Fr(blockNumber);
|
|
@@ -382,7 +486,7 @@ export class TXE implements TypedOracle {
|
|
|
382
486
|
throw new Error('Method not implemented.');
|
|
383
487
|
}
|
|
384
488
|
|
|
385
|
-
getNotes(
|
|
489
|
+
async getNotes(
|
|
386
490
|
storageSlot: Fr,
|
|
387
491
|
numSelects: number,
|
|
388
492
|
selectByIndexes: number[],
|
|
@@ -396,12 +500,16 @@ export class TXE implements TypedOracle {
|
|
|
396
500
|
sortOrder: number[],
|
|
397
501
|
limit: number,
|
|
398
502
|
offset: number,
|
|
399
|
-
|
|
503
|
+
status: NoteStatus,
|
|
400
504
|
) {
|
|
401
505
|
// Nullified pending notes are already removed from the list.
|
|
402
506
|
const pendingNotes = this.noteCache.getNotes(this.contractAddress, storageSlot);
|
|
403
507
|
|
|
404
|
-
const
|
|
508
|
+
const pendingNullifiers = this.noteCache.getNullifiers(this.contractAddress);
|
|
509
|
+
const dbNotes = await this.simulatorOracle.getNotes(this.contractAddress, storageSlot, status);
|
|
510
|
+
const dbNotesFiltered = dbNotes.filter(n => !pendingNullifiers.has((n.siloedNullifier as Fr).value));
|
|
511
|
+
|
|
512
|
+
const notes = pickNotes<NoteData>([...dbNotesFiltered, ...pendingNotes], {
|
|
405
513
|
selects: selectByIndexes.slice(0, numSelects).map((index, i) => ({
|
|
406
514
|
selector: { index, offset: selectByOffsets[i], length: selectByLengths[i] },
|
|
407
515
|
value: selectValues[i],
|
|
@@ -421,10 +529,10 @@ export class TXE implements TypedOracle {
|
|
|
421
529
|
.join(', ')}`,
|
|
422
530
|
);
|
|
423
531
|
|
|
424
|
-
return
|
|
532
|
+
return notes;
|
|
425
533
|
}
|
|
426
534
|
|
|
427
|
-
notifyCreatedNote(storageSlot: Fr,
|
|
535
|
+
notifyCreatedNote(storageSlot: Fr, _noteTypeId: NoteSelector, noteItems: Fr[], noteHash: Fr, counter: number) {
|
|
428
536
|
const note = new Note(noteItems);
|
|
429
537
|
this.noteCache.addNewNote(
|
|
430
538
|
{
|
|
@@ -441,7 +549,8 @@ export class TXE implements TypedOracle {
|
|
|
441
549
|
return Promise.resolve();
|
|
442
550
|
}
|
|
443
551
|
|
|
444
|
-
notifyNullifiedNote(innerNullifier: Fr, noteHash: Fr, counter: number) {
|
|
552
|
+
async notifyNullifiedNote(innerNullifier: Fr, noteHash: Fr, counter: number) {
|
|
553
|
+
await this.addNullifiersFromPrivate(this.contractAddress, [innerNullifier]);
|
|
445
554
|
this.noteCache.nullifyNote(this.contractAddress, innerNullifier, noteHash);
|
|
446
555
|
this.sideEffectCounter = counter + 1;
|
|
447
556
|
return Promise.resolve();
|
|
@@ -450,7 +559,7 @@ export class TXE implements TypedOracle {
|
|
|
450
559
|
async checkNullifierExists(innerNullifier: Fr): Promise<boolean> {
|
|
451
560
|
const nullifier = siloNullifier(this.contractAddress, innerNullifier!);
|
|
452
561
|
const db = await this.trees.getLatest();
|
|
453
|
-
const index = await db.
|
|
562
|
+
const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0];
|
|
454
563
|
return index !== undefined;
|
|
455
564
|
}
|
|
456
565
|
|
|
@@ -506,6 +615,56 @@ export class TXE implements TypedOracle {
|
|
|
506
615
|
return publicDataWrites.map(write => write.value);
|
|
507
616
|
}
|
|
508
617
|
|
|
618
|
+
async commitState() {
|
|
619
|
+
const blockNumber = await this.getBlockNumber();
|
|
620
|
+
if (this.committedBlocks.has(blockNumber)) {
|
|
621
|
+
throw new Error('Already committed state');
|
|
622
|
+
} else {
|
|
623
|
+
this.committedBlocks.add(blockNumber);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const txEffect = TxEffect.empty();
|
|
627
|
+
|
|
628
|
+
let i = 0;
|
|
629
|
+
txEffect.noteHashes = [
|
|
630
|
+
...this.noteCache
|
|
631
|
+
.getAllNotes()
|
|
632
|
+
.map(pendingNote =>
|
|
633
|
+
computeUniqueNoteHash(
|
|
634
|
+
computeNoteHashNonce(new Fr(this.blockNumber + 6969), i++),
|
|
635
|
+
siloNoteHash(pendingNote.note.contractAddress, pendingNote.noteHashForConsumption),
|
|
636
|
+
),
|
|
637
|
+
),
|
|
638
|
+
...this.uniqueNoteHashesFromPublic,
|
|
639
|
+
];
|
|
640
|
+
txEffect.nullifiers = [
|
|
641
|
+
new Fr(blockNumber + 6969),
|
|
642
|
+
...Array.from(this.siloedNullifiersFromPrivate).map(n => Fr.fromString(n)),
|
|
643
|
+
];
|
|
644
|
+
|
|
645
|
+
// Using block number itself, (without adding 6969) gets killed at 1 as it says the slot is already used,
|
|
646
|
+
// it seems like we commit a 1 there to the trees before ? To see what I mean, uncomment these lines below
|
|
647
|
+
// let index = await (await this.trees.getLatest()).findLeafIndex(MerkleTreeId.NULLIFIER_TREE, Fr.ONE.toBuffer());
|
|
648
|
+
// console.log('INDEX OF ONE', index);
|
|
649
|
+
// index = await (await this.trees.getLatest()).findLeafIndex(MerkleTreeId.NULLIFIER_TREE, Fr.random().toBuffer());
|
|
650
|
+
// console.log('INDEX OF RANDOM', index);
|
|
651
|
+
|
|
652
|
+
this.node.setTxEffect(blockNumber, new TxHash(new Fr(blockNumber).toBuffer()), txEffect);
|
|
653
|
+
this.node.setNullifiersIndexesWithBlock(blockNumber, txEffect.nullifiers);
|
|
654
|
+
this.node.addNoteLogsByTags(this.blockNumber, this.privateLogs);
|
|
655
|
+
this.node.addPublicLogsByTags(this.blockNumber, this.publicLogs);
|
|
656
|
+
|
|
657
|
+
await this.addUniqueNoteHashes(txEffect.noteHashes);
|
|
658
|
+
await this.addSiloedNullifiers(txEffect.nullifiers);
|
|
659
|
+
|
|
660
|
+
this.privateLogs = [];
|
|
661
|
+
this.publicLogs = [];
|
|
662
|
+
this.siloedNullifiersFromPrivate = new Set();
|
|
663
|
+
this.uniqueNoteHashesFromPublic = [];
|
|
664
|
+
this.siloedNullifiersFromPublic = [];
|
|
665
|
+
this.noteCache = new ExecutionNoteCache(new Fr(1));
|
|
666
|
+
}
|
|
667
|
+
|
|
509
668
|
emitContractClassLog(_log: UnencryptedL2Log, _counter: number): Fr {
|
|
510
669
|
throw new Error('Method not implemented.');
|
|
511
670
|
}
|
|
@@ -570,15 +729,28 @@ export class TXE implements TypedOracle {
|
|
|
570
729
|
const endSideEffectCounter = publicInputs.endSideEffectCounter;
|
|
571
730
|
this.sideEffectCounter = endSideEffectCounter.toNumber() + 1;
|
|
572
731
|
|
|
573
|
-
|
|
732
|
+
this.addPrivateLogs(
|
|
574
733
|
targetContractAddress,
|
|
575
|
-
publicInputs.
|
|
734
|
+
publicInputs.privateLogs.filter(privateLog => !privateLog.isEmpty()).map(privateLog => privateLog.log),
|
|
576
735
|
);
|
|
577
736
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
737
|
+
const executionNullifiers = publicInputs.nullifiers
|
|
738
|
+
.filter(nullifier => !nullifier.isEmpty())
|
|
739
|
+
.map(nullifier => nullifier.value);
|
|
740
|
+
// We inject nullifiers into siloedNullifiersFromPrivate from notifyNullifiedNote,
|
|
741
|
+
// so top level calls to destroyNote work as expected. As such, we are certain
|
|
742
|
+
// that we would insert duplicates if we just took the nullifiers from the public inputs and
|
|
743
|
+
// blindly inserted them into siloedNullifiersFromPrivate. To avoid this, we extract the first
|
|
744
|
+
// (and only the first!) duplicated nullifier from the public inputs, so we can just push
|
|
745
|
+
// the ones that were not created by deleting a note
|
|
746
|
+
const firstDuplicateIndexes = executionNullifiers
|
|
747
|
+
.map((nullifier, index) => {
|
|
748
|
+
const siloedNullifier = siloNullifier(targetContractAddress, nullifier);
|
|
749
|
+
return this.siloedNullifiersFromPrivate.has(siloedNullifier.toString()) ? index : -1;
|
|
750
|
+
})
|
|
751
|
+
.filter(index => index !== -1);
|
|
752
|
+
const nonNoteNullifiers = executionNullifiers.filter((_, index) => !firstDuplicateIndexes.includes(index));
|
|
753
|
+
await this.addNullifiersFromPrivate(targetContractAddress, nonNoteNullifiers);
|
|
582
754
|
|
|
583
755
|
this.setContractAddress(currentContractAddress);
|
|
584
756
|
this.setMsgSender(currentMessageSender);
|
|
@@ -634,6 +806,7 @@ export class TXE implements TypedOracle {
|
|
|
634
806
|
const executionRequest = new PublicExecutionRequest(callContext, args);
|
|
635
807
|
|
|
636
808
|
const db = await this.trees.getLatest();
|
|
809
|
+
const worldStateDb = new TXEWorldStateDB(db, new TXEPublicContractDataSource(this));
|
|
637
810
|
|
|
638
811
|
const globalVariables = GlobalVariables.empty();
|
|
639
812
|
globalVariables.chainId = this.chainId;
|
|
@@ -641,12 +814,23 @@ export class TXE implements TypedOracle {
|
|
|
641
814
|
globalVariables.blockNumber = new Fr(this.blockNumber);
|
|
642
815
|
globalVariables.gasFees = new GasFees(1, 1);
|
|
643
816
|
|
|
817
|
+
// If the contract instance exists in the TXE's world state, make sure its nullifier is present in the tree
|
|
818
|
+
// so its nullifier check passes.
|
|
819
|
+
if ((await worldStateDb.getContractInstance(callContext.contractAddress)) !== undefined) {
|
|
820
|
+
const contractAddressNullifier = siloNullifier(
|
|
821
|
+
AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
|
|
822
|
+
callContext.contractAddress.toField(),
|
|
823
|
+
);
|
|
824
|
+
if ((await worldStateDb.getNullifierIndex(contractAddressNullifier)) === undefined) {
|
|
825
|
+
await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
644
829
|
const simulator = new PublicTxSimulator(
|
|
645
830
|
db,
|
|
646
831
|
new TXEWorldStateDB(db, new TXEPublicContractDataSource(this)),
|
|
647
832
|
new NoopTelemetryClient(),
|
|
648
833
|
globalVariables,
|
|
649
|
-
/*realAvmProvingRequests=*/ false,
|
|
650
834
|
);
|
|
651
835
|
|
|
652
836
|
// When setting up a teardown call, we tell it that
|
|
@@ -655,6 +839,9 @@ export class TXE implements TypedOracle {
|
|
|
655
839
|
const tx = createTxForPublicCall(executionRequest, gasUsedByPrivate, isTeardown);
|
|
656
840
|
|
|
657
841
|
const result = await simulator.simulate(tx);
|
|
842
|
+
|
|
843
|
+
this.addPublicLogs(tx.unencryptedLogs.unrollLogs());
|
|
844
|
+
|
|
658
845
|
return Promise.resolve(result);
|
|
659
846
|
}
|
|
660
847
|
|
|
@@ -707,7 +894,7 @@ export class TXE implements TypedOracle {
|
|
|
707
894
|
const noteHashes = sideEffects.noteHashes.filter(s => !s.isEmpty());
|
|
708
895
|
const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty());
|
|
709
896
|
await this.addPublicDataWrites(publicDataWrites);
|
|
710
|
-
await this.
|
|
897
|
+
await this.addUniqueNoteHashesFromPublic(noteHashes);
|
|
711
898
|
await this.addSiloedNullifiers(nullifiers);
|
|
712
899
|
|
|
713
900
|
this.setContractAddress(currentContractAddress);
|
|
@@ -741,32 +928,43 @@ export class TXE implements TypedOracle {
|
|
|
741
928
|
}
|
|
742
929
|
|
|
743
930
|
debugLog(message: string, fields: Fr[]): void {
|
|
744
|
-
this.logger.verbose(
|
|
931
|
+
this.logger.verbose(`${applyStringFormatting(message, fields)}`, { module: `${this.logger.module}:debug_log` });
|
|
745
932
|
}
|
|
746
933
|
|
|
747
934
|
async incrementAppTaggingSecretIndexAsSender(sender: AztecAddress, recipient: AztecAddress): Promise<void> {
|
|
748
|
-
const appSecret = await this.#
|
|
935
|
+
const appSecret = await this.#calculateAppTaggingSecret(this.contractAddress, sender, recipient);
|
|
749
936
|
const [index] = await this.txeDatabase.getTaggingSecretsIndexesAsSender([appSecret]);
|
|
750
937
|
await this.txeDatabase.setTaggingSecretsIndexesAsSender([new IndexedTaggingSecret(appSecret, index + 1)]);
|
|
751
938
|
}
|
|
752
939
|
|
|
753
|
-
async
|
|
754
|
-
const secret = await this.#
|
|
940
|
+
async getIndexedTaggingSecretAsSender(sender: AztecAddress, recipient: AztecAddress): Promise<IndexedTaggingSecret> {
|
|
941
|
+
const secret = await this.#calculateAppTaggingSecret(this.contractAddress, sender, recipient);
|
|
755
942
|
const [index] = await this.txeDatabase.getTaggingSecretsIndexesAsSender([secret]);
|
|
756
943
|
return new IndexedTaggingSecret(secret, index);
|
|
757
944
|
}
|
|
758
945
|
|
|
759
|
-
async #
|
|
946
|
+
async #calculateAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) {
|
|
760
947
|
const senderCompleteAddress = await this.getCompleteAddress(sender);
|
|
761
948
|
const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender);
|
|
762
|
-
const
|
|
949
|
+
const secretPoint = computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient);
|
|
763
950
|
// Silo the secret to the app so it can't be used to track other app's notes
|
|
764
|
-
const
|
|
765
|
-
return
|
|
951
|
+
const appSecret = poseidon2Hash([secretPoint.x, secretPoint.y, contractAddress]);
|
|
952
|
+
return appSecret;
|
|
766
953
|
}
|
|
767
954
|
|
|
768
|
-
syncNotes() {
|
|
769
|
-
|
|
955
|
+
async syncNotes() {
|
|
956
|
+
const taggedLogsByRecipient = await this.simulatorOracle.syncTaggedLogs(
|
|
957
|
+
this.contractAddress,
|
|
958
|
+
await this.getBlockNumber(),
|
|
959
|
+
undefined,
|
|
960
|
+
);
|
|
961
|
+
|
|
962
|
+
for (const [recipient, taggedLogs] of taggedLogsByRecipient.entries()) {
|
|
963
|
+
await this.simulatorOracle.processTaggedLogs(taggedLogs, AztecAddress.fromString(recipient));
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
await this.simulatorOracle.removeNullifiedNotes(this.contractAddress);
|
|
967
|
+
|
|
770
968
|
return Promise.resolve();
|
|
771
969
|
}
|
|
772
970
|
|
|
@@ -797,7 +995,7 @@ export class TXE implements TypedOracle {
|
|
|
797
995
|
const noteHashes = sideEffects.noteHashes.filter(s => !s.isEmpty());
|
|
798
996
|
const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty());
|
|
799
997
|
await this.addPublicDataWrites(publicDataWrites);
|
|
800
|
-
await this.
|
|
998
|
+
await this.addUniqueNoteHashes(noteHashes);
|
|
801
999
|
await this.addSiloedNullifiers(nullifiers);
|
|
802
1000
|
}
|
|
803
1001
|
|
|
@@ -818,7 +1016,7 @@ export class TXE implements TypedOracle {
|
|
|
818
1016
|
async avmOpcodeNullifierExists(innerNullifier: Fr, targetAddress: AztecAddress): Promise<boolean> {
|
|
819
1017
|
const nullifier = siloNullifier(targetAddress, innerNullifier!);
|
|
820
1018
|
const db = await this.trees.getLatest();
|
|
821
|
-
const index = await db.
|
|
1019
|
+
const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0];
|
|
822
1020
|
return index !== undefined;
|
|
823
1021
|
}
|
|
824
1022
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { SchnorrAccountContractArtifact } from '@aztec/accounts/schnorr';
|
|
2
2
|
import { L2Block, MerkleTreeId, SimulationError } from '@aztec/circuit-types';
|
|
3
3
|
import {
|
|
4
|
+
BlockHeader,
|
|
4
5
|
Fr,
|
|
5
6
|
FunctionSelector,
|
|
6
|
-
Header,
|
|
7
7
|
PublicDataTreeLeaf,
|
|
8
8
|
PublicKeys,
|
|
9
9
|
computePartialAddress,
|
|
@@ -14,8 +14,9 @@ import { type ContractArtifact, NoteSelector } from '@aztec/foundation/abi';
|
|
|
14
14
|
import { AztecAddress } from '@aztec/foundation/aztec-address';
|
|
15
15
|
import { type Logger } from '@aztec/foundation/log';
|
|
16
16
|
import { KeyStore } from '@aztec/key-store';
|
|
17
|
-
import { openTmpStore } from '@aztec/kv-store/
|
|
18
|
-
import {
|
|
17
|
+
import { openTmpStore } from '@aztec/kv-store/lmdb';
|
|
18
|
+
import { protocolContractNames } from '@aztec/protocol-contracts';
|
|
19
|
+
import { getCanonicalProtocolContract } from '@aztec/protocol-contracts/bundle';
|
|
19
20
|
import { enrichPublicSimulationError } from '@aztec/pxe';
|
|
20
21
|
import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/simulator';
|
|
21
22
|
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
|
|
@@ -70,9 +71,12 @@ export class TXEService {
|
|
|
70
71
|
const nBlocks = fromSingle(blocks).toNumber();
|
|
71
72
|
this.logger.debug(`time traveling ${nBlocks} blocks`);
|
|
72
73
|
const trees = (this.typedOracle as TXE).getTrees();
|
|
74
|
+
|
|
75
|
+
await (this.typedOracle as TXE).commitState();
|
|
76
|
+
|
|
73
77
|
for (let i = 0; i < nBlocks; i++) {
|
|
74
78
|
const blockNumber = await this.typedOracle.getBlockNumber();
|
|
75
|
-
const header =
|
|
79
|
+
const header = BlockHeader.empty();
|
|
76
80
|
const l2Block = L2Block.empty();
|
|
77
81
|
header.state = await trees.getStateReference(true);
|
|
78
82
|
header.globalVariables.blockNumber = new Fr(blockNumber);
|
|
@@ -550,8 +554,8 @@ export class TXEService {
|
|
|
550
554
|
return toForeignCallResult([]);
|
|
551
555
|
}
|
|
552
556
|
|
|
553
|
-
async
|
|
554
|
-
const header = await this.typedOracle.
|
|
557
|
+
async getBlockHeader(blockNumber: ForeignCallSingle) {
|
|
558
|
+
const header = await this.typedOracle.getBlockHeader(fromSingle(blockNumber).toNumber());
|
|
555
559
|
if (!header) {
|
|
556
560
|
throw new Error(`Block header not found for block ${blockNumber}.`);
|
|
557
561
|
}
|
|
@@ -571,8 +575,18 @@ export class TXEService {
|
|
|
571
575
|
return toForeignCallResult([toArray(witness)]);
|
|
572
576
|
}
|
|
573
577
|
|
|
574
|
-
async
|
|
575
|
-
const
|
|
578
|
+
async getLowNullifierMembershipWitness(blockNumber: ForeignCallSingle, nullifier: ForeignCallSingle) {
|
|
579
|
+
const parsedBlockNumber = fromSingle(blockNumber).toNumber();
|
|
580
|
+
|
|
581
|
+
const witness = await this.typedOracle.getLowNullifierMembershipWitness(parsedBlockNumber, fromSingle(nullifier));
|
|
582
|
+
if (!witness) {
|
|
583
|
+
throw new Error(`Low nullifier witness not found for nullifier ${nullifier} at block ${parsedBlockNumber}.`);
|
|
584
|
+
}
|
|
585
|
+
return toForeignCallResult([toArray(witness.toFields())]);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async getIndexedTaggingSecretAsSender(sender: ForeignCallSingle, recipient: ForeignCallSingle) {
|
|
589
|
+
const secret = await this.typedOracle.getIndexedTaggingSecretAsSender(
|
|
576
590
|
AztecAddress.fromField(fromSingle(sender)),
|
|
577
591
|
AztecAddress.fromField(fromSingle(recipient)),
|
|
578
592
|
);
|
package/src/util/txe_database.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { type AztecAddress, CompleteAddress } from '@aztec/circuits.js';
|
|
2
|
-
import { type
|
|
2
|
+
import { type AztecAsyncKVStore, type AztecAsyncMap } from '@aztec/kv-store';
|
|
3
3
|
import { KVPxeDatabase } from '@aztec/pxe';
|
|
4
4
|
|
|
5
5
|
export class TXEDatabase extends KVPxeDatabase {
|
|
6
|
-
#accounts:
|
|
6
|
+
#accounts: AztecAsyncMap<string, Buffer>;
|
|
7
7
|
|
|
8
|
-
constructor(db:
|
|
8
|
+
constructor(db: AztecAsyncKVStore) {
|
|
9
9
|
super(db);
|
|
10
10
|
this.#accounts = db.openMap('accounts');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
getAccount(key: AztecAddress) {
|
|
14
|
-
const completeAddress = this.#accounts.
|
|
13
|
+
async getAccount(key: AztecAddress) {
|
|
14
|
+
const completeAddress = await this.#accounts.getAsync(key.toString());
|
|
15
15
|
if (!completeAddress) {
|
|
16
16
|
throw new Error(`Account not found: ${key.toString()}`);
|
|
17
17
|
}
|
|
@@ -20,5 +20,6 @@ export class TXEDatabase extends KVPxeDatabase {
|
|
|
20
20
|
|
|
21
21
|
async setAccount(key: AztecAddress, value: CompleteAddress) {
|
|
22
22
|
await this.#accounts.set(key.toString(), value.toBuffer());
|
|
23
|
+
await this.addCompleteAddress(value);
|
|
23
24
|
}
|
|
24
25
|
}
|