@aztec/simulator 0.63.1 → 0.64.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/avm/avm_contract_call_result.d.ts +18 -1
- package/dest/avm/avm_contract_call_result.d.ts.map +1 -1
- package/dest/avm/avm_contract_call_result.js +30 -3
- package/dest/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/avm/avm_memory_types.js +7 -11
- package/dest/avm/avm_simulator.d.ts +6 -1
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +29 -8
- package/dest/avm/avm_tree.d.ts +4 -18
- package/dest/avm/avm_tree.d.ts.map +1 -1
- package/dest/avm/avm_tree.js +71 -53
- package/dest/avm/errors.d.ts +7 -0
- package/dest/avm/errors.d.ts.map +1 -1
- package/dest/avm/errors.js +11 -1
- package/dest/avm/fixtures/index.d.ts +2 -2
- package/dest/avm/fixtures/index.d.ts.map +1 -1
- package/dest/avm/fixtures/index.js +1 -1
- package/dest/avm/index.d.ts +1 -0
- package/dest/avm/index.d.ts.map +1 -1
- package/dest/avm/index.js +2 -1
- package/dest/avm/journal/journal.d.ts +14 -13
- package/dest/avm/journal/journal.d.ts.map +1 -1
- package/dest/avm/journal/journal.js +81 -68
- package/dest/avm/journal/public_storage.d.ts +0 -1
- package/dest/avm/journal/public_storage.d.ts.map +1 -1
- package/dest/avm/journal/public_storage.js +6 -6
- package/dest/avm/opcodes/accrued_substate.js +2 -2
- package/dest/avm/opcodes/addressing_mode.d.ts.map +1 -1
- package/dest/avm/opcodes/addressing_mode.js +8 -2
- package/dest/avm/opcodes/external_calls.d.ts.map +1 -1
- package/dest/avm/opcodes/external_calls.js +2 -4
- package/dest/avm/opcodes/instruction.d.ts +1 -1
- package/dest/avm/opcodes/instruction.d.ts.map +1 -1
- package/dest/avm/opcodes/instruction.js +1 -1
- package/dest/index.d.ts +0 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- package/dest/public/dual_side_effect_trace.d.ts +3 -9
- package/dest/public/dual_side_effect_trace.d.ts.map +1 -1
- package/dest/public/dual_side_effect_trace.js +5 -11
- package/dest/public/enqueued_call_side_effect_trace.d.ts +4 -10
- package/dest/public/enqueued_call_side_effect_trace.d.ts.map +1 -1
- package/dest/public/enqueued_call_side_effect_trace.js +6 -15
- package/dest/public/executor_metrics.d.ts +1 -2
- package/dest/public/executor_metrics.d.ts.map +1 -1
- package/dest/public/executor_metrics.js +2 -8
- package/dest/public/fixtures/index.d.ts +14 -0
- package/dest/public/fixtures/index.d.ts.map +1 -0
- package/dest/public/fixtures/index.js +100 -0
- package/dest/public/index.d.ts +1 -2
- package/dest/public/index.d.ts.map +1 -1
- package/dest/public/index.js +1 -2
- package/dest/public/public_processor.d.ts +0 -2
- package/dest/public/public_processor.d.ts.map +1 -1
- package/dest/public/public_processor.js +5 -12
- package/dest/public/public_tx_context.d.ts +6 -4
- package/dest/public/public_tx_context.d.ts.map +1 -1
- package/dest/public/public_tx_context.js +11 -7
- package/dest/public/public_tx_simulator.d.ts +56 -5
- package/dest/public/public_tx_simulator.d.ts.map +1 -1
- package/dest/public/public_tx_simulator.js +106 -26
- package/dest/public/side_effect_trace.d.ts +4 -10
- package/dest/public/side_effect_trace.d.ts.map +1 -1
- package/dest/public/side_effect_trace.js +6 -15
- package/dest/public/side_effect_trace_interface.d.ts +3 -9
- package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
- package/dest/public/transitional_adapters.d.ts +1 -2
- package/dest/public/transitional_adapters.d.ts.map +1 -1
- package/dest/public/transitional_adapters.js +8 -10
- package/package.json +10 -10
- package/src/avm/avm_contract_call_result.ts +39 -2
- package/src/avm/avm_memory_types.ts +6 -10
- package/src/avm/avm_simulator.ts +58 -8
- package/src/avm/avm_tree.ts +88 -64
- package/src/avm/errors.ts +11 -0
- package/src/avm/fixtures/index.ts +4 -3
- package/src/avm/index.ts +1 -0
- package/src/avm/journal/journal.ts +118 -126
- package/src/avm/journal/public_storage.ts +5 -6
- package/src/avm/opcodes/accrued_substate.ts +1 -1
- package/src/avm/opcodes/addressing_mode.ts +7 -2
- package/src/avm/opcodes/external_calls.ts +1 -3
- package/src/avm/opcodes/instruction.ts +1 -1
- package/src/index.ts +0 -1
- package/src/public/dual_side_effect_trace.ts +4 -13
- package/src/public/enqueued_call_side_effect_trace.ts +14 -17
- package/src/public/executor_metrics.ts +1 -9
- package/src/public/fixtures/index.ts +158 -0
- package/src/public/index.ts +1 -5
- package/src/public/public_processor.ts +5 -27
- package/src/public/public_tx_context.ts +13 -8
- package/src/public/public_tx_simulator.ts +182 -50
- package/src/public/side_effect_trace.ts +9 -19
- package/src/public/side_effect_trace_interface.ts +3 -9
- package/src/public/transitional_adapters.ts +12 -12
- package/dest/mocks/fixtures.d.ts +0 -28
- package/dest/mocks/fixtures.d.ts.map +0 -1
- package/dest/mocks/fixtures.js +0 -48
- package/dest/mocks/index.d.ts +0 -2
- package/dest/mocks/index.d.ts.map +0 -1
- package/dest/mocks/index.js +0 -2
- package/dest/public/executor.d.ts +0 -47
- package/dest/public/executor.d.ts.map +0 -1
- package/dest/public/executor.js +0 -90
- package/src/mocks/fixtures.ts +0 -72
- package/src/mocks/index.ts +0 -1
- package/src/public/executor.ts +0 -154
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MerkleTreeId } from '@aztec/circuit-types';
|
|
2
2
|
import {
|
|
3
3
|
type AztecAddress,
|
|
4
4
|
type Gas,
|
|
5
5
|
type NullifierLeafPreimage,
|
|
6
6
|
type PublicCallRequest,
|
|
7
|
-
|
|
8
|
-
PublicDataTreeLeafPreimage,
|
|
7
|
+
type PublicDataTreeLeafPreimage,
|
|
9
8
|
SerializableContractInstance,
|
|
10
9
|
computePublicBytecodeCommitment,
|
|
11
10
|
} from '@aztec/circuits.js';
|
|
12
11
|
import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash';
|
|
13
12
|
import { Fr } from '@aztec/foundation/fields';
|
|
14
13
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
15
|
-
import { type IndexedTreeLeafPreimage } from '@aztec/foundation/trees';
|
|
16
14
|
|
|
17
15
|
import assert from 'assert';
|
|
18
16
|
|
|
@@ -21,7 +19,8 @@ import { type WorldStateDB } from '../../public/public_db_sources.js';
|
|
|
21
19
|
import { type PublicSideEffectTraceInterface } from '../../public/side_effect_trace_interface.js';
|
|
22
20
|
import { type AvmContractCallResult } from '../avm_contract_call_result.js';
|
|
23
21
|
import { type AvmExecutionEnvironment } from '../avm_execution_environment.js';
|
|
24
|
-
import {
|
|
22
|
+
import { AvmEphemeralForest } from '../avm_tree.js';
|
|
23
|
+
import { NullifierCollisionError, NullifierManager } from './nullifiers.js';
|
|
25
24
|
import { PublicStorage } from './public_storage.js';
|
|
26
25
|
|
|
27
26
|
/**
|
|
@@ -36,9 +35,6 @@ import { PublicStorage } from './public_storage.js';
|
|
|
36
35
|
export class AvmPersistableStateManager {
|
|
37
36
|
private readonly log = createDebugLogger('aztec:avm_simulator:state_manager');
|
|
38
37
|
|
|
39
|
-
/** Interface to perform merkle tree operations */
|
|
40
|
-
public merkleTrees: MerkleTreeWriteOperations;
|
|
41
|
-
|
|
42
38
|
/** Make sure a forked state is never merged twice. */
|
|
43
39
|
private alreadyMergedIntoParent = false;
|
|
44
40
|
|
|
@@ -53,29 +49,43 @@ export class AvmPersistableStateManager {
|
|
|
53
49
|
/** Nullifier set, including cached/recently-emitted nullifiers */
|
|
54
50
|
private readonly nullifiers: NullifierManager = new NullifierManager(worldStateDB),
|
|
55
51
|
private readonly doMerkleOperations: boolean = false,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.merkleTrees = merkleTrees;
|
|
60
|
-
} else {
|
|
61
|
-
this.merkleTrees = worldStateDB.getMerkleInterface();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
52
|
+
/** Ephmeral forest for merkle tree operations */
|
|
53
|
+
public readonly merkleTrees: AvmEphemeralForest,
|
|
54
|
+
) {}
|
|
64
55
|
|
|
65
56
|
/**
|
|
66
57
|
* Create a new state manager with some preloaded pending siloed nullifiers
|
|
67
58
|
*/
|
|
68
|
-
public static newWithPendingSiloedNullifiers(
|
|
59
|
+
public static async newWithPendingSiloedNullifiers(
|
|
69
60
|
worldStateDB: WorldStateDB,
|
|
70
61
|
trace: PublicSideEffectTraceInterface,
|
|
71
62
|
pendingSiloedNullifiers: Fr[],
|
|
63
|
+
doMerkleOperations: boolean = false,
|
|
72
64
|
) {
|
|
73
65
|
const parentNullifiers = NullifierManager.newWithPendingSiloedNullifiers(worldStateDB, pendingSiloedNullifiers);
|
|
66
|
+
const ephemeralForest = await AvmEphemeralForest.create(worldStateDB.getMerkleInterface());
|
|
74
67
|
return new AvmPersistableStateManager(
|
|
75
68
|
worldStateDB,
|
|
76
69
|
trace,
|
|
77
70
|
/*publicStorage=*/ new PublicStorage(worldStateDB),
|
|
78
71
|
/*nullifiers=*/ parentNullifiers.fork(),
|
|
72
|
+
doMerkleOperations,
|
|
73
|
+
ephemeralForest,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create a new state manager
|
|
79
|
+
*/
|
|
80
|
+
public static async create(worldStateDB: WorldStateDB, trace: PublicSideEffectTraceInterface) {
|
|
81
|
+
const ephemeralForest = await AvmEphemeralForest.create(worldStateDB.getMerkleInterface());
|
|
82
|
+
return new AvmPersistableStateManager(
|
|
83
|
+
worldStateDB,
|
|
84
|
+
trace,
|
|
85
|
+
/*publicStorage=*/ new PublicStorage(worldStateDB),
|
|
86
|
+
/*nullifiers=*/ new NullifierManager(worldStateDB),
|
|
87
|
+
/*doMerkleOperations=*/ true,
|
|
88
|
+
ephemeralForest,
|
|
79
89
|
);
|
|
80
90
|
}
|
|
81
91
|
|
|
@@ -89,6 +99,7 @@ export class AvmPersistableStateManager {
|
|
|
89
99
|
this.publicStorage.fork(),
|
|
90
100
|
this.nullifiers.fork(),
|
|
91
101
|
this.doMerkleOperations,
|
|
102
|
+
this.merkleTrees.fork(),
|
|
92
103
|
);
|
|
93
104
|
}
|
|
94
105
|
|
|
@@ -116,7 +127,11 @@ export class AvmPersistableStateManager {
|
|
|
116
127
|
|
|
117
128
|
private _merge(forkedState: AvmPersistableStateManager, reverted: boolean) {
|
|
118
129
|
// sanity check to avoid merging the same forked trace twice
|
|
119
|
-
assert(
|
|
130
|
+
assert(
|
|
131
|
+
!forkedState.alreadyMergedIntoParent,
|
|
132
|
+
'Cannot merge forked state that has already been merged into its parent!',
|
|
133
|
+
);
|
|
134
|
+
forkedState.alreadyMergedIntoParent = true;
|
|
120
135
|
this.publicStorage.acceptAndMerge(forkedState.publicStorage);
|
|
121
136
|
this.nullifiers.acceptAndMerge(forkedState.nullifiers);
|
|
122
137
|
this.trace.merge(forkedState.trace, reverted);
|
|
@@ -135,27 +150,18 @@ export class AvmPersistableStateManager {
|
|
|
135
150
|
this.publicStorage.write(contractAddress, slot, value);
|
|
136
151
|
const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot);
|
|
137
152
|
if (this.doMerkleOperations) {
|
|
138
|
-
const result = await this.merkleTrees.
|
|
139
|
-
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
140
|
-
[new PublicDataTreeLeaf(leafSlot, value).toBuffer()],
|
|
141
|
-
0,
|
|
142
|
-
);
|
|
153
|
+
const result = await this.merkleTrees.writePublicStorage(leafSlot, value);
|
|
143
154
|
assert(result !== undefined, 'Public data tree insertion error. You might want to disable skipMerkleOperations.');
|
|
144
155
|
this.log.debug(`Inserted public data tree leaf at leafSlot ${leafSlot}, value: ${value}`);
|
|
145
156
|
|
|
146
|
-
const lowLeafInfo = result.
|
|
147
|
-
const lowLeafPreimage =
|
|
157
|
+
const lowLeafInfo = result.lowWitness;
|
|
158
|
+
const lowLeafPreimage = result.lowWitness.preimage as PublicDataTreeLeafPreimage;
|
|
148
159
|
const lowLeafIndex = lowLeafInfo.index;
|
|
149
|
-
const lowLeafPath = lowLeafInfo.siblingPath
|
|
160
|
+
const lowLeafPath = lowLeafInfo.siblingPath;
|
|
161
|
+
|
|
162
|
+
const insertionPath = result.insertionPath;
|
|
163
|
+
const newLeafPreimage = result.newOrElementToUpdate.element as PublicDataTreeLeafPreimage;
|
|
150
164
|
|
|
151
|
-
const insertionPath = result.newSubtreeSiblingPath.toFields();
|
|
152
|
-
const newLeafPreimage = new PublicDataTreeLeafPreimage(
|
|
153
|
-
leafSlot,
|
|
154
|
-
value,
|
|
155
|
-
lowLeafPreimage.nextSlot,
|
|
156
|
-
lowLeafPreimage.nextIndex,
|
|
157
|
-
);
|
|
158
|
-
// FIXME: Why do we need to hint both preimages for public data writes, but not for nullifier insertions?
|
|
159
165
|
this.trace.tracePublicStorageWrite(
|
|
160
166
|
contractAddress,
|
|
161
167
|
slot,
|
|
@@ -179,23 +185,24 @@ export class AvmPersistableStateManager {
|
|
|
179
185
|
* @returns the latest value written to slot, or 0 if never written to before
|
|
180
186
|
*/
|
|
181
187
|
public async readStorage(contractAddress: AztecAddress, slot: Fr): Promise<Fr> {
|
|
182
|
-
const { value,
|
|
183
|
-
this.log.debug(
|
|
184
|
-
`Storage read (address=${contractAddress}, slot=${slot}): value=${value}, exists=${exists}, cached=${cached}`,
|
|
185
|
-
);
|
|
188
|
+
const { value, cached } = await this.publicStorage.read(contractAddress, slot);
|
|
189
|
+
this.log.debug(`Storage read (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`);
|
|
186
190
|
|
|
187
191
|
const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot);
|
|
188
192
|
|
|
189
193
|
if (this.doMerkleOperations) {
|
|
190
194
|
// Get leaf if present, low leaf if absent
|
|
191
195
|
// If leaf is present, hint/trace it. Otherwise, hint/trace the low leaf.
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
197
|
-
//
|
|
198
|
-
//
|
|
196
|
+
const {
|
|
197
|
+
preimage,
|
|
198
|
+
index: leafIndex,
|
|
199
|
+
update: exists,
|
|
200
|
+
} = await this.merkleTrees.getLeafOrLowLeafInfo(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot);
|
|
201
|
+
// The index and preimage here is either the low leaf or the leaf itself (depending on the value of update flag)
|
|
202
|
+
// In either case, we just want the sibling path to this leaf - it's up to the avm to distinguish if it's a low leaf or not
|
|
203
|
+
const leafPath = await this.merkleTrees.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex);
|
|
204
|
+
const leafPreimage = preimage as PublicDataTreeLeafPreimage;
|
|
205
|
+
|
|
199
206
|
this.log.debug(
|
|
200
207
|
`leafPreimage.nextSlot: ${leafPreimage.nextSlot}, leafPreimage.nextIndex: ${Number(leafPreimage.nextIndex)}`,
|
|
201
208
|
);
|
|
@@ -204,15 +211,16 @@ export class AvmPersistableStateManager {
|
|
|
204
211
|
if (!exists) {
|
|
205
212
|
// Sanity check that the leaf slot is skipped by low leaf when it doesn't exist
|
|
206
213
|
assert(
|
|
207
|
-
|
|
208
|
-
|
|
214
|
+
leafPreimage.slot.toBigInt() < leafSlot.toBigInt() &&
|
|
215
|
+
(leafPreimage.nextIndex === 0n || leafPreimage.nextSlot.toBigInt() > leafSlot.toBigInt()),
|
|
216
|
+
'Public data tree low leaf should skip the target leaf slot when the target leaf does not exist or is the max value.',
|
|
209
217
|
);
|
|
210
218
|
}
|
|
211
219
|
this.log.debug(
|
|
212
220
|
`Tracing storage leaf preimage slot=${slot}, leafSlot=${leafSlot}, value=${value}, nextKey=${leafPreimage.nextSlot}, nextIndex=${leafPreimage.nextIndex}`,
|
|
213
221
|
);
|
|
214
222
|
// On non-existence, AVM circuit will need to recognize that leafPreimage.slot != leafSlot,
|
|
215
|
-
// prove that this is a low leaf that skips leafSlot, and then prove
|
|
223
|
+
// prove that this is a low leaf that skips leafSlot, and then prove membership of the leaf.
|
|
216
224
|
this.trace.tracePublicStorageRead(contractAddress, slot, value, leafPreimage, new Fr(leafIndex), leafPath);
|
|
217
225
|
} else {
|
|
218
226
|
this.trace.tracePublicStorageRead(contractAddress, slot, value);
|
|
@@ -229,10 +237,8 @@ export class AvmPersistableStateManager {
|
|
|
229
237
|
* @returns the latest value written to slot, or 0 if never written to before
|
|
230
238
|
*/
|
|
231
239
|
public async peekStorage(contractAddress: AztecAddress, slot: Fr): Promise<Fr> {
|
|
232
|
-
const { value,
|
|
233
|
-
this.log.debug(
|
|
234
|
-
`Storage peek (address=${contractAddress}, slot=${slot}): value=${value}, exists=${exists}, cached=${cached}`,
|
|
235
|
-
);
|
|
240
|
+
const { value, cached } = await this.publicStorage.read(contractAddress, slot);
|
|
241
|
+
this.log.debug(`Storage peek (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`);
|
|
236
242
|
return Promise.resolve(value);
|
|
237
243
|
}
|
|
238
244
|
|
|
@@ -255,7 +261,7 @@ export class AvmPersistableStateManager {
|
|
|
255
261
|
// TODO(8287): We still return exists here, but we need to transmit both the requested noteHash and the gotLeafValue
|
|
256
262
|
// such that the VM can constrain the equality and decide on exists based on that.
|
|
257
263
|
const path = await this.merkleTrees.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex.toBigInt());
|
|
258
|
-
this.trace.traceNoteHashCheck(contractAddress, gotLeafValue, leafIndex, exists, path
|
|
264
|
+
this.trace.traceNoteHashCheck(contractAddress, gotLeafValue, leafIndex, exists, path);
|
|
259
265
|
} else {
|
|
260
266
|
this.trace.traceNoteHashCheck(contractAddress, gotLeafValue, leafIndex, exists);
|
|
261
267
|
}
|
|
@@ -266,19 +272,15 @@ export class AvmPersistableStateManager {
|
|
|
266
272
|
* Write a note hash, trace the write.
|
|
267
273
|
* @param noteHash - the unsiloed note hash to write
|
|
268
274
|
*/
|
|
269
|
-
public
|
|
275
|
+
public writeNoteHash(contractAddress: AztecAddress, noteHash: Fr): void {
|
|
270
276
|
this.log.debug(`noteHashes(${contractAddress}) += @${noteHash}.`);
|
|
271
277
|
|
|
272
278
|
if (this.doMerkleOperations) {
|
|
273
|
-
//
|
|
274
|
-
const
|
|
275
|
-
const leafIndex = new Fr(info.size + 1n);
|
|
276
|
-
|
|
277
|
-
const path = await this.merkleTrees.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex.toBigInt());
|
|
279
|
+
// Should write a helper for this
|
|
280
|
+
const leafIndex = new Fr(this.merkleTrees.treeMap.get(MerkleTreeId.NOTE_HASH_TREE)!.leafCount);
|
|
278
281
|
const siloedNoteHash = siloNoteHash(contractAddress, noteHash);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
this.trace.traceNewNoteHash(contractAddress, noteHash, leafIndex, path.toFields());
|
|
282
|
+
const insertionPath = this.merkleTrees.appendNoteHash(siloedNoteHash);
|
|
283
|
+
this.trace.traceNewNoteHash(contractAddress, noteHash, leafIndex, insertionPath);
|
|
282
284
|
} else {
|
|
283
285
|
this.trace.traceNewNoteHash(contractAddress, noteHash);
|
|
284
286
|
}
|
|
@@ -298,15 +300,15 @@ export class AvmPersistableStateManager {
|
|
|
298
300
|
if (this.doMerkleOperations) {
|
|
299
301
|
// Get leaf if present, low leaf if absent
|
|
300
302
|
// If leaf is present, hint/trace it. Otherwise, hint/trace the low leaf.
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
);
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
);
|
|
303
|
+
const {
|
|
304
|
+
preimage,
|
|
305
|
+
index: leafIndex,
|
|
306
|
+
update,
|
|
307
|
+
} = await this.merkleTrees.getLeafOrLowLeafInfo(MerkleTreeId.NULLIFIER_TREE, siloedNullifier);
|
|
308
|
+
const leafPreimage = preimage as NullifierLeafPreimage;
|
|
309
|
+
const leafPath = await this.merkleTrees.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, leafIndex);
|
|
310
|
+
|
|
311
|
+
assert(update == exists, 'WorldStateDB contains nullifier leaf, but merkle tree does not.... This is a bug!');
|
|
310
312
|
|
|
311
313
|
this.log.debug(
|
|
312
314
|
`nullifiers(${contractAddress})@${nullifier} ?? leafIndex: ${leafIndex}, exists: ${exists}, pending: ${isPending}.`,
|
|
@@ -346,40 +348,55 @@ export class AvmPersistableStateManager {
|
|
|
346
348
|
*/
|
|
347
349
|
public async writeNullifier(contractAddress: AztecAddress, nullifier: Fr) {
|
|
348
350
|
this.log.debug(`nullifiers(${contractAddress}) += ${nullifier}.`);
|
|
349
|
-
// Cache pending nullifiers for later access
|
|
350
|
-
await this.nullifiers.append(contractAddress, nullifier);
|
|
351
351
|
|
|
352
352
|
const siloedNullifier = siloNullifier(contractAddress, nullifier);
|
|
353
353
|
|
|
354
354
|
if (this.doMerkleOperations) {
|
|
355
|
+
// Maybe overkill, but we should check if the nullifier is already present in the tree before attempting to insert
|
|
356
|
+
// It might be better to catch the error from the insert operation
|
|
355
357
|
// Trace all nullifier creations, even duplicate insertions that fail
|
|
356
|
-
const
|
|
358
|
+
const { preimage, index, update } = await this.merkleTrees.getLeafOrLowLeafInfo(
|
|
357
359
|
MerkleTreeId.NULLIFIER_TREE,
|
|
358
|
-
siloedNullifier
|
|
360
|
+
siloedNullifier,
|
|
359
361
|
);
|
|
360
|
-
if (
|
|
361
|
-
this.log.verbose(`Nullifier already present in tree: ${nullifier} at index ${
|
|
362
|
+
if (update) {
|
|
363
|
+
this.log.verbose(`Nullifier already present in tree: ${nullifier} at index ${index}.`);
|
|
364
|
+
// If the nullifier is already present, we should not insert it again
|
|
365
|
+
// instead we provide the direct membership path
|
|
366
|
+
const path = await this.merkleTrees.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, index);
|
|
367
|
+
// This just becomes a nullifier read hint
|
|
368
|
+
this.trace.traceNullifierCheck(
|
|
369
|
+
contractAddress,
|
|
370
|
+
nullifier,
|
|
371
|
+
/*exists=*/ update,
|
|
372
|
+
preimage as NullifierLeafPreimage,
|
|
373
|
+
new Fr(index),
|
|
374
|
+
path,
|
|
375
|
+
);
|
|
376
|
+
throw new NullifierCollisionError(
|
|
377
|
+
`Nullifier ${nullifier} at contract ${contractAddress} already exists in parent cache or host.`,
|
|
378
|
+
);
|
|
379
|
+
} else {
|
|
380
|
+
// Cache pending nullifiers for later access
|
|
381
|
+
await this.nullifiers.append(contractAddress, nullifier);
|
|
382
|
+
// We append the new nullifier
|
|
383
|
+
const appendResult = await this.merkleTrees.appendNullifier(siloedNullifier);
|
|
384
|
+
const lowLeafPreimage = appendResult.lowWitness.preimage as NullifierLeafPreimage;
|
|
385
|
+
const lowLeafIndex = appendResult.lowWitness.index;
|
|
386
|
+
const lowLeafPath = appendResult.lowWitness.siblingPath;
|
|
387
|
+
const insertionPath = appendResult.insertionPath;
|
|
388
|
+
this.trace.traceNewNullifier(
|
|
389
|
+
contractAddress,
|
|
390
|
+
nullifier,
|
|
391
|
+
lowLeafPreimage,
|
|
392
|
+
new Fr(lowLeafIndex),
|
|
393
|
+
lowLeafPath,
|
|
394
|
+
insertionPath,
|
|
395
|
+
);
|
|
362
396
|
}
|
|
363
|
-
const insertionResult = await this.merkleTrees.batchInsert(
|
|
364
|
-
MerkleTreeId.NULLIFIER_TREE,
|
|
365
|
-
[siloedNullifier.toBuffer()],
|
|
366
|
-
0,
|
|
367
|
-
);
|
|
368
|
-
const lowLeafInfo = insertionResult.lowLeavesWitnessData![0];
|
|
369
|
-
const lowLeafPreimage = lowLeafInfo.leafPreimage as NullifierLeafPreimage;
|
|
370
|
-
const lowLeafIndex = lowLeafInfo.index;
|
|
371
|
-
const lowLeafPath = lowLeafInfo.siblingPath.toFields();
|
|
372
|
-
const insertionPath = insertionResult.newSubtreeSiblingPath.toFields();
|
|
373
|
-
|
|
374
|
-
this.trace.traceNewNullifier(
|
|
375
|
-
contractAddress,
|
|
376
|
-
nullifier,
|
|
377
|
-
lowLeafPreimage,
|
|
378
|
-
new Fr(lowLeafIndex),
|
|
379
|
-
lowLeafPath,
|
|
380
|
-
insertionPath,
|
|
381
|
-
);
|
|
382
397
|
} else {
|
|
398
|
+
// Cache pending nullifiers for later access
|
|
399
|
+
await this.nullifiers.append(contractAddress, nullifier);
|
|
383
400
|
this.trace.traceNewNullifier(contractAddress, nullifier);
|
|
384
401
|
}
|
|
385
402
|
}
|
|
@@ -404,7 +421,11 @@ export class AvmPersistableStateManager {
|
|
|
404
421
|
if (this.doMerkleOperations) {
|
|
405
422
|
// TODO(8287): We still return exists here, but we need to transmit both the requested msgHash and the value
|
|
406
423
|
// such that the VM can constrain the equality and decide on exists based on that.
|
|
407
|
-
|
|
424
|
+
// We should defintely add a helper here
|
|
425
|
+
const path = await this.merkleTrees.treeDb.getSiblingPath(
|
|
426
|
+
MerkleTreeId.L1_TO_L2_MESSAGE_TREE,
|
|
427
|
+
msgLeafIndex.toBigInt(),
|
|
428
|
+
);
|
|
408
429
|
this.trace.traceL1ToL2MessageCheck(contractAddress, valueAtIndex, msgLeafIndex, exists, path.toFields());
|
|
409
430
|
} else {
|
|
410
431
|
this.trace.traceL1ToL2MessageCheck(contractAddress, valueAtIndex, msgLeafIndex, exists);
|
|
@@ -504,7 +525,6 @@ export class AvmPersistableStateManager {
|
|
|
504
525
|
forkedState: AvmPersistableStateManager,
|
|
505
526
|
nestedEnvironment: AvmExecutionEnvironment,
|
|
506
527
|
startGasLeft: Gas,
|
|
507
|
-
endGasLeft: Gas,
|
|
508
528
|
bytecode: Buffer,
|
|
509
529
|
avmCallResults: AvmContractCallResult,
|
|
510
530
|
) {
|
|
@@ -521,7 +541,6 @@ export class AvmPersistableStateManager {
|
|
|
521
541
|
forkedState.trace,
|
|
522
542
|
nestedEnvironment,
|
|
523
543
|
startGasLeft,
|
|
524
|
-
endGasLeft,
|
|
525
544
|
bytecode,
|
|
526
545
|
avmCallResults,
|
|
527
546
|
functionName,
|
|
@@ -532,30 +551,3 @@ export class AvmPersistableStateManager {
|
|
|
532
551
|
this.trace.traceEnqueuedCall(publicCallRequest, calldata, reverted);
|
|
533
552
|
}
|
|
534
553
|
}
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* Get leaf if present, low leaf if absent
|
|
538
|
-
*/
|
|
539
|
-
export async function getLeafOrLowLeaf<TreePreimageType extends IndexedTreeLeafPreimage>(
|
|
540
|
-
treeId: IndexedTreeId,
|
|
541
|
-
key: bigint,
|
|
542
|
-
merkleTrees: MerkleTreeWriteOperations,
|
|
543
|
-
) {
|
|
544
|
-
// "key" is siloed slot (leafSlot) or siloed nullifier
|
|
545
|
-
const previousValueIndex = await merkleTrees.getPreviousValueIndex(treeId, key);
|
|
546
|
-
assert(
|
|
547
|
-
previousValueIndex !== undefined,
|
|
548
|
-
`${MerkleTreeId[treeId]} low leaf index should always be found (even if target leaf does not exist)`,
|
|
549
|
-
);
|
|
550
|
-
const { index: leafIndex, alreadyPresent } = previousValueIndex;
|
|
551
|
-
|
|
552
|
-
const leafPreimage = await merkleTrees.getLeafPreimage(treeId, leafIndex);
|
|
553
|
-
assert(
|
|
554
|
-
leafPreimage !== undefined,
|
|
555
|
-
`${MerkleTreeId[treeId]} low leaf preimage should never be undefined (even if target leaf does not exist)`,
|
|
556
|
-
);
|
|
557
|
-
|
|
558
|
-
const leafPath = await merkleTrees.getSiblingPath(treeId, leafIndex);
|
|
559
|
-
|
|
560
|
-
return [leafIndex, leafPreimage as TreePreimageType, leafPath.toFields(), alreadyPresent] as const;
|
|
561
|
-
}
|
|
@@ -5,7 +5,6 @@ import type { PublicStateDB } from '../../index.js';
|
|
|
5
5
|
|
|
6
6
|
type PublicStorageReadResult = {
|
|
7
7
|
value: Fr;
|
|
8
|
-
exists: boolean;
|
|
9
8
|
cached: boolean;
|
|
10
9
|
};
|
|
11
10
|
|
|
@@ -77,17 +76,17 @@ export class PublicStorage {
|
|
|
77
76
|
let value = this.readHereOrParent(contractAddress, slot);
|
|
78
77
|
// Finally try the host's Aztec state (a trip to the database)
|
|
79
78
|
if (!value) {
|
|
80
|
-
|
|
79
|
+
// This functions returns Fr.ZERO if it has never been written to before
|
|
80
|
+
// we explicity coalesce to Fr.ZERO in case we have some implementations that cause this to return undefined
|
|
81
|
+
value = (await this.hostPublicStorage.storageRead(contractAddress, slot)) ?? Fr.ZERO;
|
|
81
82
|
// TODO(dbanks12): if value retrieved from host storage, we can cache it here
|
|
82
83
|
// any future reads to the same slot can read from cache instead of more expensive
|
|
83
84
|
// DB access
|
|
84
85
|
} else {
|
|
85
86
|
cached = true;
|
|
86
87
|
}
|
|
87
|
-
// if value is
|
|
88
|
-
|
|
89
|
-
const valueOrZero = exists ? value : Fr.ZERO;
|
|
90
|
-
return Promise.resolve({ value: valueOrZero, exists, cached });
|
|
88
|
+
// if value is Fr.ZERO here, it that means this slot has never been written to!
|
|
89
|
+
return Promise.resolve({ value, cached });
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
/**
|
|
@@ -70,7 +70,7 @@ export class EmitNoteHash extends Instruction {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const noteHash = memory.get(noteHashOffset).toFr();
|
|
73
|
-
|
|
73
|
+
context.persistableState.writeNoteHash(context.environment.address, noteHash);
|
|
74
74
|
|
|
75
75
|
memory.assert({ reads: 1, addressing });
|
|
76
76
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { strict as assert } from 'assert';
|
|
2
2
|
|
|
3
|
-
import { type TaggedMemoryInterface } from '../avm_memory_types.js';
|
|
3
|
+
import { TaggedMemory, type TaggedMemoryInterface } from '../avm_memory_types.js';
|
|
4
|
+
import { AddressOutOfRangeError } from '../errors.js';
|
|
4
5
|
|
|
5
6
|
export enum AddressingMode {
|
|
6
7
|
DIRECT = 0,
|
|
@@ -63,7 +64,11 @@ export class Addressing {
|
|
|
63
64
|
resolved[i] = offset;
|
|
64
65
|
if (mode & AddressingMode.RELATIVE) {
|
|
65
66
|
mem.checkIsValidMemoryOffsetTag(0);
|
|
66
|
-
|
|
67
|
+
const baseAddr = Number(mem.get(0).toBigInt());
|
|
68
|
+
resolved[i] += baseAddr;
|
|
69
|
+
if (resolved[i] >= TaggedMemory.MAX_MEMORY_SIZE) {
|
|
70
|
+
throw new AddressOutOfRangeError(baseAddr, offset);
|
|
71
|
+
}
|
|
67
72
|
}
|
|
68
73
|
if (mode & AddressingMode.INDIRECT) {
|
|
69
74
|
mem.checkIsValidMemoryOffsetTag(resolved[i]);
|
|
@@ -2,7 +2,6 @@ import { Fr, FunctionSelector, Gas, PUBLIC_DISPATCH_SELECTOR } from '@aztec/circ
|
|
|
2
2
|
|
|
3
3
|
import type { AvmContext } from '../avm_context.js';
|
|
4
4
|
import { type AvmContractCallResult } from '../avm_contract_call_result.js';
|
|
5
|
-
import { gasLeftToGas } from '../avm_gas.js';
|
|
6
5
|
import { type Field, TypeTag, Uint1 } from '../avm_memory_types.js';
|
|
7
6
|
import { AvmSimulator } from '../avm_simulator.js';
|
|
8
7
|
import { Opcode, OperandType } from '../serialization/instruction_serialization.js';
|
|
@@ -95,7 +94,7 @@ abstract class ExternalCall extends Instruction {
|
|
|
95
94
|
memory.set(successOffset, new Uint1(success ? 1 : 0));
|
|
96
95
|
|
|
97
96
|
// Refund unused gas
|
|
98
|
-
context.machineState.refundGas(
|
|
97
|
+
context.machineState.refundGas(nestedCallResults.gasLeft);
|
|
99
98
|
|
|
100
99
|
// Merge nested call's state and trace based on whether it succeeded.
|
|
101
100
|
if (success) {
|
|
@@ -107,7 +106,6 @@ abstract class ExternalCall extends Instruction {
|
|
|
107
106
|
/*nestedState=*/ nestedContext.persistableState,
|
|
108
107
|
/*nestedEnvironment=*/ nestedContext.environment,
|
|
109
108
|
/*startGasLeft=*/ Gas.from(allocatedGas),
|
|
110
|
-
/*endGasLeft=*/ Gas.from(nestedContext.machineState.gasLeft),
|
|
111
109
|
/*bytecode=*/ simulator.getBytecode()!,
|
|
112
110
|
/*avmCallResults=*/ nestedCallResults,
|
|
113
111
|
);
|
|
@@ -94,7 +94,7 @@ export abstract class Instruction {
|
|
|
94
94
|
* Computes gas cost for the instruction based on its base cost and memory operations.
|
|
95
95
|
* @returns Gas cost.
|
|
96
96
|
*/
|
|
97
|
-
|
|
97
|
+
public gasCost(dynMultiplier: number = 0): Gas {
|
|
98
98
|
const baseGasCost = getBaseGasCost(this.opcode);
|
|
99
99
|
const dynGasCost = mulGas(getDynamicGasCost(this.opcode), dynMultiplier);
|
|
100
100
|
return sumGas(baseGasCost, dynGasCost);
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { type Fr } from '@aztec/foundation/fields';
|
|
|
12
12
|
|
|
13
13
|
import { assert } from 'console';
|
|
14
14
|
|
|
15
|
-
import { type AvmContractCallResult } from '../avm/avm_contract_call_result.js';
|
|
15
|
+
import { type AvmContractCallResult, type AvmFinalizedCallResult } from '../avm/avm_contract_call_result.js';
|
|
16
16
|
import { type AvmExecutionEnvironment } from '../avm/avm_execution_environment.js';
|
|
17
17
|
import { type PublicEnqueuedCallSideEffectTrace } from './enqueued_call_side_effect_trace.js';
|
|
18
18
|
import { type EnqueuedPublicCallExecutionResultWithSideEffects, type PublicFunctionCallResult } from './execution.js';
|
|
@@ -191,8 +191,6 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface {
|
|
|
191
191
|
nestedEnvironment: AvmExecutionEnvironment,
|
|
192
192
|
/** How much gas was available for this public execution. */
|
|
193
193
|
startGasLeft: Gas,
|
|
194
|
-
/** How much gas was left after this public execution. */
|
|
195
|
-
endGasLeft: Gas,
|
|
196
194
|
/** Bytecode used for this execution. */
|
|
197
195
|
bytecode: Buffer,
|
|
198
196
|
/** The call's results */
|
|
@@ -204,7 +202,6 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface {
|
|
|
204
202
|
nestedCallTrace.innerCallTrace,
|
|
205
203
|
nestedEnvironment,
|
|
206
204
|
startGasLeft,
|
|
207
|
-
endGasLeft,
|
|
208
205
|
bytecode,
|
|
209
206
|
avmCallResults,
|
|
210
207
|
functionName,
|
|
@@ -213,7 +210,6 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface {
|
|
|
213
210
|
nestedCallTrace.enqueuedCallTrace,
|
|
214
211
|
nestedEnvironment,
|
|
215
212
|
startGasLeft,
|
|
216
|
-
endGasLeft,
|
|
217
213
|
bytecode,
|
|
218
214
|
avmCallResults,
|
|
219
215
|
functionName,
|
|
@@ -235,12 +231,10 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface {
|
|
|
235
231
|
* Convert this trace to a PublicExecutionResult for use externally to the simulator.
|
|
236
232
|
*/
|
|
237
233
|
public toPublicEnqueuedCallExecutionResult(
|
|
238
|
-
/** How much gas was left after this public execution. */
|
|
239
|
-
endGasLeft: Gas,
|
|
240
234
|
/** The call's results */
|
|
241
|
-
avmCallResults:
|
|
235
|
+
avmCallResults: AvmFinalizedCallResult,
|
|
242
236
|
): EnqueuedPublicCallExecutionResultWithSideEffects {
|
|
243
|
-
return this.enqueuedCallTrace.toPublicEnqueuedCallExecutionResult(
|
|
237
|
+
return this.enqueuedCallTrace.toPublicEnqueuedCallExecutionResult(avmCallResults);
|
|
244
238
|
}
|
|
245
239
|
/**
|
|
246
240
|
* Convert this trace to a PublicExecutionResult for use externally to the simulator.
|
|
@@ -250,19 +244,16 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface {
|
|
|
250
244
|
avmEnvironment: AvmExecutionEnvironment,
|
|
251
245
|
/** How much gas was available for this public execution. */
|
|
252
246
|
startGasLeft: Gas,
|
|
253
|
-
/** How much gas was left after this public execution. */
|
|
254
|
-
endGasLeft: Gas,
|
|
255
247
|
/** Bytecode used for this execution. */
|
|
256
248
|
bytecode: Buffer,
|
|
257
249
|
/** The call's results */
|
|
258
|
-
avmCallResults:
|
|
250
|
+
avmCallResults: AvmFinalizedCallResult,
|
|
259
251
|
/** Function name for logging */
|
|
260
252
|
functionName: string = 'unknown',
|
|
261
253
|
): PublicFunctionCallResult {
|
|
262
254
|
return this.innerCallTrace.toPublicFunctionCallResult(
|
|
263
255
|
avmEnvironment,
|
|
264
256
|
startGasLeft,
|
|
265
|
-
endGasLeft,
|
|
266
257
|
bytecode,
|
|
267
258
|
avmCallResults,
|
|
268
259
|
functionName,
|