Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
tx_trace.cpp
Go to the documentation of this file.
2
7// #include "barretenberg/vm2/generated/relations/lookups_tx_context.hpp"
13
14#include <cstdint>
15#include <unordered_map>
16#include <variant>
17
18namespace bb::avm2::tracegen {
19
20namespace {
21
22using simulation::PhaseLengths;
23using simulation::TxContextEvent;
24
25// helper type for the visitor #4
26template <class... Ts> struct overloaded : Ts... {
27 using Ts::operator()...;
28};
29// explicit deduction guide (not needed as of C++20)
30template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
31
32// Helper to get phase length from PhaseLengths struct
33uint32_t get_phase_length(const PhaseLengths& phase_lengths, TransactionPhase phase)
34{
35 switch (phase) {
37 return phase_lengths.nr_nullifier_insertion;
39 return phase_lengths.nr_note_insertion;
41 return phase_lengths.nr_l2_to_l1_message;
43 return phase_lengths.setup;
45 return phase_lengths.r_nullifier_insertion;
47 return phase_lengths.r_note_insertion;
49 return phase_lengths.r_l2_to_l1_message;
51 return phase_lengths.app_logic;
53 return phase_lengths.teardown;
54 default:
55 return 1; // One-shot phases (COLLECT_GAS_FEES, TREE_PADDING, CLEANUP)
56 }
57}
58
59constexpr size_t NUM_PHASES = static_cast<size_t>(TransactionPhase::LAST);
60
61bool is_revertible(TransactionPhase phase)
62{
66}
67
68bool is_note_hash_insert_phase(TransactionPhase phase)
69{
71}
72
73bool is_nullifier_insert_phase(TransactionPhase phase)
74{
76}
77
78bool is_tree_insert_phase(TransactionPhase phase)
79{
80 return is_note_hash_insert_phase(phase) || is_nullifier_insert_phase(phase);
81}
82
83bool is_l2_l1_msg_phase(TransactionPhase phase)
84{
86}
87
88bool is_public_call_request_phase(TransactionPhase phase)
89{
90 return phase == TransactionPhase::SETUP || phase == TransactionPhase::APP_LOGIC ||
92}
93
94bool is_collect_fee_phase(TransactionPhase phase)
95{
97}
98
99bool is_tree_padding_phase(TransactionPhase phase)
100{
101 return phase == TransactionPhase::TREE_PADDING;
102}
103
104bool is_cleanup_phase(TransactionPhase phase)
105{
106 return phase == TransactionPhase::CLEANUP;
107}
108
109bool is_one_shot_phase(TransactionPhase phase)
110{
111 return is_collect_fee_phase(phase) || is_tree_padding_phase(phase) || is_cleanup_phase(phase);
112}
113
114bool is_teardown_phase(TransactionPhase phase)
115{
116 return phase == TransactionPhase::TEARDOWN;
117}
118
119// This is a helper to insert the previous and next tree state
120std::vector<std::pair<Column, FF>> insert_state(const TxContextEvent& prev_state, const TxContextEvent& next_state)
121{
122 return {
123 // Previous Tree State
124 // Note Hash
125 { Column::tx_prev_note_hash_tree_root, prev_state.tree_states.noteHashTree.tree.root },
126 { Column::tx_prev_note_hash_tree_size, prev_state.tree_states.noteHashTree.tree.nextAvailableLeafIndex },
127 { Column::tx_prev_num_note_hashes_emitted, prev_state.tree_states.noteHashTree.counter },
128 // Nullifier Tree Roots
129 { Column::tx_prev_nullifier_tree_root, prev_state.tree_states.nullifierTree.tree.root },
130 { Column::tx_prev_nullifier_tree_size, prev_state.tree_states.nullifierTree.tree.nextAvailableLeafIndex },
131 { Column::tx_prev_num_nullifiers_emitted, prev_state.tree_states.nullifierTree.counter },
132 // Public Data Tree Roots
133 { Column::tx_prev_public_data_tree_root, prev_state.tree_states.publicDataTree.tree.root },
134 { Column::tx_prev_public_data_tree_size, prev_state.tree_states.publicDataTree.tree.nextAvailableLeafIndex },
135 // Written Public Data Slots Tree Roots
136 { Column::tx_prev_written_public_data_slots_tree_root,
137 prev_state.written_public_data_slots_tree_snapshot.root },
138 { Column::tx_prev_written_public_data_slots_tree_size,
139 prev_state.written_public_data_slots_tree_snapshot.nextAvailableLeafIndex },
140 // L1 to L2 Message Tree Roots
141 { Column::tx_l1_l2_tree_root, prev_state.tree_states.l1ToL2MessageTree.tree.root },
142 // Retrieved bytecodes Tree Roots
143 { Column::tx_prev_retrieved_bytecodes_tree_root, prev_state.retrieved_bytecodes_tree_snapshot.root },
144 { Column::tx_prev_retrieved_bytecodes_tree_size,
145 prev_state.retrieved_bytecodes_tree_snapshot.nextAvailableLeafIndex },
146
147 // Next Tree State
148 { Column::tx_next_note_hash_tree_root, next_state.tree_states.noteHashTree.tree.root },
149 { Column::tx_next_note_hash_tree_size, next_state.tree_states.noteHashTree.tree.nextAvailableLeafIndex },
150 { Column::tx_next_num_note_hashes_emitted, next_state.tree_states.noteHashTree.counter },
151 // Nullifier Tree Roots
152 { Column::tx_next_nullifier_tree_root, next_state.tree_states.nullifierTree.tree.root },
153 { Column::tx_next_nullifier_tree_size, next_state.tree_states.nullifierTree.tree.nextAvailableLeafIndex },
154 { Column::tx_next_num_nullifiers_emitted, next_state.tree_states.nullifierTree.counter },
155 // Public Data Tree Roots
156 { Column::tx_next_public_data_tree_root, next_state.tree_states.publicDataTree.tree.root },
157 { Column::tx_next_public_data_tree_size, next_state.tree_states.publicDataTree.tree.nextAvailableLeafIndex },
158 // Written Public Data Slots Tree Roots
159 { Column::tx_next_written_public_data_slots_tree_root,
160 next_state.written_public_data_slots_tree_snapshot.root },
161 { Column::tx_next_written_public_data_slots_tree_size,
162 next_state.written_public_data_slots_tree_snapshot.nextAvailableLeafIndex },
163 // Retrieved bytecodes Tree Roots
164 { Column::tx_next_retrieved_bytecodes_tree_root, next_state.retrieved_bytecodes_tree_snapshot.root },
165 { Column::tx_next_retrieved_bytecodes_tree_size,
166 next_state.retrieved_bytecodes_tree_snapshot.nextAvailableLeafIndex },
167
168 // Prev sideffect state
169 { Column::tx_prev_num_unencrypted_log_fields, prev_state.side_effect_states.numUnencryptedLogFields },
170 { Column::tx_prev_num_l2_to_l1_messages, prev_state.side_effect_states.numL2ToL1Messages },
171
172 // Next sideffect state
173 { Column::tx_next_num_unencrypted_log_fields, next_state.side_effect_states.numUnencryptedLogFields },
174 { Column::tx_next_num_l2_to_l1_messages, next_state.side_effect_states.numL2ToL1Messages },
175
176 // Execution context
177 { Column::tx_next_context_id, prev_state.next_context_id },
178 };
179}
180
181std::vector<std::pair<Column, FF>> insert_side_effect_states(const SideEffectStates& prev_side_effect_states,
182 const SideEffectStates& next_side_effect_states)
183{
184 return {
185 {
186 Column::tx_prev_num_unencrypted_log_fields,
187 prev_side_effect_states.numUnencryptedLogFields,
188 },
189 {
190 Column::tx_prev_num_l2_to_l1_messages,
191 prev_side_effect_states.numL2ToL1Messages,
192 },
193 {
194 Column::tx_next_num_unencrypted_log_fields,
195 next_side_effect_states.numUnencryptedLogFields,
196 },
197 {
198 Column::tx_next_num_l2_to_l1_messages,
199 next_side_effect_states.numL2ToL1Messages,
200 },
201 };
202}
203
204// Helper to retrieve the read and write offsets and populate the read and write counters
205std::vector<std::pair<Column, FF>> handle_pi_read(TransactionPhase phase, uint32_t phase_length, uint32_t read_counter)
206
207{
208 auto [read_offset, write_offset, length_offset] = TxPhaseOffsetsTable::get_offsets(phase);
209
210 auto remaining_length = phase_length - read_counter;
211
212 return {
213 { Column::tx_read_pi_offset, read_offset + read_counter },
214 { Column::tx_read_pi_length_offset, length_offset - read_counter },
215
216 { Column::tx_remaining_phase_counter, remaining_length },
217 { Column::tx_remaining_phase_inv, remaining_length }, // Will be inverted in batch later
218 { Column::tx_remaining_phase_minus_one_inv, remaining_length - 1 }, // Will be inverted in batch later
219 };
220}
221
222std::vector<std::pair<Column, FF>> handle_prev_gas_used(Gas prev_gas_used)
223{
224 return {
225 { Column::tx_prev_da_gas_used, prev_gas_used.daGas },
226 { Column::tx_prev_l2_gas_used, prev_gas_used.l2Gas },
227 };
228}
229
230std::vector<std::pair<Column, FF>> handle_next_gas_used(Gas next_gas_used)
231{
232 return {
233 { Column::tx_next_da_gas_used, next_gas_used.daGas },
234 { Column::tx_next_l2_gas_used, next_gas_used.l2Gas },
235 };
236}
237
238std::vector<std::pair<Column, FF>> handle_gas_limit(Gas gas_limit)
239{
240 return {
241 { Column::tx_da_gas_limit, gas_limit.daGas },
242 { Column::tx_l2_gas_limit, gas_limit.l2Gas },
243 };
244}
245
246std::vector<std::pair<Column, FF>> handle_enqueued_call_event(TransactionPhase phase,
247 const simulation::EnqueuedCallEvent& event)
248{
249 return { { Column::tx_is_public_call_request, 1 },
250 { Column::tx_should_process_call_request, 1 },
251 { Column::tx_is_teardown_phase, is_teardown_phase(phase) },
252 { Column::tx_msg_sender, event.msg_sender },
253 { Column::tx_contract_addr, event.contract_address },
254 { Column::tx_fee, event.transaction_fee },
255 { Column::tx_is_static, event.is_static },
256 { Column::tx_calldata_size, event.calldata_size },
257 { Column::tx_calldata_hash, event.calldata_hash },
258 { Column::tx_reverted, !event.success },
259 { Column::tx_prev_da_gas_used_sent_to_enqueued_call, event.start_gas.daGas },
260 { Column::tx_prev_l2_gas_used_sent_to_enqueued_call, event.start_gas.l2Gas },
261 { Column::tx_next_da_gas_used_sent_to_enqueued_call, event.end_gas.daGas },
262 { Column::tx_next_l2_gas_used_sent_to_enqueued_call, event.end_gas.l2Gas },
263 { Column::tx_gas_limit_pi_offset,
264 is_teardown_phase(phase) ? AVM_PUBLIC_INPUTS_GAS_SETTINGS_TEARDOWN_GAS_LIMITS_ROW_IDX : 0 },
265 { Column::tx_should_read_gas_limit, is_teardown_phase(phase) } };
266};
267
268std::vector<std::pair<Column, FF>> handle_note_hash_append(const simulation::PrivateAppendTreeEvent& event,
269 TransactionPhase phase,
270 const TxContextEvent& state_before,
271 bool reverted)
272{
273 uint32_t remaining_note_hashes = MAX_NOTE_HASHES_PER_TX - state_before.tree_states.noteHashTree.counter;
274
275 return {
276 { Column::tx_is_tree_insert_phase, 1 },
277 { Column::tx_leaf_value, event.leaf_value },
278 { Column::tx_remaining_side_effects_inv, remaining_note_hashes }, // Will be inverted in batch later
279 { Column::tx_sel_non_revertible_append_note_hash, phase == TransactionPhase::NR_NOTE_INSERTION },
280 { Column::tx_sel_revertible_append_note_hash, phase == TransactionPhase::R_NOTE_INSERTION },
281 { Column::tx_should_try_note_hash_append, 1 },
282 { Column::tx_should_note_hash_append, remaining_note_hashes > 0 },
283 { Column::tx_reverted, reverted ? 1 : 0 },
284 };
285}
286
287std::vector<std::pair<Column, FF>> handle_nullifier_append(const simulation::PrivateAppendTreeEvent& event,
288 TransactionPhase phase,
289 const TxContextEvent& state_before,
290 bool reverted)
291{
292 uint32_t remaining_nullifiers = MAX_NULLIFIERS_PER_TX - state_before.tree_states.nullifierTree.counter;
293
294 return {
295 { Column::tx_is_tree_insert_phase, 1 },
296 { Column::tx_leaf_value, event.leaf_value },
297 { Column::tx_remaining_side_effects_inv, remaining_nullifiers }, // Will be inverted in batch later
298 { Column::tx_sel_non_revertible_append_nullifier, phase == TransactionPhase::NR_NULLIFIER_INSERTION },
299 { Column::tx_sel_revertible_append_nullifier, phase == TransactionPhase::R_NULLIFIER_INSERTION },
300 { Column::tx_should_try_nullifier_append, 1 },
301 { Column::tx_should_nullifier_append, remaining_nullifiers > 0 },
302 { Column::tx_reverted, reverted ? 1 : 0 },
303 };
304}
305
306std::vector<std::pair<Column, FF>> handle_append_tree_event(const simulation::PrivateAppendTreeEvent& event,
307 TransactionPhase phase,
308 const TxContextEvent& state_before,
309 bool reverted)
310{
311 if (is_note_hash_insert_phase(phase)) {
312 return handle_note_hash_append(event, phase, state_before, reverted);
313 }
314 if (is_nullifier_insert_phase(phase)) {
315 return handle_nullifier_append(event, phase, state_before, reverted);
316 }
317 throw std::runtime_error("Invalid phase for append tree event");
318}
319
320std::vector<std::pair<Column, FF>> handle_l2_l1_msg_event(const simulation::PrivateEmitL2L1MessageEvent& event,
321 TransactionPhase phase,
322 const TxContextEvent& state_before,
323 bool reverted)
324{
325 uint32_t remaining_l2_to_l1_msgs = MAX_L2_TO_L1_MSGS_PER_TX - state_before.side_effect_states.numL2ToL1Messages;
326 return {
327 { Column::tx_sel_revertible_append_l2_l1_msg, phase == TransactionPhase::R_L2_TO_L1_MESSAGE },
328 { Column::tx_sel_non_revertible_append_l2_l1_msg, phase == TransactionPhase::NR_L2_TO_L1_MESSAGE },
329 { Column::tx_should_try_l2_l1_msg_append, 1 },
330 { Column::tx_remaining_side_effects_inv, remaining_l2_to_l1_msgs }, // Will be inverted in batch later
331 { Column::tx_should_l2_l1_msg_append, remaining_l2_to_l1_msgs > 0 },
332 { Column::tx_l2_l1_msg_contract_address, event.scoped_msg.contractAddress },
333 { Column::tx_l2_l1_msg_recipient, event.scoped_msg.message.recipient },
334 { Column::tx_l2_l1_msg_content, event.scoped_msg.message.content },
335 { Column::tx_write_pi_offset,
337 state_before.side_effect_states.numL2ToL1Messages },
338 // Selectors
339 { Column::tx_reverted, reverted ? 1 : 0 },
340 };
341}
342
343std::vector<std::pair<Column, FF>> handle_collect_gas_fee_event(const simulation::CollectGasFeeEvent& event)
344{
345 return {
346 { Column::tx_is_collect_fee, 1 },
347 { Column::tx_effective_fee_per_da_gas, FF(event.effective_fee_per_da_gas) },
348 { Column::tx_effective_fee_per_l2_gas, FF(event.effective_fee_per_l2_gas) },
349 { Column::tx_fee_payer, event.fee_payer },
350 { Column::tx_fee_payer_pi_offset, AVM_PUBLIC_INPUTS_FEE_PAYER_ROW_IDX },
351 {
352 Column::tx_fee,
353 event.fee,
354 },
355 {
356 Column::tx_fee_juice_contract_address,
358 },
359 {
360 Column::tx_fee_juice_balances_slot,
362 },
363 {
364 Column::tx_fee_juice_balance_slot,
365 event.fee_juice_balance_slot,
366 },
367 {
368 Column::tx_fee_payer_balance,
369 event.fee_payer_balance,
370 },
371 {
372 Column::tx_fee_payer_new_balance,
373 event.fee_payer_balance - event.fee,
374 },
375 { Column::tx_uint32_max, 0xffffffff },
376 { Column::tx_write_pi_offset, AVM_PUBLIC_INPUTS_TRANSACTION_FEE_ROW_IDX },
377 };
378}
379
380std::vector<std::pair<Column, FF>> handle_tree_padding()
381{
382 return {
383 { Column::tx_is_tree_padding, 1 },
384 };
385}
386
387std::vector<std::pair<Column, FF>> handle_cleanup()
388{
389 return {
390 { Column::tx_is_cleanup, 1 },
391 // End state
392 { Column::tx_note_hash_pi_offset, AVM_PUBLIC_INPUTS_END_TREE_SNAPSHOTS_NOTE_HASH_TREE_ROW_IDX },
393 { Column::tx_should_read_note_hash_tree, 1 },
394 { Column::tx_nullifier_pi_offset, AVM_PUBLIC_INPUTS_END_TREE_SNAPSHOTS_NULLIFIER_TREE_ROW_IDX },
395 { Column::tx_should_read_nullifier_tree, 1 },
396 { Column::tx_public_data_pi_offset, AVM_PUBLIC_INPUTS_END_TREE_SNAPSHOTS_PUBLIC_DATA_TREE_ROW_IDX },
397 { Column::tx_should_read_public_data_tree, 1 },
399 { Column::tx_should_read_l1_l2_tree, 1 },
400 { Column::tx_gas_used_pi_offset, AVM_PUBLIC_INPUTS_END_GAS_USED_ROW_IDX },
401 { Column::tx_should_read_gas_used, 1 },
402 { Column::tx_array_length_note_hashes_pi_offset,
404 { Column::tx_array_length_nullifiers_pi_offset,
406 // Public data write counter is handled by the public data check trace due to squashing.
407 { Column::tx_array_length_l2_to_l1_messages_pi_offset,
409 { Column::tx_fields_length_unencrypted_logs_pi_offset,
411 };
412}
413
414std::vector<std::pair<Column, FF>> handle_first_row()
415{
417 { Column::tx_start_tx, 1 },
418 { Column::tx_note_hash_pi_offset, AVM_PUBLIC_INPUTS_START_TREE_SNAPSHOTS_NOTE_HASH_TREE_ROW_IDX },
419 { Column::tx_should_read_note_hash_tree, 1 },
420 { Column::tx_nullifier_pi_offset, AVM_PUBLIC_INPUTS_START_TREE_SNAPSHOTS_NULLIFIER_TREE_ROW_IDX },
421 { Column::tx_should_read_nullifier_tree, 1 },
422 { Column::tx_public_data_pi_offset, AVM_PUBLIC_INPUTS_START_TREE_SNAPSHOTS_PUBLIC_DATA_TREE_ROW_IDX },
423 { Column::tx_should_read_public_data_tree, 1 },
425 { Column::tx_should_read_l1_l2_tree, 1 },
426 { Column::tx_gas_used_pi_offset, AVM_PUBLIC_INPUTS_START_GAS_USED_ROW_IDX },
427 { Column::tx_should_read_gas_used, 1 },
428 { Column::tx_gas_limit_pi_offset, AVM_PUBLIC_INPUTS_GAS_SETTINGS_GAS_LIMITS_ROW_IDX },
429 { Column::tx_should_read_gas_limit, 1 },
430 };
431
432 return columns;
433}
434
435std::vector<std::pair<Column, FF>> handle_padded_row(TransactionPhase phase, Gas gas_used, bool discard)
436{
437 // We should throw here - but tests are currently unsuitable
438 // assert(phase != TransactionPhase::COLLECT_GAS_FEES);
439
440 // TODO: We should probably split this into multiple functions, that are called if the padded phase is a specific
441 // phase.
443 { Column::tx_sel, 1 },
444 { Column::tx_discard, discard ? 1 : 0 },
445 { Column::tx_phase_value, static_cast<uint8_t>(phase) },
446 { Column::tx_setup_phase_value, static_cast<uint8_t>(TransactionPhase::SETUP) },
447 { Column::tx_is_padded, 1 },
448 { Column::tx_start_phase, 1 },
449 { Column::tx_sel_read_phase_length, !is_one_shot_phase(phase) },
450 // This is temporary because AvmVerifierTests.GoodPublicInputs doesnt collect gas fees, every transaction
451 // needs a collect gas fee
452 { Column::tx_is_collect_fee, is_collect_fee_phase(phase) ? 1 : 0 },
453 { Column::tx_end_phase, 1 },
454 // Selector specific
455 { Column::tx_is_tree_insert_phase, is_tree_insert_phase(phase) ? 1 : 0 },
456 { Column::tx_is_public_call_request, is_public_call_request_phase(phase) ? 1 : 0 },
457 { Column::tx_is_collect_fee, is_collect_fee_phase(phase) ? 1 : 0 },
458
459 { Column::tx_sel_revertible_append_note_hash, phase == TransactionPhase::R_NOTE_INSERTION ? 1 : 0 },
460 { Column::tx_sel_revertible_append_nullifier, phase == TransactionPhase::R_NULLIFIER_INSERTION ? 1 : 0 },
461 { Column::tx_sel_revertible_append_l2_l1_msg, phase == TransactionPhase::R_L2_TO_L1_MESSAGE ? 1 : 0 },
462 { Column::tx_sel_non_revertible_append_note_hash, phase == TransactionPhase::NR_NOTE_INSERTION ? 1 : 0 },
463 { Column::tx_sel_non_revertible_append_nullifier, phase == TransactionPhase::NR_NULLIFIER_INSERTION ? 1 : 0 },
464 { Column::tx_sel_non_revertible_append_l2_l1_msg, phase == TransactionPhase::NR_L2_TO_L1_MESSAGE ? 1 : 0 },
465
466 { Column::tx_is_collect_fee, is_collect_fee_phase(phase) ? 1 : 0 },
467
468 { Column::tx_is_revertible, is_revertible(phase) ? 1 : 0 },
469 // Public call request specific
470 { Column::tx_is_teardown_phase, is_teardown_phase(phase) },
471 { Column::tx_gas_limit_pi_offset,
472 is_teardown_phase(phase) ? AVM_PUBLIC_INPUTS_GAS_SETTINGS_TEARDOWN_GAS_LIMITS_ROW_IDX : 0 },
473 { Column::tx_should_read_gas_limit, is_teardown_phase(phase) },
474 // Gas used does not change in padding rows
475 { Column::tx_prev_da_gas_used_sent_to_enqueued_call,
476 is_public_call_request_phase(phase) && phase != TransactionPhase::TEARDOWN ? gas_used.daGas : 0 },
477 { Column::tx_prev_l2_gas_used_sent_to_enqueued_call,
478 is_public_call_request_phase(phase) && phase != TransactionPhase::TEARDOWN ? gas_used.l2Gas : 0 },
479 { Column::tx_next_da_gas_used_sent_to_enqueued_call,
480 is_public_call_request_phase(phase) && phase != TransactionPhase::TEARDOWN ? gas_used.daGas : 0 },
481 { Column::tx_next_l2_gas_used_sent_to_enqueued_call,
482 is_public_call_request_phase(phase) && phase != TransactionPhase::TEARDOWN ? gas_used.l2Gas : 0 },
483 };
484
485 return columns;
486}
487
488std::vector<std::pair<Column, FF>> handle_state_change_selectors(TransactionPhase phase)
489{
490 return {
491 { Column::tx_sel_can_emit_note_hash,
492 is_note_hash_insert_phase(phase) || is_public_call_request_phase(phase) || is_tree_padding_phase(phase) },
493 { Column::tx_sel_can_emit_nullifier,
494 is_nullifier_insert_phase(phase) || is_public_call_request_phase(phase) || is_tree_padding_phase(phase) },
495 { Column::tx_sel_can_write_public_data, is_collect_fee_phase(phase) || is_public_call_request_phase(phase) },
496 { Column::tx_sel_can_emit_unencrypted_log, is_public_call_request_phase(phase) },
497 { Column::tx_sel_can_emit_l2_l1_msg, is_l2_l1_msg_phase(phase) || is_public_call_request_phase(phase) },
498 };
499}
500
501} // namespace
502
505{
506 using C = Column;
507 uint32_t row = 1; // Shifts
508
509 // A nuance of the tracegen for the tx trace is that if there are no events in a phase, we still need to emit a
510 // row for this "skipped" row. This row is needed to simplify the circuit constraints and ensure that we have
511 // continuity in the tree state propagation
512
513 // We bucket the events by phase to make it easier to detect phases with no events
514 std::array<std::vector<const simulation::TxPhaseEvent*>, NUM_PHASES> phase_buckets = {};
515 // We have the phases in iterable form so that in the main loop when we and empty phase
516 // we can map back to this enum
529
531 PhaseLengths phase_lengths{}; // Will be populated from startup event
532
533 bool r_insertion_or_app_logic_failure = false;
534 bool teardown_failure = false;
535 for (const auto& tx_event : events) {
537 startup_event = std::get<simulation::TxStartupEvent>(tx_event);
538 phase_lengths = startup_event.value().phase_lengths;
539 } else {
540 const simulation::TxPhaseEvent& tx_phase_event = std::get<simulation::TxPhaseEvent>(tx_event);
541 // Minus 1 since the enum is 1-indexed
542 phase_buckets[static_cast<uint8_t>(tx_phase_event.phase) - 1].push_back(&tx_phase_event);
543
544 // Set some flags for use when populating the discard column.
545 if (tx_phase_event.reverted) {
546 if (!is_revertible(tx_phase_event.phase)) {
547 throw std::runtime_error("Reverted in non-revertible phase: " +
548 std::to_string(static_cast<uint8_t>(tx_phase_event.phase)));
549 }
550
551 if (tx_phase_event.phase == TransactionPhase::TEARDOWN) {
552 teardown_failure = true;
553 } else {
554 r_insertion_or_app_logic_failure = true;
555 }
556 }
557 }
558 }
559
560 if (!startup_event.has_value()) {
561 throw std::runtime_error("Transaction startup event is missing");
562 }
563
564 const auto& startup_event_data = startup_event.value();
565
566 // This is the tree state we will use during the "skipped" phases
567 TxContextEvent propagated_state = startup_event_data.state;
568 // Used to track the gas limit for the "padded" phases.
569 Gas current_gas_limit = startup_event_data.gas_limit;
570 Gas teardown_gas_limit = startup_event_data.teardown_gas_limit;
571 Gas gas_used = startup_event_data.state.gas_used;
572
573 // Go through each phase except startup and process the events in the phase
574 for (uint32_t i = 0; i < NUM_PHASES; i++) {
575 const auto& phase_events = phase_buckets[i];
576 if (phase_events.empty()) {
577 // There will be no events for a phase if it is skipped (jumped over) due to a revert.
578 // This is different from a phase that has an EmptyPhaseEvent, which is a phase that has no contents to
579 // process, like when app logic starts but has no enqueued calls.
580 continue;
581 }
582
583 TransactionPhase phase = phase_array[i];
584
585 bool discard = false;
586 if (is_revertible(phase)) {
587 if (phase == TransactionPhase::TEARDOWN) {
589 } else {
590 // Even if we don't fail until later in teardown, all revertible phases discard.
591 discard = teardown_failure || r_insertion_or_app_logic_failure;
592 }
593 }
594
595 if (is_teardown_phase(phase)) {
596 current_gas_limit = teardown_gas_limit;
597 }
598
599 // Count the number of steps in this phase
600 uint32_t phase_counter = 0;
601 // Get the phase length from the startup event's phase_lengths
602 // This represents how many items were specified for this phase in the transaction itself.
603 // In case of a revert, we may process less items in a phase as we'll stop at the revert.
604 uint32_t phase_length = get_phase_length(phase_lengths, phase);
605
606 // We have events to process in this phase
607 for (const auto& tx_phase_event : phase_events) {
608 // We always set the tree state
609 trace.set(row, insert_state(tx_phase_event->state_before, tx_phase_event->state_after));
610 trace.set(row,
611 insert_side_effect_states(tx_phase_event->state_before.side_effect_states,
612 tx_phase_event->state_after.side_effect_states));
613 trace.set(
614 row,
615 { {
616 { C::tx_sel, 1 },
617 { C::tx_discard, discard ? 1 : 0 },
618 { C::tx_phase_value, static_cast<uint8_t>(tx_phase_event->phase) },
619 { Column::tx_setup_phase_value, static_cast<uint8_t>(TransactionPhase::SETUP) },
620 { C::tx_is_padded, 0 }, // overidden below if this is a skipped phase event
621 { C::tx_start_phase, phase_counter == 0 ? 1 : 0 },
622 { C::tx_sel_read_phase_length, phase_counter == 0 && !is_one_shot_phase(tx_phase_event->phase) },
623 { C::tx_is_revertible, is_revertible(tx_phase_event->phase) ? 1 : 0 },
624 { C::tx_end_phase, phase_counter == phase_events.size() - 1 ? 1 : 0 },
625 } });
626 trace.set(row, handle_prev_gas_used(gas_used));
627 trace.set(row, handle_state_change_selectors(tx_phase_event->phase));
628 if (row == 1) {
629 trace.set(row, handle_first_row());
630 }
631
632 // Pattern match on the variant event type and call the appropriate handler
633 std::visit(
634 overloaded{ [&](const simulation::EnqueuedCallEvent& event) {
635 trace.set(row, handle_enqueued_call_event(tx_phase_event->phase, event));
636 // No explicit write counter for this phase
637 trace.set(row, handle_pi_read(tx_phase_event->phase, phase_length, phase_counter));
638
639 gas_used = tx_phase_event->state_after.gas_used;
640 },
642 trace.set(row,
643 handle_append_tree_event(event,
644 tx_phase_event->phase,
645 tx_phase_event->state_before,
646 tx_phase_event->reverted));
647
648 trace.set(row, handle_pi_read(tx_phase_event->phase, phase_length, phase_counter));
649 },
651 trace.set(row,
652 handle_l2_l1_msg_event(event,
653 tx_phase_event->phase,
654 tx_phase_event->state_before,
655 tx_phase_event->reverted));
656 trace.set(row, handle_pi_read(tx_phase_event->phase, phase_length, phase_counter));
657 },
659 trace.set(row, handle_pi_read(tx_phase_event->phase, 1, 0));
660 trace.set(row, handle_collect_gas_fee_event(event));
661 },
662 [&](const simulation::PadTreesEvent&) {
663 trace.set(row, handle_pi_read(tx_phase_event->phase, 1, 0));
664 trace.set(row, handle_tree_padding());
665 },
666 [&](const simulation::CleanupEvent&) {
667 trace.set(row, handle_pi_read(tx_phase_event->phase, 1, 0));
668 trace.set(row, handle_cleanup());
669 },
670 [&](const simulation::EmptyPhaseEvent&) {
671 // EmptyPhaseEvent represents a phase that is not explicitly skipped because of a
672 // revert, but just has no contents to process, like when app logic starts but has no
673 // enqueued calls.
674 trace.set(row, handle_pi_read(tx_phase_event->phase, 0, 0));
675 trace.set(row, handle_padded_row(tx_phase_event->phase, gas_used, discard));
676 } },
677 tx_phase_event->event);
678 trace.set(row, handle_next_gas_used(gas_used));
679 trace.set(row, handle_gas_limit(current_gas_limit));
680
681 phase_counter++;
682 row++;
683 }
684 // In case we encounter another skip row
685 propagated_state = phase_events.back()->state_after;
686 }
687
688 // Batch invert the columns.
689 trace.invert_columns(
690 { { C::tx_remaining_phase_inv, C::tx_remaining_phase_minus_one_inv, C::tx_remaining_side_effects_inv } });
691}
692
695 // These are all generic, think which, if any, can be made sequential.
697 .add<lookup_tx_phase_jump_on_revert_settings, InteractionType::LookupGeneric>()
699 .add<lookup_tx_read_calldata_hash_settings, InteractionType::LookupSequential>()
701 .add<lookup_tx_dispatch_exec_start_settings, InteractionType::LookupGeneric>()
703 .add<lookup_tx_read_tree_insert_value_settings, InteractionType::LookupGeneric>()
705 .add<lookup_tx_write_l2_l1_msg_settings, InteractionType::LookupGeneric>()
707 .add<lookup_tx_read_fee_payer_public_inputs_settings, InteractionType::LookupGeneric>()
709 .add<lookup_tx_note_hash_append_settings, InteractionType::LookupGeneric>()
711 .add<lookup_tx_balance_read_settings, InteractionType::LookupGeneric>()
713 .add<lookup_tx_balance_slot_poseidon2_settings, InteractionType::LookupGeneric>()
715 .add<lookup_tx_context_public_inputs_nullifier_tree_settings, InteractionType::LookupIntoIndexedByClk>()
717 .add<lookup_tx_context_public_inputs_l1_l2_tree_settings, InteractionType::LookupIntoIndexedByClk>()
719 .add<lookup_tx_context_public_inputs_read_gas_limit_settings, InteractionType::LookupIntoIndexedByClk>()
721 .add<lookup_tx_context_public_inputs_write_nullifier_count_settings, InteractionType::LookupIntoIndexedByClk>()
727
728} // namespace bb::avm2::tracegen
#define AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_ARRAY_LENGTHS_NOTE_HASHES_ROW_IDX
#define AVM_PUBLIC_INPUTS_END_TREE_SNAPSHOTS_L1_TO_L2_MESSAGE_TREE_ROW_IDX
#define AVM_PUBLIC_INPUTS_FEE_PAYER_ROW_IDX
#define AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX
#define AVM_PUBLIC_INPUTS_GAS_SETTINGS_TEARDOWN_GAS_LIMITS_ROW_IDX
#define AVM_PUBLIC_INPUTS_TRANSACTION_FEE_ROW_IDX
#define AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_ARRAY_LENGTHS_L2_TO_L1_MSGS_ROW_IDX
#define AVM_PUBLIC_INPUTS_END_GAS_USED_ROW_IDX
#define AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_ARRAY_LENGTHS_NULLIFIERS_ROW_IDX
#define AVM_PUBLIC_INPUTS_START_TREE_SNAPSHOTS_L1_TO_L2_MESSAGE_TREE_ROW_IDX
#define AVM_PUBLIC_INPUTS_END_TREE_SNAPSHOTS_NULLIFIER_TREE_ROW_IDX
#define FEE_JUICE_ADDRESS
#define AVM_PUBLIC_INPUTS_START_TREE_SNAPSHOTS_NOTE_HASH_TREE_ROW_IDX
#define AVM_PUBLIC_INPUTS_END_TREE_SNAPSHOTS_NOTE_HASH_TREE_ROW_IDX
#define MAX_L2_TO_L1_MSGS_PER_TX
#define MAX_NOTE_HASHES_PER_TX
#define AVM_PUBLIC_INPUTS_START_TREE_SNAPSHOTS_PUBLIC_DATA_TREE_ROW_IDX
#define FEE_JUICE_BALANCES_SLOT
#define AVM_PUBLIC_INPUTS_START_TREE_SNAPSHOTS_NULLIFIER_TREE_ROW_IDX
#define MAX_NULLIFIERS_PER_TX
#define AVM_PUBLIC_INPUTS_START_GAS_USED_ROW_IDX
#define AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_PUBLIC_LOGS_ROW_IDX
#define AVM_PUBLIC_INPUTS_GAS_SETTINGS_GAS_LIMITS_ROW_IDX
#define AVM_PUBLIC_INPUTS_END_TREE_SNAPSHOTS_PUBLIC_DATA_TREE_ROW_IDX
InteractionDefinition & add(auto &&... args)
static const Offsets & get_offsets(TransactionPhase phase)
void process(const simulation::EventEmitterInterface< simulation::TxEvent >::Container &events, TraceContainer &trace)
Definition tx_trace.cpp:503
static const InteractionDefinition interactions
Definition tx_trace.hpp:19
TestTraceContainer trace
bool teardown_failure
lookup_settings< lookup_tx_read_effective_fee_public_inputs_settings_ > lookup_tx_read_effective_fee_public_inputs_settings
lookup_settings< lookup_tx_context_public_inputs_write_l2_to_l1_message_count_settings_ > lookup_tx_context_public_inputs_write_l2_to_l1_message_count_settings
lookup_settings< lookup_tx_context_public_inputs_note_hash_tree_settings_ > lookup_tx_context_public_inputs_note_hash_tree_settings
lookup_settings< lookup_tx_dispatch_exec_end_settings_ > lookup_tx_dispatch_exec_end_settings
lookup_settings< lookup_tx_balance_validation_settings_ > lookup_tx_balance_validation_settings
lookup_settings< lookup_tx_read_phase_table_settings_ > lookup_tx_read_phase_table_settings
@ NR_NOTE_INSERTION
@ R_NULLIFIER_INSERTION
@ NR_L2_TO_L1_MESSAGE
@ R_L2_TO_L1_MESSAGE
@ NR_NULLIFIER_INSERTION
lookup_settings< lookup_tx_write_fee_public_inputs_settings_ > lookup_tx_write_fee_public_inputs_settings
lookup_settings< lookup_tx_context_public_inputs_public_data_tree_settings_ > lookup_tx_context_public_inputs_public_data_tree_settings
lookup_settings< lookup_tx_context_public_inputs_gas_used_settings_ > lookup_tx_context_public_inputs_gas_used_settings
lookup_settings< lookup_tx_nullifier_append_settings_ > lookup_tx_nullifier_append_settings
lookup_settings< lookup_tx_read_l2_l1_msg_settings_ > lookup_tx_read_l2_l1_msg_settings
lookup_settings< lookup_tx_context_restore_state_on_revert_settings_ > lookup_tx_context_restore_state_on_revert_settings
lookup_settings< lookup_tx_context_public_inputs_write_note_hash_count_settings_ > lookup_tx_context_public_inputs_write_note_hash_count_settings
lookup_settings< lookup_tx_read_public_call_request_phase_settings_ > lookup_tx_read_public_call_request_phase_settings
AvmFlavorSettings::FF FF
Definition field.hpp:10
lookup_settings< lookup_tx_read_phase_length_settings_ > lookup_tx_read_phase_length_settings
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
simulation::PublicDataTreeReadWriteEvent event