1use super::{
26 ordered_outputs::{ordered_worker_outputs, OrderedWorkerOutputError},
27 worker, BalExecutionError,
28};
29use alloy_eip7928::{
30 bal::{Bal, DecodedBal},
31 compute_block_access_list_hash, BlockAccessList,
32};
33use alloy_primitives::Address;
34use crossbeam_channel::{Receiver, Sender};
35use evm2::evm::Bal as EvmBal;
36use reth_engine_primitives::BlockAccessListDecodeError;
37use reth_evm::{
38 BlockExecutionOutput, BlockExecutor, BlockExecutorFactory, BlockExecutorFor, ConfigureEvm,
39 Database, EvmEnvFor, ExecutableTxFor, ExecutionCtxFor,
40};
41use reth_primitives_traits::ReceiptTy;
42use reth_tasks::Runtime;
43use std::sync::Arc;
44
45use crate::tree::payload_processor::receipt_root_task::IndexedReceipt;
46
47#[expect(clippy::too_many_arguments, clippy::type_complexity)]
52pub fn execute_block<'a, Evm, Tx, Err, DB, MakeDb>(
53 runtime: &Runtime,
54 evm_config: &'a Evm,
55 make_db: &'a MakeDb,
56 input_bal: Arc<DecodedBal>,
57 evm_env: EvmEnvFor<Evm>,
58 ctx: ExecutionCtxFor<'a, Evm>,
59 transaction_count: usize,
60 txs: Receiver<(usize, Result<Tx, Err>)>,
61 receipt_tx: Sender<IndexedReceipt<ReceiptTy<Evm::Primitives>>>,
62) -> Result<
63 (BlockExecutionOutput<ReceiptTy<Evm::Primitives>>, Vec<Address>, BlockAccessList, Arc<EvmBal>),
64 BalExecutionError,
65>
66where
67 Evm: ConfigureEvm + 'static,
68 Tx: ExecutableTxFor<Evm> + Send + 'a,
69 Err: core::error::Error + Send + Sync + 'static,
70 DB: Database + Send + 'a,
71 MakeDb: Fn(bool) -> Result<DB, BalExecutionError> + Sync + 'a,
72{
73 let worker_pool = runtime.bal_streaming_pool();
74 let worker_count = worker_pool.current_num_threads().max(1).min(transaction_count);
75
76 worker_pool.in_place_scope(|scope| {
77 execute_block_inner(
78 scope,
79 evm_config,
80 make_db,
81 input_bal,
82 evm_env,
83 ctx,
84 transaction_count,
85 txs,
86 receipt_tx,
87 worker_count,
88 )
89 })
90}
91
92#[expect(clippy::too_many_arguments, clippy::type_complexity)]
93fn execute_block_inner<'scope, Evm, Tx, Err, DB, MakeDb>(
94 scope: &rayon::Scope<'scope>,
95 evm_config: &'scope Evm,
96 make_db: &'scope MakeDb,
97 input_bal: Arc<DecodedBal>,
98 evm_env: EvmEnvFor<Evm>,
99 ctx: ExecutionCtxFor<'scope, Evm>,
100 transaction_count: usize,
101 txs: Receiver<(usize, Result<Tx, Err>)>,
102 receipt_tx: Sender<IndexedReceipt<ReceiptTy<Evm::Primitives>>>,
103 worker_count: usize,
104) -> Result<
105 (BlockExecutionOutput<ReceiptTy<Evm::Primitives>>, Vec<Address>, BlockAccessList, Arc<EvmBal>),
106 BalExecutionError,
107>
108where
109 Evm: ConfigureEvm + 'scope,
110 Tx: ExecutableTxFor<Evm> + Send + 'scope,
111 Err: core::error::Error + Send + Sync + 'static,
112 DB: Database + Send + 'scope,
113 MakeDb: Fn(bool) -> Result<DB, BalExecutionError> + Sync + 'scope,
114{
115 let received_bal = Arc::new(
128 <BlockExecutorFor<'scope, Evm> as BlockExecutor>::convert_block_access_list(
129 input_bal.as_bal().as_vec(),
130 )
131 .map_err(BlockAccessListDecodeError::new)?,
132 );
133 let (result_tx, result_rx) = crossbeam_channel::unbounded();
134 let (abort_guard, abort_rx) = AbortGuard::new();
135
136 for _ in 0..worker_count {
137 worker::spawn_worker(
138 scope,
139 txs.clone(),
140 abort_rx.clone(),
141 result_tx.clone(),
142 evm_config,
143 make_db,
144 Arc::clone(&received_bal),
145 evm_env.clone(),
146 ctx.clone(),
147 );
148 }
149 drop(result_tx);
150
151 let evm = evm_config.evm_with_env(make_db(false)?, evm_env);
152 let mut canonical_executor =
153 evm_config.block_executor_factory().create_executor(evm, ctx.clone());
154 canonical_executor.enable_block_access_list_builder();
155 canonical_executor.apply_pre_execution_changes()?;
156
157 let mut senders = Vec::with_capacity(transaction_count);
158 let mut last_sent_len = 0;
159 for output in ordered_worker_outputs(&result_rx, transaction_count) {
160 let output = match output {
161 Ok(output) => output,
162 Err(OrderedWorkerOutputError::Worker(worker::BalWorkerError::Execution {
163 tx_index,
164 tx_gas_limit,
165 source,
166 })) => {
167 canonical_executor.validate_transaction_gas_limit(tx_gas_limit)?;
170
171 return Err(
174 worker::BalWorkerError::Execution { tx_index, tx_gas_limit, source }.into()
175 );
176 }
177 Err(error) => return Err(error.into()),
178 };
179 canonical_executor.validate_transaction_gas_limit(output.tx_gas_limit)?;
180 canonical_executor.commit_transaction(output.result)?;
181 senders.push(output.signer);
182
183 let current_len = canonical_executor.receipts().len();
184 if current_len > last_sent_len {
185 last_sent_len = current_len;
186 if let Some(receipt) = canonical_executor.receipts().last() {
187 let _ = receipt_tx.send(IndexedReceipt::new(current_len - 1, receipt.clone()));
188 }
189 }
190 }
191 drop(abort_guard);
192
193 let (output, built_bal) = canonical_executor.finish_with_block_access_list()?;
194 let built_bal = built_bal.expect("BAL builder was enabled for parallel execution");
195 log_bal_divergence(&built_bal, input_bal.as_bal());
196 Ok((output, senders, built_bal, received_bal))
197}
198
199fn log_bal_divergence(built_bal: &BlockAccessList, received_bal: &Bal) {
200 if tracing::enabled!(target: "engine::tree::payload_processor::bal", tracing::Level::DEBUG) &&
201 built_bal.as_slice() != received_bal.as_slice()
202 {
203 let rebuilt = compute_block_access_list_hash(built_bal);
204 let expected = compute_block_access_list_hash(received_bal.as_slice());
205 let div = received_bal.diff(built_bal.as_slice());
206 tracing::debug!(
207 target: "engine::tree::payload_processor::bal",
208 %rebuilt,
209 %expected,
210 %div,
211 "first BAL divergence",
212 );
213 }
214}
215
216struct AbortGuard {
218 _tx: Sender<()>,
219}
220
221impl AbortGuard {
222 fn new() -> (Self, Receiver<()>) {
223 let (tx, rx) = crossbeam_channel::bounded(0);
224 (Self { _tx: tx }, rx)
225 }
226}
227
228#[cfg(test)]
229mod tests {
230
231 use super::*;
232 use alloy_consensus::{BlockHeader, Header, TxLegacy};
233 use alloy_eip7928::{
234 bal::Bal as AlloyBal, AccountChanges, BlockAccessIndex, BlockAccessList, CodeChange,
235 };
236 use alloy_eips::{
237 eip2935::{HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE},
238 eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_CODE},
239 eip7002::{WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_CODE},
240 eip7251::{CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_PREDEPLOY_CODE},
241 };
242 use alloy_primitives::{Bytes, B256, U256};
243 use evm2::evm::{
244 CacheDB, EmptyDB, BUILDER_DEPOSIT_REQUEST_ADDRESS, BUILDER_EXIT_REQUEST_ADDRESS,
245 };
246 use reth_chainspec::{ChainSpecBuilder, MAINNET};
247 use reth_engine_primitives::{InsertBlockErrorKind, InsertBlockValidationError};
248 use reth_ethereum_primitives::{Block, BlockBody, Receipt, Transaction, TransactionSigned};
249 use reth_evm::{
250 cached::{AccountInfo, Bytecode},
251 BlockExecutionOutput, BlockValidationError,
252 };
253 use reth_evm_ethereum::EthEvmConfig;
254 use reth_primitives_traits::{Block as _, Recovered, SealedBlock};
255 use reth_testing_utils::generators::{generate_key, rng, sign_tx_with_key_pair};
256 use std::{
257 convert::Infallible,
258 sync::atomic::{AtomicUsize, Ordering},
259 };
260
261 fn test_evm_config() -> EthEvmConfig {
262 EthEvmConfig::new(Arc::new(ChainSpecBuilder::mainnet().amsterdam_activated().build()))
263 }
264
265 fn non_amsterdam_evm_config() -> EthEvmConfig {
266 EthEvmConfig::new(Arc::new(ChainSpecBuilder::mainnet().osaka_activated().build()))
267 }
268
269 fn to_arc_decoded(bal: BlockAccessList) -> Arc<DecodedBal> {
271 let alloy_bal: AlloyBal = bal.into();
272 let raw = alloy_rlp::encode(&alloy_bal).into();
273 Arc::new(DecodedBal::new(alloy_bal, raw))
274 }
275
276 fn system_contracts_db() -> CacheDB<EmptyDB> {
281 let mut db = CacheDB::<EmptyDB>::default();
282 db.insert_account_info(
283 &BEACON_ROOTS_ADDRESS,
284 AccountInfo::default()
285 .with_nonce(1)
286 .with_code(Bytecode::new_raw(BEACON_ROOTS_CODE.clone())),
287 );
288 db.insert_account_info(
289 &WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
290 AccountInfo::default()
291 .with_nonce(1)
292 .with_code(Bytecode::new_raw(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone())),
293 );
294 db.insert_account_info(
295 &CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
296 AccountInfo::default()
297 .with_nonce(1)
298 .with_code(Bytecode::new_raw(CONSOLIDATION_REQUEST_PREDEPLOY_CODE.clone())),
299 );
300 for address in [BUILDER_DEPOSIT_REQUEST_ADDRESS, BUILDER_EXIT_REQUEST_ADDRESS] {
301 db.insert_account_info(
302 &address,
303 AccountInfo::default()
304 .with_nonce(1)
305 .with_code(Bytecode::new_legacy(Bytes::from_static(&[0x00]))),
306 );
307 }
308 db.insert_account_info(
309 &HISTORY_STORAGE_ADDRESS,
310 AccountInfo::default()
311 .with_nonce(1)
312 .with_code(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())),
313 );
314 db
315 }
316
317 fn empty_amsterdam_block(header_bal_hash: B256) -> SealedBlock<Block> {
319 empty_amsterdam_block_with_gas_limit(header_bal_hash, 30_000_000)
320 }
321
322 fn empty_amsterdam_block_with_gas_limit(
323 header_bal_hash: B256,
324 gas_limit: u64,
325 ) -> SealedBlock<Block> {
326 let header = Header {
327 timestamp: 1,
328 number: 1,
329 gas_limit,
330 parent_beacon_block_root: Some(B256::ZERO),
331 withdrawals_root: Some(alloy_consensus::EMPTY_ROOT_HASH),
332 requests_hash: Some(alloy_eips::eip7685::EMPTY_REQUESTS_HASH),
333 excess_blob_gas: Some(0),
334 blob_gas_used: Some(0),
335 block_access_list_hash: Some(header_bal_hash),
336 slot_number: Some(0),
337 ..Header::default()
338 };
339 Block {
340 header,
341 body: BlockBody {
342 transactions: vec![],
343 ommers: vec![],
344 withdrawals: Some(vec![].into()),
345 },
346 }
347 .seal_slow()
348 }
349
350 fn db_factory(
351 db: CacheDB<EmptyDB>,
352 ) -> impl Fn(bool) -> Result<CacheDB<EmptyDB>, BalExecutionError> + Sync {
353 move |_| Ok(db.clone())
354 }
355
356 fn tx_stream<Tx>(txs: Vec<Tx>) -> Receiver<(usize, Result<Tx, Infallible>)> {
357 let (tx, rx) = crossbeam_channel::unbounded();
358 for (index, transaction) in txs.into_iter().enumerate() {
359 tx.send((index, Ok(transaction))).unwrap();
360 }
361 rx
362 }
363
364 fn run_execute_block<Tx, DB, MakeDb>(
365 runtime: &Runtime,
366 evm_config: EthEvmConfig,
367 make_db: MakeDb,
368 input_bal: Arc<DecodedBal>,
369 block: &SealedBlock<Block>,
370 txs: Vec<Tx>,
371 ) -> Result<BlockExecutionOutput<Receipt>, BalExecutionError>
372 where
373 Tx: ExecutableTxFor<EthEvmConfig> + Send,
374 DB: Database + Send,
375 MakeDb: Fn(bool) -> Result<DB, BalExecutionError> + Sync,
376 {
377 run_execute_block_full(runtime, evm_config, make_db, input_bal, block, txs)
378 .map(|(output, _)| output)
379 }
380
381 fn run_execute_block_full<Tx, DB, MakeDb>(
382 runtime: &Runtime,
383 evm_config: EthEvmConfig,
384 make_db: MakeDb,
385 input_bal: Arc<DecodedBal>,
386 block: &SealedBlock<Block>,
387 txs: Vec<Tx>,
388 ) -> Result<(BlockExecutionOutput<Receipt>, BlockAccessList), BalExecutionError>
389 where
390 Tx: ExecutableTxFor<EthEvmConfig> + Send,
391 DB: Database + Send,
392 MakeDb: Fn(bool) -> Result<DB, BalExecutionError> + Sync,
393 {
394 let transaction_count = txs.len();
395 let (receipt_tx, _receipt_rx) = crossbeam_channel::unbounded();
396 let evm_env = evm_config.evm_env(block.header()).unwrap();
397 let execution_ctx = evm_config.context_for_block(block).unwrap();
398 execute_block(
399 runtime,
400 &evm_config,
401 &make_db,
402 input_bal,
403 evm_env,
404 execution_ctx,
405 transaction_count,
406 tx_stream(txs),
407 receipt_tx,
408 )
409 .map(|(output, _, built_bal, _)| (output, built_bal))
410 }
411
412 fn insert_funded(db: &mut CacheDB<EmptyDB>, address: Address, balance: U256) {
414 db.insert_account_info(&address, AccountInfo::default().with_balance(balance));
415 }
416
417 fn run_serial_path(
420 evm_config: &EthEvmConfig,
421 canonical_db: CacheDB<EmptyDB>,
422 block: &SealedBlock<Block>,
423 txs: &[Recovered<TransactionSigned>],
424 ) -> (BlockExecutionOutput<Receipt>, BlockAccessList) {
425 let mut executor =
426 evm_config.executor_for_block(canonical_db, block).expect("build serial executor");
427 executor.enable_block_access_list_builder();
428 executor.apply_pre_execution_changes().expect("serial pre-exec");
429 for (index, tx) in txs.iter().cloned().enumerate() {
430 executor
431 .execute_transaction(tx)
432 .unwrap_or_else(|err| panic!("serial tx {index} failed: {err:?}"));
433 }
434 let (output, bal) = executor.finish_with_block_access_list().expect("serial post-exec");
435 (output, bal.expect("BAL builder was enabled"))
436 }
437
438 fn reference_bal_for_empty_block(evm_config: &EthEvmConfig) -> BlockAccessList {
445 run_serial_path(evm_config, system_contracts_db(), &empty_amsterdam_block(B256::ZERO), &[])
446 .1
447 }
448
449 fn reference_bal_for_block(
452 evm_config: &EthEvmConfig,
453 db: CacheDB<EmptyDB>,
454 block: &SealedBlock<Block>,
455 txs: &[Recovered<TransactionSigned>],
456 ) -> BlockAccessList {
457 run_serial_path(evm_config, db, block, txs).1
458 }
459
460 fn transfer(to: Address, value: u64, nonce: u64, gas_limit: u64) -> Transaction {
461 Transaction::Legacy(TxLegacy {
462 chain_id: Some(MAINNET.chain.id()),
463 nonce,
464 gas_price: 1,
465 gas_limit,
466 to: alloy_primitives::TxKind::Call(to),
467 value: U256::from(value),
468 input: Default::default(),
469 })
470 }
471
472 macro_rules! signed_transfer {
473 ($key:expr, $signer:expr, $to:expr, $value:expr, $nonce:expr, $gas_limit:expr) => {
474 Recovered::new_unchecked(
475 sign_tx_with_key_pair($key, transfer($to, $value, $nonce, $gas_limit)),
476 $signer,
477 )
478 };
479 }
480
481 fn assert_shadow_equal(
488 evm_config: EthEvmConfig,
489 canonical_db_template: CacheDB<EmptyDB>,
490 block_header_only: SealedBlock<Block>,
491 txs: Vec<Recovered<TransactionSigned>>,
492 ) {
493 let (mut serial, reference_bal) =
495 run_serial_path(&evm_config, canonical_db_template.clone(), &block_header_only, &txs);
496 let block = empty_amsterdam_block_with_gas_limit(
498 compute_block_access_list_hash(&reference_bal),
499 block_header_only.header().gas_limit(),
500 );
501 let (mut bal_out, built_bal) = run_execute_block_full(
502 &Runtime::test(),
503 evm_config,
504 db_factory(canonical_db_template),
505 to_arc_decoded(reference_bal.clone()),
506 &block,
507 txs,
508 )
509 .unwrap_or_else(|e| panic!("BAL path failed: {e:?}"));
510
511 assert_eq!(reference_bal, built_bal, "rebuilt BAL diverged");
513 assert_eq!(serial.receipts, bal_out.receipts, "receipts diverged");
514 assert_eq!(serial.gas_used, bal_out.gas_used, "gas used diverged");
515 assert_eq!(serial.requests, bal_out.requests, "requests diverged");
516 serial.state.reverts.sort();
517 bal_out.state.reverts.sort();
518 assert_eq!(serial.state, bal_out.state, "execution state diverged");
519 }
520
521 #[test]
522 fn invalid_bal_bytecode_is_validation_error() {
523 let bal = vec![AccountChanges {
524 address: Address::ZERO,
525 code_changes: vec![CodeChange::new(
526 BlockAccessIndex::new(1),
527 vec![0xef, 0x01, 0xde].into(),
528 )],
529 ..Default::default()
530 }];
531 let error =
532 <BlockExecutorFor<'_, EthEvmConfig> as BlockExecutor>::convert_block_access_list(&bal)
533 .unwrap_err();
534 let error = BalExecutionError::from(BlockAccessListDecodeError::new(error));
535 let error = InsertBlockErrorKind::from(error);
536 assert!(matches!(
537 error.ensure_validation_error(),
538 Ok(InsertBlockValidationError::BlockAccessListDecode(_))
539 ));
540 }
541
542 #[test]
543 fn empty_block_happy_path_round_trip() {
544 let evm_config = test_evm_config();
550 let input_bal = reference_bal_for_empty_block(&evm_config);
551 assert!(!input_bal.is_empty(), "system calls should populate the BAL");
553 let block = empty_amsterdam_block(compute_block_access_list_hash(&input_bal));
554
555 let output = run_execute_block(
556 &Runtime::test(),
557 evm_config,
558 db_factory(system_contracts_db()),
559 to_arc_decoded(input_bal),
560 &block,
561 Vec::<Recovered<TransactionSigned>>::new(),
562 )
563 .expect("empty BAL block should execute");
564 assert!(output.receipts.is_empty());
565 }
566
567 #[test]
568 fn multi_tx_happy_path_round_trip() {
569 let evm_config = test_evm_config();
577 let carol = Address::repeat_byte(0xca);
578 let sender_balance = U256::from(alloy_consensus::constants::ETH_TO_WEI);
579
580 let alice_kp = generate_key(&mut rng());
582 let alice =
583 Address::from_raw_public_key(&alice_kp.public_key().serialize_uncompressed()[1..]);
584 let bob_kp = generate_key(&mut rng());
585 let bob = Address::from_raw_public_key(&bob_kp.public_key().serialize_uncompressed()[1..]);
586
587 let txs = vec![
589 signed_transfer!(alice_kp, alice, carol, 100, 0, 21_000),
590 signed_transfer!(bob_kp, bob, carol, 200, 0, 21_000),
591 ];
592
593 let mut db = system_contracts_db();
595 insert_funded(&mut db, alice, sender_balance);
596 insert_funded(&mut db, bob, sender_balance);
597
598 let reference_bal = reference_bal_for_block(
600 &evm_config,
601 db.clone(),
602 &empty_amsterdam_block(B256::ZERO),
603 &txs,
604 );
605 let block = empty_amsterdam_block(compute_block_access_list_hash(&reference_bal));
606
607 let output = run_execute_block(
608 &Runtime::test(),
609 evm_config,
610 db_factory(db),
611 to_arc_decoded(reference_bal),
612 &block,
613 txs,
614 )
615 .expect("multi-transaction BAL block should execute");
616 assert_eq!(output.receipts.len(), 2);
617 assert!(output.gas_used >= 42_000);
618 }
619
620 #[test]
621 fn shadow_empty_block() {
622 assert_shadow_equal(
625 test_evm_config(),
626 system_contracts_db(),
627 empty_amsterdam_block(B256::ZERO),
628 Vec::new(),
629 );
630 }
631
632 #[test]
633 fn shadow_multi_value_transfer() {
634 let evm_config = test_evm_config();
637 let carol = Address::repeat_byte(0xca);
638 let sender_balance = U256::from(alloy_consensus::constants::ETH_TO_WEI);
639 let alice_kp = generate_key(&mut rng());
640 let alice =
641 Address::from_raw_public_key(&alice_kp.public_key().serialize_uncompressed()[1..]);
642 let bob_kp = generate_key(&mut rng());
643 let bob = Address::from_raw_public_key(&bob_kp.public_key().serialize_uncompressed()[1..]);
644 let mut db = system_contracts_db();
645 insert_funded(&mut db, alice, sender_balance);
646 insert_funded(&mut db, bob, sender_balance);
647 let txs = vec![
648 signed_transfer!(alice_kp, alice, carol, 100, 0, 21_000),
649 signed_transfer!(bob_kp, bob, carol, 200, 0, 21_000),
650 ];
651 assert_shadow_equal(evm_config, db, empty_amsterdam_block(B256::ZERO), txs);
652 }
653
654 #[test]
655 fn rejects_tx_gas_limit_that_exceeds_remaining_block_gas() {
656 let evm_config = test_evm_config();
660 let carol = Address::repeat_byte(0xca);
661 let sender_balance = U256::from(alloy_consensus::constants::ETH_TO_WEI);
662 let alice_kp = generate_key(&mut rng());
663 let alice =
664 Address::from_raw_public_key(&alice_kp.public_key().serialize_uncompressed()[1..]);
665 let bob_kp = generate_key(&mut rng());
666 let bob = Address::from_raw_public_key(&bob_kp.public_key().serialize_uncompressed()[1..]);
667 let txs = vec![
668 signed_transfer!(alice_kp, alice, carol, 100, 0, 990_000),
669 signed_transfer!(bob_kp, bob, carol, 200, 0, 990_000),
670 ];
671 let mut pre_block_db = system_contracts_db();
672 insert_funded(&mut pre_block_db, alice, sender_balance);
673 insert_funded(&mut pre_block_db, bob, sender_balance);
674 let reference_bal = reference_bal_for_block(
678 &evm_config,
679 pre_block_db.clone(),
680 &empty_amsterdam_block(B256::ZERO),
681 &txs,
682 );
683 let block = empty_amsterdam_block_with_gas_limit(
684 compute_block_access_list_hash(&reference_bal),
685 1_000_000,
686 );
687
688 let result = run_execute_block(
689 &Runtime::test(),
690 evm_config,
691 db_factory(pre_block_db),
692 to_arc_decoded(reference_bal),
693 &block,
694 txs,
695 );
696 match result {
697 Err(BalExecutionError::Execution(err)) => assert!(matches!(
698 err.as_validation(),
699 Some(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { .. })
700 )),
701 other => panic!("expected block gas validation error, got {other:?}"),
702 }
703 }
704
705 #[test]
706 fn shadow_tx_with_revert() {
707 let evm_config = test_evm_config();
713 let contract = Address::repeat_byte(0xde);
714 let key = generate_key(&mut rng());
715 let signer = Address::from_raw_public_key(&key.public_key().serialize_uncompressed()[1..]);
716 let mut db = system_contracts_db();
717 insert_funded(&mut db, signer, U256::from(alloy_consensus::constants::ETH_TO_WEI));
718 db.insert_account_info(
719 &contract,
720 AccountInfo::default()
721 .with_nonce(1)
722 .with_code(Bytecode::new_raw(Bytes::from_static(&[0x60, 0x00, 0x60, 0x00, 0xfd]))),
723 );
724 let tx = signed_transfer!(key, signer, contract, 0, 0, 50_000);
725 assert_shadow_equal(evm_config, db, empty_amsterdam_block(B256::ZERO), vec![tx]);
726 }
727
728 #[test]
729 fn shadow_tx_with_sstore() {
730 let evm_config = test_evm_config();
736 let contract = Address::repeat_byte(0x55);
737 let key = generate_key(&mut rng());
738 let signer = Address::from_raw_public_key(&key.public_key().serialize_uncompressed()[1..]);
739 let mut db = system_contracts_db();
740 insert_funded(&mut db, signer, U256::from(alloy_consensus::constants::ETH_TO_WEI));
741 db.insert_account_info(
742 &contract,
743 AccountInfo::default().with_nonce(1).with_code(Bytecode::new_raw(Bytes::from_static(
744 &[0x60, 0x42, 0x60, 0x00, 0x55, 0x00],
745 ))),
746 );
747 let tx = signed_transfer!(key, signer, contract, 0, 0, 100_000);
748 assert_shadow_equal(evm_config, db, empty_amsterdam_block(B256::ZERO), vec![tx]);
749 }
750
751 #[test]
752 fn returns_built_bal_for_final_hash_mismatch() {
753 let evm_config = test_evm_config();
758
759 let real_bal = reference_bal_for_empty_block(&evm_config);
761
762 let mut tampered_entries: Vec<AccountChanges> = real_bal;
764 tampered_entries.push(AccountChanges::new(Address::repeat_byte(0xff)));
765 let tampered_bal = AlloyBal::new(tampered_entries);
766
767 let tampered_access_list: BlockAccessList = tampered_bal.clone().into();
769 let tampered_hash = compute_block_access_list_hash(&tampered_access_list);
770 let block = empty_amsterdam_block(tampered_hash);
771 let received = Arc::new(DecodedBal::new(
772 tampered_bal.clone(),
773 alloy_rlp::encode(&tampered_bal).into(),
774 ));
775
776 let (_, built_bal) = run_execute_block_full(
777 &Runtime::test(),
778 evm_config,
779 db_factory(system_contracts_db()),
780 received,
781 &block,
782 Vec::<Recovered<TransactionSigned>>::new(),
783 )
784 .expect("BAL execution should return the rebuilt list");
785 assert_ne!(compute_block_access_list_hash(&built_bal), tampered_hash);
786 }
787
788 #[test]
789 fn canonical_make_db_failure() {
790 let block = empty_amsterdam_block(B256::ZERO);
792 let failing_make_db = |_: bool| -> Result<CacheDB<EmptyDB>, BalExecutionError> {
793 Err(reth_provider::ProviderError::BestBlockNotFound.into())
794 };
795 let result = run_execute_block(
796 &Runtime::test(),
797 test_evm_config(),
798 failing_make_db,
799 to_arc_decoded(BlockAccessList::default()),
800 &block,
801 Vec::<Recovered<TransactionSigned>>::new(),
802 );
803 assert!(matches!(result, Err(BalExecutionError::Provider(_))));
804 }
805
806 #[test]
807 fn worker_tx_recovery_error_becomes_validation_error() {
808 let evm_config = test_evm_config();
812 let block = empty_amsterdam_block(B256::ZERO);
813 let (tx_tx, tx_rx) = crossbeam_channel::unbounded::<(
814 usize,
815 Result<Recovered<TransactionSigned>, std::io::Error>,
816 )>();
817 tx_tx.send((0, Err(std::io::Error::other("sig fail")))).unwrap();
818 drop(tx_tx);
819 let (receipt_tx, _receipt_rx) = crossbeam_channel::unbounded();
820 let evm_env = evm_config.evm_env(block.header()).unwrap();
821 let execution_ctx = evm_config.context_for_block(&block).unwrap();
822
823 let result = execute_block(
824 &Runtime::test(),
825 &evm_config,
826 &db_factory(system_contracts_db()),
827 to_arc_decoded(BlockAccessList::default()),
828 evm_env,
829 execution_ctx,
830 1, tx_rx,
832 receipt_tx,
833 );
834 assert!(matches!(
835 result,
836 Err(BalExecutionError::Execution(reth_evm::BlockExecutionError::Validation(_)))
837 ));
838 }
839
840 #[test]
841 fn gas_tracker_non_amsterdam_uses_cumulative_gas() {
842 let evm_config = non_amsterdam_evm_config();
845 let recipient = Address::repeat_byte(0xca);
846 let balance = U256::from(alloy_consensus::constants::ETH_TO_WEI);
847 let alice_key = generate_key(&mut rng());
848 let alice =
849 Address::from_raw_public_key(&alice_key.public_key().serialize_uncompressed()[1..]);
850 let bob_key = generate_key(&mut rng());
851 let bob = Address::from_raw_public_key(&bob_key.public_key().serialize_uncompressed()[1..]);
852 let txs = vec![
853 signed_transfer!(alice_key, alice, recipient, 1, 0, 21_000),
854 signed_transfer!(bob_key, bob, recipient, 1, 0, 980_000),
855 ];
856 let mut db = system_contracts_db();
857 insert_funded(&mut db, alice, balance);
858 insert_funded(&mut db, bob, balance);
859 let reference_bal = reference_bal_for_block(
860 &evm_config,
861 db.clone(),
862 &empty_amsterdam_block(B256::ZERO),
863 &txs,
864 );
865 let block = empty_amsterdam_block_with_gas_limit(
866 compute_block_access_list_hash(&reference_bal),
867 1_000_000,
868 );
869
870 let result = run_execute_block(
871 &Runtime::test(),
872 evm_config,
873 db_factory(db),
874 to_arc_decoded(reference_bal),
875 &block,
876 txs,
877 );
878 assert!(matches!(result, Err(BalExecutionError::Execution(_))));
879 }
880
881 #[test]
882 fn gas_tracker_caps_oversized_tx_gas_limit_at_tx_gas_limit_cap() {
883 const TX_GAS_LIMIT_CAP: u64 = 1 << 24;
886
887 let evm_config = test_evm_config();
888 let loop_contract = Address::repeat_byte(0x44);
889 let recipient = Address::repeat_byte(0xca);
890 let key = generate_key(&mut rng());
891 let signer = Address::from_raw_public_key(&key.public_key().serialize_uncompressed()[1..]);
892 let mut db = system_contracts_db();
893 insert_funded(&mut db, signer, U256::from(alloy_consensus::constants::ETH_TO_WEI));
894 db.insert_account_info(
895 &loop_contract,
896 AccountInfo::default()
897 .with_nonce(1)
898 .with_code(Bytecode::new_raw(Bytes::from_static(&[0x5b, 0x60, 0x00, 0x56]))),
899 );
900 let txs = vec![
901 signed_transfer!(key, signer, loop_contract, 0, 0, 13_100_000),
902 signed_transfer!(key, signer, recipient, 1, 1, TX_GAS_LIMIT_CAP + 1_000_000),
903 ];
904 let reference_block = empty_amsterdam_block(B256::ZERO);
905 let reference_bal =
906 reference_bal_for_block(&evm_config, db.clone(), &reference_block, &txs);
907 let block = empty_amsterdam_block(compute_block_access_list_hash(&reference_bal));
908
909 let output = run_execute_block(
910 &Runtime::test(),
911 evm_config,
912 db_factory(db),
913 to_arc_decoded(reference_bal),
914 &block,
915 txs,
916 )
917 .expect("the capped regular gas limit should fit the remaining block gas");
918 assert_eq!(output.receipts.len(), 2);
919 }
920
921 fn two_transfers(
924 tx1_gas_limit: u64,
925 tx2_gas_limit: u64,
926 fund_recipient: bool,
927 ) -> (CacheDB<EmptyDB>, Recovered<TransactionSigned>, Recovered<TransactionSigned>) {
928 let recipient = Address::repeat_byte(0xca);
929 let balance = U256::from(alloy_consensus::constants::ETH_TO_WEI);
930
931 let alice_kp = generate_key(&mut rng());
932 let alice =
933 Address::from_raw_public_key(&alice_kp.public_key().serialize_uncompressed()[1..]);
934 let bob_kp = generate_key(&mut rng());
935 let bob = Address::from_raw_public_key(&bob_kp.public_key().serialize_uncompressed()[1..]);
936
937 let mut pre_block_db = system_contracts_db();
938 insert_funded(&mut pre_block_db, alice, balance);
939 insert_funded(&mut pre_block_db, bob, balance);
940 if fund_recipient {
941 insert_funded(&mut pre_block_db, recipient, balance);
942 }
943
944 let chain_id = MAINNET.chain.id();
945 let make_tx = |kp, gas_limit, value| {
946 sign_tx_with_key_pair(
947 kp,
948 Transaction::Legacy(TxLegacy {
949 chain_id: Some(chain_id),
950 nonce: 0,
951 gas_price: 1,
952 gas_limit,
953 to: alloy_primitives::TxKind::Call(recipient),
954 value: U256::from(value),
955 input: Default::default(),
956 }),
957 )
958 };
959 let tx1 = Recovered::new_unchecked(make_tx(alice_kp, tx1_gas_limit, 100u64), alice);
960 let tx2 = Recovered::new_unchecked(make_tx(bob_kp, tx2_gas_limit, 200u64), bob);
961 (pre_block_db, tx1, tx2)
962 }
963
964 fn two_transfers_with_reference_bal(
967 evm_config: &EthEvmConfig,
968 tx_gas_limit: u64,
969 bal_covers_first: bool,
970 ) -> (
971 CacheDB<EmptyDB>,
972 BlockAccessList,
973 Recovered<reth_ethereum_primitives::TransactionSigned>,
974 Recovered<reth_ethereum_primitives::TransactionSigned>,
975 ) {
976 let (pre_block_db, tx1, tx2) = two_transfers(tx_gas_limit, tx_gas_limit, false);
977 let reference_block = empty_amsterdam_block(B256::ZERO);
978 let covered = if bal_covers_first { vec![tx1.clone()] } else { vec![] };
979 let reference_bal =
980 reference_bal_for_block(evm_config, pre_block_db.clone(), &reference_block, &covered);
981 (pre_block_db, reference_bal, tx1, tx2)
982 }
983
984 #[test]
985 fn default_tx_gas_limit_cap_matches_serial_execution() {
986 let evm_config = test_evm_config();
987 let block_gas_limit = 20_000_000;
988 let (db, tx1, tx2) = two_transfers(100_000, 19_990_000, true);
991 let txs = vec![tx1, tx2];
992 let (serial, bal) = run_serial_path(
993 &evm_config,
994 db.clone(),
995 &empty_amsterdam_block_with_gas_limit(B256::ZERO, block_gas_limit),
996 &txs,
997 );
998 let block = empty_amsterdam_block_with_gas_limit(
999 compute_block_access_list_hash(&bal),
1000 block_gas_limit,
1001 );
1002 let parallel = run_execute_block(
1003 &Runtime::test(),
1004 evm_config,
1005 db_factory(db),
1006 to_arc_decoded(bal),
1007 &block,
1008 txs,
1009 )
1010 .expect("the default regular cap must match serial execution");
1011 assert_eq!(parallel.receipts, serial.receipts);
1012 }
1013
1014 #[test]
1015 fn propagates_bal_miss_of_admitted_transaction_as_block_verdict() {
1016 let (pre_block_db, reference_bal, tx1, tx2) =
1019 two_transfers_with_reference_bal(&test_evm_config(), 100_000, false);
1020 let bal_hash = alloy_eip7928::compute_block_access_list_hash(&reference_bal);
1021 let block = empty_amsterdam_block(bal_hash);
1022
1023 let result = run_execute_block(
1024 &Runtime::test(),
1025 test_evm_config(),
1026 db_factory(pre_block_db),
1027 to_arc_decoded(reference_bal),
1028 &block,
1029 vec![tx1, tx2],
1030 );
1031
1032 match result {
1033 Err(BalExecutionError::Execution(err)) => {
1034 let Some(BlockValidationError::EVM { error, .. }) = err.as_validation() else {
1035 panic!("expected EVM validation error, got {err:?}");
1036 };
1037 let Some(evm2::registry::HandlerError::Database(database_error)) =
1038 error.downcast_ref::<evm2::registry::HandlerError>()
1039 else {
1040 panic!("expected owned database error, got {error:?}");
1041 };
1042 assert!(!database_error.is_fatal());
1043 assert!(database_error.downcast_ref::<evm2::evm::bal::BalError>().is_some());
1044 }
1045 Err(err) => panic!("expected BAL execution error, got {err:?}"),
1046 Ok(_) => panic!("expected BAL execution error, got Ok"),
1047 }
1048 }
1049
1050 #[test]
1051 fn admission_rejects_before_speculative_worker_failure_surfaces() {
1052 let (pre_block_db, reference_bal, tx1, tx2) =
1055 two_transfers_with_reference_bal(&test_evm_config(), 990_000, true);
1056 let bal_hash = alloy_eip7928::compute_block_access_list_hash(&reference_bal);
1057 let low_gas_block = empty_amsterdam_block_with_gas_limit(bal_hash, 1_000_000);
1058
1059 let result = run_execute_block(
1060 &Runtime::test(),
1061 test_evm_config(),
1062 db_factory(pre_block_db),
1063 to_arc_decoded(reference_bal),
1064 &low_gas_block,
1065 vec![tx1, tx2],
1066 );
1067
1068 match result {
1069 Err(BalExecutionError::Execution(err)) => assert!(
1070 matches!(
1071 err.as_validation(),
1072 Some(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { .. })
1073 ),
1074 "expected block gas validation error, got {err:?}"
1075 ),
1076 Err(err) => panic!("expected block gas validation error, got {err:?}"),
1077 Ok(_) => panic!("expected block gas validation error, got Ok"),
1078 }
1079 }
1080
1081 #[test]
1082 fn worker_reuses_provider_after_failure_and_finishes_earlier_transactions() {
1083 let evm_config = test_evm_config();
1084 let (pre_block_db, reference_bal, tx1, tx2) =
1085 two_transfers_with_reference_bal(&evm_config, 100_000, true);
1086 let block = empty_amsterdam_block(B256::ZERO);
1087 let received_bal = Arc::new(
1088 <BlockExecutorFor<'_, EthEvmConfig> as BlockExecutor>::convert_block_access_list(
1089 &reference_bal,
1090 )
1091 .unwrap(),
1092 );
1093 let setups = AtomicUsize::new(0);
1094 let make_db = |_: bool| {
1095 if setups.fetch_add(1, Ordering::Relaxed) != 0 {
1098 return Err(BalExecutionError::Provider(
1099 reth_errors::ProviderError::HeaderNotFound(B256::ZERO.into()),
1100 ));
1101 }
1102 Ok(pre_block_db.clone())
1103 };
1104 let (tx_tx, tx_rx) = crossbeam_channel::unbounded();
1105 tx_tx.send((2, Err(std::io::Error::other("sig fail")))).unwrap();
1108 tx_tx.send((1, Ok(tx2))).unwrap();
1109 tx_tx.send((0, Ok(tx1.clone()))).unwrap();
1110 drop(tx_tx);
1111 let (_abort_guard, abort_rx) = AbortGuard::new();
1112 let (result_tx, result_rx) = crossbeam_channel::unbounded();
1113 let runtime = Runtime::test();
1114 runtime.bal_streaming_pool().in_place_scope(|scope| {
1115 worker::spawn_worker(
1116 scope,
1117 tx_rx,
1118 abort_rx,
1119 result_tx,
1120 &evm_config,
1121 &make_db,
1122 received_bal,
1123 evm_config.evm_env(block.header()).unwrap(),
1124 evm_config.context_for_block(&block).unwrap(),
1125 );
1126 });
1127 let mut outputs = ordered_worker_outputs(&result_rx, 3);
1128 let output = outputs.next().unwrap().unwrap();
1129 assert_eq!(output.index, 0);
1130 assert_eq!(output.signer, tx1.signer());
1131 let mut serial = evm_config.executor_for_block(pre_block_db, &block).unwrap();
1132 serial.apply_pre_execution_changes().unwrap();
1133 let expected = serial.execute_transaction_without_commit(tx1).unwrap();
1134 assert_eq!(output.result.result(), expected.result());
1135 assert!(matches!(
1136 outputs.next().unwrap(),
1137 Err(OrderedWorkerOutputError::Worker(worker::BalWorkerError::Execution {
1138 tx_index: 1,
1139 ..
1140 }))
1141 ));
1142 let error = outputs.next().unwrap().err().unwrap();
1143 assert!(matches!(
1144 error,
1145 OrderedWorkerOutputError::Worker(worker::BalWorkerError::Transaction {
1146 tx_index: 2,
1147 ..
1148 })
1149 ));
1150 assert!(matches!(
1151 BalExecutionError::from(error),
1152 BalExecutionError::Execution(reth_evm::BlockExecutionError::Validation(_))
1153 ));
1154 assert!(outputs.next().is_none());
1155 assert_eq!(setups.load(Ordering::Relaxed), 1);
1156 }
1157
1158 #[test]
1159 fn amsterdam_state_gas_admission_matches_serial_execution() {
1160 let evm_config = EthEvmConfig::new(Arc::new(
1161 reth_chainspec::ChainSpecBuilder::mainnet().amsterdam_activated().build(),
1162 ));
1163 for (tx_gas_limit, complete_bal) in [(500_000, true), (990_000, true), (990_000, false)] {
1164 let (pre_block_db, partial_bal, tx1, tx2) =
1165 two_transfers_with_reference_bal(&evm_config, tx_gas_limit, true);
1166 let reference_block = empty_amsterdam_block(B256::ZERO);
1167 let reference_bal = if complete_bal {
1168 reference_bal_for_block(
1169 &evm_config,
1170 pre_block_db.clone(),
1171 &reference_block,
1172 &[tx1.clone(), tx2.clone()],
1173 )
1174 } else {
1175 partial_bal
1177 };
1178 let block = empty_amsterdam_block_with_gas_limit(
1179 compute_block_access_list_hash(&reference_bal),
1180 1_000_000,
1181 );
1182 let mut evm_env = evm_config.evm_env(block.header()).unwrap();
1183 assert!(evm_env.version.feature(evm2::EvmFeatures::EIP8037));
1184 evm_env.version.tx_gas_limit_cap = 400_000;
1187 let ctx = evm_config.context_for_block(&block).unwrap();
1188 let evm = evm_config.evm_with_env(pre_block_db.clone(), evm_env.clone());
1189 let mut serial = evm_config.block_executor_factory().create_executor(evm, ctx.clone());
1190 serial.apply_pre_execution_changes().unwrap();
1191 serial.execute_transaction(tx1.clone()).unwrap();
1192 let serial_result = serial.execute_transaction(tx2.clone());
1193
1194 let (receipt_tx, _receipt_rx) = crossbeam_channel::unbounded();
1195 let parallel_result = execute_block(
1196 &Runtime::test(),
1197 &evm_config,
1198 &|_: bool| Ok(pre_block_db.clone()),
1199 to_arc_decoded(reference_bal),
1200 evm_env,
1201 ctx,
1202 2,
1203 tx_stream(vec![tx1, tx2]),
1204 receipt_tx,
1205 );
1206 if tx_gas_limit == 500_000 {
1207 serial_result.unwrap();
1208 assert_eq!(parallel_result.unwrap().0.receipts, serial.receipts());
1209 } else {
1210 let serial_error = serial_result.unwrap_err();
1211 let Some(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
1212 block_available_gas,
1213 ..
1214 }) = serial_error.as_validation()
1215 else {
1216 panic!("expected serial state-gas admission error, got {serial_error:?}");
1217 };
1218 assert!(*block_available_gas < 990_000);
1219 let Err(BalExecutionError::Execution(parallel_error)) = parallel_result else {
1220 panic!("expected parallel state-gas admission error");
1221 };
1222 assert_eq!(parallel_error.to_string(), serial_error.to_string());
1223 }
1224 }
1225 }
1226}