Skip to main content

revmc_statetest/
diagnostic.rs

1// Diagnostic utilities for comparing interpreter vs JIT execution.
2
3use crate::merkle_trie::compute_test_roots;
4use revm_context::{Context, block::BlockEnv, cfg::CfgEnv, tx::TxEnv};
5use revm_context_interface::result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction};
6use revm_database::{self as database, bal::EvmDatabaseError};
7use revm_database_interface::{DatabaseCommit, EmptyDB};
8use revm_handler::{ExecuteCommitEvm, Handler, MainBuilder, MainContext};
9use revm_inspector::{InspectCommitEvm, inspectors::TracerEip3155};
10use revm_primitives::{B256, Bytes};
11use revmc::{revm_evm::JitEvm, runtime::JitBackend};
12use std::{convert::Infallible, io::stderr};
13
14type ExecResult =
15    Result<ExecutionResult<HaltReason>, EVMError<EvmDatabaseError<Infallible>, InvalidTransaction>>;
16
17/// Snapshot of a single test execution for comparison.
18#[derive(Debug)]
19pub struct ExecutionSnapshot {
20    pub status: ExecStatus,
21    pub output: Option<Bytes>,
22    pub gas_used: u64,
23    pub state_root: B256,
24    pub logs_root: B256,
25    pub post_state_dump: String,
26}
27
28/// Summarized execution outcome.
29#[derive(Debug)]
30pub enum ExecStatus {
31    Success(String),
32    Revert,
33    Halt(String),
34    Error(String),
35}
36
37impl std::fmt::Display for ExecStatus {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            Self::Success(r) => write!(f, "Success({r})"),
41            Self::Revert => write!(f, "Revert"),
42            Self::Halt(r) => write!(f, "Halt({r})"),
43            Self::Error(e) => write!(f, "Error({e})"),
44        }
45    }
46}
47
48fn snapshot_from_result(
49    exec_result: &ExecResult,
50    db: &database::State<EmptyDB>,
51) -> ExecutionSnapshot {
52    let validation = compute_test_roots(exec_result, db);
53
54    let (status, output, gas_used) = match exec_result {
55        Ok(result) => {
56            let status = match result {
57                ExecutionResult::Success { reason, .. } => {
58                    ExecStatus::Success(format!("{reason:?}"))
59                }
60                ExecutionResult::Revert { .. } => ExecStatus::Revert,
61                ExecutionResult::Halt { reason, .. } => ExecStatus::Halt(format!("{reason:?}")),
62            };
63            (status, result.output().cloned(), result.tx_gas_used())
64        }
65        Err(e) => (ExecStatus::Error(e.to_string()), None, 0),
66    };
67
68    ExecutionSnapshot {
69        status,
70        output,
71        gas_used,
72        state_root: validation.state_root,
73        logs_root: validation.logs_root,
74        post_state_dump: db.cache.pretty_print(),
75    }
76}
77
78/// Run a single test case with the interpreter, returning an execution snapshot.
79pub fn run_interpreter(
80    cfg: &CfgEnv,
81    block: &BlockEnv,
82    tx: &TxEnv,
83    cache_state: &database::CacheState,
84) -> ExecutionSnapshot {
85    let prestate = cache_state.clone();
86    let mut state =
87        database::State::builder().with_cached_prestate(prestate).with_bundle_update().build();
88
89    let mut evm = Context::mainnet()
90        .with_block(block)
91        .with_tx(tx)
92        .with_cfg(cfg.clone())
93        .with_db(&mut state)
94        .build_mainnet();
95    let exec_result = evm.transact_commit(tx);
96    let db = evm.ctx.journaled_state.database;
97
98    snapshot_from_result(&exec_result, db)
99}
100
101/// Run a single test case with JIT-compiled functions, returning an execution snapshot.
102pub fn run_jit(
103    backend: &JitBackend,
104    cfg: &CfgEnv,
105    block: &BlockEnv,
106    tx: &TxEnv,
107    cache_state: &database::CacheState,
108) -> ExecutionSnapshot {
109    let prestate = cache_state.clone();
110    let state =
111        database::State::builder().with_cached_prestate(prestate).with_bundle_update().build();
112
113    let evm_context = Context::mainnet()
114        .with_block(block.clone())
115        .with_tx(tx.clone())
116        .with_cfg(cfg.clone())
117        .with_db(state);
118    let inner = evm_context.build_mainnet();
119    let mut evm = JitEvm::new(inner, backend.clone());
120    let mut handler = revm_handler::MainnetHandler::default();
121    let exec_result = handler.run(&mut evm);
122    if exec_result.is_ok() {
123        let s = evm.ctx.journaled_state.finalize();
124        DatabaseCommit::commit(&mut evm.ctx.journaled_state.database, s);
125    }
126
127    snapshot_from_result(&exec_result, &evm.ctx.journaled_state.database)
128}
129
130/// Re-run a test case with the interpreter and EIP-3155 tracing to stderr.
131pub fn trace_interpreter(
132    cfg: &CfgEnv,
133    block: &BlockEnv,
134    tx: &TxEnv,
135    cache_state: &database::CacheState,
136) {
137    let prestate = cache_state.clone();
138    let mut state =
139        database::State::builder().with_cached_prestate(prestate).with_bundle_update().build();
140
141    let mut evm = Context::mainnet()
142        .with_db(&mut state)
143        .with_block(block)
144        .with_tx(tx)
145        .with_cfg(cfg.clone())
146        .build_mainnet_with_inspector(TracerEip3155::buffered(stderr()).without_summary());
147    let exec_result = evm.inspect_tx_commit(tx);
148
149    eprintln!("\nExecution result: {exec_result:#?}");
150    eprintln!("\nState after:\n{}", evm.ctx.journaled_state.database.cache.pretty_print());
151}
152
153/// Compare two snapshots and return a list of mismatched fields.
154pub fn compare(interp: &ExecutionSnapshot, jit: &ExecutionSnapshot) -> Vec<Mismatch> {
155    let mut mismatches = Vec::new();
156
157    let interp_status = format!("{}", interp.status);
158    let jit_status = format!("{}", jit.status);
159    if interp_status != jit_status {
160        mismatches.push(Mismatch { field: "status", interpreter: interp_status, jit: jit_status });
161    }
162
163    if interp.output != jit.output {
164        mismatches.push(Mismatch {
165            field: "output",
166            interpreter: format!("{:?}", interp.output),
167            jit: format!("{:?}", jit.output),
168        });
169    }
170
171    if interp.gas_used != jit.gas_used {
172        mismatches.push(Mismatch {
173            field: "gas_used",
174            interpreter: interp.gas_used.to_string(),
175            jit: jit.gas_used.to_string(),
176        });
177    }
178
179    if interp.state_root != jit.state_root {
180        mismatches.push(Mismatch {
181            field: "state_root",
182            interpreter: format!("{}", interp.state_root),
183            jit: format!("{}", jit.state_root),
184        });
185    }
186
187    if interp.logs_root != jit.logs_root {
188        mismatches.push(Mismatch {
189            field: "logs_root",
190            interpreter: format!("{}", interp.logs_root),
191            jit: format!("{}", jit.logs_root),
192        });
193    }
194
195    mismatches
196}
197
198/// A single field mismatch between interpreter and JIT execution.
199pub struct Mismatch {
200    pub field: &'static str,
201    pub interpreter: String,
202    pub jit: String,
203}
204
205impl std::fmt::Display for Mismatch {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        write!(f, "  {}: interpreter={}, jit={}", self.field, self.interpreter, self.jit)
208    }
209}