revme/cmd/statetest/
runner.rs

1use super::{
2    merkle_trie::{log_rlp_hash, state_merkle_trie_root},
3    utils::recover_address,
4};
5use database::State;
6use indicatif::{ProgressBar, ProgressDrawTarget};
7use inspector::{inspectors::TracerEip3155, InspectCommitEvm};
8use revm::{
9    bytecode::Bytecode,
10    context::{block::BlockEnv, cfg::CfgEnv, tx::TxEnv},
11    context_interface::{
12        block::calc_excess_blob_gas,
13        result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction},
14        Cfg,
15    },
16    database_interface::EmptyDB,
17    primitives::{keccak256, Bytes, TxKind, B256},
18    specification::{eip4844::TARGET_BLOB_GAS_PER_BLOCK_CANCUN, hardfork::SpecId},
19    Context, ExecuteCommitEvm, MainBuilder, MainContext,
20};
21use serde_json::json;
22use statetest_types::{SpecName, Test, TestSuite};
23
24use std::{
25    convert::Infallible,
26    fmt::Debug,
27    io::stderr,
28    path::{Path, PathBuf},
29    sync::{
30        atomic::{AtomicBool, AtomicUsize, Ordering},
31        Arc, Mutex,
32    },
33    time::{Duration, Instant},
34};
35use thiserror::Error;
36use walkdir::{DirEntry, WalkDir};
37
38#[derive(Debug, Error)]
39#[error("Path: {path}\nName: {name}\nError: {kind}")]
40pub struct TestError {
41    pub name: String,
42    pub path: String,
43    pub kind: TestErrorKind,
44}
45
46#[derive(Debug, Error)]
47pub enum TestErrorKind {
48    #[error("logs root mismatch: got {got}, expected {expected}")]
49    LogsRootMismatch { got: B256, expected: B256 },
50    #[error("state root mismatch: got {got}, expected {expected}")]
51    StateRootMismatch { got: B256, expected: B256 },
52    #[error("unknown private key: {0:?}")]
53    UnknownPrivateKey(B256),
54    #[error("unexpected exception: got {got_exception:?}, expected {expected_exception:?}")]
55    UnexpectedException {
56        expected_exception: Option<String>,
57        got_exception: Option<String>,
58    },
59    #[error("unexpected output: got {got_output:?}, expected {expected_output:?}")]
60    UnexpectedOutput {
61        expected_output: Option<Bytes>,
62        got_output: Option<Bytes>,
63    },
64    #[error(transparent)]
65    SerdeDeserialize(#[from] serde_json::Error),
66    #[error("thread panicked")]
67    Panic,
68}
69
70pub fn find_all_json_tests(path: &Path) -> Vec<PathBuf> {
71    if path.is_file() {
72        vec![path.to_path_buf()]
73    } else {
74        WalkDir::new(path)
75            .into_iter()
76            .filter_map(Result::ok)
77            .filter(|e| e.path().extension() == Some("json".as_ref()))
78            .map(DirEntry::into_path)
79            .collect()
80    }
81}
82
83fn skip_test(path: &Path) -> bool {
84    let name = path.file_name().unwrap().to_str().unwrap();
85
86    matches!(
87        name,
88        // Funky test with `bigint 0x00` value in json :) not possible to happen on mainnet and require
89        // custom json parser. https://github.com/ethereum/tests/issues/971
90        |"ValueOverflow.json"| "ValueOverflowParis.json"
91
92        // Precompiles having storage is not possible
93        | "RevertPrecompiledTouch_storage.json"
94        | "RevertPrecompiledTouch.json"
95
96        // `txbyte` is of type 02 and we don't parse tx bytes for this test to fail.
97        | "typeTwoBerlin.json"
98
99        // Need to handle Test errors
100        | "transactionIntinsicBug.json"
101
102        // Test check if gas price overflows, we handle this correctly but does not match tests specific exception.
103        | "HighGasPrice.json"
104        | "CREATE_HighNonce.json"
105        | "CREATE_HighNonceMinus1.json"
106        | "CreateTransactionHighNonce.json"
107
108        // Skip test where basefee/accesslist/difficulty is present but it shouldn't be supported in
109        // London/Berlin/TheMerge. https://github.com/ethereum/tests/blob/5b7e1ab3ffaf026d99d20b17bb30f533a2c80c8b/GeneralStateTests/stExample/eip1559.json#L130
110        // It is expected to not execute these tests.
111        | "basefeeExample.json"
112        | "eip1559.json"
113        | "mergeTest.json"
114
115        // Test with some storage check.
116        | "RevertInCreateInInit_Paris.json"
117        | "RevertInCreateInInit.json"
118        | "dynamicAccountOverwriteEmpty.json"
119        | "dynamicAccountOverwriteEmpty_Paris.json"
120        | "RevertInCreateInInitCreate2Paris.json"
121        | "create2collisionStorage.json"
122        | "RevertInCreateInInitCreate2.json"
123        | "create2collisionStorageParis.json"
124        | "InitCollision.json"
125        | "InitCollisionParis.json"
126
127        // These tests are passing, but they take a lot of time to execute so we are going to skip them.
128        | "loopExp.json"
129        | "Call50000_sha256.json"
130        | "static_Call50000_sha256.json"
131        | "loopMul.json"
132        | "CALLBlake2f_MaxRounds.json"
133    )
134}
135
136fn check_evm_execution(
137    test: &Test,
138    expected_output: Option<&Bytes>,
139    test_name: &str,
140    exec_result: &Result<ExecutionResult<HaltReason>, EVMError<Infallible, InvalidTransaction>>,
141    db: &mut State<EmptyDB>,
142    spec: SpecId,
143    print_json_outcome: bool,
144) -> Result<(), TestErrorKind> {
145    let logs_root = log_rlp_hash(exec_result.as_ref().map(|r| r.logs()).unwrap_or_default());
146    let state_root = state_merkle_trie_root(db.cache.trie_account());
147
148    let print_json_output = |error: Option<String>| {
149        if print_json_outcome {
150            let json = json!({
151                "stateRoot": state_root,
152                "logsRoot": logs_root,
153                "output": exec_result.as_ref().ok().and_then(|r| r.output().cloned()).unwrap_or_default(),
154                "gasUsed": exec_result.as_ref().ok().map(|r| r.gas_used()).unwrap_or_default(),
155                "pass": error.is_none(),
156                "errorMsg": error.unwrap_or_default(),
157                "evmResult": match exec_result {
158                    Ok(r) => match r {
159                        ExecutionResult::Success { reason, .. } => format!("Success: {reason:?}"),
160                        ExecutionResult::Revert { .. } => "Revert".to_string(),
161                        ExecutionResult::Halt { reason, .. } => format!("Halt: {reason:?}"),
162                    },
163                    Err(e) => e.to_string(),
164                },
165                "postLogsHash": logs_root,
166                "fork": spec,
167                "test": test_name,
168                "d": test.indexes.data,
169                "g": test.indexes.gas,
170                "v": test.indexes.value,
171            });
172            eprintln!("{json}");
173        }
174    };
175
176    // If we expect exception revm should return error from execution.
177    // So we do not check logs and state root.
178    //
179    // Note that some tests that have exception and run tests from before state clear
180    // would touch the caller account and make it appear in state root calculation.
181    // This is not something that we would expect as invalid tx should not touch state.
182    // but as this is a cleanup of invalid tx it is not properly defined and in the end
183    // it does not matter.
184    // Test where this happens: `tests/GeneralStateTests/stTransactionTest/NoSrcAccountCreate.json`
185    // and you can check that we have only two "hash" values for before and after state clear.
186    match (&test.expect_exception, exec_result) {
187        // Do nothing
188        (None, Ok(result)) => {
189            // Check output
190            if let Some((expected_output, output)) = expected_output.zip(result.output()) {
191                if expected_output != output {
192                    let kind = TestErrorKind::UnexpectedOutput {
193                        expected_output: Some(expected_output.clone()),
194                        got_output: result.output().cloned(),
195                    };
196                    print_json_output(Some(kind.to_string()));
197                    return Err(kind);
198                }
199            }
200        }
201        // Return okay, exception is expected.
202        (Some(_), Err(_)) => return Ok(()),
203        _ => {
204            let kind = TestErrorKind::UnexpectedException {
205                expected_exception: test.expect_exception.clone(),
206                got_exception: exec_result.clone().err().map(|e| e.to_string()),
207            };
208            print_json_output(Some(kind.to_string()));
209            return Err(kind);
210        }
211    }
212
213    if logs_root != test.logs {
214        let kind = TestErrorKind::LogsRootMismatch {
215            got: logs_root,
216            expected: test.logs,
217        };
218        print_json_output(Some(kind.to_string()));
219        return Err(kind);
220    }
221
222    if state_root != test.hash {
223        let kind = TestErrorKind::StateRootMismatch {
224            got: state_root,
225            expected: test.hash,
226        };
227        print_json_output(Some(kind.to_string()));
228        return Err(kind);
229    }
230
231    print_json_output(None);
232
233    Ok(())
234}
235
236pub fn execute_test_suite(
237    path: &Path,
238    elapsed: &Arc<Mutex<Duration>>,
239    trace: bool,
240    print_json_outcome: bool,
241) -> Result<(), TestError> {
242    if skip_test(path) {
243        return Ok(());
244    }
245
246    let s = std::fs::read_to_string(path).unwrap();
247    let path = path.to_string_lossy().into_owned();
248    let suite: TestSuite = serde_json::from_str(&s).map_err(|e| TestError {
249        name: "Unknown".to_string(),
250        path: path.clone(),
251        kind: e.into(),
252    })?;
253
254    for (name, unit) in suite.0 {
255        // Create database and insert cache
256        let mut cache_state = database::CacheState::new(false);
257        for (address, info) in unit.pre {
258            let code_hash = keccak256(&info.code);
259            let bytecode = Bytecode::new_raw_checked(info.code.clone())
260                .unwrap_or(Bytecode::new_legacy(info.code));
261            let acc_info = revm::state::AccountInfo {
262                balance: info.balance,
263                code_hash,
264                code: Some(bytecode),
265                nonce: info.nonce,
266            };
267            cache_state.insert_account_with_storage(address, acc_info, info.storage);
268        }
269
270        let mut cfg = CfgEnv::default();
271        let mut block = BlockEnv::default();
272        let mut tx = TxEnv::default();
273        // For mainnet
274        cfg.chain_id = 1;
275
276        // Block env
277        block.number = unit.env.current_number.try_into().unwrap_or(u64::MAX);
278        block.beneficiary = unit.env.current_coinbase;
279        block.timestamp = unit.env.current_timestamp.try_into().unwrap_or(u64::MAX);
280        block.gas_limit = unit.env.current_gas_limit.try_into().unwrap_or(u64::MAX);
281        block.basefee = unit
282            .env
283            .current_base_fee
284            .unwrap_or_default()
285            .try_into()
286            .unwrap_or(u64::MAX);
287        block.difficulty = unit.env.current_difficulty;
288        // After the Merge prevrandao replaces mix_hash field in block and replaced difficulty opcode in EVM.
289        block.prevrandao = unit.env.current_random;
290
291        // Tx env
292        tx.caller = if let Some(address) = unit.transaction.sender {
293            address
294        } else {
295            recover_address(unit.transaction.secret_key.as_slice()).ok_or_else(|| TestError {
296                name: name.clone(),
297                path: path.clone(),
298                kind: TestErrorKind::UnknownPrivateKey(unit.transaction.secret_key),
299            })?
300        };
301        tx.gas_price = unit
302            .transaction
303            .gas_price
304            .or(unit.transaction.max_fee_per_gas)
305            .unwrap_or_default()
306            .try_into()
307            .unwrap_or(u128::MAX);
308        tx.gas_priority_fee = unit
309            .transaction
310            .max_priority_fee_per_gas
311            .map(|b| u128::try_from(b).expect("max priority fee less than u128::MAX"));
312        // EIP-4844
313        tx.blob_hashes = unit.transaction.blob_versioned_hashes.clone();
314        tx.max_fee_per_blob_gas = unit
315            .transaction
316            .max_fee_per_blob_gas
317            .map(|b| u128::try_from(b).expect("max fee less than u128::MAX"))
318            .unwrap_or(u128::MAX);
319
320        // Post and execution
321        for (spec_name, tests) in unit.post {
322            // Constantinople was immediately extended by Petersburg.
323            // There isn't any production Constantinople transaction
324            // so we don't support it and skip right to Petersburg.
325            if spec_name == SpecName::Constantinople {
326                continue;
327            }
328
329            cfg.spec = spec_name.to_spec_id();
330
331            // EIP-4844
332            if let Some(current_excess_blob_gas) = unit.env.current_excess_blob_gas {
333                block.set_blob_excess_gas_and_price(
334                    current_excess_blob_gas.to(),
335                    cfg.spec.is_enabled_in(SpecId::PRAGUE),
336                );
337            } else if let (Some(parent_blob_gas_used), Some(parent_excess_blob_gas)) = (
338                unit.env.parent_blob_gas_used,
339                unit.env.parent_excess_blob_gas,
340            ) {
341                block.set_blob_excess_gas_and_price(
342                    calc_excess_blob_gas(
343                        parent_blob_gas_used.to(),
344                        parent_excess_blob_gas.to(),
345                        unit.env
346                            .parent_target_blobs_per_block
347                            .map(|i| i.to())
348                            .unwrap_or(TARGET_BLOB_GAS_PER_BLOCK_CANCUN),
349                    ),
350                    cfg.spec.is_enabled_in(SpecId::PRAGUE),
351                );
352            }
353
354            if cfg.spec.is_enabled_in(SpecId::MERGE) && block.prevrandao.is_none() {
355                // If spec is merge and prevrandao is not set, set it to default
356                block.prevrandao = Some(B256::default());
357            }
358
359            for (index, test) in tests.into_iter().enumerate() {
360                let Some(tx_type) = unit.transaction.tx_type(test.indexes.data) else {
361                    if test.expect_exception.is_some() {
362                        continue;
363                    } else {
364                        panic!("Invalid transaction type without expected exception");
365                    }
366                };
367
368                tx.tx_type = tx_type as u8;
369
370                tx.gas_limit = unit.transaction.gas_limit[test.indexes.gas].saturating_to();
371
372                tx.data = unit
373                    .transaction
374                    .data
375                    .get(test.indexes.data)
376                    .unwrap()
377                    .clone();
378
379                tx.nonce = u64::try_from(unit.transaction.nonce).unwrap();
380                tx.value = unit.transaction.value[test.indexes.value];
381
382                tx.access_list = unit
383                    .transaction
384                    .access_lists
385                    .get(test.indexes.data)
386                    .cloned()
387                    .flatten()
388                    .unwrap_or_default();
389
390                tx.authorization_list = unit
391                    .transaction
392                    .authorization_list
393                    .clone()
394                    .map(|auth_list| auth_list.into_iter().map(Into::into).collect::<Vec<_>>())
395                    .unwrap_or_default();
396
397                let to = match unit.transaction.to {
398                    Some(add) => TxKind::Call(add),
399                    None => TxKind::Create,
400                };
401                tx.kind = to;
402
403                let mut cache = cache_state.clone();
404                cache.set_state_clear_flag(cfg.spec.is_enabled_in(SpecId::SPURIOUS_DRAGON));
405                let mut state = database::State::builder()
406                    .with_cached_prestate(cache)
407                    .with_bundle_update()
408                    .build();
409                let mut evm = Context::mainnet()
410                    .with_block(&block)
411                    .with_tx(&tx)
412                    .with_cfg(&cfg)
413                    .with_db(&mut state)
414                    .build_mainnet();
415
416                // Do the deed
417                let (e, exec_result) = if trace {
418                    let mut evm = Context::mainnet()
419                        .with_block(&block)
420                        .with_tx(&tx)
421                        .with_cfg(&cfg)
422                        .with_db(&mut state)
423                        .build_mainnet_with_inspector(
424                            TracerEip3155::buffered(stderr()).without_summary(),
425                        );
426
427                    let timer = Instant::now();
428                    let res = evm.inspect_commit_previous();
429                    *elapsed.lock().unwrap() += timer.elapsed();
430
431                    let spec = cfg.spec();
432                    let db = &mut evm.data.ctx.journaled_state.database;
433                    // Dump state and traces if test failed
434                    let output = check_evm_execution(
435                        &test,
436                        unit.out.as_ref(),
437                        &name,
438                        &res,
439                        db,
440                        spec,
441                        print_json_outcome,
442                    );
443                    let Err(e) = output else {
444                        continue;
445                    };
446                    (e, res)
447                } else {
448                    let timer = Instant::now();
449                    let res = evm.transact_commit_previous();
450                    *elapsed.lock().unwrap() += timer.elapsed();
451
452                    let spec = cfg.spec();
453                    let db = evm.data.ctx.journaled_state.database;
454                    // Dump state and traces if test failed
455                    let output = check_evm_execution(
456                        &test,
457                        unit.out.as_ref(),
458                        &name,
459                        &res,
460                        db,
461                        spec,
462                        print_json_outcome,
463                    );
464                    let Err(e) = output else {
465                        continue;
466                    };
467                    (e, res)
468                };
469
470                // Print only once or if we are already in trace mode, just return error
471                // If trace is true that print_json_outcome will be also true.
472                static FAILED: AtomicBool = AtomicBool::new(false);
473                if print_json_outcome || FAILED.swap(true, Ordering::SeqCst) {
474                    return Err(TestError {
475                        name: name.clone(),
476                        path: path.clone(),
477                        kind: e,
478                    });
479                }
480
481                // Re-build to run with tracing
482                let mut cache = cache_state.clone();
483                cache.set_state_clear_flag(cfg.spec.is_enabled_in(SpecId::SPURIOUS_DRAGON));
484                let mut state = database::State::builder()
485                    .with_cached_prestate(cache)
486                    .with_bundle_update()
487                    .build();
488
489                println!("\nTraces:");
490
491                let mut evm = Context::mainnet()
492                    .with_db(&mut state)
493                    .with_block(&block)
494                    .with_tx(&tx)
495                    .with_cfg(&cfg)
496                    .build_mainnet_with_inspector(
497                        TracerEip3155::buffered(stderr()).without_summary(),
498                    );
499
500                let _ = evm.inspect_commit_previous();
501
502                println!("\nExecution result: {exec_result:#?}");
503                println!("\nExpected exception: {:?}", test.expect_exception);
504                println!("\nState before: {cache_state:#?}");
505                println!(
506                    "\nState after: {:#?}",
507                    evm.data.ctx.journaled_state.database.cache
508                );
509                println!("\nSpecification: {:?}", cfg.spec);
510                println!("\nTx: {tx:#?}");
511                println!("Block: {block:#?}");
512                println!("Cfg: {cfg:#?}");
513                println!("\nTest name: {name:?} (index: {index}, path: {path:?}) failed:\n{e}");
514
515                return Err(TestError {
516                    path: path.clone(),
517                    name: name.clone(),
518                    kind: e,
519                });
520            }
521        }
522    }
523    Ok(())
524}
525
526pub fn run(
527    test_files: Vec<PathBuf>,
528    mut single_thread: bool,
529    trace: bool,
530    mut print_outcome: bool,
531    keep_going: bool,
532) -> Result<(), TestError> {
533    // Trace implies print_outcome
534    if trace {
535        print_outcome = true;
536    }
537    // `print_outcome` or trace implies single_thread
538    if print_outcome {
539        single_thread = true;
540    }
541    let n_files = test_files.len();
542
543    let n_errors = Arc::new(AtomicUsize::new(0));
544    let console_bar = Arc::new(ProgressBar::with_draw_target(
545        Some(n_files as u64),
546        ProgressDrawTarget::stdout(),
547    ));
548    let queue = Arc::new(Mutex::new((0usize, test_files)));
549    let elapsed = Arc::new(Mutex::new(std::time::Duration::ZERO));
550
551    let num_threads = match (single_thread, std::thread::available_parallelism()) {
552        (true, _) | (false, Err(_)) => 1,
553        (false, Ok(n)) => n.get(),
554    };
555    let num_threads = num_threads.min(n_files);
556    let mut handles = Vec::with_capacity(num_threads);
557    for i in 0..num_threads {
558        let queue = queue.clone();
559        let n_errors = n_errors.clone();
560        let console_bar = console_bar.clone();
561        let elapsed = elapsed.clone();
562
563        let thread = std::thread::Builder::new().name(format!("runner-{i}"));
564
565        let f = move || loop {
566            if !keep_going && n_errors.load(Ordering::SeqCst) > 0 {
567                return Ok(());
568            }
569
570            let (_index, test_path) = {
571                let (current_idx, queue) = &mut *queue.lock().unwrap();
572                let prev_idx = *current_idx;
573                let Some(test_path) = queue.get(prev_idx).cloned() else {
574                    return Ok(());
575                };
576                *current_idx = prev_idx + 1;
577                (prev_idx, test_path)
578            };
579
580            let result = execute_test_suite(&test_path, &elapsed, trace, print_outcome);
581
582            // Increment after the test is done.
583            console_bar.inc(1);
584
585            if let Err(err) = result {
586                n_errors.fetch_add(1, Ordering::SeqCst);
587                if !keep_going {
588                    return Err(err);
589                }
590            }
591        };
592        handles.push(thread.spawn(f).unwrap());
593    }
594
595    // join all threads before returning an error
596    let mut thread_errors = Vec::new();
597    for (i, handle) in handles.into_iter().enumerate() {
598        match handle.join() {
599            Ok(Ok(())) => {}
600            Ok(Err(e)) => thread_errors.push(e),
601            Err(_) => thread_errors.push(TestError {
602                name: format!("thread {i} panicked"),
603                path: "".to_string(),
604                kind: TestErrorKind::Panic,
605            }),
606        }
607    }
608    console_bar.finish();
609
610    println!(
611        "Finished execution. Total CPU time: {:.6}s",
612        elapsed.lock().unwrap().as_secs_f64()
613    );
614
615    let n_errors = n_errors.load(Ordering::SeqCst);
616    let n_thread_errors = thread_errors.len();
617    if n_errors == 0 && n_thread_errors == 0 {
618        println!("All tests passed!");
619        Ok(())
620    } else {
621        println!("Encountered {n_errors} errors out of {n_files} total tests");
622
623        if n_thread_errors == 0 {
624            std::process::exit(1);
625        }
626
627        if n_thread_errors > 1 {
628            println!("{n_thread_errors} threads returned an error, out of {num_threads} total:");
629            for error in &thread_errors {
630                println!("{error}");
631            }
632        }
633        Err(thread_errors.swap_remove(0))
634    }
635}