Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
tx_execution.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <stdexcept>
5
13
14namespace bb::avm2::simulation {
15namespace {
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.
19class TxExecutionException : public std::runtime_error {
20 public:
21 TxExecutionException(const std::string& message)
22 : std::runtime_error(message)
23 {}
24};
25
26} // namespace
27
29 TransactionPhase phase,
30 const FF& transaction_fee,
31 bool success,
32 const Gas& start_gas,
33 const Gas& end_gas,
34 const TxContextEvent& state_before,
35 const TxContextEvent& state_after)
36{
37 events.emit(TxPhaseEvent{ .phase = phase,
38 .state_before = state_before,
39 .state_after = state_after,
40 .reverted = !success,
41 .event = EnqueuedCallEvent{
43 .contract_address = call.request.contractAddress,
44 .transaction_fee = transaction_fee,
45 .is_static = call.request.isStaticCall,
46 .calldata_size = static_cast<uint32_t>(call.calldata.size()),
47 .calldata_hash = call.request.calldataHash,
48 .start_gas = start_gas,
49 .end_gas = end_gas,
50 .success = success,
51 } });
52}
53
54// Simulates the entire transaction execution phases.
55// There are multiple distinct transaction phases that are executed in order:
56// (1) Non-revertible insertions of nullifiers, note hashes, and L2 to L1 messages.
57// (2) Setup phase, where the setup enqueued calls are executed.
58// (3) Revertible insertions of nullifiers, note hashes, and L2 to L1 messages.
59// (4) App logic phase, where the app logic enqueued calls are executed.
60// (5) Collec Gas fee
62{
63 Gas gas_limit = tx.gasSettings.gasLimits;
64 Gas teardown_gas_limit = tx.gasSettings.teardownGasLimits;
65 tx_context.gas_used = tx.gasUsedByPrivate;
66
68 .state = tx_context.serialize_tx_context_event(),
69 .gas_limit = gas_limit,
70 .teardown_gas_limit = teardown_gas_limit,
71 .phase_lengths = PhaseLengths::from_tx(tx), // extract lengths of each phase at start
72 });
73
74 info("Simulating tx ",
75 tx.hash,
76 " with ",
77 tx.setupEnqueuedCalls.size(),
78 " setup enqueued calls, ",
79 tx.appLogicEnqueuedCalls.size(),
80 " app logic enqueued calls, and ",
81 tx.teardownEnqueuedCall ? "1 teardown enqueued call" : "no teardown enqueued call");
82
83 // Insert non-revertibles. This can throw if there is a nullifier collision.
84 // That would result in an unprovable tx.
86
87 // Setup.
88 if (tx.setupEnqueuedCalls.empty()) {
90 } else {
91 for (const auto& call : tx.setupEnqueuedCalls) {
92 vinfo("[SETUP] Executing enqueued call to ", call.request.contractAddress);
93 TxContextEvent state_before = tx_context.serialize_tx_context_event();
94 Gas start_gas = tx_context.gas_used;
95 auto context = context_provider.make_enqueued_context(call.request.contractAddress,
96 call.request.msgSender,
97 /*transaction_fee=*/FF(0),
98 call.calldata,
99 call.request.isStaticCall,
100 gas_limit,
101 start_gas,
102 tx_context.side_effect_states,
104 // This call should not throw unless it's an unexpected unrecoverable failure.
106 tx_context.side_effect_states = result.side_effect_states;
107 tx_context.gas_used = result.gas_used;
110 /*transaction_fee=*/FF(0),
111 result.success,
112 start_gas,
113 tx_context.gas_used,
114 state_before,
115 tx_context.serialize_tx_context_event());
116 if (!result.success) {
117 // This will result in an unprovable tx.
118 throw TxExecutionException(
119 format("[SETUP] UNRECOVERABLE ERROR! Enqueued call to ", call.request.contractAddress, " failed"));
120 }
121 }
122 }
123 SideEffectStates end_setup_side_effect_states = tx_context.side_effect_states;
124
125 // The checkpoint we should go back to if anything from now on reverts.
127
128 try {
129 // Insert revertibles. This can throw if there is a nullifier collision.
130 // Such an exception should be handled and the tx be provable.
132
133 // App logic.
134 if (tx.appLogicEnqueuedCalls.empty()) {
136 } else {
137 for (const auto& call : tx.appLogicEnqueuedCalls) {
138 vinfo("[APP_LOGIC] Executing enqueued call to ", call.request.contractAddress);
139 TxContextEvent state_before = tx_context.serialize_tx_context_event();
140 Gas start_gas = tx_context.gas_used;
141 auto context = context_provider.make_enqueued_context(call.request.contractAddress,
142 call.request.msgSender,
143 /*transaction_fee=*/FF(0),
144 call.calldata,
145 call.request.isStaticCall,
146 gas_limit,
147 start_gas,
148 tx_context.side_effect_states,
150 // This call should not throw unless it's an unexpected unrecoverable failure.
152 tx_context.side_effect_states = result.side_effect_states;
153 tx_context.gas_used = result.gas_used;
156 /*transaction_fee=*/FF(0),
157 result.success,
158 start_gas,
159 tx_context.gas_used,
160 state_before,
161 tx_context.serialize_tx_context_event());
162 if (!result.success) {
163 // This exception should be handled and the tx be provable.
164 throw TxExecutionException(
165 format("[APP_LOGIC] Enqueued call to ", call.request.contractAddress, " failed"));
166 }
167 }
168 }
169 } catch (const TxExecutionException& e) {
170 info("Revertible failure while simulating tx ", tx.hash, ": ", e.what());
171 // We revert to the post-setup state.
173 tx_context.side_effect_states = end_setup_side_effect_states;
174 // But we also create a new fork so that the teardown phase can transparently
175 // commit or rollback to the end of teardown.
177 }
178
179 // Compute the transaction fee here so it can be passed to teardown
180 Gas gas_used_before_teardown = tx_context.gas_used;
181 uint128_t fee_per_da_gas = tx.effectiveGasFees.feePerDaGas;
182 uint128_t fee_per_l2_gas = tx.effectiveGasFees.feePerL2Gas;
183 FF fee = FF(fee_per_da_gas) * FF(gas_used_before_teardown.daGas) +
184 FF(fee_per_l2_gas) * FF(gas_used_before_teardown.l2Gas);
185
186 // Teardown.
187 try {
188 if (!tx.teardownEnqueuedCall) {
190 } else {
191 vinfo("[TEARDOWN] Executing enqueued call to ", tx.teardownEnqueuedCall->request.contractAddress);
192 // Teardown has its own gas limit and usage.
193 Gas start_gas = { 0, 0 };
194 gas_limit = teardown_gas_limit;
195 TxContextEvent state_before = tx_context.serialize_tx_context_event();
196 auto context = context_provider.make_enqueued_context(tx.teardownEnqueuedCall->request.contractAddress,
197 tx.teardownEnqueuedCall->request.msgSender,
198 fee,
199 tx.teardownEnqueuedCall->calldata,
200 tx.teardownEnqueuedCall->request.isStaticCall,
201 gas_limit,
202 start_gas,
203 tx_context.side_effect_states,
205 // This call should not throw unless it's an unexpected unrecoverable failure.
207 tx_context.side_effect_states = result.side_effect_states;
208 // Check what to do here for GAS
209 emit_public_call_request(*tx.teardownEnqueuedCall,
211 fee,
212 result.success,
213 start_gas,
214 result.gas_used,
215 state_before,
216 tx_context.serialize_tx_context_event());
217 if (!result.success) {
218 // This exception should be handled and the tx be provable.
219 throw TxExecutionException(format(
220 "[TEARDOWN] Enqueued call to ", tx.teardownEnqueuedCall->request.contractAddress, " failed"));
221 }
222 }
223
224 // We commit the forked state and we are done.
226 } catch (const TxExecutionException& e) {
227 info("Teardown failure while simulating tx ", tx.hash, ": ", e.what());
228 // We rollback to the post-setup state.
230 }
231
232 // Fee payment
233 pay_fee(tx.feePayer, fee, fee_per_da_gas, fee_per_l2_gas);
234
235 pad_trees();
236
237 cleanup();
238}
239
240void TxExecution::emit_nullifier(bool revertible, const FF& nullifier)
241{
242 TransactionPhase phase =
244 TxContextEvent state_before = tx_context.serialize_tx_context_event();
245 try {
246 uint32_t prev_nullifier_count = merkle_db.get_tree_state().nullifierTree.counter;
247
248 if (prev_nullifier_count == MAX_NULLIFIERS_PER_TX) {
249 throw TxExecutionException("Maximum number of nullifiers reached");
250 }
251
252 try {
254 } catch (const NullifierCollisionException& e) {
255 throw TxExecutionException(e.what());
256 }
257
258 events.emit(TxPhaseEvent{ .phase = phase,
259 .state_before = state_before,
260 .state_after = tx_context.serialize_tx_context_event(),
262
263 } catch (const TxExecutionException& e) {
265 .phase = phase,
266 .state_before = state_before,
267 .state_after = tx_context.serialize_tx_context_event(),
268 .reverted = true,
270 });
271 // Rethrow the error
272 throw e;
273 }
274}
275
276void TxExecution::emit_note_hash(bool revertible, const FF& note_hash)
277{
279 TxContextEvent state_before = tx_context.serialize_tx_context_event();
280
281 try {
282 uint32_t prev_note_hash_count = merkle_db.get_tree_state().noteHashTree.counter;
283
284 if (prev_note_hash_count == MAX_NOTE_HASHES_PER_TX) {
285 throw TxExecutionException("Maximum number of note hashes reached");
286 }
287
288 if (revertible) {
290 } else {
292 }
293
294 events.emit(TxPhaseEvent{ .phase = phase,
295 .state_before = state_before,
296 .state_after = tx_context.serialize_tx_context_event(),
297 .event = PrivateAppendTreeEvent{ .leaf_value = note_hash } });
298 } catch (const TxExecutionException& e) {
299 events.emit(TxPhaseEvent{ .phase = phase,
300 .state_before = state_before,
301 .state_after = tx_context.serialize_tx_context_event(),
302 .reverted = true,
303 .event = PrivateAppendTreeEvent{ .leaf_value = note_hash } });
304 // Rethrow the error
305 throw e;
306 }
307}
308
309void TxExecution::emit_l2_to_l1_message(bool revertible, const ScopedL2ToL1Message& l2_to_l1_message)
310{
312 TxContextEvent state_before = tx_context.serialize_tx_context_event();
313
314 try {
315 if (tx_context.side_effect_states.numL2ToL1Messages == MAX_L2_TO_L1_MSGS_PER_TX) {
316 throw TxExecutionException("Maximum number of L2 to L1 messages reached");
317 }
318 // TODO: We don't store the l2 to l1 message in the context since it's not needed until cpp has to generate
319 // public inputs.
320 tx_context.side_effect_states.numL2ToL1Messages++;
321 events.emit(TxPhaseEvent{ .phase = phase,
322 .state_before = state_before,
323 .state_after = tx_context.serialize_tx_context_event(),
324 .event = PrivateEmitL2L1MessageEvent{ .scoped_msg = l2_to_l1_message } });
325 } catch (const TxExecutionException& e) {
326 events.emit(TxPhaseEvent{ .phase = phase,
327 .state_before = state_before,
328 .state_after = tx_context.serialize_tx_context_event(),
329 .reverted = true,
330 .event = PrivateEmitL2L1MessageEvent{ .scoped_msg = l2_to_l1_message } });
331 // Rethrow the error
332 throw e;
333 }
334}
335
336// TODO: How to increment the context id here?
337// This function inserts the non-revertible accumulated data into the Merkle DB.
338// It might error if the limits for number of allowable inserts are exceeded, but this result in an unprovable tx.
340{
341 vinfo("[NON_REVERTIBLE] Inserting ",
342 tx.nonRevertibleAccumulatedData.nullifiers.size(),
343 " nullifiers, ",
344 tx.nonRevertibleAccumulatedData.noteHashes.size(),
345 " note hashes, and ",
346 tx.nonRevertibleAccumulatedData.l2ToL1Messages.size(),
347 " L2 to L1 messages for tx ",
348 tx.hash);
349
350 // 1. Write the already siloed nullifiers.
351 if (tx.nonRevertibleAccumulatedData.nullifiers.empty()) {
353 } else {
354 for (const auto& nullifier : tx.nonRevertibleAccumulatedData.nullifiers) {
356 }
357 }
358
359 // 2. Write already unique note hashes.
360 if (tx.nonRevertibleAccumulatedData.noteHashes.empty()) {
362 } else {
363 for (const auto& unique_note_hash : tx.nonRevertibleAccumulatedData.noteHashes) {
364 emit_note_hash(false, unique_note_hash);
365 }
366 }
367
368 // 3. Write l2_l1 messages
369 if (tx.nonRevertibleAccumulatedData.l2ToL1Messages.empty()) {
371 } else {
372 for (const auto& l2_to_l1_msg : tx.nonRevertibleAccumulatedData.l2ToL1Messages) {
373 emit_l2_to_l1_message(false, l2_to_l1_msg);
374 }
375 }
376}
377
378// TODO: Error Handling
380{
381 vinfo("[REVERTIBLE] Inserting ",
382 tx.revertibleAccumulatedData.nullifiers.size(),
383 " nullifiers, ",
384 tx.revertibleAccumulatedData.noteHashes.size(),
385 " note hashes, and ",
386 tx.revertibleAccumulatedData.l2ToL1Messages.size(),
387 " L2 to L1 messages for tx ",
388 tx.hash);
389
390 // 1. Write the already siloed nullifiers.
391 if (tx.revertibleAccumulatedData.nullifiers.empty()) {
393 } else {
394 for (const auto& siloed_nullifier : tx.revertibleAccumulatedData.nullifiers) {
395 emit_nullifier(true, siloed_nullifier);
396 }
397 }
398
399 // 2. Write the siloed non uniqued note hashes
400 if (tx.revertibleAccumulatedData.noteHashes.empty()) {
402 } else {
403 for (const auto& siloed_note_hash : tx.revertibleAccumulatedData.noteHashes) {
404 emit_note_hash(true, siloed_note_hash);
405 }
406 }
407
408 // 3. Write L2 to L1 messages.
409 if (tx.revertibleAccumulatedData.l2ToL1Messages.empty()) {
411 } else {
412 for (const auto& l2_to_l1_msg : tx.revertibleAccumulatedData.l2ToL1Messages) {
413 emit_l2_to_l1_message(true, l2_to_l1_msg);
414 }
415 }
416}
417
418void TxExecution::pay_fee(const FF& fee_payer,
419 const FF& fee,
420 const uint128_t& fee_per_da_gas,
421 const uint128_t& fee_per_l2_gas)
422{
423 TxContextEvent state_before = tx_context.serialize_tx_context_event();
424
425 FF fee_juice_balance_slot = poseidon2.hash({ FEE_JUICE_BALANCES_SLOT, fee_payer });
426
427 FF fee_payer_balance = merkle_db.storage_read(FEE_JUICE_ADDRESS, fee_juice_balance_slot);
428
429 if (field_gt.ff_gt(fee, fee_payer_balance)) {
430 // Unrecoverable error.
431 throw TxExecutionException("Not enough balance for fee payer to pay for transaction");
432 }
433
434 merkle_db.storage_write(FEE_JUICE_ADDRESS, fee_juice_balance_slot, fee_payer_balance - fee, true);
435
437 .state_before = state_before,
438 .state_after = tx_context.serialize_tx_context_event(),
440 .effective_fee_per_da_gas = fee_per_da_gas,
441 .effective_fee_per_l2_gas = fee_per_l2_gas,
442 .fee_payer = fee_payer,
443 .fee_payer_balance = fee_payer_balance,
444 .fee_juice_balance_slot = fee_juice_balance_slot,
445 .fee = fee,
446 } });
447}
448
450{
451 TxContextEvent state_before = tx_context.serialize_tx_context_event();
454 .state_before = state_before,
455 .state_after = tx_context.serialize_tx_context_event(),
456 .event = PadTreesEvent{} });
457}
458
460{
462 .state_before = tx_context.serialize_tx_context_event(),
463 .state_after = tx_context.serialize_tx_context_event(),
464 .event = CleanupEvent{} });
465}
466
468{
469 TxContextEvent current_state = tx_context.serialize_tx_context_event();
470 events.emit(TxPhaseEvent{ .phase = phase,
471 .state_before = current_state,
472 .state_after = current_state,
473 .reverted = false,
474 .event = EmptyPhaseEvent{} });
475}
476
477} // namespace bb::avm2::simulation
#define FEE_JUICE_ADDRESS
#define MAX_L2_TO_L1_MSGS_PER_TX
#define MAX_NOTE_HASHES_PER_TX
#define FEE_JUICE_BALANCES_SLOT
#define MAX_NULLIFIERS_PER_TX
virtual std::unique_ptr< ContextInterface > make_enqueued_context(AztecAddress address, AztecAddress msg_sender, FF transaction_fee, std::span< const FF > calldata, bool is_static, Gas gas_limit, Gas gas_used, SideEffectStates side_effect_states, TransactionPhase phase)=0
virtual void emit(Event &&event)=0
virtual ExecutionResult execute(std::unique_ptr< ContextInterface > context)=0
virtual bool ff_gt(const FF &a, const FF &b)=0
virtual void unique_note_hash_write(const FF &note_hash)=0
virtual FF storage_read(const AztecAddress &contract_address, const FF &slot) const =0
virtual void siloed_note_hash_write(const FF &note_hash)=0
virtual void storage_write(const AztecAddress &contract_address, const FF &slot, const FF &value, bool is_protocol_write)=0
virtual void siloed_nullifier_write(const FF &nullifier)=0
virtual TreeStates get_tree_state() const =0
void emit_public_call_request(const PublicCallRequestWithCalldata &call, TransactionPhase phase, const FF &transaction_fee, bool success, const Gas &start_gas, const Gas &end_gas, const TxContextEvent &state_before, const TxContextEvent &state_after)
FieldGreaterThanInterface & field_gt
void emit_empty_phase(TransactionPhase phase)
void insert_non_revertibles(const Tx &tx)
HighLevelMerkleDBInterface & merkle_db
void pay_fee(const FF &fee_payer, const FF &fee, const uint128_t &fee_per_da_gas, const uint128_t &fee_per_l2_gas)
EventEmitterInterface< TxEvent > & events
void emit_nullifier(bool revertible, const FF &nullifier)
void emit_note_hash(bool revertible, const FF &note_hash)
ContextProviderInterface & context_provider
void emit_l2_to_l1_message(bool revertible, const ScopedL2ToL1Message &l2_to_l1_message)
ExecutionInterface & call_execution
static FF hash(const std::vector< FF > &input)
Hashes a vector of field elements.
std::string format(Args... args)
Definition log.hpp:21
#define vinfo(...)
Definition log.hpp:79
void info(Args... args)
Definition log.hpp:74
@ NR_NOTE_INSERTION
@ R_NULLIFIER_INSERTION
@ NR_L2_TO_L1_MESSAGE
@ R_L2_TO_L1_MESSAGE
@ NR_NULLIFIER_INSERTION
AvmFlavorSettings::FF FF
Definition field.hpp:10
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
simulation::PublicDataTreeReadWriteEvent event
unsigned __int128 uint128_t
Definition serialize.hpp:44
static PhaseLengths from_tx(const Tx &tx)
Definition tx_events.hpp:24