@aztec/simulator 0.24.0 → 0.26.2
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/acvm/deserialize.d.ts +5 -0
- package/dest/acvm/deserialize.d.ts.map +1 -1
- package/dest/acvm/deserialize.js +8 -1
- package/dest/acvm/oracle/oracle.d.ts +5 -4
- package/dest/acvm/oracle/oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/oracle.js +24 -11
- package/dest/acvm/oracle/typed_oracle.d.ts +7 -9
- package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/typed_oracle.js +9 -9
- package/dest/avm/avm_context.d.ts +4 -4
- package/dest/avm/avm_context.d.ts.map +1 -1
- package/dest/avm/avm_context.js +6 -6
- package/dest/avm/avm_memory_types.d.ts +11 -2
- package/dest/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/avm/avm_memory_types.js +11 -1
- package/dest/avm/avm_simulator.d.ts +6 -4
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +17 -18
- package/dest/avm/fixtures/index.d.ts +17 -5
- package/dest/avm/fixtures/index.d.ts.map +1 -1
- package/dest/avm/fixtures/index.js +19 -8
- package/dest/avm/journal/host_storage.d.ts.map +1 -1
- package/dest/avm/journal/host_storage.js +1 -1
- package/dest/avm/journal/journal.d.ts +78 -50
- package/dest/avm/journal/journal.d.ts.map +1 -1
- package/dest/avm/journal/journal.js +125 -169
- package/dest/avm/journal/nullifiers.d.ts +85 -0
- package/dest/avm/journal/nullifiers.d.ts.map +1 -0
- package/dest/avm/journal/nullifiers.js +147 -0
- package/dest/avm/journal/public_storage.d.ts +88 -0
- package/dest/avm/journal/public_storage.d.ts.map +1 -0
- package/dest/avm/journal/public_storage.js +135 -0
- package/dest/avm/journal/trace.d.ts +43 -0
- package/dest/avm/journal/trace.d.ts.map +1 -0
- package/dest/avm/journal/trace.js +204 -0
- package/dest/avm/journal/trace_types.d.ts +26 -0
- package/dest/avm/journal/trace_types.d.ts.map +1 -0
- package/dest/avm/journal/trace_types.js +6 -0
- package/dest/avm/opcodes/accrued_substate.d.ts +37 -4
- package/dest/avm/opcodes/accrued_substate.d.ts.map +1 -1
- package/dest/avm/opcodes/accrued_substate.js +109 -12
- package/dest/avm/opcodes/comparators.d.ts.map +1 -1
- package/dest/avm/opcodes/comparators.js +5 -8
- package/dest/avm/opcodes/environment_getters.d.ts +14 -13
- package/dest/avm/opcodes/environment_getters.d.ts.map +1 -1
- package/dest/avm/opcodes/environment_getters.js +1 -1
- package/dest/avm/opcodes/external_calls.js +5 -5
- package/dest/avm/opcodes/hashing.d.ts +48 -0
- package/dest/avm/opcodes/hashing.d.ts.map +1 -0
- package/dest/avm/opcodes/hashing.js +127 -0
- package/dest/avm/opcodes/memory.d.ts.map +1 -1
- package/dest/avm/opcodes/memory.js +1 -1
- package/dest/avm/opcodes/storage.d.ts.map +1 -1
- package/dest/avm/opcodes/storage.js +3 -3
- package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/bytecode_serialization.js +12 -8
- package/dest/avm/serialization/instruction_serialization.d.ts +10 -7
- package/dest/avm/serialization/instruction_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/instruction_serialization.js +12 -9
- package/dest/avm/temporary_executor_migration.d.ts.map +1 -1
- package/dest/avm/temporary_executor_migration.js +5 -5
- package/dest/client/client_execution_context.d.ts +9 -5
- package/dest/client/client_execution_context.d.ts.map +1 -1
- package/dest/client/client_execution_context.js +46 -24
- package/dest/client/db_oracle.d.ts +7 -0
- package/dest/client/db_oracle.d.ts.map +1 -1
- package/dest/client/db_oracle.js +1 -1
- package/dest/client/execution_note_cache.js +1 -1
- package/dest/client/execution_result.d.ts +2 -2
- package/dest/client/execution_result.d.ts.map +1 -1
- package/dest/client/private_execution.d.ts.map +1 -1
- package/dest/client/private_execution.js +4 -4
- package/dest/client/simulator.d.ts +1 -1
- package/dest/client/simulator.d.ts.map +1 -1
- package/dest/client/simulator.js +3 -2
- package/dest/client/view_data_oracle.d.ts +9 -2
- package/dest/client/view_data_oracle.d.ts.map +1 -1
- package/dest/client/view_data_oracle.js +13 -5
- package/dest/public/db.d.ts +17 -4
- package/dest/public/db.d.ts.map +1 -1
- package/dest/public/execution.d.ts +9 -4
- package/dest/public/execution.d.ts.map +1 -1
- package/dest/public/execution.js +17 -4
- package/dest/public/executor.d.ts.map +1 -1
- package/dest/public/executor.js +18 -9
- package/dest/public/public_execution_context.d.ts +5 -4
- package/dest/public/public_execution_context.d.ts.map +1 -1
- package/dest/public/public_execution_context.js +23 -12
- package/dest/public/state_actions.js +2 -2
- package/dest/test/utils.js +4 -4
- package/dest/utils.js +2 -3
- package/package.json +6 -5
- package/src/acvm/deserialize.ts +8 -0
- package/src/acvm/oracle/oracle.ts +30 -6
- package/src/acvm/oracle/typed_oracle.ts +13 -5
- package/src/avm/avm_context.ts +5 -5
- package/src/avm/avm_memory_types.ts +18 -3
- package/src/avm/avm_simulator.ts +22 -24
- package/src/avm/fixtures/index.ts +34 -9
- package/src/avm/journal/host_storage.ts +5 -11
- package/src/avm/journal/journal.ts +147 -182
- package/src/avm/journal/nullifiers.ts +170 -0
- package/src/avm/journal/public_storage.ts +149 -0
- package/src/avm/journal/trace.ts +223 -0
- package/src/avm/journal/trace_types.ts +79 -0
- package/src/avm/opcodes/accrued_substate.ts +132 -10
- package/src/avm/opcodes/comparators.ts +4 -7
- package/src/avm/opcodes/environment_getters.ts +15 -13
- package/src/avm/opcodes/external_calls.ts +4 -4
- package/src/avm/opcodes/hashing.ts +170 -0
- package/src/avm/opcodes/memory.ts +1 -0
- package/src/avm/opcodes/storage.ts +5 -2
- package/src/avm/serialization/bytecode_serialization.ts +13 -6
- package/src/avm/serialization/instruction_serialization.ts +6 -3
- package/src/avm/temporary_executor_migration.ts +4 -3
- package/src/client/client_execution_context.ts +53 -23
- package/src/client/db_oracle.ts +8 -0
- package/src/client/execution_note_cache.ts +1 -1
- package/src/client/execution_result.ts +2 -2
- package/src/client/private_execution.ts +5 -4
- package/src/client/simulator.ts +2 -1
- package/src/client/view_data_oracle.ts +14 -4
- package/src/public/db.ts +19 -4
- package/src/public/execution.ts +30 -6
- package/src/public/executor.ts +29 -9
- package/src/public/public_execution_context.ts +36 -12
- package/src/public/state_actions.ts +1 -1
- package/src/test/utils.ts +3 -3
- package/src/utils.ts +1 -1
|
@@ -1,15 +1,26 @@
|
|
|
1
|
+
import { UnencryptedL2Log } from '@aztec/circuit-types';
|
|
2
|
+
import { AztecAddress, EthAddress, L2ToL1Message } from '@aztec/circuits.js';
|
|
3
|
+
import { EventSelector } from '@aztec/foundation/abi';
|
|
1
4
|
import { Fr } from '@aztec/foundation/fields';
|
|
2
5
|
|
|
3
6
|
import { HostStorage } from './host_storage.js';
|
|
7
|
+
import { Nullifiers } from './nullifiers.js';
|
|
8
|
+
import { PublicStorage } from './public_storage.js';
|
|
9
|
+
import { WorldStateAccessTrace } from './trace.js';
|
|
10
|
+
import { TracedL1toL2MessageCheck, TracedNoteHashCheck, TracedNullifierCheck } from './trace_types.js';
|
|
4
11
|
|
|
5
12
|
/**
|
|
6
13
|
* Data held within the journal
|
|
7
14
|
*/
|
|
8
15
|
export type JournalData = {
|
|
16
|
+
noteHashChecks: TracedNoteHashCheck[];
|
|
9
17
|
newNoteHashes: Fr[];
|
|
18
|
+
nullifierChecks: TracedNullifierCheck[];
|
|
10
19
|
newNullifiers: Fr[];
|
|
11
|
-
|
|
12
|
-
|
|
20
|
+
l1ToL2MessageChecks: TracedL1toL2MessageCheck[];
|
|
21
|
+
|
|
22
|
+
newL1Messages: L2ToL1Message[];
|
|
23
|
+
newLogs: UnencryptedL2Log[];
|
|
13
24
|
|
|
14
25
|
/** contract address -\> key -\> value */
|
|
15
26
|
currentStorageValue: Map<bigint, Map<bigint, Fr>>;
|
|
@@ -21,167 +32,181 @@ export type JournalData = {
|
|
|
21
32
|
};
|
|
22
33
|
|
|
23
34
|
/**
|
|
24
|
-
* A
|
|
25
|
-
*
|
|
35
|
+
* A class to manage persistable AVM state for contract calls.
|
|
36
|
+
* Maintains a cache of the current world state,
|
|
37
|
+
* a trace of all world state accesses, and a list of accrued substate items.
|
|
38
|
+
*
|
|
39
|
+
* The simulator should make any world state and accrued substate queries through this object.
|
|
26
40
|
*
|
|
27
|
-
*
|
|
28
|
-
* When a call fails, it's journal is discarded and the parent is used from this point forward
|
|
29
|
-
* When a call succeeds's we can merge a child into its parent
|
|
41
|
+
* Manages merging of successful/reverted child state into current state.
|
|
30
42
|
*/
|
|
31
|
-
export class
|
|
43
|
+
export class AvmPersistableStateManager {
|
|
32
44
|
/** Reference to node storage */
|
|
33
45
|
public readonly hostStorage: HostStorage;
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// New written state
|
|
41
|
-
private newNoteHashes: Fr[] = [];
|
|
42
|
-
private newNullifiers: Fr[] = [];
|
|
43
|
-
|
|
44
|
-
// New Substate
|
|
45
|
-
private newL1Messages: Fr[][] = [];
|
|
46
|
-
private newLogs: Fr[][] = [];
|
|
47
|
+
/** World State */
|
|
48
|
+
/** Public storage, including cached writes */
|
|
49
|
+
private publicStorage: PublicStorage;
|
|
50
|
+
/** Nullifier set, including cached/recently-emitted nullifiers */
|
|
51
|
+
private nullifiers: Nullifiers;
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
private
|
|
53
|
+
/** World State Access Trace */
|
|
54
|
+
private trace: WorldStateAccessTrace;
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
/** Accrued Substate **/
|
|
57
|
+
private newL1Messages: L2ToL1Message[] = [];
|
|
58
|
+
private newLogs: UnencryptedL2Log[] = [];
|
|
52
59
|
|
|
53
|
-
constructor(hostStorage: HostStorage,
|
|
60
|
+
constructor(hostStorage: HostStorage, parent?: AvmPersistableStateManager) {
|
|
54
61
|
this.hostStorage = hostStorage;
|
|
55
|
-
this.
|
|
62
|
+
this.publicStorage = new PublicStorage(hostStorage.publicStateDb, parent?.publicStorage);
|
|
63
|
+
this.nullifiers = new Nullifiers(hostStorage.commitmentsDb, parent?.nullifiers);
|
|
64
|
+
this.trace = new WorldStateAccessTrace(parent?.trace);
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
/**
|
|
59
|
-
* Create a new
|
|
68
|
+
* Create a new state manager forked from this one
|
|
60
69
|
*/
|
|
61
70
|
public fork() {
|
|
62
|
-
return new
|
|
71
|
+
return new AvmPersistableStateManager(this.hostStorage, this);
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
/**
|
|
66
|
-
* Write storage
|
|
75
|
+
* Write to public storage, journal/trace the write.
|
|
67
76
|
*
|
|
68
|
-
* @param
|
|
69
|
-
* @param
|
|
70
|
-
* @param value -
|
|
77
|
+
* @param storageAddress - the address of the contract whose storage is being written to
|
|
78
|
+
* @param slot - the slot in the contract's storage being written to
|
|
79
|
+
* @param value - the value being written to the slot
|
|
71
80
|
*/
|
|
72
|
-
public writeStorage(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
contractMap.set(key.toBigInt(), value);
|
|
79
|
-
|
|
80
|
-
// We want to keep track of all performed writes in the journal
|
|
81
|
-
this.journalWrite(contractAddress, key, value);
|
|
81
|
+
public writeStorage(storageAddress: Fr, slot: Fr, value: Fr) {
|
|
82
|
+
// Cache storage writes for later reference/reads
|
|
83
|
+
this.publicStorage.write(storageAddress, slot, value);
|
|
84
|
+
// Trace all storage writes (even reverted ones)
|
|
85
|
+
this.trace.tracePublicStorageWrite(storageAddress, slot, value);
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
/**
|
|
85
|
-
* Read storage
|
|
86
|
-
* Read from host storage on cache miss
|
|
89
|
+
* Read from public storage, trace the read.
|
|
87
90
|
*
|
|
88
|
-
* @param
|
|
89
|
-
* @param
|
|
90
|
-
* @returns
|
|
91
|
+
* @param storageAddress - the address of the contract whose storage is being read from
|
|
92
|
+
* @param slot - the slot in the contract's storage being read from
|
|
93
|
+
* @returns the latest value written to slot, or 0 if never written to before
|
|
91
94
|
*/
|
|
92
|
-
public async readStorage(
|
|
93
|
-
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// Do not early return as we want to keep track of reads in this.storageReads
|
|
98
|
-
let value = this.currentStorageValue.get(contractAddress.toBigInt())?.get(key.toBigInt());
|
|
99
|
-
if (!value && this.parentJournal) {
|
|
100
|
-
value = await this.parentJournal?.readStorage(contractAddress, key);
|
|
101
|
-
}
|
|
102
|
-
if (!value) {
|
|
103
|
-
value = await this.hostStorage.publicStateDb.storageRead(contractAddress, key);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
this.journalRead(contractAddress, key, value);
|
|
95
|
+
public async readStorage(storageAddress: Fr, slot: Fr): Promise<Fr> {
|
|
96
|
+
const [_exists, value] = await this.publicStorage.read(storageAddress, slot);
|
|
97
|
+
// We want to keep track of all performed reads (even reverted ones)
|
|
98
|
+
this.trace.tracePublicStorageRead(storageAddress, slot, value);
|
|
107
99
|
return Promise.resolve(value);
|
|
108
100
|
}
|
|
109
101
|
|
|
102
|
+
// TODO(4886): We currently don't silo note hashes.
|
|
110
103
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
|
|
114
|
-
* @param
|
|
115
|
-
* @param
|
|
116
|
-
* @
|
|
104
|
+
* Check if a note hash exists at the given leaf index, trace the check.
|
|
105
|
+
*
|
|
106
|
+
* @param storageAddress - the address of the contract whose storage is being read from
|
|
107
|
+
* @param noteHash - the unsiloed note hash being checked
|
|
108
|
+
* @param leafIndex - the leaf index being checked
|
|
109
|
+
* @returns true if the note hash exists at the given leaf index, false otherwise
|
|
117
110
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
111
|
+
public async checkNoteHashExists(storageAddress: Fr, noteHash: Fr, leafIndex: Fr): Promise<boolean> {
|
|
112
|
+
const gotLeafIndex = await this.hostStorage.commitmentsDb.getCommitmentIndex(noteHash);
|
|
113
|
+
const exists = gotLeafIndex === leafIndex.toBigInt();
|
|
114
|
+
this.trace.traceNoteHashCheck(storageAddress, noteHash, exists, leafIndex);
|
|
115
|
+
return Promise.resolve(exists);
|
|
116
|
+
}
|
|
124
117
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Write a note hash, trace the write.
|
|
120
|
+
* @param noteHash - the unsiloed note hash to write
|
|
121
|
+
*/
|
|
122
|
+
public writeNoteHash(noteHash: Fr) {
|
|
123
|
+
this.trace.traceNewNoteHash(/*storageAddress*/ Fr.ZERO, noteHash);
|
|
131
124
|
}
|
|
132
125
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Check if a nullifier exists, trace the check.
|
|
128
|
+
* @param storageAddress - address of the contract that the nullifier is associated with
|
|
129
|
+
* @param nullifier - the unsiloed nullifier to check
|
|
130
|
+
* @returns exists - whether the nullifier exists in the nullifier set
|
|
131
|
+
*/
|
|
132
|
+
public async checkNullifierExists(storageAddress: Fr, nullifier: Fr): Promise<boolean> {
|
|
133
|
+
const [exists, isPending, leafIndex] = await this.nullifiers.checkExists(storageAddress, nullifier);
|
|
134
|
+
this.trace.traceNullifierCheck(storageAddress, nullifier, exists, isPending, leafIndex);
|
|
135
|
+
return Promise.resolve(exists);
|
|
136
|
+
}
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Write a nullifier to the nullifier set, trace the write.
|
|
140
|
+
* @param storageAddress - address of the contract that the nullifier is associated with
|
|
141
|
+
* @param nullifier - the unsiloed nullifier to write
|
|
142
|
+
*/
|
|
143
|
+
public async writeNullifier(storageAddress: Fr, nullifier: Fr) {
|
|
144
|
+
// Cache pending nullifiers for later access
|
|
145
|
+
await this.nullifiers.append(storageAddress, nullifier);
|
|
146
|
+
// Trace all nullifier creations (even reverted ones)
|
|
147
|
+
this.trace.traceNewNullifier(storageAddress, nullifier);
|
|
140
148
|
}
|
|
141
149
|
|
|
142
|
-
|
|
143
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Check if an L1 to L2 message exists, trace the check.
|
|
152
|
+
* @param msgHash - the message hash to check existence of
|
|
153
|
+
* @param msgLeafIndex - the message leaf index to use in the check
|
|
154
|
+
* @returns exists - whether the message exists in the L1 to L2 Messages tree
|
|
155
|
+
*/
|
|
156
|
+
public async checkL1ToL2MessageExists(msgHash: Fr, msgLeafIndex: Fr): Promise<boolean> {
|
|
157
|
+
let exists = false;
|
|
158
|
+
try {
|
|
159
|
+
const gotMessage = await this.hostStorage.commitmentsDb.getL1ToL2MembershipWitness(msgHash);
|
|
160
|
+
exists = gotMessage !== undefined && gotMessage.index == msgLeafIndex.toBigInt();
|
|
161
|
+
} catch {
|
|
162
|
+
// error getting message - doesn't exist!
|
|
163
|
+
exists = false;
|
|
164
|
+
}
|
|
165
|
+
this.trace.traceL1ToL2MessageCheck(msgHash, msgLeafIndex, exists);
|
|
166
|
+
return Promise.resolve(exists);
|
|
144
167
|
}
|
|
145
168
|
|
|
146
|
-
|
|
147
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Write an L2 to L1 message.
|
|
171
|
+
* @param recipient - L1 contract address to send the message to.
|
|
172
|
+
* @param content - Message content.
|
|
173
|
+
*/
|
|
174
|
+
public writeL1Message(recipient: EthAddress | Fr, content: Fr) {
|
|
175
|
+
const recipientAddress = recipient instanceof EthAddress ? recipient : EthAddress.fromField(recipient);
|
|
176
|
+
this.newL1Messages.push(new L2ToL1Message(recipientAddress, content));
|
|
148
177
|
}
|
|
149
178
|
|
|
150
|
-
public writeLog(log: Fr[]) {
|
|
151
|
-
this.newLogs.push(
|
|
179
|
+
public writeLog(contractAddress: Fr, event: Fr, log: Fr[]) {
|
|
180
|
+
this.newLogs.push(
|
|
181
|
+
new UnencryptedL2Log(
|
|
182
|
+
AztecAddress.fromField(contractAddress),
|
|
183
|
+
EventSelector.fromField(event),
|
|
184
|
+
Buffer.concat(log.map(f => f.toBuffer())),
|
|
185
|
+
),
|
|
186
|
+
);
|
|
152
187
|
}
|
|
153
188
|
|
|
154
189
|
/**
|
|
155
|
-
* Accept nested world state, merging in its
|
|
156
|
-
* - Utxo objects are concatenated
|
|
157
|
-
* - Public state changes are merged, with the value in the incoming journal taking precedent
|
|
158
|
-
* - Public state journals (r/w logs), with the accessing being appended in chronological order
|
|
190
|
+
* Accept nested world state modifications, merging in its trace and accrued substate
|
|
159
191
|
*/
|
|
160
|
-
public
|
|
161
|
-
// Merge
|
|
162
|
-
this.
|
|
163
|
-
this.newL1Messages = this.newL1Messages.concat(nestedJournal.newL1Messages);
|
|
164
|
-
this.newNullifiers = this.newNullifiers.concat(nestedJournal.newNullifiers);
|
|
165
|
-
this.newLogs = this.newLogs.concat(nestedJournal.newLogs);
|
|
192
|
+
public acceptNestedCallState(nestedJournal: AvmPersistableStateManager) {
|
|
193
|
+
// Merge Public Storage
|
|
194
|
+
this.publicStorage.acceptAndMerge(nestedJournal.publicStorage);
|
|
166
195
|
|
|
167
|
-
// Merge
|
|
168
|
-
|
|
196
|
+
// Merge World State Access Trace
|
|
197
|
+
this.trace.acceptAndMerge(nestedJournal.trace);
|
|
169
198
|
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
199
|
+
// Accrued Substate
|
|
200
|
+
this.newL1Messages = this.newL1Messages.concat(nestedJournal.newL1Messages);
|
|
201
|
+
this.newLogs = this.newLogs.concat(nestedJournal.newLogs);
|
|
173
202
|
}
|
|
174
203
|
|
|
175
204
|
/**
|
|
176
|
-
* Reject nested world state, merging in its
|
|
177
|
-
* - Utxo objects are concatenated
|
|
178
|
-
* - Public state changes are dropped
|
|
179
|
-
* - Public state journals (r/w logs) are maintained, with the accessing being appended in chronological order
|
|
205
|
+
* Reject nested world state, merging in its trace, but not accepting any state modifications
|
|
180
206
|
*/
|
|
181
|
-
public
|
|
182
|
-
// Merge
|
|
183
|
-
|
|
184
|
-
mergeContractJournalMaps(this.storageWrites, nestedJournal.storageWrites);
|
|
207
|
+
public rejectNestedCallState(nestedJournal: AvmPersistableStateManager) {
|
|
208
|
+
// Merge World State Access Trace
|
|
209
|
+
this.trace.acceptAndMerge(nestedJournal.trace);
|
|
185
210
|
}
|
|
186
211
|
|
|
187
212
|
/**
|
|
@@ -191,76 +216,16 @@ export class AvmWorldStateJournal {
|
|
|
191
216
|
*/
|
|
192
217
|
public flush(): JournalData {
|
|
193
218
|
return {
|
|
194
|
-
|
|
195
|
-
|
|
219
|
+
noteHashChecks: this.trace.noteHashChecks,
|
|
220
|
+
newNoteHashes: this.trace.newNoteHashes,
|
|
221
|
+
nullifierChecks: this.trace.nullifierChecks,
|
|
222
|
+
newNullifiers: this.trace.newNullifiers,
|
|
223
|
+
l1ToL2MessageChecks: this.trace.l1ToL2MessageChecks,
|
|
196
224
|
newL1Messages: this.newL1Messages,
|
|
197
225
|
newLogs: this.newLogs,
|
|
198
|
-
currentStorageValue: this.
|
|
199
|
-
storageReads: this.
|
|
200
|
-
storageWrites: this.
|
|
226
|
+
currentStorageValue: this.publicStorage.getCache().cachePerContract,
|
|
227
|
+
storageReads: this.trace.publicStorageReads,
|
|
228
|
+
storageWrites: this.trace.publicStorageWrites,
|
|
201
229
|
};
|
|
202
230
|
}
|
|
203
231
|
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Merges two contract current value together
|
|
207
|
-
* Where childMap keys will take precedent over the hostMap
|
|
208
|
-
* The assumption being that the child map is created at a later time
|
|
209
|
-
* And thus contains more up to date information
|
|
210
|
-
*
|
|
211
|
-
* @param hostMap - The map to be merged into
|
|
212
|
-
* @param childMap - The map to be merged from
|
|
213
|
-
*/
|
|
214
|
-
function mergeCurrentValueMaps(hostMap: Map<bigint, Map<bigint, Fr>>, childMap: Map<bigint, Map<bigint, Fr>>) {
|
|
215
|
-
for (const [key, value] of childMap) {
|
|
216
|
-
const map1Value = hostMap.get(key);
|
|
217
|
-
if (!map1Value) {
|
|
218
|
-
hostMap.set(key, value);
|
|
219
|
-
} else {
|
|
220
|
-
mergeStorageCurrentValueMaps(map1Value, value);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* @param hostMap - The map to be merge into
|
|
227
|
-
* @param childMap - The map to be merged from
|
|
228
|
-
*/
|
|
229
|
-
function mergeStorageCurrentValueMaps(hostMap: Map<bigint, Fr>, childMap: Map<bigint, Fr>) {
|
|
230
|
-
for (const [key, value] of childMap) {
|
|
231
|
-
hostMap.set(key, value);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Merges two contract journalling maps together
|
|
237
|
-
* For read maps, we just append the childMap arrays into the host map arrays, as the order is important
|
|
238
|
-
*
|
|
239
|
-
* @param hostMap - The map to be merged into
|
|
240
|
-
* @param childMap - The map to be merged from
|
|
241
|
-
*/
|
|
242
|
-
function mergeContractJournalMaps(hostMap: Map<bigint, Map<bigint, Fr[]>>, childMap: Map<bigint, Map<bigint, Fr[]>>) {
|
|
243
|
-
for (const [key, value] of childMap) {
|
|
244
|
-
const map1Value = hostMap.get(key);
|
|
245
|
-
if (!map1Value) {
|
|
246
|
-
hostMap.set(key, value);
|
|
247
|
-
} else {
|
|
248
|
-
mergeStorageJournalMaps(map1Value, value);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* @param hostMap - The map to be merge into
|
|
255
|
-
* @param childMap - The map to be merged from
|
|
256
|
-
*/
|
|
257
|
-
function mergeStorageJournalMaps(hostMap: Map<bigint, Fr[]>, childMap: Map<bigint, Fr[]>) {
|
|
258
|
-
for (const [key, value] of childMap) {
|
|
259
|
-
const readArr = hostMap.get(key);
|
|
260
|
-
if (!readArr) {
|
|
261
|
-
hostMap.set(key, value);
|
|
262
|
-
} else {
|
|
263
|
-
hostMap.set(key, readArr?.concat(...value));
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { siloNullifier } from '@aztec/circuits.js/hash';
|
|
2
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
3
|
+
|
|
4
|
+
import type { CommitmentsDB } from '../../index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A class to manage new nullifier staging and existence checks during a contract call's AVM simulation.
|
|
8
|
+
* Maintains a nullifier cache, and ensures that existence checks fall back to the correct source.
|
|
9
|
+
* When a contract call completes, its cached nullifier set can be merged into its parent's.
|
|
10
|
+
*/
|
|
11
|
+
export class Nullifiers {
|
|
12
|
+
/** Cached nullifiers. */
|
|
13
|
+
private cache: NullifierCache;
|
|
14
|
+
/** Parent's nullifier cache. Checked on cache-miss. */
|
|
15
|
+
private readonly parentCache: NullifierCache | undefined;
|
|
16
|
+
/** Reference to node storage. Checked on parent cache-miss. */
|
|
17
|
+
private readonly hostNullifiers: CommitmentsDB;
|
|
18
|
+
|
|
19
|
+
constructor(hostNullifiers: CommitmentsDB, parent?: Nullifiers) {
|
|
20
|
+
this.hostNullifiers = hostNullifiers;
|
|
21
|
+
this.parentCache = parent?.cache;
|
|
22
|
+
this.cache = new NullifierCache();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get a nullifier's existence status.
|
|
27
|
+
* 1. Check cache.
|
|
28
|
+
* 2. Check parent's cache.
|
|
29
|
+
* 3. Fall back to the host state.
|
|
30
|
+
* 4. Not found! Nullifier does not exist.
|
|
31
|
+
*
|
|
32
|
+
* @param storageAddress - the address of the contract whose storage is being read from
|
|
33
|
+
* @param nullifier - the nullifier to check for
|
|
34
|
+
* @returns exists: whether the nullifier exists at all,
|
|
35
|
+
* isPending: whether the nullifier was found in a cache,
|
|
36
|
+
* leafIndex: the nullifier's leaf index if it exists and is not pending (comes from host state).
|
|
37
|
+
*/
|
|
38
|
+
public async checkExists(
|
|
39
|
+
storageAddress: Fr,
|
|
40
|
+
nullifier: Fr,
|
|
41
|
+
): Promise<[/*exists=*/ boolean, /*isPending=*/ boolean, /*leafIndex=*/ Fr]> {
|
|
42
|
+
// First check this cache
|
|
43
|
+
let existsAsPending = this.cache.exists(storageAddress, nullifier);
|
|
44
|
+
// Then check parent's cache
|
|
45
|
+
if (!existsAsPending && this.parentCache) {
|
|
46
|
+
existsAsPending = this.parentCache?.exists(storageAddress, nullifier);
|
|
47
|
+
}
|
|
48
|
+
// Finally try the host's Aztec state (a trip to the database)
|
|
49
|
+
// If the value is found in the database, it will be associated with a leaf index!
|
|
50
|
+
let leafIndex: bigint | undefined = undefined;
|
|
51
|
+
if (!existsAsPending) {
|
|
52
|
+
// silo the nullifier before checking for its existence in the host
|
|
53
|
+
leafIndex = await this.hostNullifiers.getNullifierIndex(siloNullifier(storageAddress, nullifier));
|
|
54
|
+
}
|
|
55
|
+
const exists = existsAsPending || leafIndex !== undefined;
|
|
56
|
+
leafIndex = leafIndex === undefined ? BigInt(0) : leafIndex;
|
|
57
|
+
return Promise.resolve([exists, existsAsPending, new Fr(leafIndex)]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Stage a new nullifier (append it to the cache).
|
|
62
|
+
*
|
|
63
|
+
* @param storageAddress - the address of the contract that the nullifier is associated with
|
|
64
|
+
* @param nullifier - the nullifier to stage
|
|
65
|
+
*/
|
|
66
|
+
public async append(storageAddress: Fr, nullifier: Fr) {
|
|
67
|
+
const [exists, ,] = await this.checkExists(storageAddress, nullifier);
|
|
68
|
+
if (exists) {
|
|
69
|
+
throw new NullifierCollisionError(
|
|
70
|
+
`Nullifier ${nullifier} at contract ${storageAddress} already exists in parent cache or host.`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
this.cache.append(storageAddress, nullifier);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Merges another nullifier cache into this one.
|
|
78
|
+
*
|
|
79
|
+
* @param incomingNullifiers - the incoming cached nullifiers to merge into this instance's
|
|
80
|
+
*/
|
|
81
|
+
public acceptAndMerge(incomingNullifiers: Nullifiers) {
|
|
82
|
+
this.cache.acceptAndMerge(incomingNullifiers.cache);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A class to cache nullifiers created during a contract call's AVM simulation.
|
|
88
|
+
* "append" updates a map, "exists" checks that map.
|
|
89
|
+
* An instance of this class can merge another instance's cached nullifiers into its own.
|
|
90
|
+
*/
|
|
91
|
+
export class NullifierCache {
|
|
92
|
+
/**
|
|
93
|
+
* Map for staging nullifiers.
|
|
94
|
+
* One inner-set per contract storage address,
|
|
95
|
+
* each entry being a nullifier.
|
|
96
|
+
*/
|
|
97
|
+
private cachePerContract: Map<bigint, Set<bigint>> = new Map();
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check whether a nullifier exists in the cache.
|
|
101
|
+
*
|
|
102
|
+
* @param storageAddress - the address of the contract that the nullifier is associated with
|
|
103
|
+
* @param nullifier - the nullifier to check existence of
|
|
104
|
+
* @returns whether the nullifier is found in the cache
|
|
105
|
+
*/
|
|
106
|
+
public exists(storageAddress: Fr, nullifier: Fr): boolean {
|
|
107
|
+
const exists = this.cachePerContract.get(storageAddress.toBigInt())?.has(nullifier.toBigInt());
|
|
108
|
+
return exists ? true : false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Stage a new nullifier (append it to the cache).
|
|
113
|
+
*
|
|
114
|
+
* @param storageAddress - the address of the contract that the nullifier is associated with
|
|
115
|
+
* @param nullifier - the nullifier to stage
|
|
116
|
+
*/
|
|
117
|
+
public append(storageAddress: Fr, nullifier: Fr) {
|
|
118
|
+
let nullifiersForContract = this.cachePerContract.get(storageAddress.toBigInt());
|
|
119
|
+
// If this contract's nullifier set has no cached nullifiers, create a new Set to store them
|
|
120
|
+
if (!nullifiersForContract) {
|
|
121
|
+
nullifiersForContract = new Set();
|
|
122
|
+
this.cachePerContract.set(storageAddress.toBigInt(), nullifiersForContract);
|
|
123
|
+
}
|
|
124
|
+
if (nullifiersForContract.has(nullifier.toBigInt())) {
|
|
125
|
+
throw new NullifierCollisionError(
|
|
126
|
+
`Nullifier ${nullifier} at contract ${storageAddress} already exists in cache.`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
nullifiersForContract.add(nullifier.toBigInt());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Merge another cache's nullifiers into this instance's.
|
|
134
|
+
*
|
|
135
|
+
* Cached nullifiers in "incoming" must not collide with any present in "this".
|
|
136
|
+
*
|
|
137
|
+
* In practice, "this" is a parent call's pending nullifiers, and "incoming" is a nested call's.
|
|
138
|
+
*
|
|
139
|
+
* @param incomingNullifiers - the incoming cached nullifiers to merge into this instance's
|
|
140
|
+
*/
|
|
141
|
+
public acceptAndMerge(incomingNullifiers: NullifierCache) {
|
|
142
|
+
// Iterate over all contracts with staged writes in the child.
|
|
143
|
+
for (const [incomingAddress, incomingCacheAtContract] of incomingNullifiers.cachePerContract) {
|
|
144
|
+
const thisCacheAtContract = this.cachePerContract.get(incomingAddress);
|
|
145
|
+
if (!thisCacheAtContract) {
|
|
146
|
+
// This contract has no nullifiers cached here
|
|
147
|
+
// so just accept incoming cache as-is for this contract.
|
|
148
|
+
this.cachePerContract.set(incomingAddress, incomingCacheAtContract);
|
|
149
|
+
} else {
|
|
150
|
+
// "Incoming" and "this" both have cached nullifiers for this contract.
|
|
151
|
+
// Merge in incoming nullifiers, erroring if there are any duplicates.
|
|
152
|
+
for (const nullifier of incomingCacheAtContract) {
|
|
153
|
+
if (thisCacheAtContract.has(nullifier)) {
|
|
154
|
+
throw new NullifierCollisionError(
|
|
155
|
+
`Failed to accept child call's nullifiers. Nullifier ${nullifier} already exists at contract ${incomingAddress}.`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
thisCacheAtContract.add(nullifier);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export class NullifierCollisionError extends Error {
|
|
166
|
+
constructor(message: string, ...rest: any[]) {
|
|
167
|
+
super(message, ...rest);
|
|
168
|
+
this.name = 'NullifierCollisionError';
|
|
169
|
+
}
|
|
170
|
+
}
|