1use aztec_core::constants::*;
9use aztec_core::error::Error;
10use aztec_core::fee::Gas;
11use aztec_core::hash::{
12 compute_note_hash_nonce, compute_siloed_private_log_first_field, compute_unique_note_hash,
13 poseidon2_hash, silo_note_hash, silo_nullifier,
14};
15use aztec_core::kernel_types::{
16 pad_fields, PartialPrivateTailPublicInputsForPublic, PartialPrivateTailPublicInputsForRollup,
17 PrivateKernelTailPublicInputs, PrivateLog, PrivateToPublicAccumulatedData,
18 PrivateToRollupAccumulatedData, PublicCallRequest, ScopedL2ToL1Message, ScopedLogHash,
19 ScopedNoteHash, ScopedNullifier, TxConstantData,
20};
21use aztec_core::types::Fr;
22
23use crate::execution::execution_result::{PrivateExecutionResult, PrivateLogData};
24
25#[derive(Debug, Clone)]
27pub struct SimulatedKernelOutput {
28 pub public_inputs: PrivateKernelTailPublicInputs,
30}
31
32pub struct SimulatedKernel;
36
37impl SimulatedKernel {
38 pub fn process(
50 execution_result: &PrivateExecutionResult,
51 constants: TxConstantData,
52 fee_payer: &Fr,
53 expiration_timestamp: u64,
54 ) -> Result<SimulatedKernelOutput, Error> {
55 let all_note_hashes: Vec<ScopedNoteHash> = execution_result
57 .all_note_hashes()
58 .into_iter()
59 .cloned()
60 .collect();
61 let all_nullifiers: Vec<ScopedNullifier> = execution_result
62 .all_nullifiers()
63 .into_iter()
64 .cloned()
65 .collect();
66 let all_private_logs: Vec<PrivateLogData> = execution_result
67 .all_private_logs()
68 .into_iter()
69 .cloned()
70 .collect();
71 let mut all_public_call_requests: Vec<_> = execution_result
72 .all_public_call_requests()
73 .into_iter()
74 .cloned()
75 .collect();
76 let all_contract_class_logs = execution_result.all_contract_class_logs_sorted();
77 let note_hash_nullifier_counter_map =
78 execution_result.all_note_hash_nullifier_counter_maps();
79
80 let is_private_only = all_public_call_requests.is_empty()
81 && execution_result.get_teardown_call_request().is_none();
82
83 let min_revertible_counter = execution_result
84 .entrypoint
85 .min_revertible_side_effect_counter;
86
87 let (mut filtered_note_hashes, mut filtered_nullifiers, mut filtered_private_logs) =
89 squash_transient_side_effects(
90 &all_note_hashes,
91 &all_nullifiers,
92 &all_private_logs,
93 ¬e_hash_nullifier_counter_map,
94 min_revertible_counter,
95 );
96
97 filtered_note_hashes.sort_by_key(|nh| nh.note_hash.counter);
98 filtered_nullifiers.sort_by_key(|n| n.nullifier.counter);
99 filtered_private_logs.sort_by_key(|log| log.counter);
100 all_public_call_requests.sort_by_key(|req| req.counter);
101
102 let siloed_note_hashes: Vec<Fr> = filtered_note_hashes
104 .iter()
105 .map(|nh| {
106 if nh.is_empty() {
107 Fr::zero()
108 } else {
109 silo_note_hash(&nh.contract_address, &nh.note_hash.value)
110 }
111 })
112 .collect();
113
114 let siloed_nullifiers: Vec<Fr> = filtered_nullifiers
115 .iter()
116 .map(|n| {
117 if n.is_empty() {
118 Fr::zero()
119 } else {
120 silo_nullifier(&n.contract_address, &n.nullifier.value)
121 }
122 })
123 .collect();
124
125 let siloed_private_logs: Vec<PrivateLog> = filtered_private_logs
126 .iter()
127 .map(|log| {
128 let mut fields = log.fields.clone();
129 if !fields.is_empty() && fields[0] != Fr::zero() {
130 fields[0] =
131 compute_siloed_private_log_first_field(&log.contract_address, &fields[0]);
132 }
133 PrivateLog {
134 fields: pad_fields(fields, PRIVATE_LOG_SIZE_IN_FIELDS),
135 emitted_length: log.emitted_length,
136 }
137 })
138 .collect();
139
140 let contract_class_log_hashes: Vec<ScopedLogHash> = all_contract_class_logs
142 .iter()
143 .map(|ccl| ScopedLogHash {
144 log_hash: aztec_core::kernel_types::LogHash {
145 value: poseidon2_hash(
146 &aztec_core::tx::ContractClassLogFields::from_emitted_fields(
147 ccl.log.fields.clone(),
148 )
149 .fields,
150 ),
151 length: ccl.log.emitted_length,
152 },
153 contract_address: ccl.log.contract_address,
154 })
155 .collect();
156
157 let l2_to_l1_msgs: Vec<ScopedL2ToL1Message> = Vec::new();
159
160 if is_private_only {
161 let first_nullifier = execution_result.first_nullifier;
163 let mut rollup_nullifiers = vec![first_nullifier];
165 rollup_nullifiers.extend(siloed_nullifiers.iter().copied());
166 let unique_note_hashes: Vec<Fr> = siloed_note_hashes
167 .iter()
168 .enumerate()
169 .map(|(i, siloed_hash)| {
170 if *siloed_hash == Fr::zero() {
171 Fr::zero()
172 } else {
173 let nonce = compute_note_hash_nonce(&first_nullifier, i);
174 compute_unique_note_hash(&nonce, siloed_hash)
175 }
176 })
177 .collect();
178
179 let end = PrivateToRollupAccumulatedData {
181 note_hashes: pad_fields(unique_note_hashes, MAX_NOTE_HASHES_PER_TX),
182 nullifiers: pad_fields(rollup_nullifiers, MAX_NULLIFIERS_PER_TX),
183 l2_to_l1_msgs: pad_to_scoped_l2_to_l1(l2_to_l1_msgs),
184 private_logs: pad_private_logs(siloed_private_logs),
185 contract_class_logs_hashes: pad_scoped_log_hashes(contract_class_log_hashes),
186 };
187
188 let gas_used = meter_gas_rollup(&end);
190
191 Ok(SimulatedKernelOutput {
192 public_inputs: PrivateKernelTailPublicInputs {
193 constants,
194 gas_used,
195 fee_payer: aztec_core::types::AztecAddress(*fee_payer),
196 expiration_timestamp,
197 for_public: None,
198 for_rollup: Some(PartialPrivateTailPublicInputsForRollup { end }),
199 },
200 })
201 } else {
202 let (nr_note_hashes, r_note_hashes) = split_note_hashes_by_counter(
204 &siloed_note_hashes,
205 &filtered_note_hashes,
206 min_revertible_counter,
207 );
208 let (mut nr_nullifiers, r_nullifiers) = split_nullifiers_by_counter(
209 &siloed_nullifiers,
210 &filtered_nullifiers,
211 min_revertible_counter,
212 );
213 let (nr_private_logs, r_private_logs) = split_private_logs(
214 &siloed_private_logs,
215 &filtered_private_logs,
216 min_revertible_counter,
217 );
218
219 nr_nullifiers.insert(0, execution_result.first_nullifier);
223
224 let nonce_generator = execution_result.first_nullifier;
227 let nr_unique_note_hashes: Vec<Fr> = nr_note_hashes
228 .iter()
229 .enumerate()
230 .map(|(i, h)| {
231 if *h == Fr::zero() {
232 Fr::zero()
233 } else {
234 let nonce = compute_note_hash_nonce(&nonce_generator, i);
235 compute_unique_note_hash(&nonce, h)
236 }
237 })
238 .collect();
239 let mut nr_public_calls = Vec::new();
244 let mut r_public_calls = Vec::new();
245 for req in &all_public_call_requests {
246 let pcr = PublicCallRequest {
247 msg_sender: req.msg_sender,
248 contract_address: req.contract_address,
249 is_static_call: req.is_static_call,
250 calldata_hash: req.calldata_hash,
251 };
252 if req.counter < min_revertible_counter {
253 nr_public_calls.push(pcr);
254 } else {
255 r_public_calls.push(pcr);
256 }
257 }
258
259 let teardown = execution_result
260 .get_teardown_call_request()
261 .map(|req| PublicCallRequest {
262 msg_sender: req.msg_sender,
263 contract_address: req.contract_address,
264 is_static_call: req.is_static_call,
265 calldata_hash: req.calldata_hash,
266 })
267 .unwrap_or_default();
268
269 let non_revertible = PrivateToPublicAccumulatedData {
270 note_hashes: pad_fields(nr_unique_note_hashes, MAX_NOTE_HASHES_PER_TX),
271 nullifiers: pad_fields(nr_nullifiers, MAX_NULLIFIERS_PER_TX),
272 l2_to_l1_msgs: pad_to_scoped_l2_to_l1(Vec::new()),
273 private_logs: pad_private_logs(nr_private_logs),
274 contract_class_logs_hashes: pad_scoped_log_hashes(
275 contract_class_log_hashes.clone(),
276 ),
277 public_call_requests: pad_public_call_requests(nr_public_calls),
278 };
279
280 let revertible = PrivateToPublicAccumulatedData {
281 note_hashes: pad_fields(r_note_hashes, MAX_NOTE_HASHES_PER_TX),
282 nullifiers: pad_fields(r_nullifiers, MAX_NULLIFIERS_PER_TX),
283 l2_to_l1_msgs: pad_to_scoped_l2_to_l1(Vec::new()),
284 private_logs: pad_private_logs(r_private_logs),
285 contract_class_logs_hashes: pad_scoped_log_hashes(Vec::new()),
286 public_call_requests: pad_public_call_requests(r_public_calls),
287 };
288
289 let gas_used = meter_gas_public(&non_revertible, &revertible);
291
292 Ok(SimulatedKernelOutput {
293 public_inputs: PrivateKernelTailPublicInputs {
294 constants,
295 gas_used,
296 fee_payer: aztec_core::types::AztecAddress(*fee_payer),
297 expiration_timestamp,
298 for_public: Some(PartialPrivateTailPublicInputsForPublic {
299 non_revertible_accumulated_data: non_revertible,
300 revertible_accumulated_data: revertible,
301 public_teardown_call_request: teardown,
302 }),
303 for_rollup: None,
304 },
305 })
306 }
307 }
308}
309
310fn squash_transient_side_effects(
316 note_hashes: &[ScopedNoteHash],
317 nullifiers: &[ScopedNullifier],
318 private_logs: &[PrivateLogData],
319 note_hash_nullifier_counter_map: &std::collections::HashMap<u32, u32>,
320 _min_revertible_counter: u32,
321) -> (
322 Vec<ScopedNoteHash>,
323 Vec<ScopedNullifier>,
324 Vec<PrivateLogData>,
325) {
326 let mut squashed_note_hash_counters = std::collections::HashSet::new();
328 let mut squashed_nullifier_counters = std::collections::HashSet::new();
329
330 for (nh_counter, null_counter) in note_hash_nullifier_counter_map {
331 squashed_note_hash_counters.insert(*nh_counter);
333 squashed_nullifier_counters.insert(*null_counter);
334 }
335
336 let filtered_note_hashes: Vec<ScopedNoteHash> = note_hashes
337 .iter()
338 .filter(|nh| !squashed_note_hash_counters.contains(&nh.note_hash.counter))
339 .cloned()
340 .collect();
341
342 let filtered_nullifiers: Vec<ScopedNullifier> = nullifiers
343 .iter()
344 .filter(|n| !squashed_nullifier_counters.contains(&n.nullifier.counter))
345 .cloned()
346 .collect();
347
348 let filtered_logs: Vec<PrivateLogData> = private_logs
350 .iter()
351 .filter(|log| !squashed_note_hash_counters.contains(&log.note_hash_counter))
352 .cloned()
353 .collect();
354
355 (filtered_note_hashes, filtered_nullifiers, filtered_logs)
356}
357
358fn split_note_hashes_by_counter(
364 siloed: &[Fr],
365 originals: &[ScopedNoteHash],
366 min_revertible: u32,
367) -> (Vec<Fr>, Vec<Fr>) {
368 let mut non_revertible = Vec::new();
369 let mut revertible = Vec::new();
370 for (i, s) in siloed.iter().enumerate() {
371 if let Some(orig) = originals.get(i) {
372 if orig.note_hash.counter < min_revertible {
373 non_revertible.push(*s);
374 } else {
375 revertible.push(*s);
376 }
377 }
378 }
379 (non_revertible, revertible)
380}
381
382fn split_nullifiers_by_counter(
384 siloed: &[Fr],
385 originals: &[ScopedNullifier],
386 min_revertible: u32,
387) -> (Vec<Fr>, Vec<Fr>) {
388 let mut non_revertible = Vec::new();
389 let mut revertible = Vec::new();
390 for (i, s) in siloed.iter().enumerate() {
391 if let Some(orig) = originals.get(i) {
392 if orig.nullifier.counter < min_revertible {
393 non_revertible.push(*s);
394 } else {
395 revertible.push(*s);
396 }
397 }
398 }
399 (non_revertible, revertible)
400}
401
402fn split_private_logs(
403 siloed: &[PrivateLog],
404 originals: &[PrivateLogData],
405 min_revertible: u32,
406) -> (Vec<PrivateLog>, Vec<PrivateLog>) {
407 let mut non_revertible = Vec::new();
408 let mut revertible = Vec::new();
409 for (i, s) in siloed.iter().enumerate() {
410 if let Some(orig) = originals.get(i) {
411 if orig.counter < min_revertible {
412 non_revertible.push(s.clone());
413 } else {
414 revertible.push(s.clone());
415 }
416 }
417 }
418 (non_revertible, revertible)
419}
420
421fn meter_gas_rollup(data: &PrivateToRollupAccumulatedData) -> Gas {
426 let note_hash_count = data
427 .note_hashes
428 .iter()
429 .filter(|h| **h != Fr::zero())
430 .count() as u64;
431 let nullifier_count = data.nullifiers.iter().filter(|h| **h != Fr::zero()).count() as u64;
432 let l2_to_l1_count = data.l2_to_l1_msgs.iter().filter(|m| !m.is_empty()).count() as u64;
433 let log_count = data.private_logs.iter().filter(|l| !l.is_empty()).count() as u64;
434 let class_log_count = data
435 .contract_class_logs_hashes
436 .iter()
437 .filter(|h| !h.is_empty())
438 .count() as u64;
439
440 let l2_gas = PRIVATE_TX_L2_GAS_OVERHEAD
441 + note_hash_count * L2_GAS_PER_NOTE_HASH
442 + nullifier_count * L2_GAS_PER_NULLIFIER
443 + l2_to_l1_count * L2_GAS_PER_L2_TO_L1_MSG
444 + log_count * L2_GAS_PER_PRIVATE_LOG
445 + class_log_count * L2_GAS_PER_CONTRACT_CLASS_LOG;
446
447 let da_fields =
448 note_hash_count + nullifier_count + (log_count * PRIVATE_LOG_SIZE_IN_FIELDS as u64);
449 let da_gas = TX_DA_GAS_OVERHEAD + da_fields * DA_GAS_PER_FIELD;
450
451 Gas::new(da_gas, l2_gas)
452}
453
454fn meter_gas_public(
455 non_revertible: &PrivateToPublicAccumulatedData,
456 revertible: &PrivateToPublicAccumulatedData,
457) -> Gas {
458 let count_non_empty_fields =
459 |fields: &[Fr]| -> u64 { fields.iter().filter(|h| **h != Fr::zero()).count() as u64 };
460
461 let note_hashes = count_non_empty_fields(&non_revertible.note_hashes)
462 + count_non_empty_fields(&revertible.note_hashes);
463 let nullifiers = count_non_empty_fields(&non_revertible.nullifiers)
464 + count_non_empty_fields(&revertible.nullifiers);
465 let logs = (non_revertible
466 .private_logs
467 .iter()
468 .filter(|l| !l.is_empty())
469 .count()
470 + revertible
471 .private_logs
472 .iter()
473 .filter(|l| !l.is_empty())
474 .count()) as u64;
475
476 let l2_gas = PUBLIC_TX_L2_GAS_OVERHEAD
477 + note_hashes * L2_GAS_PER_NOTE_HASH
478 + nullifiers * L2_GAS_PER_NULLIFIER
479 + logs * L2_GAS_PER_PRIVATE_LOG;
480
481 let da_fields = note_hashes + nullifiers + (logs * PRIVATE_LOG_SIZE_IN_FIELDS as u64);
482 let da_gas = TX_DA_GAS_OVERHEAD + da_fields * DA_GAS_PER_FIELD;
483
484 Gas::new(da_gas, l2_gas)
485}
486
487fn pad_to_scoped_l2_to_l1(mut v: Vec<ScopedL2ToL1Message>) -> Vec<ScopedL2ToL1Message> {
492 while v.len() < MAX_L2_TO_L1_MSGS_PER_TX {
493 v.push(ScopedL2ToL1Message::empty());
494 }
495 v
496}
497
498fn pad_private_logs(mut v: Vec<PrivateLog>) -> Vec<PrivateLog> {
499 while v.len() < MAX_PRIVATE_LOGS_PER_TX {
500 v.push(PrivateLog::empty());
501 }
502 v
503}
504
505fn pad_scoped_log_hashes(mut v: Vec<ScopedLogHash>) -> Vec<ScopedLogHash> {
506 while v.len() < MAX_CONTRACT_CLASS_LOGS_PER_TX {
507 v.push(ScopedLogHash::empty());
508 }
509 v
510}
511
512fn pad_public_call_requests(mut v: Vec<PublicCallRequest>) -> Vec<PublicCallRequest> {
513 while v.len() < MAX_ENQUEUED_CALLS_PER_TX {
514 v.push(PublicCallRequest::empty());
515 }
516 v
517}