Skip to content

Commit 3a379e8

Browse files
authored
chore(avm): remove trace truncation (#16609)
Given that Execution should never throw, every exception coming from there will result in an unprovable transaction (crash). I created a `TxExecutionException` for handleable TX-level errors. We could've avoided exceptions altogether but the semantics of a try-catch lets us skip the rest of the code when throwing and that makes it easier to read, I think.
2 parents a64a6eb + 9369396 commit 3a379e8

File tree

1 file changed

+37
-17
lines changed

1 file changed

+37
-17
lines changed

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

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@
1212
#include "barretenberg/vm2/simulation/tx_context.hpp"
1313

1414
namespace 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

1628
void 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.
303323
void 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

Comments
 (0)