revme/cmd/
blockchaintest.rs

1pub mod post_block;
2pub mod pre_block;
3
4use clap::Parser;
5use context::ContextTr;
6use context_interface::block::BlobExcessGasAndPrice;
7use database::states::bundle_state::BundleRetention;
8use database::{EmptyDB, State};
9use inspector::inspectors::TracerEip3155;
10use primitives::{hardfork::SpecId, hex, Address, HashMap, U256};
11use revm::handler::EvmTr;
12use revm::{
13    context::cfg::CfgEnv, context_interface::result::HaltReason, Context, MainBuilder, MainContext,
14};
15use revm::{Database, ExecuteCommitEvm, ExecuteEvm, InspectEvm};
16use serde_json::json;
17use state::AccountInfo;
18use statetest_types::blockchain::{
19    Account, BlockchainTest, BlockchainTestCase, ForkSpec, Withdrawal,
20};
21use std::collections::BTreeMap;
22use std::fs;
23use std::path::{Path, PathBuf};
24use std::time::Instant;
25use thiserror::Error;
26use walkdir::{DirEntry, WalkDir};
27
28/// `blockchaintest` subcommand
29#[derive(Parser, Debug)]
30pub struct Cmd {
31    /// Path to folder or file containing the blockchain tests
32    ///
33    /// If multiple paths are specified they will be run in sequence.
34    ///
35    /// Folders will be searched recursively for files with the extension `.json`.
36    #[arg(required = true, num_args = 1..)]
37    paths: Vec<PathBuf>,
38    /// Omit progress output
39    #[arg(long)]
40    omit_progress: bool,
41    /// Keep going after a test failure
42    #[arg(long, alias = "no-fail-fast")]
43    keep_going: bool,
44    /// Print environment information (pre-state, post-state, env) when an error occurs
45    #[arg(long)]
46    print_env_on_error: bool,
47    /// Output results in JSON format
48    #[arg(long)]
49    json: bool,
50}
51
52impl Cmd {
53    /// Runs `blockchaintest` command.
54    pub fn run(&self) -> Result<(), Error> {
55        for path in &self.paths {
56            if !path.exists() {
57                return Err(Error::PathNotFound(path.clone()));
58            }
59
60            if !self.json {
61                println!("\nRunning blockchain tests in {}...", path.display());
62            }
63            let test_files = find_all_json_tests(path);
64
65            if test_files.is_empty() {
66                return Err(Error::NoJsonFiles(path.clone()));
67            }
68
69            run_tests(
70                test_files,
71                self.omit_progress,
72                self.keep_going,
73                self.print_env_on_error,
74                self.json,
75            )?;
76        }
77        Ok(())
78    }
79}
80
81/// Find all JSON test files in the given path
82/// If path is a file, returns it in a vector
83/// If path is a directory, recursively finds all .json files
84pub fn find_all_json_tests(path: &Path) -> Vec<PathBuf> {
85    if path.is_file() {
86        vec![path.to_path_buf()]
87    } else {
88        WalkDir::new(path)
89            .into_iter()
90            .filter_map(Result::ok)
91            .filter(|e| e.path().extension() == Some("json".as_ref()))
92            .map(DirEntry::into_path)
93            .collect()
94    }
95}
96
97/// Run all blockchain tests from the given files
98fn run_tests(
99    test_files: Vec<PathBuf>,
100    omit_progress: bool,
101    keep_going: bool,
102    print_env_on_error: bool,
103    json_output: bool,
104) -> Result<(), Error> {
105    let mut passed = 0;
106    let mut failed = 0;
107    let mut skipped = 0;
108    let mut failed_paths = Vec::new();
109
110    let start_time = Instant::now();
111    let total_files = test_files.len();
112
113    for (file_index, file_path) in test_files.into_iter().enumerate() {
114        let current_file = file_index + 1;
115        if skip_test(&file_path) {
116            skipped += 1;
117            if json_output {
118                let output = json!({
119                    "file": file_path.display().to_string(),
120                    "status": "skipped",
121                    "reason": "known_issue"
122                });
123                println!("{}", serde_json::to_string(&output).unwrap());
124            } else if !omit_progress {
125                println!(
126                    "Skipping ({}/{}): {}",
127                    current_file,
128                    total_files,
129                    file_path.display()
130                );
131            }
132            continue;
133        }
134
135        let result = run_test_file(&file_path, json_output, print_env_on_error);
136
137        match result {
138            Ok(test_count) => {
139                passed += test_count;
140                if json_output {
141                    // JSON output handled in run_test_file
142                } else if !omit_progress {
143                    println!(
144                        "āœ“ ({}/{}) {} ({} tests)",
145                        current_file,
146                        total_files,
147                        file_path.display(),
148                        test_count
149                    );
150                }
151            }
152            Err(e) => {
153                failed += 1;
154                if keep_going {
155                    failed_paths.push(file_path.clone());
156                }
157                if json_output {
158                    let output = json!({
159                        "file": file_path.display().to_string(),
160                        "error": e.to_string(),
161                        "status": "failed"
162                    });
163                    println!("{}", serde_json::to_string(&output).unwrap());
164                } else if !omit_progress {
165                    eprintln!(
166                        "āœ— ({}/{}) {} - {}",
167                        current_file,
168                        total_files,
169                        file_path.display(),
170                        e
171                    );
172                }
173
174                if !keep_going {
175                    return Err(e);
176                }
177            }
178        }
179    }
180
181    let duration = start_time.elapsed();
182
183    if json_output {
184        let results = json!({
185            "summary": {
186                "passed": passed,
187                "failed": failed,
188                "skipped": skipped,
189                "duration_secs": duration.as_secs_f64(),
190            }
191        });
192        println!("{}", serde_json::to_string(&results).unwrap());
193    } else {
194        // Print failed test paths if keep-going was enabled
195        if keep_going && !failed_paths.is_empty() {
196            println!("\nFailed test files:");
197            for path in &failed_paths {
198                println!("  {}", path.display());
199            }
200        }
201
202        println!("\nTest results:");
203        println!("  Passed:  {passed}");
204        println!("  Failed:  {failed}");
205        println!("  Skipped: {skipped}");
206        println!("  Time:    {:.2}s", duration.as_secs_f64());
207    }
208
209    if failed > 0 {
210        Err(Error::TestsFailed { failed })
211    } else {
212        Ok(())
213    }
214}
215
216/// Run tests from a single file
217fn run_test_file(
218    file_path: &Path,
219    json_output: bool,
220    print_env_on_error: bool,
221) -> Result<usize, Error> {
222    let content =
223        fs::read_to_string(file_path).map_err(|e| Error::FileRead(file_path.to_path_buf(), e))?;
224
225    let blockchain_test: BlockchainTest = serde_json::from_str(&content)
226        .map_err(|e| Error::JsonDecode(file_path.to_path_buf(), e))?;
227
228    let mut test_count = 0;
229
230    for (test_name, test_case) in blockchain_test.0 {
231        if json_output {
232            // Output test start in JSON format
233            let output = json!({
234                "test": test_name,
235                "file": file_path.display().to_string(),
236                "status": "running"
237            });
238            println!("{}", serde_json::to_string(&output).unwrap());
239        } else {
240            println!("  Running: {test_name}");
241        }
242        // Execute the blockchain test
243        let result = execute_blockchain_test(&test_case, print_env_on_error, json_output);
244
245        match result {
246            Ok(()) => {
247                if json_output {
248                    let output = json!({
249                        "test": test_name,
250                        "file": file_path.display().to_string(),
251                        "status": "passed"
252                    });
253                    println!("{}", serde_json::to_string(&output).unwrap());
254                }
255                test_count += 1;
256            }
257            Err(e) => {
258                if json_output {
259                    let output = json!({
260                        "test": test_name,
261                        "file": file_path.display().to_string(),
262                        "status": "failed",
263                        "error": e.to_string()
264                    });
265                    println!("{}", serde_json::to_string(&output).unwrap());
266                }
267                return Err(Error::TestExecution {
268                    test_name: test_name.clone(),
269                    test_path: file_path.to_path_buf(),
270                    error: e.to_string(),
271                });
272            }
273        }
274    }
275
276    Ok(test_count)
277}
278
279/// Debug information captured during test execution
280#[derive(Debug, Clone)]
281struct DebugInfo {
282    /// Initial pre-state before any execution
283    pre_state: HashMap<Address, (AccountInfo, HashMap<U256, U256>)>,
284    /// Transaction environment
285    tx_env: Option<revm::context::tx::TxEnv>,
286    /// Block environment
287    block_env: revm::context::block::BlockEnv,
288    /// Configuration environment
289    cfg_env: CfgEnv,
290    /// Block index where error occurred
291    block_idx: usize,
292    /// Transaction index where error occurred
293    tx_idx: usize,
294    /// Withdrawals in the block
295    withdrawals: Option<Vec<Withdrawal>>,
296}
297
298impl DebugInfo {
299    /// Capture current state from the State database
300    fn capture_committed_state(
301        state: &State<EmptyDB>,
302    ) -> HashMap<Address, (AccountInfo, HashMap<U256, U256>)> {
303        let mut committed_state = HashMap::new();
304
305        // Access the cache state to get all accounts
306        for (address, cache_account) in &state.cache.accounts {
307            if let Some(plain_account) = &cache_account.account {
308                let mut storage = HashMap::new();
309                for (key, value) in &plain_account.storage {
310                    storage.insert(*key, *value);
311                }
312                committed_state.insert(*address, (plain_account.info.clone(), storage));
313            }
314        }
315
316        committed_state
317    }
318}
319
320/// Validate post state against expected values
321fn validate_post_state(
322    state: &mut State<EmptyDB>,
323    expected_post_state: &BTreeMap<Address, Account>,
324    debug_info: &DebugInfo,
325    print_env_on_error: bool,
326) -> Result<(), TestExecutionError> {
327    for (address, expected_account) in expected_post_state {
328        // Load account from final state
329        let actual_account = state
330            .load_cache_account(*address)
331            .map_err(|e| TestExecutionError::Database(format!("Account load failed: {e}")))?;
332        let info = actual_account
333            .account
334            .as_ref()
335            .map(|a| a.info.clone())
336            .unwrap_or_default();
337
338        // Validate balance
339        if info.balance != expected_account.balance {
340            if print_env_on_error {
341                print_error_with_state(debug_info, state, Some(expected_post_state));
342            }
343            return Err(TestExecutionError::PostStateValidation {
344                address: *address,
345                field: "balance".to_string(),
346                expected: format!("{}", expected_account.balance),
347                actual: format!("{}", info.balance),
348            });
349        }
350
351        // Validate nonce
352        let expected_nonce = expected_account.nonce.to::<u64>();
353        if info.nonce != expected_nonce {
354            if print_env_on_error {
355                print_error_with_state(debug_info, state, Some(expected_post_state));
356            }
357            return Err(TestExecutionError::PostStateValidation {
358                address: *address,
359                field: "nonce".to_string(),
360                expected: format!("{expected_nonce}"),
361                actual: format!("{}", info.nonce),
362            });
363        }
364
365        // Validate code if present
366        if !expected_account.code.is_empty() {
367            if let Some(actual_code) = &info.code {
368                if actual_code.original_bytes() != expected_account.code {
369                    if print_env_on_error {
370                        print_error_with_state(debug_info, state, Some(expected_post_state));
371                    }
372                    return Err(TestExecutionError::PostStateValidation {
373                        address: *address,
374                        field: "code".to_string(),
375                        expected: format!("0x{}", hex::encode(&expected_account.code)),
376                        actual: format!("0x{}", hex::encode(actual_code.bytecode())),
377                    });
378                }
379            } else {
380                if print_env_on_error {
381                    print_error_with_state(debug_info, state, Some(expected_post_state));
382                }
383                return Err(TestExecutionError::PostStateValidation {
384                    address: *address,
385                    field: "code".to_string(),
386                    expected: format!("0x{}", hex::encode(&expected_account.code)),
387                    actual: "empty".to_string(),
388                });
389            }
390        }
391
392        // Check for unexpected storage entries
393        for (slot, actual_value) in actual_account
394            .account
395            .as_ref()
396            .map(|a| &a.storage)
397            .unwrap_or(&HashMap::new())
398            .iter()
399        {
400            let slot = *slot;
401            let actual_value = *actual_value;
402            if !expected_account.storage.contains_key(&slot) && !actual_value.is_zero() {
403                if print_env_on_error {
404                    print_error_with_state(debug_info, state, Some(expected_post_state));
405                }
406                return Err(TestExecutionError::PostStateValidation {
407                    address: *address,
408                    field: format!("storage_unexpected[{slot}]"),
409                    expected: "0x0".to_string(),
410                    actual: format!("{actual_value}"),
411                });
412            }
413        }
414
415        // Validate storage slots
416        for (slot, expected_value) in &expected_account.storage {
417            let actual_value = state.storage(*address, *slot);
418            let actual_value = actual_value.unwrap_or_default();
419
420            if actual_value != *expected_value {
421                if print_env_on_error {
422                    print_error_with_state(debug_info, state, Some(expected_post_state));
423                }
424
425                return Err(TestExecutionError::PostStateValidation {
426                    address: *address,
427                    field: format!("storage_validation[{slot}]"),
428                    expected: format!("{expected_value}"),
429                    actual: format!("{actual_value}"),
430                });
431            }
432        }
433    }
434    Ok(())
435}
436
437/// Print comprehensive error information including environment and state comparison
438fn print_error_with_state(
439    debug_info: &DebugInfo,
440    current_state: &State<EmptyDB>,
441    expected_post_state: Option<&BTreeMap<Address, Account>>,
442) {
443    eprintln!("\n========== TEST EXECUTION ERROR ==========");
444
445    // Print error location
446    eprintln!(
447        "\nšŸ“ Error occurred at block {} transaction {}",
448        debug_info.block_idx, debug_info.tx_idx
449    );
450
451    // Print configuration environment
452    eprintln!("\nšŸ“‹ Configuration Environment:");
453    eprintln!("  Spec ID: {:?}", debug_info.cfg_env.spec);
454    eprintln!("  Chain ID: {}", debug_info.cfg_env.chain_id);
455    eprintln!(
456        "  Limit contract code size: {:?}",
457        debug_info.cfg_env.limit_contract_code_size
458    );
459    eprintln!(
460        "  Limit contract initcode size: {:?}",
461        debug_info.cfg_env.limit_contract_initcode_size
462    );
463
464    // Print block environment
465    eprintln!("\nšŸ”Ø Block Environment:");
466    eprintln!("  Number: {}", debug_info.block_env.number);
467    eprintln!("  Timestamp: {}", debug_info.block_env.timestamp);
468    eprintln!("  Gas limit: {}", debug_info.block_env.gas_limit);
469    eprintln!("  Base fee: {:?}", debug_info.block_env.basefee);
470    eprintln!("  Difficulty: {}", debug_info.block_env.difficulty);
471    eprintln!("  Prevrandao: {:?}", debug_info.block_env.prevrandao);
472    eprintln!("  Beneficiary: {:?}", debug_info.block_env.beneficiary);
473    eprintln!(
474        "  Blob excess gas: {:?}",
475        debug_info.block_env.blob_excess_gas_and_price
476    );
477
478    // Print withdrawals
479    if let Some(withdrawals) = &debug_info.withdrawals {
480        eprintln!("  Withdrawals: {} items", withdrawals.len());
481        if !withdrawals.is_empty() {
482            for (i, withdrawal) in withdrawals.iter().enumerate().take(3) {
483                eprintln!("    Withdrawal {i}:");
484                eprintln!("      Index: {}", withdrawal.index);
485                eprintln!("      Validator Index: {}", withdrawal.validator_index);
486                eprintln!("      Address: {:?}", withdrawal.address);
487                eprintln!(
488                    "      Amount: {} Gwei ({:.6} ETH)",
489                    withdrawal.amount,
490                    withdrawal.amount.to::<u128>() as f64 / 1_000_000_000.0
491                );
492            }
493            if withdrawals.len() > 3 {
494                eprintln!("    ... and {} more withdrawals", withdrawals.len() - 3);
495            }
496        }
497    }
498
499    // Print transaction environment if available
500    if let Some(tx_env) = &debug_info.tx_env {
501        eprintln!("\nšŸ“„ Transaction Environment:");
502        eprintln!("  Transaction type: {}", tx_env.tx_type);
503        eprintln!("  Caller: {:?}", tx_env.caller);
504        eprintln!("  Gas limit: {}", tx_env.gas_limit);
505        eprintln!("  Gas price: {}", tx_env.gas_price);
506        eprintln!("  Gas priority fee: {:?}", tx_env.gas_priority_fee);
507        eprintln!("  Transaction kind: {:?}", tx_env.kind);
508        eprintln!("  Value: {}", tx_env.value);
509        eprintln!("  Data length: {} bytes", tx_env.data.len());
510        if !tx_env.data.is_empty() {
511            let preview_len = std::cmp::min(64, tx_env.data.len());
512            eprintln!(
513                "  Data preview: 0x{}{}",
514                hex::encode(&tx_env.data[..preview_len]),
515                if tx_env.data.len() > 64 { "..." } else { "" }
516            );
517        }
518        eprintln!("  Nonce: {}", tx_env.nonce);
519        eprintln!("  Chain ID: {:?}", tx_env.chain_id);
520        eprintln!("  Access list: {} entries", tx_env.access_list.len());
521        if !tx_env.access_list.is_empty() {
522            for (i, access) in tx_env.access_list.iter().enumerate().take(3) {
523                eprintln!(
524                    "    Access {}: address={:?}, {} storage keys",
525                    i,
526                    access.address,
527                    access.storage_keys.len()
528                );
529            }
530            if tx_env.access_list.len() > 3 {
531                eprintln!(
532                    "    ... and {} more access list entries",
533                    tx_env.access_list.len() - 3
534                );
535            }
536        }
537        eprintln!("  Blob hashes: {} blobs", tx_env.blob_hashes.len());
538        if !tx_env.blob_hashes.is_empty() {
539            for (i, hash) in tx_env.blob_hashes.iter().enumerate().take(3) {
540                eprintln!("    Blob {i}: {hash:?}");
541            }
542            if tx_env.blob_hashes.len() > 3 {
543                eprintln!(
544                    "    ... and {} more blob hashes",
545                    tx_env.blob_hashes.len() - 3
546                );
547            }
548        }
549        eprintln!("  Max fee per blob gas: {}", tx_env.max_fee_per_blob_gas);
550        eprintln!(
551            "  Authorization list: {} items",
552            tx_env.authorization_list.len()
553        );
554        if !tx_env.authorization_list.is_empty() {
555            eprintln!("    (EIP-7702 authorizations present)");
556        }
557    } else {
558        eprintln!(
559            "\nšŸ“„ Transaction Environment: Not available (error occurred before tx creation)"
560        );
561    }
562
563    // Print state comparison
564    eprintln!("\nšŸ’¾ Pre-State (Initial):");
565    for (address, (info, storage)) in &debug_info.pre_state {
566        eprintln!("  Account {address:?}:");
567        eprintln!("    Balance: 0x{:x}", info.balance);
568        eprintln!("    Nonce: {}", info.nonce);
569        eprintln!("    Code hash: {:?}", info.code_hash);
570        eprintln!(
571            "    Code size: {} bytes",
572            info.code.as_ref().map_or(0, |c| c.bytecode().len())
573        );
574        if !storage.is_empty() {
575            eprintln!("    Storage ({} slots):", storage.len());
576            for (key, value) in storage.iter().take(5) {
577                eprintln!("      {key:?} => {value:?}");
578            }
579            if storage.len() > 5 {
580                eprintln!("      ... and {} more slots", storage.len() - 5);
581            }
582        }
583    }
584
585    eprintln!("\nšŸ“ Current State (Actual):");
586    let committed_state = DebugInfo::capture_committed_state(current_state);
587    for (address, (info, storage)) in &committed_state {
588        eprintln!("  Account {address:?}:");
589        eprintln!("    Balance: 0x{:x}", info.balance);
590        eprintln!("    Nonce: {}", info.nonce);
591        eprintln!("    Code hash: {:?}", info.code_hash);
592        eprintln!(
593            "    Code size: {} bytes",
594            info.code.as_ref().map_or(0, |c| c.bytecode().len())
595        );
596        if !storage.is_empty() {
597            eprintln!("    Storage ({} slots):", storage.len());
598            for (key, value) in storage.iter().take(5) {
599                eprintln!("      {key:?} => {value:?}");
600            }
601            if storage.len() > 5 {
602                eprintln!("      ... and {} more slots", storage.len() - 5);
603            }
604        }
605    }
606
607    // Print expected post-state if available
608    if let Some(expected_post_state) = expected_post_state {
609        eprintln!("\nāœ… Expected Post-State:");
610        for (address, account) in expected_post_state {
611            eprintln!("  Account {address:?}:");
612            eprintln!("    Balance: 0x{:x}", account.balance);
613            eprintln!("    Nonce: {}", account.nonce);
614            if !account.code.is_empty() {
615                eprintln!("    Code size: {} bytes", account.code.len());
616            }
617            if !account.storage.is_empty() {
618                eprintln!("    Storage ({} slots):", account.storage.len());
619                for (key, value) in account.storage.iter().take(5) {
620                    eprintln!("      {key:?} => {value:?}");
621                }
622                if account.storage.len() > 5 {
623                    eprintln!("      ... and {} more slots", account.storage.len() - 5);
624                }
625            }
626        }
627    }
628
629    eprintln!("\n===========================================\n");
630}
631
632/// Execute a single blockchain test case
633fn execute_blockchain_test(
634    test_case: &BlockchainTestCase,
635    print_env_on_error: bool,
636    json_output: bool,
637) -> Result<(), TestExecutionError> {
638    // Skip all transition forks for now.
639    if matches!(
640        test_case.network,
641        ForkSpec::ByzantiumToConstantinopleAt5
642            | ForkSpec::ParisToShanghaiAtTime15k
643            | ForkSpec::ShanghaiToCancunAtTime15k
644            | ForkSpec::CancunToPragueAtTime15k
645            | ForkSpec::PragueToOsakaAtTime15k
646            | ForkSpec::BPO1ToBPO2AtTime15k
647    ) {
648        eprintln!("āš ļø  Skipping transition fork: {:?}", test_case.network);
649        return Ok(());
650    }
651
652    // Create database with initial state
653    let mut state = State::builder().build();
654
655    // Capture pre-state for debug info
656    let mut pre_state_debug = HashMap::new();
657
658    // Insert genesis state into database
659    let genesis_state = test_case.pre.clone().into_genesis_state();
660    for (address, account) in genesis_state {
661        let account_info = AccountInfo {
662            balance: account.balance,
663            nonce: account.nonce,
664            code_hash: primitives::keccak256(&account.code),
665            code: Some(bytecode::Bytecode::new_raw(account.code.clone())),
666        };
667
668        // Store for debug info
669        if print_env_on_error {
670            pre_state_debug.insert(address, (account_info.clone(), account.storage.clone()));
671        }
672
673        state.insert_account_with_storage(address, account_info, account.storage);
674    }
675
676    // Setup configuration based on fork
677    let spec_id = fork_to_spec_id(test_case.network);
678    let mut cfg = CfgEnv::default();
679    cfg.spec = spec_id;
680
681    // Genesis block is not used yet.
682    let mut parent_block_hash = Some(test_case.genesis_block_header.hash);
683    let mut parent_excess_blob_gas = test_case
684        .genesis_block_header
685        .excess_blob_gas
686        .unwrap_or_default()
687        .to::<u64>();
688    let mut block_env = test_case.genesis_block_env();
689
690    // Process each block in the test
691    for (block_idx, block) in test_case.blocks.iter().enumerate() {
692        println!("Run block {block_idx}/{}", test_case.blocks.len());
693
694        // Check if this block should fail
695        let should_fail = block.expect_exception.is_some();
696
697        let transactions = block.transactions.as_deref().unwrap_or_default();
698
699        // Update block environment for this blockk
700
701        let mut block_hash = None;
702        let mut beacon_root = None;
703        let this_excess_blob_gas;
704
705        if let Some(block_header) = block.block_header.as_ref() {
706            block_hash = Some(block_header.hash);
707            beacon_root = block_header.parent_beacon_block_root;
708            block_env = block_header.to_block_env(Some(BlobExcessGasAndPrice::new_with_spec(
709                parent_excess_blob_gas,
710                spec_id,
711            )));
712            this_excess_blob_gas = block_header.excess_blob_gas.map(|i| i.to::<u64>());
713        } else {
714            this_excess_blob_gas = None;
715        }
716
717        // Create EVM context for each transaction to ensure fresh state access
718        let evm_context = Context::mainnet()
719            .with_block(&block_env)
720            .with_cfg(&cfg)
721            .with_db(&mut state);
722
723        // Build and execute with EVM - always use inspector when JSON output is enabled
724        let mut evm = evm_context.build_mainnet_with_inspector(TracerEip3155::new_stdout());
725
726        // Pre block system calls
727        pre_block::pre_block_transition(&mut evm, spec_id, parent_block_hash, beacon_root);
728
729        // Execute each transaction in the block
730        for (tx_idx, tx) in transactions.iter().enumerate() {
731            if tx.sender.is_none() {
732                if print_env_on_error {
733                    let debug_info = DebugInfo {
734                        pre_state: pre_state_debug.clone(),
735                        tx_env: None,
736                        block_env: block_env.clone(),
737                        cfg_env: cfg.clone(),
738                        block_idx,
739                        tx_idx,
740                        withdrawals: block.withdrawals.clone(),
741                    };
742                    print_error_with_state(
743                        &debug_info,
744                        evm.ctx().db_ref(),
745                        test_case.post_state.as_ref(),
746                    );
747                }
748                if json_output {
749                    let output = json!({
750                        "block": block_idx,
751                        "tx": tx_idx,
752                        "error": "missing sender",
753                        "status": "skipped"
754                    });
755                    println!("{}", serde_json::to_string(&output).unwrap());
756                } else {
757                    eprintln!("āš ļø  Skipping block {block_idx} due to missing sender");
758                }
759                break; // Skip to next block
760            }
761
762            let tx_env = match tx.to_tx_env() {
763                Ok(env) => env,
764                Err(e) => {
765                    if should_fail {
766                        // Expected failure during tx env creation
767                        continue;
768                    }
769                    if print_env_on_error {
770                        let debug_info = DebugInfo {
771                            pre_state: pre_state_debug.clone(),
772                            tx_env: None,
773                            block_env: block_env.clone(),
774                            cfg_env: cfg.clone(),
775                            block_idx,
776                            tx_idx,
777                            withdrawals: block.withdrawals.clone(),
778                        };
779                        print_error_with_state(
780                            &debug_info,
781                            evm.ctx().db_ref(),
782                            test_case.post_state.as_ref(),
783                        );
784                    }
785                    if json_output {
786                        let output = json!({
787                            "block": block_idx,
788                            "tx": tx_idx,
789                            "error": format!("tx env creation error: {e}"),
790                            "status": "skipped"
791                        });
792                        println!("{}", serde_json::to_string(&output).unwrap());
793                    } else {
794                        eprintln!(
795                            "āš ļø  Skipping block {block_idx} due to transaction env creation error: {e}"
796                        );
797                    }
798                    break; // Skip to next block
799                }
800            };
801
802            // If JSON output requested, output transaction details
803            let execution_result = if json_output {
804                evm.inspect_tx(tx_env.clone())
805            } else {
806                evm.transact(tx_env.clone())
807            };
808
809            match execution_result {
810                Ok(result) => {
811                    if should_fail {
812                        // Unexpected success - should have failed but didn't
813                        // If not expected to fail, use inspector to trace the transaction
814                        if print_env_on_error {
815                            // Re-run with inspector to get detailed trace
816                            if json_output {
817                                eprintln!("=== Transaction trace (unexpected success) ===");
818                            }
819                            let _ = evm.inspect_tx(tx_env.clone());
820                        }
821
822                        if print_env_on_error {
823                            let debug_info = DebugInfo {
824                                pre_state: pre_state_debug.clone(),
825                                tx_env: Some(tx_env.clone()),
826                                block_env: block_env.clone(),
827                                cfg_env: cfg.clone(),
828                                block_idx,
829                                tx_idx,
830                                withdrawals: block.withdrawals.clone(),
831                            };
832                            print_error_with_state(
833                                &debug_info,
834                                evm.ctx().db_ref(),
835                                test_case.post_state.as_ref(),
836                            );
837                        }
838                        let exception = block.expect_exception.clone().unwrap_or_default();
839                        if json_output {
840                            let output = json!({
841                                "block": block_idx,
842                                "tx": tx_idx,
843                                "error": format!("expected failure: {exception}"),
844                                "gas_used": result.result.gas_used(),
845                                "status": "unexpected_success"
846                            });
847                            println!("{}", serde_json::to_string(&output).unwrap());
848                        } else {
849                            eprintln!(
850                                "āš ļø  Skipping block {block_idx} due to expected failure: {exception}"
851                            );
852                        }
853                        break; // Skip to next block
854                    }
855                    evm.commit(result.state);
856                }
857                Err(e) => {
858                    if !should_fail {
859                        // Unexpected error - use inspector to trace the transaction
860                        if print_env_on_error {
861                            if json_output {
862                                eprintln!("=== Transaction trace (unexpected failure) ===");
863                            }
864                            let _ = evm.inspect_tx(tx_env.clone());
865                        }
866
867                        if print_env_on_error {
868                            let debug_info = DebugInfo {
869                                pre_state: pre_state_debug.clone(),
870                                tx_env: Some(tx_env.clone()),
871                                block_env: block_env.clone(),
872                                cfg_env: cfg.clone(),
873                                block_idx,
874                                tx_idx,
875                                withdrawals: block.withdrawals.clone(),
876                            };
877                            print_error_with_state(
878                                &debug_info,
879                                evm.ctx().db_ref(),
880                                test_case.post_state.as_ref(),
881                            );
882                        }
883                        if json_output {
884                            let output = json!({
885                                "block": block_idx,
886                                "tx": tx_idx,
887                                "error": format!("{e:?}"),
888                                "status": "unexpected_failure"
889                            });
890                            println!("{}", serde_json::to_string(&output).unwrap());
891                        } else {
892                            eprintln!(
893                                "āš ļø  Skipping block {block_idx} due to unexpected failure: {e:?}"
894                            );
895                        }
896                        break; // Skip to next block
897                    } else if json_output {
898                        // Expected failure
899                        let output = json!({
900                            "block": block_idx,
901                            "tx": tx_idx,
902                            "error": format!("{e:?}"),
903                            "status": "expected_failure"
904                        });
905                        println!("{}", serde_json::to_string(&output).unwrap());
906                    }
907                }
908            }
909        }
910
911        // uncle rewards are not implemented yet
912        post_block::post_block_transition(
913            &mut evm,
914            &block_env,
915            block.withdrawals.as_deref().unwrap_or_default(),
916            spec_id,
917        );
918
919        parent_block_hash = block_hash;
920        if let Some(excess_blob_gas) = this_excess_blob_gas {
921            parent_excess_blob_gas = excess_blob_gas;
922        }
923
924        state.merge_transitions(BundleRetention::Reverts);
925    }
926
927    // Validate post state if present
928    if let Some(expected_post_state) = &test_case.post_state {
929        // Create debug info for post-state validation
930        let debug_info = DebugInfo {
931            pre_state: pre_state_debug.clone(),
932            tx_env: None, // Last transaction is done
933            block_env: block_env.clone(),
934            cfg_env: cfg.clone(),
935            block_idx: test_case.blocks.len(),
936            tx_idx: 0,
937            withdrawals: test_case.blocks.last().and_then(|b| b.withdrawals.clone()),
938        };
939        validate_post_state(
940            &mut state,
941            expected_post_state,
942            &debug_info,
943            print_env_on_error,
944        )?;
945    }
946
947    Ok(())
948}
949
950/// Convert ForkSpec to SpecId
951fn fork_to_spec_id(fork: ForkSpec) -> SpecId {
952    match fork {
953        ForkSpec::Frontier => SpecId::FRONTIER,
954        ForkSpec::Homestead | ForkSpec::FrontierToHomesteadAt5 => SpecId::HOMESTEAD,
955        ForkSpec::EIP150 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 => {
956            SpecId::TANGERINE
957        }
958        ForkSpec::EIP158 => SpecId::SPURIOUS_DRAGON,
959        ForkSpec::Byzantium
960        | ForkSpec::EIP158ToByzantiumAt5
961        | ForkSpec::ByzantiumToConstantinopleFixAt5 => SpecId::BYZANTIUM,
962        ForkSpec::Constantinople | ForkSpec::ByzantiumToConstantinopleAt5 => SpecId::PETERSBURG,
963        ForkSpec::ConstantinopleFix => SpecId::PETERSBURG,
964        ForkSpec::Istanbul => SpecId::ISTANBUL,
965        ForkSpec::Berlin => SpecId::BERLIN,
966        ForkSpec::London | ForkSpec::BerlinToLondonAt5 => SpecId::LONDON,
967        ForkSpec::Paris | ForkSpec::ParisToShanghaiAtTime15k => SpecId::MERGE,
968        ForkSpec::Shanghai => SpecId::SHANGHAI,
969        ForkSpec::Cancun | ForkSpec::ShanghaiToCancunAtTime15k => SpecId::CANCUN,
970        ForkSpec::Prague | ForkSpec::CancunToPragueAtTime15k => SpecId::PRAGUE,
971        ForkSpec::Osaka | ForkSpec::PragueToOsakaAtTime15k => SpecId::OSAKA,
972        _ => SpecId::OSAKA, // For any unknown forks, use latest available
973    }
974}
975
976/// Check if a test should be skipped based on its filename
977fn skip_test(path: &Path) -> bool {
978    let name = path.file_name().unwrap().to_str().unwrap();
979
980    // Add any problematic tests here that should be skipped
981    matches!(
982        name,
983        // Test check if gas price overflows, we handle this correctly but does not match tests specific exception.
984        "CreateTransactionHighNonce.json"
985
986        // Test with some storage check.
987        | "RevertInCreateInInit_Paris.json"
988        | "RevertInCreateInInit.json"
989        | "dynamicAccountOverwriteEmpty.json"
990        | "dynamicAccountOverwriteEmpty_Paris.json"
991        | "RevertInCreateInInitCreate2Paris.json"
992        | "create2collisionStorage.json"
993        | "RevertInCreateInInitCreate2.json"
994        | "create2collisionStorageParis.json"
995        | "InitCollision.json"
996        | "InitCollisionParis.json"
997
998        // Malformed value.
999        | "ValueOverflow.json"
1000        | "ValueOverflowParis.json"
1001
1002        // These tests are passing, but they take a lot of time to execute so we are going to skip them.
1003        | "Call50000_sha256.json"
1004        | "static_Call50000_sha256.json"
1005        | "loopMul.json"
1006        | "CALLBlake2f_MaxRounds.json"
1007        // TODO tests not checked, maybe related to parent block hashes as it is currently not supported in test.
1008        | "scenarios.json"
1009        // IT seems that post state is wrong, we properly handle max blob gas and state should stay the same.
1010        | "invalid_tx_max_fee_per_blob_gas.json"
1011        | "correct_increasing_blob_gas_costs.json"
1012        | "correct_decreasing_blob_gas_costs.json"
1013
1014        // test-fixtures/main/develop/blockchain_tests/cancun/eip4844_blobs/excess_blob_gas/invalid_negative_excess_blob_gas.json
1015        | "invalid_negative_excess_blob_gas.json"
1016        // test-fixtures/main/develop/blockchain_tests/cancun/eip4844_blobs/excess_blob_gas/invalid_excess_blob_gas_change.json
1017        | "invalid_excess_blob_gas_change.json"
1018        // test-fixtures/main/develop/blockchain_tests/cancun/eip4844_blobs/excess_blob_gas/invalid_static_excess_blob_gas.json
1019        | "invalid_static_excess_blob_gas.json"
1020        // test-fixtures/main/develop/blockchain_tests/cancun/eip4844_blobs/excess_blob_gas/invalid_excess_blob_gas_target_blobs_increase_from_zero.json
1021        | "invalid_excess_blob_gas_target_blobs_increase_from_zero.json"
1022        // test-fixtures/main/develop/blockchain_tests/cancun/eip4844_blobs/excess_blob_gas/invalid_zero_excess_blob_gas_in_header.json
1023        | "invalid_zero_excess_blob_gas_in_header.json"
1024        // test-fixtures/main/develop/blockchain_tests/cancun/eip4844_blobs/excess_blob_gas/invalid_excess_blob_gas_above_target_change.json
1025        | "invalid_excess_blob_gas_above_target_change.json"
1026        // test-fixtures/main/develop/blockchain_tests/cancun/eip4844_blobs/excess_blob_gas/invalid_non_multiple_excess_blob_gas.json
1027        | "invalid_non_multiple_excess_blob_gas.json"
1028        // test-fixtures/main/develop/blockchain_tests/cancun/eip4844_blobs/excess_blob_gas/invalid_static_excess_blob_gas_from_zero_on_blobs_above_target.json
1029        | "invalid_static_excess_blob_gas_from_zero_on_blobs_above_target.json"
1030        // test-fixtures/main/develop/blockchain_tests/prague/eip7251_consolidations/modified_consolidation_contract/system_contract_errors.json
1031        | "system_contract_errors.json"
1032        // test-fixtures/main/develop/blockchain_tests/prague/eip7251_consolidations/consolidations/consolidation_requests.json
1033        | "consolidation_requests.json"
1034        // test-fixtures/main/develop/blockchain_tests/prague/eip2935_historical_block_hashes_from_state/block_hashes/block_hashes_history.json
1035        | "block_hashes_history.json"
1036        // test-fixtures/main/develop/blockchain_tests/prague/eip7685_general_purpose_el_requests/multi_type_requests/valid_multi_type_request_from_same_tx.json
1037        | "valid_multi_type_request_from_same_tx.json"
1038        // test-fixtures/main/develop/blockchain_tests/prague/eip7685_general_purpose_el_requests/multi_type_requests/valid_multi_type_requests.json
1039        | "valid_multi_type_requests.json"
1040        // test-fixtures/main/develop/blockchain_tests/prague/eip7002_el_triggerable_withdrawals/modified_withdrawal_contract/system_contract_errors.json
1041        //| "system_contract_errors.json"
1042        // test-fixtures/main/develop/blockchain_tests/prague/eip7002_el_triggerable_withdrawals/withdrawal_requests/withdrawal_requests.json
1043        | "withdrawal_requests.json"
1044    )
1045}
1046
1047#[derive(Debug, Error)]
1048pub enum TestExecutionError {
1049    #[error("Database error: {0}")]
1050    Database(String),
1051
1052    #[error("Skipped fork: {0}")]
1053    SkippedFork(String),
1054
1055    #[error("Sender is required")]
1056    SenderRequired,
1057
1058    #[error("Expected failure at block {block_idx}, tx {tx_idx}: {message}")]
1059    ExpectedFailure {
1060        block_idx: usize,
1061        tx_idx: usize,
1062        message: String,
1063    },
1064
1065    #[error("Unexpected failure at block {block_idx}, tx {tx_idx}: {error}")]
1066    UnexpectedFailure {
1067        block_idx: usize,
1068        tx_idx: usize,
1069        error: String,
1070    },
1071
1072    #[error("Transaction env creation failed at block {block_idx}, tx {tx_idx}: {error}")]
1073    TransactionEnvCreation {
1074        block_idx: usize,
1075        tx_idx: usize,
1076        error: String,
1077    },
1078
1079    #[error("Unexpected revert at block {block_idx}, tx {tx_idx}, gas used: {gas_used}")]
1080    UnexpectedRevert {
1081        block_idx: usize,
1082        tx_idx: usize,
1083        gas_used: u64,
1084    },
1085
1086    #[error("Unexpected halt at block {block_idx}, tx {tx_idx}: {reason:?}, gas used: {gas_used}")]
1087    UnexpectedHalt {
1088        block_idx: usize,
1089        tx_idx: usize,
1090        reason: HaltReason,
1091        gas_used: u64,
1092    },
1093
1094    #[error(
1095        "Post-state validation failed for {address:?}.{field}: expected {expected}, got {actual}"
1096    )]
1097    PostStateValidation {
1098        address: Address,
1099        field: String,
1100        expected: String,
1101        actual: String,
1102    },
1103}
1104
1105#[derive(Debug, Error)]
1106pub enum Error {
1107    #[error("Path not found: {0}")]
1108    PathNotFound(PathBuf),
1109
1110    #[error("No JSON files found in: {0}")]
1111    NoJsonFiles(PathBuf),
1112
1113    #[error("Failed to read file {0}: {1}")]
1114    FileRead(PathBuf, std::io::Error),
1115
1116    #[error("Failed to decode JSON from {0}: {1}")]
1117    JsonDecode(PathBuf, serde_json::Error),
1118
1119    #[error("Test execution failed for {test_name} in {test_path}: {error}")]
1120    TestExecution {
1121        test_name: String,
1122        test_path: PathBuf,
1123        error: String,
1124    },
1125
1126    #[error("Directory traversal error: {0}")]
1127    WalkDir(#[from] walkdir::Error),
1128
1129    #[error("{failed} tests failed")]
1130    TestsFailed { failed: usize },
1131}