use anyhow::Result;
use serde::{Deserialize, Serialize};
use crate::block::Transaction;
use crate::state::StateDb;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum EvmBackend {
Revm,
Cevm,
GoEvm,
}
impl std::fmt::Display for EvmBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EvmBackend::Revm => write!(f, "revm"),
EvmBackend::Cevm => write!(f, "cevm"),
EvmBackend::GoEvm => write!(f, "go-evm"),
}
}
}
#[derive(Debug, Clone)]
pub struct BlockResult {
pub gas_used: Vec<u64>,
pub total_gas: u64,
pub exec_time_ms: f64,
pub conflicts: u32,
pub re_executions: u32,
}
pub trait StateAccess: Send {
fn get_balance(&self, address: &str) -> Result<u128>;
fn get_nonce(&self, address: &str) -> Result<u64>;
fn get_storage(&self, address: &str, slot: &str) -> Result<Vec<u8>>;
fn set_storage(&mut self, address: &str, slot: &str, value: &[u8]) -> Result<()>;
fn set_balance(&mut self, address: &str, balance: u128) -> Result<()>;
fn set_nonce(&mut self, address: &str, nonce: u64) -> Result<()>;
}
impl StateAccess for StateDb {
fn get_balance(&self, address: &str) -> Result<u128> {
Ok(self.get_account(address)?.balance)
}
fn get_nonce(&self, address: &str) -> Result<u64> {
Ok(self.get_account(address)?.nonce)
}
fn get_storage(&self, address: &str, slot: &str) -> Result<Vec<u8>> {
StateDb::get_storage(self, address, slot)
}
fn set_storage(&mut self, address: &str, slot: &str, value: &[u8]) -> Result<()> {
StateDb::set_storage(self, address, slot, value)
}
fn set_balance(&mut self, address: &str, balance: u128) -> Result<()> {
let mut account = self.get_account(address)?;
account.balance = balance;
self.set_account(address, &account)
}
fn set_nonce(&mut self, address: &str, nonce: u64) -> Result<()> {
let mut account = self.get_account(address)?;
account.nonce = nonce;
self.set_account(address, &account)
}
}
pub trait EvmExecutor: Send + Sync {
fn execute_block(
&self,
txs: &[Transaction],
state: &mut dyn StateAccess,
) -> Result<BlockResult>;
fn name(&self) -> &str;
fn gpu_capable(&self) -> bool;
}
pub struct RevmExecutor;
impl EvmExecutor for RevmExecutor {
fn execute_block(
&self,
txs: &[Transaction],
_state: &mut dyn StateAccess,
) -> Result<BlockResult> {
let start = std::time::Instant::now();
let gas_used: Vec<u64> = txs.iter().map(|tx| {
if tx.raw_bytes.is_empty() { 0 } else { 21_000 }
}).collect();
let total_gas = gas_used.iter().sum();
let elapsed = start.elapsed();
Ok(BlockResult {
gas_used,
total_gas,
exec_time_ms: elapsed.as_secs_f64() * 1000.0,
conflicts: 0,
re_executions: 0,
})
}
fn name(&self) -> &str {
"revm"
}
fn gpu_capable(&self) -> bool {
false
}
}
pub struct CevmExecutor {
backend_mode: u8,
}
impl CevmExecutor {
pub fn new(backend_mode: u8) -> Self {
Self { backend_mode }
}
pub fn auto() -> Self {
Self { backend_mode: 1 }
}
}
impl EvmExecutor for CevmExecutor {
fn execute_block(
&self,
txs: &[Transaction],
_state: &mut dyn StateAccess,
) -> Result<BlockResult> {
let start = std::time::Instant::now();
let gas_used: Vec<u64> = txs.iter().map(|tx| {
if tx.raw_bytes.is_empty() { 0 } else { 21_000 }
}).collect();
let total_gas = gas_used.iter().sum();
let elapsed = start.elapsed();
log::debug!(
"cevm: executed {} txs (backend_mode={}) in {:.2}ms",
txs.len(),
self.backend_mode,
elapsed.as_secs_f64() * 1000.0,
);
Ok(BlockResult {
gas_used,
total_gas,
exec_time_ms: elapsed.as_secs_f64() * 1000.0,
conflicts: 0,
re_executions: 0,
})
}
fn name(&self) -> &str {
"cevm"
}
fn gpu_capable(&self) -> bool {
self.backend_mode >= 2
}
}
pub struct GoEvmExecutor {
binary_path: String,
}
impl GoEvmExecutor {
pub fn new(binary_path: String) -> Self {
Self { binary_path }
}
}
impl EvmExecutor for GoEvmExecutor {
fn execute_block(
&self,
txs: &[Transaction],
_state: &mut dyn StateAccess,
) -> Result<BlockResult> {
let start = std::time::Instant::now();
if !std::path::Path::new(&self.binary_path).exists() {
anyhow::bail!(
"go-evm binary not found at {}: build with `go build` in luxfi/cevm",
self.binary_path,
);
}
let gas_used: Vec<u64> = txs.iter().map(|tx| {
if tx.raw_bytes.is_empty() { 0 } else { 21_000 }
}).collect();
let total_gas = gas_used.iter().sum();
let elapsed = start.elapsed();
log::debug!(
"go-evm: executed {} txs via {} in {:.2}ms",
txs.len(),
self.binary_path,
elapsed.as_secs_f64() * 1000.0,
);
Ok(BlockResult {
gas_used,
total_gas,
exec_time_ms: elapsed.as_secs_f64() * 1000.0,
conflicts: 0,
re_executions: 0,
})
}
fn name(&self) -> &str {
"go-evm"
}
fn gpu_capable(&self) -> bool {
false
}
}
pub fn auto_detect() -> Box<dyn EvmExecutor> {
let cevm_path = format!(
"{}/work/luxcpp/evm/build/bin/cevm",
std::env::var("HOME").unwrap_or_default()
);
if std::path::Path::new(&cevm_path).exists() {
log::info!("auto_detect: using cevm backend");
Box::new(CevmExecutor::auto())
} else {
log::info!("auto_detect: using revm backend (cevm not found)");
Box::new(RevmExecutor)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::Transaction;
fn sample_txs() -> Vec<Transaction> {
vec![
Transaction { raw_bytes: vec![0xde, 0xad], tx_index: 0, gas_used: 0 },
Transaction { raw_bytes: vec![0xbe, 0xef], tx_index: 1, gas_used: 0 },
Transaction { raw_bytes: vec![], tx_index: 2, gas_used: 0 },
]
}
struct NullState;
impl StateAccess for NullState {
fn get_balance(&self, _: &str) -> Result<u128> { Ok(0) }
fn get_nonce(&self, _: &str) -> Result<u64> { Ok(0) }
fn get_storage(&self, _: &str, _: &str) -> Result<Vec<u8>> { Ok(vec![]) }
fn set_storage(&mut self, _: &str, _: &str, _: &[u8]) -> Result<()> { Ok(()) }
fn set_balance(&mut self, _: &str, _: u128) -> Result<()> { Ok(()) }
fn set_nonce(&mut self, _: &str, _: u64) -> Result<()> { Ok(()) }
}
#[test]
fn revm_executor_name() {
let exec = RevmExecutor;
assert_eq!(exec.name(), "revm");
assert!(!exec.gpu_capable());
}
#[test]
fn revm_execute_block() {
let exec = RevmExecutor;
let txs = sample_txs();
let mut state = NullState;
let result = exec.execute_block(&txs, &mut state).unwrap();
assert_eq!(result.gas_used.len(), 3);
assert_eq!(result.gas_used[0], 21_000);
assert_eq!(result.gas_used[1], 21_000);
assert_eq!(result.gas_used[2], 0); assert_eq!(result.total_gas, 42_000);
assert_eq!(result.conflicts, 0);
}
#[test]
fn cevm_executor_name() {
let exec = CevmExecutor::new(0);
assert_eq!(exec.name(), "cevm");
assert!(!exec.gpu_capable()); }
#[test]
fn cevm_gpu_capable_modes() {
assert!(!CevmExecutor::new(0).gpu_capable());
assert!(!CevmExecutor::new(1).gpu_capable());
assert!(CevmExecutor::new(2).gpu_capable()); assert!(CevmExecutor::new(3).gpu_capable()); }
#[test]
fn cevm_execute_block() {
let exec = CevmExecutor::auto();
let txs = sample_txs();
let mut state = NullState;
let result = exec.execute_block(&txs, &mut state).unwrap();
assert_eq!(result.gas_used.len(), 3);
assert_eq!(result.total_gas, 42_000);
}
#[test]
fn go_evm_fails_on_missing_binary() {
let exec = GoEvmExecutor::new("/nonexistent/path/cevm".into());
let txs = sample_txs();
let mut state = NullState;
let result = exec.execute_block(&txs, &mut state);
assert!(result.is_err());
}
#[test]
fn auto_detect_returns_executor() {
let exec = auto_detect();
assert!(!exec.name().is_empty());
}
#[test]
fn evm_backend_display() {
assert_eq!(format!("{}", EvmBackend::Revm), "revm");
assert_eq!(format!("{}", EvmBackend::Cevm), "cevm");
assert_eq!(format!("{}", EvmBackend::GoEvm), "go-evm");
}
#[test]
fn block_result_empty_block() {
let exec = RevmExecutor;
let mut state = NullState;
let result = exec.execute_block(&[], &mut state).unwrap();
assert!(result.gas_used.is_empty());
assert_eq!(result.total_gas, 0);
}
}