1212#include " barretenberg/vm2/simulation/tx_context.hpp"
1313
1414namespace bb ::avm2::simulation {
15+ namespace {
16+
17+ // A tx-level exception that is expected to be handled.
18+ // This is in contrast to other runtime exceptions that might happen and should be propagated.
19+ class TxExecutionException : public std ::runtime_error {
20+ public:
21+ TxExecutionException (const std::string& message)
22+ : std::runtime_error(message)
23+ {}
24+ };
25+
26+ } // namespace
1527
1628void TxExecution::emit_public_call_request (const PublicCallRequestWithCalldata& call,
1729 TransactionPhase phase,
@@ -67,6 +79,7 @@ void TxExecution::simulate(const Tx& tx)
6779 tx.teardownEnqueuedCall ? " 1 teardown enqueued call" : " no teardown enqueued call" );
6880
6981 // Insert non-revertibles. This can throw if there is a nullifier collision.
82+ // That would result in an unprovable tx.
7083 insert_non_revertibles (tx);
7184
7285 // Setup.
@@ -83,11 +96,8 @@ void TxExecution::simulate(const Tx& tx)
8396 start_gas,
8497 tx_context.side_effect_states ,
8598 TransactionPhase::SETUP);
99+ // This call should not throw unless it's an unexpected unrecoverable failure.
86100 ExecutionResult result = call_execution.execute (std::move (context));
87- if (!result.success ) {
88- throw std::runtime_error (
89- format (" [SETUP] UNRECOVERABLE ERROR! Enqueued call to " , call.request .contractAddress , " failed" ));
90- }
91101 tx_context.side_effect_states = result.side_effect_states ;
92102 tx_context.gas_used = result.gas_used ;
93103 emit_public_call_request (call,
@@ -98,13 +108,19 @@ void TxExecution::simulate(const Tx& tx)
98108 tx_context.gas_used ,
99109 state_before,
100110 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+ }
101116 }
102117
103118 // The checkpoint we should go back to if anything from now on reverts.
104119 merkle_db.create_checkpoint ();
105120
106121 try {
107122 // Insert revertibles. This can throw if there is a nullifier collision.
123+ // Such an exception should be handled and the tx be provable.
108124 insert_revertibles (tx);
109125
110126 // App logic.
@@ -121,6 +137,7 @@ void TxExecution::simulate(const Tx& tx)
121137 start_gas,
122138 tx_context.side_effect_states ,
123139 TransactionPhase::APP_LOGIC);
140+ // This call should not throw unless it's an unexpected unrecoverable failure.
124141 ExecutionResult result = call_execution.execute (std::move (context));
125142 tx_context.side_effect_states = result.side_effect_states ;
126143 tx_context.gas_used = result.gas_used ;
@@ -133,11 +150,12 @@ void TxExecution::simulate(const Tx& tx)
133150 state_before,
134151 tx_context.serialize_tx_context_event ());
135152 if (!result.success ) {
136- throw std::runtime_error (
153+ // This exception should be handled and the tx be provable.
154+ throw TxExecutionException (
137155 format (" [APP_LOGIC] Enqueued call to " , call.request .contractAddress , " failed" ));
138156 }
139157 }
140- } catch (const std::runtime_error & e) {
158+ } catch (const TxExecutionException & e) {
141159 info (" Revertible failure while simulating tx " , tx.hash , " : " , e.what ());
142160 // We revert to the post-setup state.
143161 merkle_db.revert_checkpoint ();
@@ -170,6 +188,7 @@ void TxExecution::simulate(const Tx& tx)
170188 start_gas,
171189 tx_context.side_effect_states ,
172190 TransactionPhase::TEARDOWN);
191+ // This call should not throw unless it's an unexpected unrecoverable failure.
173192 ExecutionResult result = call_execution.execute (std::move (context));
174193 tx_context.side_effect_states = result.side_effect_states ;
175194 // Check what to do here for GAS
@@ -182,14 +201,15 @@ void TxExecution::simulate(const Tx& tx)
182201 state_before,
183202 tx_context.serialize_tx_context_event ());
184203 if (!result.success ) {
185- throw std::runtime_error (format (
204+ // This exception should be handled and the tx be provable.
205+ throw TxExecutionException (format (
186206 " [TEARDOWN] Enqueued call to " , tx.teardownEnqueuedCall ->request .contractAddress , " failed" ));
187207 }
188208 }
189209
190210 // We commit the forked state and we are done.
191211 merkle_db.commit_checkpoint ();
192- } catch (const std::runtime_error & e) {
212+ } catch (const TxExecutionException & e) {
193213 info (" Teardown failure while simulating tx " , tx.hash , " : " , e.what ());
194214 // We rollback to the post-setup state.
195215 merkle_db.revert_checkpoint ();
@@ -212,19 +232,19 @@ void TxExecution::emit_nullifier(bool revertible, const FF& nullifier)
212232 uint32_t prev_nullifier_count = merkle_db.get_tree_state ().nullifierTree .counter ;
213233
214234 if (prev_nullifier_count == MAX_NULLIFIERS_PER_TX) {
215- throw std::runtime_error (" Maximum number of nullifiers reached" );
235+ throw TxExecutionException (" Maximum number of nullifiers reached" );
216236 }
217237 bool success = merkle_db.siloed_nullifier_write (nullifier);
218238 if (!success) {
219- throw std::runtime_error (" Nullifier collision" );
239+ throw TxExecutionException (" Nullifier collision" );
220240 }
221241
222242 events.emit (TxPhaseEvent{ .phase = phase,
223243 .state_before = state_before,
224244 .state_after = tx_context.serialize_tx_context_event (),
225245 .event = PrivateAppendTreeEvent{ .leaf_value = nullifier } });
226246
227- } catch (const std::runtime_error & e) {
247+ } catch (const TxExecutionException & e) {
228248 events.emit (TxPhaseEvent{
229249 .phase = phase,
230250 .state_before = state_before,
@@ -246,7 +266,7 @@ void TxExecution::emit_note_hash(bool revertible, const FF& note_hash)
246266 uint32_t prev_note_hash_count = merkle_db.get_tree_state ().noteHashTree .counter ;
247267
248268 if (prev_note_hash_count == MAX_NOTE_HASHES_PER_TX) {
249- throw std::runtime_error (" Maximum number of note hashes reached" );
269+ throw TxExecutionException (" Maximum number of note hashes reached" );
250270 }
251271
252272 if (revertible) {
@@ -259,7 +279,7 @@ void TxExecution::emit_note_hash(bool revertible, const FF& note_hash)
259279 .state_before = state_before,
260280 .state_after = tx_context.serialize_tx_context_event (),
261281 .event = PrivateAppendTreeEvent{ .leaf_value = note_hash } });
262- } catch (const std::runtime_error & e) {
282+ } catch (const TxExecutionException & e) {
263283 events.emit (TxPhaseEvent{ .phase = phase,
264284 .state_before = state_before,
265285 .state_after = tx_context.serialize_tx_context_event (),
@@ -277,7 +297,7 @@ void TxExecution::emit_l2_to_l1_message(bool revertible, const ScopedL2ToL1Messa
277297
278298 try {
279299 if (tx_context.side_effect_states .numL2ToL1Messages == MAX_L2_TO_L1_MSGS_PER_TX) {
280- throw std::runtime_error (" Maximum number of L2 to L1 messages reached" );
300+ throw TxExecutionException (" Maximum number of L2 to L1 messages reached" );
281301 }
282302 // TODO: We don't store the l2 to l1 message in the context since it's not needed until cpp has to generate
283303 // public inputs.
@@ -286,7 +306,7 @@ void TxExecution::emit_l2_to_l1_message(bool revertible, const ScopedL2ToL1Messa
286306 .state_before = state_before,
287307 .state_after = tx_context.serialize_tx_context_event (),
288308 .event = PrivateEmitL2L1MessageEvent{ .scoped_msg = l2_to_l1_message } });
289- } catch (const std::runtime_error & e) {
309+ } catch (const TxExecutionException & e) {
290310 events.emit (TxPhaseEvent{ .phase = phase,
291311 .state_before = state_before,
292312 .state_after = tx_context.serialize_tx_context_event (),
@@ -299,7 +319,7 @@ void TxExecution::emit_l2_to_l1_message(bool revertible, const ScopedL2ToL1Messa
299319
300320// TODO: How to increment the context id here?
301321// This function inserts the non-revertible accumulated data into the Merkle DB.
302- // It might error if the limits for number of allowable inserts are exceeded, but this result in an unprovable tx
322+ // It might error if the limits for number of allowable inserts are exceeded, but this result in an unprovable tx.
303323void TxExecution::insert_non_revertibles (const Tx& tx)
304324{
305325 info (" [NON_REVERTIBLE] Inserting " ,
@@ -368,7 +388,7 @@ void TxExecution::pay_fee(const FF& fee_payer,
368388
369389 if (field_gt.ff_gt (fee, fee_payer_balance)) {
370390 // Unrecoverable error.
371- throw std::runtime_error (" Not enough balance for fee payer to pay for transaction" );
391+ throw TxExecutionException (" Not enough balance for fee payer to pay for transaction" );
372392 }
373393
374394 merkle_db.storage_write (FEE_JUICE_ADDRESS, fee_juice_balance_slot, fee_payer_balance - fee, true );
0 commit comments