revme/cmd/
blockchaintest.rs

1pub mod post_block;
2pub mod pre_block;
3
4use clap::Parser;
5
6use revm::{
7    bytecode::Bytecode,
8    context::{cfg::CfgEnv, ContextTr},
9    context_interface::{block::BlobExcessGasAndPrice, result::HaltReason},
10    database::{states::bundle_state::BundleRetention, EmptyDB, State},
11    handler::EvmTr,
12    inspector::inspectors::TracerEip3155,
13    primitives::{hardfork::SpecId, hex, Address, HashMap, U256},
14    state::AccountInfo,
15    Context, Database, ExecuteCommitEvm, ExecuteEvm, InspectEvm, MainBuilder, MainContext,
16};
17use serde_json::json;
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. Avoid allocating a temporary HashMap when the account is None.
393        if let Some(acc) = actual_account.account.as_ref() {
394            for (slot, actual_value) in &acc.storage {
395                let slot = *slot;
396                let actual_value = *actual_value;
397                if !expected_account.storage.contains_key(&slot) && !actual_value.is_zero() {
398                    if print_env_on_error {
399                        print_error_with_state(debug_info, state, Some(expected_post_state));
400                    }
401                    return Err(TestExecutionError::PostStateValidation {
402                        address: *address,
403                        field: format!("storage_unexpected[{slot}]"),
404                        expected: "0x0".to_string(),
405                        actual: format!("{actual_value}"),
406                    });
407                }
408            }
409        }
410
411        // Validate storage slots
412        for (slot, expected_value) in &expected_account.storage {
413            let actual_value = state.storage(*address, *slot);
414            let actual_value = actual_value.unwrap_or_default();
415
416            if actual_value != *expected_value {
417                if print_env_on_error {
418                    print_error_with_state(debug_info, state, Some(expected_post_state));
419                }
420
421                return Err(TestExecutionError::PostStateValidation {
422                    address: *address,
423                    field: format!("storage_validation[{slot}]"),
424                    expected: format!("{expected_value}"),
425                    actual: format!("{actual_value}"),
426                });
427            }
428        }
429    }
430    Ok(())
431}
432
433/// Print comprehensive error information including environment and state comparison
434fn print_error_with_state(
435    debug_info: &DebugInfo,
436    current_state: &State<EmptyDB>,
437    expected_post_state: Option<&BTreeMap<Address, Account>>,
438) {
439    eprintln!("\n========== TEST EXECUTION ERROR ==========");
440
441    // Print error location
442    eprintln!(
443        "\nšŸ“ Error occurred at block {} transaction {}",
444        debug_info.block_idx, debug_info.tx_idx
445    );
446
447    // Print configuration environment
448    eprintln!("\nšŸ“‹ Configuration Environment:");
449    eprintln!("  Spec ID: {:?}", debug_info.cfg_env.spec);
450    eprintln!("  Chain ID: {}", debug_info.cfg_env.chain_id);
451    eprintln!(
452        "  Limit contract code size: {:?}",
453        debug_info.cfg_env.limit_contract_code_size
454    );
455    eprintln!(
456        "  Limit contract initcode size: {:?}",
457        debug_info.cfg_env.limit_contract_initcode_size
458    );
459
460    // Print block environment
461    eprintln!("\nšŸ”Ø Block Environment:");
462    eprintln!("  Number: {}", debug_info.block_env.number);
463    eprintln!("  Timestamp: {}", debug_info.block_env.timestamp);
464    eprintln!("  Gas limit: {}", debug_info.block_env.gas_limit);
465    eprintln!("  Base fee: {:?}", debug_info.block_env.basefee);
466    eprintln!("  Difficulty: {}", debug_info.block_env.difficulty);
467    eprintln!("  Prevrandao: {:?}", debug_info.block_env.prevrandao);
468    eprintln!("  Beneficiary: {:?}", debug_info.block_env.beneficiary);
469    eprintln!(
470        "  Blob excess gas: {:?}",
471        debug_info.block_env.blob_excess_gas_and_price
472    );
473
474    // Print withdrawals
475    if let Some(withdrawals) = &debug_info.withdrawals {
476        eprintln!("  Withdrawals: {} items", withdrawals.len());
477        if !withdrawals.is_empty() {
478            for (i, withdrawal) in withdrawals.iter().enumerate().take(3) {
479                eprintln!("    Withdrawal {i}:");
480                eprintln!("      Index: {}", withdrawal.index);
481                eprintln!("      Validator Index: {}", withdrawal.validator_index);
482                eprintln!("      Address: {:?}", withdrawal.address);
483                eprintln!(
484                    "      Amount: {} Gwei ({:.6} ETH)",
485                    withdrawal.amount,
486                    withdrawal.amount.to::<u128>() as f64 / 1_000_000_000.0
487                );
488            }
489            if withdrawals.len() > 3 {
490                eprintln!("    ... and {} more withdrawals", withdrawals.len() - 3);
491            }
492        }
493    }
494
495    // Print transaction environment if available
496    if let Some(tx_env) = &debug_info.tx_env {
497        eprintln!("\nšŸ“„ Transaction Environment:");
498        eprintln!("  Transaction type: {}", tx_env.tx_type);
499        eprintln!("  Caller: {:?}", tx_env.caller);
500        eprintln!("  Gas limit: {}", tx_env.gas_limit);
501        eprintln!("  Gas price: {}", tx_env.gas_price);
502        eprintln!("  Gas priority fee: {:?}", tx_env.gas_priority_fee);
503        eprintln!("  Transaction kind: {:?}", tx_env.kind);
504        eprintln!("  Value: {}", tx_env.value);
505        eprintln!("  Data length: {} bytes", tx_env.data.len());
506        if !tx_env.data.is_empty() {
507            let preview_len = std::cmp::min(64, tx_env.data.len());
508            eprintln!(
509                "  Data preview: 0x{}{}",
510                hex::encode(&tx_env.data[..preview_len]),
511                if tx_env.data.len() > 64 { "..." } else { "" }
512            );
513        }
514        eprintln!("  Nonce: {}", tx_env.nonce);
515        eprintln!("  Chain ID: {:?}", tx_env.chain_id);
516        eprintln!("  Access list: {} entries", tx_env.access_list.len());
517        if !tx_env.access_list.is_empty() {
518            for (i, access) in tx_env.access_list.iter().enumerate().take(3) {
519                eprintln!(
520                    "    Access {}: address={:?}, {} storage keys",
521                    i,
522                    access.address,
523                    access.storage_keys.len()
524                );
525            }
526            if tx_env.access_list.len() > 3 {
527                eprintln!(
528                    "    ... and {} more access list entries",
529                    tx_env.access_list.len() - 3
530                );
531            }
532        }
533        eprintln!("  Blob hashes: {} blobs", tx_env.blob_hashes.len());
534        if !tx_env.blob_hashes.is_empty() {
535            for (i, hash) in tx_env.blob_hashes.iter().enumerate().take(3) {
536                eprintln!("    Blob {i}: {hash:?}");
537            }
538            if tx_env.blob_hashes.len() > 3 {
539                eprintln!(
540                    "    ... and {} more blob hashes",
541                    tx_env.blob_hashes.len() - 3
542                );
543            }
544        }
545        eprintln!("  Max fee per blob gas: {}", tx_env.max_fee_per_blob_gas);
546        eprintln!(
547            "  Authorization list: {} items",
548            tx_env.authorization_list.len()
549        );
550        if !tx_env.authorization_list.is_empty() {
551            eprintln!("    (EIP-7702 authorizations present)");
552        }
553    } else {
554        eprintln!(
555            "\nšŸ“„ Transaction Environment: Not available (error occurred before tx creation)"
556        );
557    }
558
559    // Print state comparison
560    eprintln!("\nšŸ’¾ Pre-State (Initial):");
561    for (address, (info, storage)) in &debug_info.pre_state {
562        eprintln!("  Account {address:?}:");
563        eprintln!("    Balance: 0x{:x}", info.balance);
564        eprintln!("    Nonce: {}", info.nonce);
565        eprintln!("    Code hash: {:?}", info.code_hash);
566        eprintln!(
567            "    Code size: {} bytes",
568            info.code.as_ref().map_or(0, |c| c.bytecode().len())
569        );
570        if !storage.is_empty() {
571            eprintln!("    Storage ({} slots):", storage.len());
572            for (key, value) in storage.iter().take(5) {
573                eprintln!("      {key:?} => {value:?}");
574            }
575            if storage.len() > 5 {
576                eprintln!("      ... and {} more slots", storage.len() - 5);
577            }
578        }
579    }
580
581    eprintln!("\nšŸ“ Current State (Actual):");
582    let committed_state = DebugInfo::capture_committed_state(current_state);
583    for (address, (info, storage)) in &committed_state {
584        eprintln!("  Account {address:?}:");
585        eprintln!("    Balance: 0x{:x}", info.balance);
586        eprintln!("    Nonce: {}", info.nonce);
587        eprintln!("    Code hash: {:?}", info.code_hash);
588        eprintln!(
589            "    Code size: {} bytes",
590            info.code.as_ref().map_or(0, |c| c.bytecode().len())
591        );
592        if !storage.is_empty() {
593            eprintln!("    Storage ({} slots):", storage.len());
594            for (key, value) in storage.iter().take(5) {
595                eprintln!("      {key:?} => {value:?}");
596            }
597            if storage.len() > 5 {
598                eprintln!("      ... and {} more slots", storage.len() - 5);
599            }
600        }
601    }
602
603    // Print expected post-state if available
604    if let Some(expected_post_state) = expected_post_state {
605        eprintln!("\nāœ… Expected Post-State:");
606        for (address, account) in expected_post_state {
607            eprintln!("  Account {address:?}:");
608            eprintln!("    Balance: 0x{:x}", account.balance);
609            eprintln!("    Nonce: {}", account.nonce);
610            if !account.code.is_empty() {
611                eprintln!("    Code size: {} bytes", account.code.len());
612            }
613            if !account.storage.is_empty() {
614                eprintln!("    Storage ({} slots):", account.storage.len());
615                for (key, value) in account.storage.iter().take(5) {
616                    eprintln!("      {key:?} => {value:?}");
617                }
618                if account.storage.len() > 5 {
619                    eprintln!("      ... and {} more slots", account.storage.len() - 5);
620                }
621            }
622        }
623    }
624
625    eprintln!("\n===========================================\n");
626}
627
628/// Execute a single blockchain test case
629fn execute_blockchain_test(
630    test_case: &BlockchainTestCase,
631    print_env_on_error: bool,
632    json_output: bool,
633) -> Result<(), TestExecutionError> {
634    // Skip all transition forks for now.
635    if matches!(
636        test_case.network,
637        ForkSpec::ByzantiumToConstantinopleAt5
638            | ForkSpec::ParisToShanghaiAtTime15k
639            | ForkSpec::ShanghaiToCancunAtTime15k
640            | ForkSpec::CancunToPragueAtTime15k
641            | ForkSpec::PragueToOsakaAtTime15k
642            | ForkSpec::BPO1ToBPO2AtTime15k
643    ) {
644        eprintln!("āš ļø  Skipping transition fork: {:?}", test_case.network);
645        return Ok(());
646    }
647
648    // Create database with initial state
649    let mut state = State::builder().build();
650
651    // Capture pre-state for debug info
652    let mut pre_state_debug = HashMap::new();
653
654    // Insert genesis state into database
655    let genesis_state = test_case.pre.clone().into_genesis_state();
656    for (address, account) in genesis_state {
657        let account_info = AccountInfo {
658            balance: account.balance,
659            nonce: account.nonce,
660            code_hash: revm::primitives::keccak256(&account.code),
661            code: Some(Bytecode::new_raw(account.code.clone())),
662        };
663
664        // Store for debug info
665        if print_env_on_error {
666            pre_state_debug.insert(address, (account_info.clone(), account.storage.clone()));
667        }
668
669        state.insert_account_with_storage(address, account_info, account.storage);
670    }
671
672    // insert genesis hash
673    state
674        .block_hashes
675        .insert(0, test_case.genesis_block_header.hash);
676
677    // Setup configuration based on fork
678    let spec_id = fork_to_spec_id(test_case.network);
679    let mut cfg = CfgEnv::default();
680    cfg.spec = spec_id;
681
682    // Genesis block is not used yet.
683    let mut parent_block_hash = Some(test_case.genesis_block_header.hash);
684    let mut parent_excess_blob_gas = test_case
685        .genesis_block_header
686        .excess_blob_gas
687        .unwrap_or_default()
688        .to::<u64>();
689    let mut block_env = test_case.genesis_block_env();
690
691    // Process each block in the test
692    for (block_idx, block) in test_case.blocks.iter().enumerate() {
693        println!("Run block {block_idx}/{}", test_case.blocks.len());
694
695        // Check if this block should fail
696        let should_fail = block.expect_exception.is_some();
697
698        let transactions = block.transactions.as_deref().unwrap_or_default();
699
700        // Update block environment for this blockk
701
702        let mut block_hash = None;
703        let mut beacon_root = None;
704        let this_excess_blob_gas;
705
706        if let Some(block_header) = block.block_header.as_ref() {
707            block_hash = Some(block_header.hash);
708            beacon_root = block_header.parent_beacon_block_root;
709            block_env = block_header.to_block_env(Some(BlobExcessGasAndPrice::new_with_spec(
710                parent_excess_blob_gas,
711                spec_id,
712            )));
713            this_excess_blob_gas = block_header.excess_blob_gas.map(|i| i.to::<u64>());
714        } else {
715            this_excess_blob_gas = None;
716        }
717
718        // Create EVM context for each transaction to ensure fresh state access
719        let evm_context = Context::mainnet()
720            .with_block(&block_env)
721            .with_cfg(&cfg)
722            .with_db(&mut state);
723
724        // Build and execute with EVM - always use inspector when JSON output is enabled
725        let mut evm = evm_context.build_mainnet_with_inspector(TracerEip3155::new_stdout());
726
727        // Pre block system calls
728        pre_block::pre_block_transition(&mut evm, spec_id, parent_block_hash, beacon_root);
729
730        // Execute each transaction in the block
731        for (tx_idx, tx) in transactions.iter().enumerate() {
732            if tx.sender.is_none() {
733                if print_env_on_error {
734                    let debug_info = DebugInfo {
735                        pre_state: pre_state_debug.clone(),
736                        tx_env: None,
737                        block_env: block_env.clone(),
738                        cfg_env: cfg.clone(),
739                        block_idx,
740                        tx_idx,
741                        withdrawals: block.withdrawals.clone(),
742                    };
743                    print_error_with_state(
744                        &debug_info,
745                        evm.ctx().db_ref(),
746                        test_case.post_state.as_ref(),
747                    );
748                }
749                if json_output {
750                    let output = json!({
751                        "block": block_idx,
752                        "tx": tx_idx,
753                        "error": "missing sender",
754                        "status": "skipped"
755                    });
756                    println!("{}", serde_json::to_string(&output).unwrap());
757                } else {
758                    eprintln!("āš ļø  Skipping block {block_idx} due to missing sender");
759                }
760                break; // Skip to next block
761            }
762
763            let tx_env = match tx.to_tx_env() {
764                Ok(env) => env,
765                Err(e) => {
766                    if should_fail {
767                        // Expected failure during tx env creation
768                        continue;
769                    }
770                    if print_env_on_error {
771                        let debug_info = DebugInfo {
772                            pre_state: pre_state_debug.clone(),
773                            tx_env: None,
774                            block_env: block_env.clone(),
775                            cfg_env: cfg.clone(),
776                            block_idx,
777                            tx_idx,
778                            withdrawals: block.withdrawals.clone(),
779                        };
780                        print_error_with_state(
781                            &debug_info,
782                            evm.ctx().db_ref(),
783                            test_case.post_state.as_ref(),
784                        );
785                    }
786                    if json_output {
787                        let output = json!({
788                            "block": block_idx,
789                            "tx": tx_idx,
790                            "error": format!("tx env creation error: {e}"),
791                            "status": "skipped"
792                        });
793                        println!("{}", serde_json::to_string(&output).unwrap());
794                    } else {
795                        eprintln!(
796                            "āš ļø  Skipping block {block_idx} due to transaction env creation error: {e}"
797                        );
798                    }
799                    break; // Skip to next block
800                }
801            };
802
803            // If JSON output requested, output transaction details
804            let execution_result = if json_output {
805                evm.inspect_tx(tx_env.clone())
806            } else {
807                evm.transact(tx_env.clone())
808            };
809
810            match execution_result {
811                Ok(result) => {
812                    if should_fail {
813                        // Unexpected success - should have failed but didn't
814                        // If not expected to fail, use inspector to trace the transaction
815                        if print_env_on_error {
816                            // Re-run with inspector to get detailed trace
817                            if json_output {
818                                eprintln!("=== Transaction trace (unexpected success) ===");
819                            }
820                            let _ = evm.inspect_tx(tx_env.clone());
821                        }
822
823                        if print_env_on_error {
824                            let debug_info = DebugInfo {
825                                pre_state: pre_state_debug.clone(),
826                                tx_env: Some(tx_env.clone()),
827                                block_env: block_env.clone(),
828                                cfg_env: cfg.clone(),
829                                block_idx,
830                                tx_idx,
831                                withdrawals: block.withdrawals.clone(),
832                            };
833                            print_error_with_state(
834                                &debug_info,
835                                evm.ctx().db_ref(),
836                                test_case.post_state.as_ref(),
837                            );
838                        }
839                        let exception = block.expect_exception.clone().unwrap_or_default();
840                        if json_output {
841                            let output = json!({
842                                "block": block_idx,
843                                "tx": tx_idx,
844                                "error": format!("expected failure: {exception}"),
845                                "gas_used": result.result.gas_used(),
846                                "status": "unexpected_success"
847                            });
848                            println!("{}", serde_json::to_string(&output).unwrap());
849                        } else {
850                            eprintln!(
851                                "āš ļø  Skipping block {block_idx} due to expected failure: {exception}"
852                            );
853                        }
854                        break; // Skip to next block
855                    }
856                    evm.commit(result.state);
857                }
858                Err(e) => {
859                    if !should_fail {
860                        // Unexpected error - use inspector to trace the transaction
861                        if print_env_on_error {
862                            if json_output {
863                                eprintln!("=== Transaction trace (unexpected failure) ===");
864                            }
865                            let _ = evm.inspect_tx(tx_env.clone());
866                        }
867
868                        if print_env_on_error {
869                            let debug_info = DebugInfo {
870                                pre_state: pre_state_debug.clone(),
871                                tx_env: Some(tx_env.clone()),
872                                block_env: block_env.clone(),
873                                cfg_env: cfg.clone(),
874                                block_idx,
875                                tx_idx,
876                                withdrawals: block.withdrawals.clone(),
877                            };
878                            print_error_with_state(
879                                &debug_info,
880                                evm.ctx().db_ref(),
881                                test_case.post_state.as_ref(),
882                            );
883                        }
884                        if json_output {
885                            let output = json!({
886                                "block": block_idx,
887                                "tx": tx_idx,
888                                "error": format!("{e:?}"),
889                                "status": "unexpected_failure"
890                            });
891                            println!("{}", serde_json::to_string(&output).unwrap());
892                        } else {
893                            eprintln!(
894                                "āš ļø  Skipping block {block_idx} due to unexpected failure: {e:?}"
895                            );
896                        }
897                        break; // Skip to next block
898                    } else if json_output {
899                        // Expected failure
900                        let output = json!({
901                            "block": block_idx,
902                            "tx": tx_idx,
903                            "error": format!("{e:?}"),
904                            "status": "expected_failure"
905                        });
906                        println!("{}", serde_json::to_string(&output).unwrap());
907                    }
908                }
909            }
910        }
911
912        // uncle rewards are not implemented yet
913        post_block::post_block_transition(
914            &mut evm,
915            &block_env,
916            block.withdrawals.as_deref().unwrap_or_default(),
917            spec_id,
918        );
919
920        // insert present block hash.
921        state
922            .block_hashes
923            .insert(block_env.number.to::<u64>(), block_hash.unwrap_or_default());
924
925        parent_block_hash = block_hash;
926        if let Some(excess_blob_gas) = this_excess_blob_gas {
927            parent_excess_blob_gas = excess_blob_gas;
928        }
929
930        state.merge_transitions(BundleRetention::Reverts);
931    }
932
933    // Validate post state if present
934    if let Some(expected_post_state) = &test_case.post_state {
935        // Create debug info for post-state validation
936        let debug_info = DebugInfo {
937            pre_state: pre_state_debug.clone(),
938            tx_env: None, // Last transaction is done
939            block_env: block_env.clone(),
940            cfg_env: cfg.clone(),
941            block_idx: test_case.blocks.len(),
942            tx_idx: 0,
943            withdrawals: test_case.blocks.last().and_then(|b| b.withdrawals.clone()),
944        };
945        validate_post_state(
946            &mut state,
947            expected_post_state,
948            &debug_info,
949            print_env_on_error,
950        )?;
951    }
952
953    Ok(())
954}
955
956/// Convert ForkSpec to SpecId
957fn fork_to_spec_id(fork: ForkSpec) -> SpecId {
958    match fork {
959        ForkSpec::Frontier => SpecId::FRONTIER,
960        ForkSpec::Homestead | ForkSpec::FrontierToHomesteadAt5 => SpecId::HOMESTEAD,
961        ForkSpec::EIP150 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 => {
962            SpecId::TANGERINE
963        }
964        ForkSpec::EIP158 => SpecId::SPURIOUS_DRAGON,
965        ForkSpec::Byzantium
966        | ForkSpec::EIP158ToByzantiumAt5
967        | ForkSpec::ByzantiumToConstantinopleFixAt5 => SpecId::BYZANTIUM,
968        ForkSpec::Constantinople | ForkSpec::ByzantiumToConstantinopleAt5 => SpecId::PETERSBURG,
969        ForkSpec::ConstantinopleFix => SpecId::PETERSBURG,
970        ForkSpec::Istanbul => SpecId::ISTANBUL,
971        ForkSpec::Berlin => SpecId::BERLIN,
972        ForkSpec::London | ForkSpec::BerlinToLondonAt5 => SpecId::LONDON,
973        ForkSpec::Paris | ForkSpec::ParisToShanghaiAtTime15k => SpecId::MERGE,
974        ForkSpec::Shanghai => SpecId::SHANGHAI,
975        ForkSpec::Cancun | ForkSpec::ShanghaiToCancunAtTime15k => SpecId::CANCUN,
976        ForkSpec::Prague | ForkSpec::CancunToPragueAtTime15k => SpecId::PRAGUE,
977        ForkSpec::Osaka | ForkSpec::PragueToOsakaAtTime15k => SpecId::OSAKA,
978        _ => SpecId::OSAKA, // For any unknown forks, use latest available
979    }
980}
981
982/// Check if a test should be skipped based on its filename
983fn skip_test(path: &Path) -> bool {
984    let path_str = path.to_str().unwrap_or_default();
985    // blobs excess gas calculation is not supported or osaka BPO configuration
986    if path_str.contains("paris/eip7610_create_collision")
987        || path_str.contains("cancun/eip4844_blobs")
988        || path_str.contains("prague/eip7251_consolidations")
989        || path_str.contains("prague/eip7685_general_purpose_el_requests")
990        || path_str.contains("prague/eip7002_el_triggerable_withdrawals")
991        || path_str.contains("osaka/eip7918_blob_reserve_price")
992    {
993        return true;
994    }
995
996    let name = path.file_name().unwrap().to_str().unwrap();
997    // Add any problematic tests here that should be skipped
998    matches!(
999        name,
1000        // Test check if gas price overflows, we handle this correctly but does not match tests specific exception.
1001        "CreateTransactionHighNonce.json"
1002
1003        // Test with some storage check.
1004        | "RevertInCreateInInit_Paris.json"
1005        | "RevertInCreateInInit.json"
1006        | "dynamicAccountOverwriteEmpty.json"
1007        | "dynamicAccountOverwriteEmpty_Paris.json"
1008        | "RevertInCreateInInitCreate2Paris.json"
1009        | "create2collisionStorage.json"
1010        | "RevertInCreateInInitCreate2.json"
1011        | "create2collisionStorageParis.json"
1012        | "InitCollision.json"
1013        | "InitCollisionParis.json"
1014
1015        // Malformed value.
1016        | "ValueOverflow.json"
1017        | "ValueOverflowParis.json"
1018
1019        // These tests are passing, but they take a lot of time to execute so we are going to skip them.
1020        | "Call50000_sha256.json"
1021        | "static_Call50000_sha256.json"
1022        | "loopMul.json"
1023        | "CALLBlake2f_MaxRounds.json"
1024        // TODO tests not checked, maybe related to parent block hashes as it is currently not supported in test.
1025        | "scenarios.json"
1026        // IT seems that post state is wrong, we properly handle max blob gas and state should stay the same.
1027        | "invalid_tx_max_fee_per_blob_gas.json"
1028        | "correct_increasing_blob_gas_costs.json"
1029        | "correct_decreasing_blob_gas_costs.json"
1030
1031        // test-fixtures/main/develop/blockchain_tests/prague/eip2935_historical_block_hashes_from_state/block_hashes/block_hashes_history.json
1032        | "block_hashes_history.json"
1033    )
1034}
1035
1036#[derive(Debug, Error)]
1037pub enum TestExecutionError {
1038    #[error("Database error: {0}")]
1039    Database(String),
1040
1041    #[error("Skipped fork: {0}")]
1042    SkippedFork(String),
1043
1044    #[error("Sender is required")]
1045    SenderRequired,
1046
1047    #[error("Expected failure at block {block_idx}, tx {tx_idx}: {message}")]
1048    ExpectedFailure {
1049        block_idx: usize,
1050        tx_idx: usize,
1051        message: String,
1052    },
1053
1054    #[error("Unexpected failure at block {block_idx}, tx {tx_idx}: {error}")]
1055    UnexpectedFailure {
1056        block_idx: usize,
1057        tx_idx: usize,
1058        error: String,
1059    },
1060
1061    #[error("Transaction env creation failed at block {block_idx}, tx {tx_idx}: {error}")]
1062    TransactionEnvCreation {
1063        block_idx: usize,
1064        tx_idx: usize,
1065        error: String,
1066    },
1067
1068    #[error("Unexpected revert at block {block_idx}, tx {tx_idx}, gas used: {gas_used}")]
1069    UnexpectedRevert {
1070        block_idx: usize,
1071        tx_idx: usize,
1072        gas_used: u64,
1073    },
1074
1075    #[error("Unexpected halt at block {block_idx}, tx {tx_idx}: {reason:?}, gas used: {gas_used}")]
1076    UnexpectedHalt {
1077        block_idx: usize,
1078        tx_idx: usize,
1079        reason: HaltReason,
1080        gas_used: u64,
1081    },
1082
1083    #[error(
1084        "Post-state validation failed for {address:?}.{field}: expected {expected}, got {actual}"
1085    )]
1086    PostStateValidation {
1087        address: Address,
1088        field: String,
1089        expected: String,
1090        actual: String,
1091    },
1092}
1093
1094#[derive(Debug, Error)]
1095pub enum Error {
1096    #[error("Path not found: {0}")]
1097    PathNotFound(PathBuf),
1098
1099    #[error("No JSON files found in: {0}")]
1100    NoJsonFiles(PathBuf),
1101
1102    #[error("Failed to read file {0}: {1}")]
1103    FileRead(PathBuf, std::io::Error),
1104
1105    #[error("Failed to decode JSON from {0}: {1}")]
1106    JsonDecode(PathBuf, serde_json::Error),
1107
1108    #[error("Test execution failed for {test_name} in {test_path}: {error}")]
1109    TestExecution {
1110        test_name: String,
1111        test_path: PathBuf,
1112        error: String,
1113    },
1114
1115    #[error("Directory traversal error: {0}")]
1116    WalkDir(#[from] walkdir::Error),
1117
1118    #[error("{failed} tests failed")]
1119    TestsFailed { failed: usize },
1120}