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_hash = call.request.calldataHash,
47 .start_gas = start_gas,
48 .end_gas = end_gas,
49 .success = success,
50 } });
51}
52
53// Simulates the entire transaction execution phases.
54// There are multiple distinct transaction phases that are executed in order:
55// (1) Non-revertible insertions of nullifiers, note hashes, and L2 to L1 messages.
56// (2) Setup phase, where the setup enqueued calls are executed.
57// (3) Revertible insertions of nullifiers, note hashes, and L2 to L1 messages.
58// (4) App logic phase, where the app logic enqueued calls are executed.
59// (5) Collec Gas fee
61{
62 Gas gas_limit = tx.gasSettings.gasLimits;
63 Gas teardown_gas_limit = tx.gasSettings.teardownGasLimits;
64 tx_context.gas_used = tx.gasUsedByPrivate;
65
67 .state = tx_context.serialize_tx_context_event(),
68 .gas_limit = gas_limit,
69 .teardown_gas_limit = teardown_gas_limit,
70 });
71
72 info("Simulating tx ",
73 tx.hash,
74 " with ",
75 tx.setupEnqueuedCalls.size(),
76 " setup enqueued calls, ",
77 tx.appLogicEnqueuedCalls.size(),
78 " app logic enqueued calls, and ",
79 tx.teardownEnqueuedCall ? "1 teardown enqueued call" : "no teardown enqueued call");
80
81 // Insert non-revertibles. This can throw if there is a nullifier collision.
82 // That would result in an unprovable tx.
84
85 // 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,
99 // This call should not throw unless it's an unexpected unrecoverable failure.
101 tx_context.side_effect_states = result.side_effect_states;
102 tx_context.gas_used = result.gas_used;
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.
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.
126
127 // App logic.
128 for (const auto& call : tx.appLogicEnqueuedCalls) {
129 info("[APP_LOGIC] Executing enqueued call to ", call.request.contractAddress);
130 TxContextEvent state_before = tx_context.serialize_tx_context_event();
131 Gas start_gas = tx_context.gas_used;
132 auto context = context_provider.make_enqueued_context(call.request.contractAddress,
133 call.request.msgSender,
134 /*transaction_fee=*/FF(0),
135 call.calldata,
136 call.request.isStaticCall,
137 gas_limit,
138 start_gas,
139 tx_context.side_effect_states,
141 // This call should not throw unless it's an unexpected unrecoverable failure.
143 tx_context.side_effect_states = result.side_effect_states;
144 tx_context.gas_used = result.gas_used;
147 /*transaction_fee=*/FF(0),
148 result.success,
149 start_gas,
150 tx_context.gas_used,
151 state_before,
152 tx_context.serialize_tx_context_event());
153 if (!result.success) {
154 // This exception should be handled and the tx be provable.
155 throw TxExecutionException(
156 format("[APP_LOGIC] Enqueued call to ", call.request.contractAddress, " failed"));
157 }
158 }
159 } catch (const TxExecutionException& e) {
160 info("Revertible failure while simulating tx ", tx.hash, ": ", e.what());
161 // We revert to the post-setup state.
163 tx_context.side_effect_states = end_setup_side_effect_states;
164 // But we also create a new fork so that the teardown phase can transparently
165 // commit or rollback to the end of teardown.
167 }
168
169 // Compute the transaction fee here so it can be passed to teardown
170 Gas gas_used_before_teardown = tx_context.gas_used;
171 uint128_t fee_per_da_gas = tx.effectiveGasFees.feePerDaGas;
172 uint128_t fee_per_l2_gas = tx.effectiveGasFees.feePerL2Gas;
173 FF fee = FF(fee_per_da_gas) * FF(gas_used_before_teardown.daGas) +
174 FF(fee_per_l2_gas) * FF(gas_used_before_teardown.l2Gas);
175
176 // Teardown.
177 try {
178 if (tx.teardownEnqueuedCall) {
179 info("[TEARDOWN] Executing enqueued call to ", tx.teardownEnqueuedCall->request.contractAddress);
180 // Teardown has its own gas limit and usage.
181 Gas start_gas = { 0, 0 };
182 gas_limit = teardown_gas_limit;
183 TxContextEvent state_before = tx_context.serialize_tx_context_event();
184 auto context = context_provider.make_enqueued_context(tx.teardownEnqueuedCall->request.contractAddress,
185 tx.teardownEnqueuedCall->request.msgSender,
186 fee,
187 tx.teardownEnqueuedCall->calldata,
188 tx.teardownEnqueuedCall->request.isStaticCall,
189 gas_limit,
190 start_gas,
191 tx_context.side_effect_states,
193 // This call should not throw unless it's an unexpected unrecoverable failure.
195 tx_context.side_effect_states = result.side_effect_states;
196 // Check what to do here for GAS
197 emit_public_call_request(*tx.teardownEnqueuedCall,
199 fee,
200 result.success,
201 start_gas,
202 result.gas_used,
203 state_before,
204 tx_context.serialize_tx_context_event());
205 if (!result.success) {
206 // This exception should be handled and the tx be provable.
207 throw TxExecutionException(format(
208 "[TEARDOWN] Enqueued call to ", tx.teardownEnqueuedCall->request.contractAddress, " failed"));
209 }
210 }
211
212 // We commit the forked state and we are done.
214 } catch (const TxExecutionException& e) {
215 info("Teardown failure while simulating tx ", tx.hash, ": ", e.what());
216 // We rollback to the post-setup state.
218 }
219
220 // Fee payment
221 pay_fee(tx.feePayer, fee, fee_per_da_gas, fee_per_l2_gas);
222
223 pad_trees();
224
225 cleanup();
226}
227
228void TxExecution::emit_nullifier(bool revertible, const FF& nullifier)
229{
230 TransactionPhase phase =
232 TxContextEvent state_before = tx_context.serialize_tx_context_event();
233 try {
234 uint32_t prev_nullifier_count = merkle_db.get_tree_state().nullifierTree.counter;
235
236 if (prev_nullifier_count == MAX_NULLIFIERS_PER_TX) {
237 throw TxExecutionException("Maximum number of nullifiers reached");
238 }
240 if (!success) {
241 throw TxExecutionException("Nullifier collision");
242 }
243
244 events.emit(TxPhaseEvent{ .phase = phase,
245 .state_before = state_before,
246 .state_after = tx_context.serialize_tx_context_event(),
248
249 } catch (const TxExecutionException& e) {
251 .phase = phase,
252 .state_before = state_before,
253 .state_after = tx_context.serialize_tx_context_event(),
254 .reverted = true,
256 });
257 // Rethrow the error
258 throw e;
259 }
260}
261
262void TxExecution::emit_note_hash(bool revertible, const FF& note_hash)
263{
265 TxContextEvent state_before = tx_context.serialize_tx_context_event();
266
267 try {
268 uint32_t prev_note_hash_count = merkle_db.get_tree_state().noteHashTree.counter;
269
270 if (prev_note_hash_count == MAX_NOTE_HASHES_PER_TX) {
271 throw TxExecutionException("Maximum number of note hashes reached");
272 }
273
274 if (revertible) {
276 } else {
278 }
279
280 events.emit(TxPhaseEvent{ .phase = phase,
281 .state_before = state_before,
282 .state_after = tx_context.serialize_tx_context_event(),
283 .event = PrivateAppendTreeEvent{ .leaf_value = note_hash } });
284 } catch (const TxExecutionException& e) {
285 events.emit(TxPhaseEvent{ .phase = phase,
286 .state_before = state_before,
287 .state_after = tx_context.serialize_tx_context_event(),
288 .reverted = true,
289 .event = PrivateAppendTreeEvent{ .leaf_value = note_hash } });
290 // Rethrow the error
291 throw e;
292 }
293}
294
295void TxExecution::emit_l2_to_l1_message(bool revertible, const ScopedL2ToL1Message& l2_to_l1_message)
296{
298 TxContextEvent state_before = tx_context.serialize_tx_context_event();
299
300 try {
301 if (tx_context.side_effect_states.numL2ToL1Messages == MAX_L2_TO_L1_MSGS_PER_TX) {
302 throw TxExecutionException("Maximum number of L2 to L1 messages reached");
303 }
304 // TODO: We don't store the l2 to l1 message in the context since it's not needed until cpp has to generate
305 // public inputs.
306 tx_context.side_effect_states.numL2ToL1Messages++;
307 events.emit(TxPhaseEvent{ .phase = phase,
308 .state_before = state_before,
309 .state_after = tx_context.serialize_tx_context_event(),
310 .event = PrivateEmitL2L1MessageEvent{ .scoped_msg = l2_to_l1_message } });
311 } catch (const TxExecutionException& e) {
312 events.emit(TxPhaseEvent{ .phase = phase,
313 .state_before = state_before,
314 .state_after = tx_context.serialize_tx_context_event(),
315 .reverted = true,
316 .event = PrivateEmitL2L1MessageEvent{ .scoped_msg = l2_to_l1_message } });
317 // Rethrow the error
318 throw e;
319 }
320}
321
322// TODO: How to increment the context id here?
323// This function inserts the non-revertible accumulated data into the Merkle DB.
324// It might error if the limits for number of allowable inserts are exceeded, but this result in an unprovable tx.
326{
327 info("[NON_REVERTIBLE] Inserting ",
328 tx.nonRevertibleAccumulatedData.nullifiers.size(),
329 " nullifiers, ",
330 tx.nonRevertibleAccumulatedData.noteHashes.size(),
331 " note hashes, and ",
332 tx.nonRevertibleAccumulatedData.l2ToL1Messages.size(),
333 " L2 to L1 messages for tx ",
334 tx.hash);
335
336 // 1. Write the already siloed nullifiers.
337 for (const auto& nullifier : tx.nonRevertibleAccumulatedData.nullifiers) {
339 }
340
341 // 2. Write already unique note hashes.
342 for (const auto& unique_note_hash : tx.nonRevertibleAccumulatedData.noteHashes) {
343 emit_note_hash(false, unique_note_hash);
344 }
345
346 // 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);
349 }
350}
351
352// TODO: Error Handling
354{
355 info("[REVERTIBLE] Inserting ",
356 tx.revertibleAccumulatedData.nullifiers.size(),
357 " nullifiers, ",
358 tx.revertibleAccumulatedData.noteHashes.size(),
359 " note hashes, and ",
360 tx.revertibleAccumulatedData.l2ToL1Messages.size(),
361 " L2 to L1 messages for tx ",
362 tx.hash);
363
364 // 1. Write the already siloed nullifiers.
365 for (const auto& siloed_nullifier : tx.revertibleAccumulatedData.nullifiers) {
366 emit_nullifier(true, siloed_nullifier);
367 }
368
369 // 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);
372 }
373
374 // 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);
377 }
378}
379
380void TxExecution::pay_fee(const FF& fee_payer,
381 const FF& fee,
382 const uint128_t& fee_per_da_gas,
383 const uint128_t& fee_per_l2_gas)
384{
385 TxContextEvent state_before = tx_context.serialize_tx_context_event();
386
387 FF fee_juice_balance_slot = poseidon2.hash({ FEE_JUICE_BALANCES_SLOT, fee_payer });
388
389 FF fee_payer_balance = merkle_db.storage_read(FEE_JUICE_ADDRESS, fee_juice_balance_slot);
390
391 if (field_gt.ff_gt(fee, fee_payer_balance)) {
392 // Unrecoverable error.
393 throw TxExecutionException("Not enough balance for fee payer to pay for transaction");
394 }
395
396 merkle_db.storage_write(FEE_JUICE_ADDRESS, fee_juice_balance_slot, fee_payer_balance - fee, true);
397
399 .state_before = state_before,
400 .state_after = tx_context.serialize_tx_context_event(),
402 .effective_fee_per_da_gas = fee_per_da_gas,
403 .effective_fee_per_l2_gas = fee_per_l2_gas,
404 .fee_payer = fee_payer,
405 .fee_payer_balance = fee_payer_balance,
406 .fee_juice_balance_slot = fee_juice_balance_slot,
407 .fee = fee,
408 } });
409}
410
412{
413 TxContextEvent state_before = tx_context.serialize_tx_context_event();
416 .state_before = state_before,
417 .state_after = tx_context.serialize_tx_context_event(),
418 .event = PadTreesEvent{} });
419}
420
422{
424 .state_before = tx_context.serialize_tx_context_event(),
425 .state_after = tx_context.serialize_tx_context_event(),
426 .event = CleanupEvent{} });
427}
428
429} // 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 bool siloed_nullifier_write(const FF &nullifier)=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 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 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:20
void info(Args... args)
Definition log.hpp:70
@ 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