Skip to content

Commit 02cd521

Browse files
committed
chore!: refactor avm tx execution to generate skipped phase events
1 parent e446a77 commit 02cd521

File tree

4 files changed

+119
-84
lines changed

4 files changed

+119
-84
lines changed

barretenberg/cpp/src/barretenberg/vm2/simulation/events/tx_events.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,15 @@ struct PadTreesEvent {};
4848

4949
struct CleanupEvent {};
5050

51+
struct SkippedPhaseEvent {};
52+
5153
using TxPhaseEventType = std::variant<EnqueuedCallEvent,
5254
PrivateAppendTreeEvent,
5355
PrivateEmitL2L1MessageEvent,
5456
CollectGasFeeEvent,
5557
PadTreesEvent,
56-
CleanupEvent>;
58+
CleanupEvent,
59+
SkippedPhaseEvent>;
5760

5861
struct TxPhaseEvent {
5962
TransactionPhase phase;

barretenberg/cpp/src/barretenberg/vm2/simulation/tx_execution.cpp

Lines changed: 105 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -83,50 +83,11 @@ void TxExecution::simulate(const Tx& tx)
8383
insert_non_revertibles(tx);
8484

8585
// Setup.
86-
for (const auto& call : tx.setupEnqueuedCalls) {
87-
info("[SETUP] Executing enqueued call to ", call.request.contractAddress);
88-
TxContextEvent state_before = tx_context.serialize_tx_context_event();
89-
Gas start_gas = tx_context.gas_used;
90-
auto context = context_provider.make_enqueued_context(call.request.contractAddress,
91-
call.request.msgSender,
92-
/*transaction_fee=*/FF(0),
93-
call.calldata,
94-
call.request.isStaticCall,
95-
gas_limit,
96-
start_gas,
97-
tx_context.side_effect_states,
98-
TransactionPhase::SETUP);
99-
// This call should not throw unless it's an unexpected unrecoverable failure.
100-
ExecutionResult result = call_execution.execute(std::move(context));
101-
tx_context.side_effect_states = result.side_effect_states;
102-
tx_context.gas_used = result.gas_used;
103-
emit_public_call_request(call,
104-
TransactionPhase::SETUP,
105-
/*transaction_fee=*/FF(0),
106-
result.success,
107-
start_gas,
108-
tx_context.gas_used,
109-
state_before,
110-
tx_context.serialize_tx_context_event());
111-
if (!result.success) {
112-
// This will result in an unprovable tx.
113-
throw TxExecutionException(
114-
format("[SETUP] UNRECOVERABLE ERROR! Enqueued call to ", call.request.contractAddress, " failed"));
115-
}
116-
}
117-
SideEffectStates end_setup_side_effect_states = tx_context.side_effect_states;
118-
119-
// The checkpoint we should go back to if anything from now on reverts.
120-
merkle_db.create_checkpoint();
121-
122-
try {
123-
// Insert revertibles. This can throw if there is a nullifier collision.
124-
// Such an exception should be handled and the tx be provable.
125-
insert_revertibles(tx);
126-
127-
// App logic.
128-
for (const auto& call : tx.appLogicEnqueuedCalls) {
129-
info("[APP_LOGIC] Executing enqueued call to ", call.request.contractAddress);
86+
if (tx.setupEnqueuedCalls.empty()) {
87+
emit_skipped_phase(TransactionPhase::SETUP);
88+
} else {
89+
for (const auto& call : tx.setupEnqueuedCalls) {
90+
info("[SETUP] Executing enqueued call to ", call.request.contractAddress);
13091
TxContextEvent state_before = tx_context.serialize_tx_context_event();
13192
Gas start_gas = tx_context.gas_used;
13293
auto context = context_provider.make_enqueued_context(call.request.contractAddress,
@@ -137,23 +98,70 @@ void TxExecution::simulate(const Tx& tx)
13798
gas_limit,
13899
start_gas,
139100
tx_context.side_effect_states,
140-
TransactionPhase::APP_LOGIC);
101+
TransactionPhase::SETUP);
141102
// This call should not throw unless it's an unexpected unrecoverable failure.
142103
ExecutionResult result = call_execution.execute(std::move(context));
143104
tx_context.side_effect_states = result.side_effect_states;
144105
tx_context.gas_used = result.gas_used;
145106
emit_public_call_request(call,
146-
TransactionPhase::APP_LOGIC,
107+
TransactionPhase::SETUP,
147108
/*transaction_fee=*/FF(0),
148109
result.success,
149110
start_gas,
150111
tx_context.gas_used,
151112
state_before,
152113
tx_context.serialize_tx_context_event());
153114
if (!result.success) {
154-
// This exception should be handled and the tx be provable.
115+
// This will result in an unprovable tx.
155116
throw TxExecutionException(
156-
format("[APP_LOGIC] Enqueued call to ", call.request.contractAddress, " failed"));
117+
format("[SETUP] UNRECOVERABLE ERROR! Enqueued call to ", call.request.contractAddress, " failed"));
118+
}
119+
}
120+
}
121+
SideEffectStates end_setup_side_effect_states = tx_context.side_effect_states;
122+
123+
// The checkpoint we should go back to if anything from now on reverts.
124+
merkle_db.create_checkpoint();
125+
126+
try {
127+
// Insert revertibles. This can throw if there is a nullifier collision.
128+
// Such an exception should be handled and the tx be provable.
129+
insert_revertibles(tx);
130+
131+
// App logic.
132+
if (tx.appLogicEnqueuedCalls.empty()) {
133+
emit_skipped_phase(TransactionPhase::APP_LOGIC);
134+
} else {
135+
for (const auto& call : tx.appLogicEnqueuedCalls) {
136+
info("[APP_LOGIC] Executing enqueued call to ", call.request.contractAddress);
137+
TxContextEvent state_before = tx_context.serialize_tx_context_event();
138+
Gas start_gas = tx_context.gas_used;
139+
auto context = context_provider.make_enqueued_context(call.request.contractAddress,
140+
call.request.msgSender,
141+
/*transaction_fee=*/FF(0),
142+
call.calldata,
143+
call.request.isStaticCall,
144+
gas_limit,
145+
start_gas,
146+
tx_context.side_effect_states,
147+
TransactionPhase::APP_LOGIC);
148+
// This call should not throw unless it's an unexpected unrecoverable failure.
149+
ExecutionResult result = call_execution.execute(std::move(context));
150+
tx_context.side_effect_states = result.side_effect_states;
151+
tx_context.gas_used = result.gas_used;
152+
emit_public_call_request(call,
153+
TransactionPhase::APP_LOGIC,
154+
/*transaction_fee=*/FF(0),
155+
result.success,
156+
start_gas,
157+
tx_context.gas_used,
158+
state_before,
159+
tx_context.serialize_tx_context_event());
160+
if (!result.success) {
161+
// This exception should be handled and the tx be provable.
162+
throw TxExecutionException(
163+
format("[APP_LOGIC] Enqueued call to ", call.request.contractAddress, " failed"));
164+
}
157165
}
158166
}
159167
} catch (const TxExecutionException& e) {
@@ -175,7 +183,9 @@ void TxExecution::simulate(const Tx& tx)
175183

176184
// Teardown.
177185
try {
178-
if (tx.teardownEnqueuedCall) {
186+
if (!tx.teardownEnqueuedCall) {
187+
emit_skipped_phase(TransactionPhase::TEARDOWN);
188+
} else {
179189
info("[TEARDOWN] Executing enqueued call to ", tx.teardownEnqueuedCall->request.contractAddress);
180190
// Teardown has its own gas limit and usage.
181191
Gas start_gas = { 0, 0 };
@@ -334,18 +344,30 @@ void TxExecution::insert_non_revertibles(const Tx& tx)
334344
tx.hash);
335345

336346
// 1. Write the already siloed nullifiers.
337-
for (const auto& nullifier : tx.nonRevertibleAccumulatedData.nullifiers) {
338-
emit_nullifier(false, nullifier);
347+
if (tx.nonRevertibleAccumulatedData.nullifiers.empty()) {
348+
emit_skipped_phase(TransactionPhase::NR_NULLIFIER_INSERTION);
349+
} else {
350+
for (const auto& nullifier : tx.nonRevertibleAccumulatedData.nullifiers) {
351+
emit_nullifier(false, nullifier);
352+
}
339353
}
340354

341355
// 2. Write already unique note hashes.
342-
for (const auto& unique_note_hash : tx.nonRevertibleAccumulatedData.noteHashes) {
343-
emit_note_hash(false, unique_note_hash);
356+
if (tx.nonRevertibleAccumulatedData.noteHashes.empty()) {
357+
emit_skipped_phase(TransactionPhase::NR_NOTE_INSERTION);
358+
} else {
359+
for (const auto& unique_note_hash : tx.nonRevertibleAccumulatedData.noteHashes) {
360+
emit_note_hash(false, unique_note_hash);
361+
}
344362
}
345363

346364
// 3. Write l2_l1 messages
347-
for (const auto& l2_to_l1_msg : tx.nonRevertibleAccumulatedData.l2ToL1Messages) {
348-
emit_l2_to_l1_message(false, l2_to_l1_msg);
365+
if (tx.nonRevertibleAccumulatedData.l2ToL1Messages.empty()) {
366+
emit_skipped_phase(TransactionPhase::NR_L2_TO_L1_MESSAGE);
367+
} else {
368+
for (const auto& l2_to_l1_msg : tx.nonRevertibleAccumulatedData.l2ToL1Messages) {
369+
emit_l2_to_l1_message(false, l2_to_l1_msg);
370+
}
349371
}
350372
}
351373

@@ -362,18 +384,30 @@ void TxExecution::insert_revertibles(const Tx& tx)
362384
tx.hash);
363385

364386
// 1. Write the already siloed nullifiers.
365-
for (const auto& siloed_nullifier : tx.revertibleAccumulatedData.nullifiers) {
366-
emit_nullifier(true, siloed_nullifier);
387+
if (tx.revertibleAccumulatedData.nullifiers.empty()) {
388+
emit_skipped_phase(TransactionPhase::R_NULLIFIER_INSERTION);
389+
} else {
390+
for (const auto& siloed_nullifier : tx.revertibleAccumulatedData.nullifiers) {
391+
emit_nullifier(true, siloed_nullifier);
392+
}
367393
}
368394

369395
// 2. Write the siloed non uniqued note hashes
370-
for (const auto& siloed_note_hash : tx.revertibleAccumulatedData.noteHashes) {
371-
emit_note_hash(true, siloed_note_hash);
396+
if (tx.revertibleAccumulatedData.noteHashes.empty()) {
397+
emit_skipped_phase(TransactionPhase::R_NOTE_INSERTION);
398+
} else {
399+
for (const auto& siloed_note_hash : tx.revertibleAccumulatedData.noteHashes) {
400+
emit_note_hash(true, siloed_note_hash);
401+
}
372402
}
373403

374404
// 3. Write L2 to L1 messages.
375-
for (const auto& l2_to_l1_msg : tx.revertibleAccumulatedData.l2ToL1Messages) {
376-
emit_l2_to_l1_message(true, l2_to_l1_msg);
405+
if (tx.revertibleAccumulatedData.l2ToL1Messages.empty()) {
406+
emit_skipped_phase(TransactionPhase::R_L2_TO_L1_MESSAGE);
407+
} else {
408+
for (const auto& l2_to_l1_msg : tx.revertibleAccumulatedData.l2ToL1Messages) {
409+
emit_l2_to_l1_message(true, l2_to_l1_msg);
410+
}
377411
}
378412
}
379413

@@ -426,4 +460,14 @@ void TxExecution::cleanup()
426460
.event = CleanupEvent{} });
427461
}
428462

463+
void TxExecution::emit_skipped_phase(TransactionPhase phase)
464+
{
465+
TxContextEvent current_state = tx_context.serialize_tx_context_event();
466+
events.emit(TxPhaseEvent{ .phase = phase,
467+
.state_before = current_state,
468+
.state_after = current_state,
469+
.reverted = false,
470+
.event = SkippedPhaseEvent{} });
471+
}
472+
429473
} // namespace bb::avm2::simulation

barretenberg/cpp/src/barretenberg/vm2/simulation/tx_execution.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ class TxExecution final {
6666
void pad_trees();
6767

6868
void cleanup();
69+
70+
void emit_skipped_phase(TransactionPhase phase);
6971
};
7072

7173
} // namespace bb::avm2::simulation

barretenberg/cpp/src/barretenberg/vm2/tracegen/tx_trace.cpp

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,6 @@ void TxTraceBuilder::process(const simulation::EventEmitterInterface<simulation:
529529

530530
// This is the tree state we will use during the "skipped" phases
531531
TxContextEvent propagated_state = startup_event_data.state;
532-
TxContextEvent end_setup_snapshot = startup_event_data.state;
533532
// Used to track the gas limit for the "padded" phases.
534533
Gas current_gas_limit = startup_event_data.gas_limit;
535534
Gas teardown_gas_limit = startup_event_data.teardown_gas_limit;
@@ -566,10 +565,7 @@ void TxTraceBuilder::process(const simulation::EventEmitterInterface<simulation:
566565
if (row == 1) {
567566
trace.set(row, handle_first_row());
568567
}
569-
if (phase == TransactionPhase::SETUP) {
570-
// If setup is empty, the end-setup-snapshot should just be current/propagated state
571-
end_setup_snapshot = propagated_state;
572-
}
568+
573569
row++;
574570
continue;
575571
}
@@ -591,7 +587,7 @@ void TxTraceBuilder::process(const simulation::EventEmitterInterface<simulation:
591587
{ C::tx_discard, discard ? 1 : 0 },
592588
{ C::tx_phase_value, static_cast<uint8_t>(tx_phase_event->phase) },
593589
{ Column::tx_setup_phase_value, static_cast<uint8_t>(TransactionPhase::SETUP) },
594-
{ C::tx_is_padded, 0 },
590+
{ C::tx_is_padded, 0 }, // overidden below if this is a skipped phase event
595591
{ C::tx_start_phase, phase_counter == 0 ? 1 : 0 },
596592
{ C::tx_sel_read_phase_length, phase_counter == 0 && !is_one_shot_phase(tx_phase_event->phase) },
597593
{ C::tx_is_revertible, is_revertible(tx_phase_event->phase) ? 1 : 0 },
@@ -640,6 +636,12 @@ void TxTraceBuilder::process(const simulation::EventEmitterInterface<simulation:
640636
[&](const simulation::CleanupEvent&) {
641637
trace.set(row, handle_pi_read(tx_phase_event->phase, 1, 0));
642638
trace.set(row, handle_cleanup());
639+
},
640+
[&](const simulation::SkippedPhaseEvent&) {
641+
// SkippedPhaseEvent is just a marker - the state and phase handling
642+
// is already done through the common TxPhaseEvent handling above
643+
trace.set(row, handle_pi_read(tx_phase_event->phase, 0, 0));
644+
trace.set(row, handle_padded_row(tx_phase_event->phase, gas_used, discard));
643645
} },
644646
tx_phase_event->event);
645647
trace.set(row, handle_next_gas_used(gas_used));
@@ -658,22 +660,6 @@ void TxTraceBuilder::process(const simulation::EventEmitterInterface<simulation:
658660
}
659661
// In case we encounter another skip row
660662
propagated_state = phase_events.back()->state_after;
661-
662-
if (phase == TransactionPhase::SETUP) {
663-
// Store off the state at the end of setup to rollback to later on revert
664-
end_setup_snapshot = phase_events.back()->state_after;
665-
}
666-
if (phase_events.back()->reverted) {
667-
// On revert, roll back to end-setup-snapshot
668-
// Even though tx-execution events should already do this,
669-
// we need to update propagated state here so that any padded rows
670-
// get the correct rolled-back state rather then the pre-rollback state.
671-
propagated_state.tree_states = end_setup_snapshot.tree_states;
672-
propagated_state.written_public_data_slots_tree_snapshot =
673-
end_setup_snapshot.written_public_data_slots_tree_snapshot;
674-
propagated_state.side_effect_states = end_setup_snapshot.side_effect_states;
675-
// Note: we only rollback tree/side-effect states, not gas used or next_context_id.
676-
}
677663
}
678664
}
679665

0 commit comments

Comments
 (0)