use std::{collections::HashMap, marker::PhantomData, mem, sync::Arc};
use alloy::{
consensus::{BlockHeader, TxType},
eips::{eip1898::RpcBlockHash, BlockId},
network::TransactionBuilder,
rpc::types::{state::StateOverride, Block, Header, Transaction, TransactionRequest},
};
use eyre::Result;
use revm::{
context::{result::ExecutionResult, BlockEnv, CfgEnv, ContextTr, TxEnv},
context_interface::block::BlobExcessGasAndPrice,
primitives::{eip7825, hardfork::SpecId, Address, U256},
Context, ExecuteEvm, MainBuilder, MainContext,
};
use tracing::debug;
use helios_common::{
execution_provider::ExecutionProvider,
fork_schedule::ForkSchedule,
types::{Account, EvmError},
};
use helios_core::execution::errors::ExecutionError;
use helios_revm_utils::proof_db::ProofDB;
use phos_precompiles::DataNetworkPrecompiles;
use crate::spec::DataNetwork;
pub struct DataNetworkEvm<E: ExecutionProvider<DataNetwork>> {
execution: Arc<E>,
chain_id: u64,
block_id: BlockId,
fork_schedule: ForkSchedule,
phantom: PhantomData<DataNetwork>,
}
impl<E: ExecutionProvider<DataNetwork>> DataNetworkEvm<E> {
pub fn new(
execution: Arc<E>,
chain_id: u64,
fork_schedule: ForkSchedule,
block_id: BlockId,
) -> Self {
Self {
execution,
chain_id,
block_id,
fork_schedule,
phantom: PhantomData,
}
}
pub async fn transact_inner(
&mut self,
tx: &TransactionRequest,
validate_tx: bool,
state_overrides: Option<StateOverride>,
) -> Result<(ExecutionResult, HashMap<Address, Account>), EvmError> {
let block = self
.execution
.get_block(self.block_id, false)
.await
.map_err(|err| EvmError::Generic(err.to_string()))?
.ok_or(ExecutionError::BlockNotFound(self.block_id))
.map_err(|err| EvmError::Generic(err.to_string()))?;
let pinned_block: RpcBlockHash = block.header.hash.into();
let mut db = ProofDB::new(pinned_block, self.execution.clone(), state_overrides);
_ = db.state.prefetch_state(tx, validate_tx).await;
let mut iteration: u32 = 0;
let tx_res = loop {
iteration += 1;
if db.state.needs_update() {
debug!(
"evm cache miss (iteration {}): {:?}",
iteration,
db.state.access.as_ref().unwrap()
);
db.state
.update_state()
.await
.map_err(|e| EvmError::Generic(e.to_string()))?;
}
let context = self.get_context(tx, &block, validate_tx);
let (result, needs_update) = {
let mut evm = context
.with_db(&mut db)
.build_mainnet()
.with_precompiles(DataNetworkPrecompiles::default());
let res = evm.replay();
let needs_update = evm.db_mut().state.needs_update();
(res, needs_update)
};
if result.is_ok() || !needs_update {
break result.map(|res| (res.result, mem::take(&mut db.state.accounts)));
}
};
tx_res.map_err(|err| EvmError::Generic(format!("generic: {err}")))
}
fn get_context(
&self,
tx: &TransactionRequest,
block: &Block<Transaction>,
validate_tx: bool,
) -> Context {
let spec = get_spec_id_for_block_timestamp(block.header.timestamp, &self.fork_schedule);
let mut tx_env = Self::tx_env(tx, spec);
if <TxType as Into<u8>>::into(
<TransactionRequest as TransactionBuilder<DataNetwork>>::output_tx_type(tx),
) == 0u8
{
tx_env.chain_id = None;
} else {
tx_env.chain_id = Some(self.chain_id);
}
let mut cfg = CfgEnv::default();
cfg.spec = get_spec_id_for_block_timestamp(block.header.timestamp, &self.fork_schedule);
cfg.chain_id = self.chain_id;
cfg.disable_block_gas_limit = !validate_tx;
cfg.disable_eip3607 = !validate_tx;
cfg.disable_base_fee = !validate_tx;
cfg.disable_nonce_check = !validate_tx;
Context::mainnet()
.with_tx(tx_env)
.with_block(Self::block_env(block, &self.fork_schedule))
.with_cfg(cfg)
}
fn tx_env(tx: &TransactionRequest, spec: SpecId) -> TxEnv {
let default_gas_limit = if spec >= SpecId::OSAKA {
eip7825::TX_GAS_LIMIT_CAP
} else {
u64::MAX
};
TxEnv {
tx_type: tx.transaction_type.unwrap_or_default(),
caller: tx.from.unwrap_or_default(),
gas_limit: <TransactionRequest as TransactionBuilder<DataNetwork>>::gas_limit(tx)
.unwrap_or(default_gas_limit),
gas_price: <TransactionRequest as TransactionBuilder<DataNetwork>>::gas_price(tx)
.unwrap_or_default(),
kind: tx.to.unwrap_or_default(),
value: tx.value.unwrap_or_default(),
data: <TransactionRequest as TransactionBuilder<DataNetwork>>::input(tx)
.unwrap_or_default()
.clone(),
nonce: <TransactionRequest as TransactionBuilder<DataNetwork>>::nonce(tx)
.unwrap_or_default(),
chain_id: <TransactionRequest as TransactionBuilder<DataNetwork>>::chain_id(tx),
access_list: <TransactionRequest as TransactionBuilder<DataNetwork>>::access_list(tx)
.cloned()
.unwrap_or_default(),
gas_priority_fee:
<TransactionRequest as TransactionBuilder<DataNetwork>>::max_priority_fee_per_gas(
tx,
),
max_fee_per_blob_gas: tx.max_fee_per_blob_gas.unwrap_or_default(),
blob_hashes: tx
.blob_versioned_hashes
.as_ref()
.map(|v| v.to_vec())
.unwrap_or_default(),
authorization_list: vec![],
}
}
fn block_env(block: &Block<Transaction, Header>, fork_schedule: &ForkSchedule) -> BlockEnv {
let blob_base_fee_update_fraction =
fork_schedule.get_blob_base_fee_update_fraction(block.header.timestamp());
let blob_excess_gas_and_price = block
.header
.excess_blob_gas()
.map(|v| BlobExcessGasAndPrice::new(v, blob_base_fee_update_fraction))
.unwrap_or_else(|| BlobExcessGasAndPrice::new(0, blob_base_fee_update_fraction));
BlockEnv {
number: U256::from(block.header.number()),
beneficiary: block.header.beneficiary(),
timestamp: U256::from(block.header.timestamp()),
gas_limit: block.header.gas_limit(),
basefee: block.header.base_fee_per_gas().unwrap_or_default(),
difficulty: block.header.difficulty(),
prevrandao: block.header.mix_hash(),
blob_excess_gas_and_price: Some(blob_excess_gas_and_price),
}
}
}
pub fn get_spec_id_for_block_timestamp(timestamp: u64, fork_schedule: &ForkSchedule) -> SpecId {
if timestamp >= fork_schedule.osaka_timestamp {
SpecId::OSAKA
} else if timestamp >= fork_schedule.prague_timestamp {
SpecId::PRAGUE
} else if timestamp >= fork_schedule.cancun_timestamp {
SpecId::CANCUN
} else if timestamp >= fork_schedule.shanghai_timestamp {
SpecId::SHANGHAI
} else if timestamp >= fork_schedule.paris_timestamp {
SpecId::MERGE
} else if timestamp >= fork_schedule.gray_glacier_timestamp {
SpecId::GRAY_GLACIER
} else if timestamp >= fork_schedule.arrow_glacier_timestamp {
SpecId::ARROW_GLACIER
} else if timestamp >= fork_schedule.london_timestamp {
SpecId::LONDON
} else if timestamp >= fork_schedule.berlin_timestamp {
SpecId::BERLIN
} else if timestamp >= fork_schedule.muir_glacier_timestamp {
SpecId::MUIR_GLACIER
} else if timestamp >= fork_schedule.istanbul_timestamp {
SpecId::ISTANBUL
} else if timestamp >= fork_schedule.petersburg_timestamp {
SpecId::PETERSBURG
} else if timestamp >= fork_schedule.constantinople_timestamp {
SpecId::CONSTANTINOPLE
} else if timestamp >= fork_schedule.byzantium_timestamp {
SpecId::BYZANTIUM
} else if timestamp >= fork_schedule.spurious_dragon_timestamp {
SpecId::SPURIOUS_DRAGON
} else if timestamp >= fork_schedule.tangerine_timestamp {
SpecId::TANGERINE
} else if timestamp >= fork_schedule.dao_timestamp {
SpecId::DAO_FORK
} else if timestamp >= fork_schedule.homestead_timestamp {
SpecId::HOMESTEAD
} else if timestamp >= fork_schedule.frontier_timestamp {
SpecId::FRONTIER
} else {
SpecId::default()
}
}