Skip to main content

phos_data_network/
evm.rs

1use std::{collections::HashMap, marker::PhantomData, mem, sync::Arc};
2
3use alloy::{
4    consensus::{BlockHeader, TxType},
5    eips::{eip1898::RpcBlockHash, BlockId},
6    network::TransactionBuilder,
7    rpc::types::{state::StateOverride, Block, Header, Transaction, TransactionRequest},
8};
9use eyre::Result;
10use revm::{
11    context::{result::ExecutionResult, BlockEnv, CfgEnv, ContextTr, TxEnv},
12    context_interface::block::BlobExcessGasAndPrice,
13    primitives::{eip7825, hardfork::SpecId, Address, U256},
14    Context, ExecuteEvm, MainBuilder, MainContext,
15};
16use tracing::debug;
17
18use helios_common::{
19    execution_provider::ExecutionProvider,
20    fork_schedule::ForkSchedule,
21    types::{Account, EvmError},
22};
23use helios_core::execution::errors::ExecutionError;
24use helios_revm_utils::proof_db::ProofDB;
25use phos_precompiles::DataNetworkPrecompiles;
26
27use crate::spec::DataNetwork;
28
29pub struct DataNetworkEvm<E: ExecutionProvider<DataNetwork>> {
30    execution: Arc<E>,
31    chain_id: u64,
32    block_id: BlockId,
33    fork_schedule: ForkSchedule,
34    phantom: PhantomData<DataNetwork>,
35}
36
37impl<E: ExecutionProvider<DataNetwork>> DataNetworkEvm<E> {
38    pub fn new(
39        execution: Arc<E>,
40        chain_id: u64,
41        fork_schedule: ForkSchedule,
42        block_id: BlockId,
43    ) -> Self {
44        Self {
45            execution,
46            chain_id,
47            block_id,
48            fork_schedule,
49            phantom: PhantomData,
50        }
51    }
52
53    pub async fn transact_inner(
54        &mut self,
55        tx: &TransactionRequest,
56        validate_tx: bool,
57        state_overrides: Option<StateOverride>,
58    ) -> Result<(ExecutionResult, HashMap<Address, Account>), EvmError> {
59        let block = self
60            .execution
61            .get_block(self.block_id, false)
62            .await
63            .map_err(|err| EvmError::Generic(err.to_string()))?
64            .ok_or(ExecutionError::BlockNotFound(self.block_id))
65            .map_err(|err| EvmError::Generic(err.to_string()))?;
66
67        // Pin block to a specific hash for the entire EVM run.
68        let pinned_block: RpcBlockHash = block.header.hash.into();
69
70        let mut db = ProofDB::new(pinned_block, self.execution.clone(), state_overrides);
71        _ = db.state.prefetch_state(tx, validate_tx).await;
72
73        // Track iterations for debugging
74        let mut iteration: u32 = 0;
75
76        let tx_res = loop {
77            iteration += 1;
78
79            // Update state first if needed
80            if db.state.needs_update() {
81                debug!(
82                    "evm cache miss (iteration {}): {:?}",
83                    iteration,
84                    db.state.access.as_ref().unwrap()
85                );
86                db.state
87                    .update_state()
88                    .await
89                    .map_err(|e| EvmError::Generic(e.to_string()))?;
90            }
91
92            // Create EVM after any async operations
93            let context = self.get_context(tx, &block, validate_tx);
94
95            // Execute in a scope to ensure EVM is dropped before any potential async operations
96            let (result, needs_update) = {
97                let mut evm = context
98                    .with_db(&mut db)
99                    .build_mainnet()
100                    .with_precompiles(DataNetworkPrecompiles::default());
101                let res = evm.replay();
102                let needs_update = evm.db_mut().state.needs_update();
103                (res, needs_update)
104            };
105
106            if result.is_ok() || !needs_update {
107                break result.map(|res| (res.result, mem::take(&mut db.state.accounts)));
108            }
109        };
110
111        tx_res.map_err(|err| EvmError::Generic(format!("generic: {err}")))
112    }
113
114    fn get_context(
115        &self,
116        tx: &TransactionRequest,
117        block: &Block<Transaction>,
118        validate_tx: bool,
119    ) -> Context {
120        let spec = get_spec_id_for_block_timestamp(block.header.timestamp, &self.fork_schedule);
121        let mut tx_env = Self::tx_env(tx, spec);
122
123        if <TxType as Into<u8>>::into(
124            <TransactionRequest as TransactionBuilder<DataNetwork>>::output_tx_type(tx),
125        ) == 0u8
126        {
127            tx_env.chain_id = None;
128        } else {
129            tx_env.chain_id = Some(self.chain_id);
130        }
131
132        let mut cfg = CfgEnv::default();
133        cfg.spec = get_spec_id_for_block_timestamp(block.header.timestamp, &self.fork_schedule);
134        cfg.chain_id = self.chain_id;
135        cfg.disable_block_gas_limit = !validate_tx;
136        cfg.disable_eip3607 = !validate_tx;
137        cfg.disable_base_fee = !validate_tx;
138        cfg.disable_nonce_check = !validate_tx;
139
140        Context::mainnet()
141            .with_tx(tx_env)
142            .with_block(Self::block_env(block, &self.fork_schedule))
143            .with_cfg(cfg)
144    }
145
146    fn tx_env(tx: &TransactionRequest, spec: SpecId) -> TxEnv {
147        let default_gas_limit = if spec >= SpecId::OSAKA {
148            eip7825::TX_GAS_LIMIT_CAP
149        } else {
150            u64::MAX
151        };
152        TxEnv {
153            tx_type: tx.transaction_type.unwrap_or_default(),
154            caller: tx.from.unwrap_or_default(),
155            gas_limit: <TransactionRequest as TransactionBuilder<DataNetwork>>::gas_limit(tx)
156                .unwrap_or(default_gas_limit),
157            gas_price: <TransactionRequest as TransactionBuilder<DataNetwork>>::gas_price(tx)
158                .unwrap_or_default(),
159            kind: tx.to.unwrap_or_default(),
160            value: tx.value.unwrap_or_default(),
161            data: <TransactionRequest as TransactionBuilder<DataNetwork>>::input(tx)
162                .unwrap_or_default()
163                .clone(),
164            nonce: <TransactionRequest as TransactionBuilder<DataNetwork>>::nonce(tx)
165                .unwrap_or_default(),
166            chain_id: <TransactionRequest as TransactionBuilder<DataNetwork>>::chain_id(tx),
167            access_list: <TransactionRequest as TransactionBuilder<DataNetwork>>::access_list(tx)
168                .cloned()
169                .unwrap_or_default(),
170            gas_priority_fee:
171                <TransactionRequest as TransactionBuilder<DataNetwork>>::max_priority_fee_per_gas(
172                    tx,
173                ),
174            max_fee_per_blob_gas: tx.max_fee_per_blob_gas.unwrap_or_default(),
175            blob_hashes: tx
176                .blob_versioned_hashes
177                .as_ref()
178                .map(|v| v.to_vec())
179                .unwrap_or_default(),
180            authorization_list: vec![],
181        }
182    }
183
184    fn block_env(block: &Block<Transaction, Header>, fork_schedule: &ForkSchedule) -> BlockEnv {
185        // Get blob base fee update fraction based on fork
186        let blob_base_fee_update_fraction =
187            fork_schedule.get_blob_base_fee_update_fraction(block.header.timestamp());
188
189        let blob_excess_gas_and_price = block
190            .header
191            .excess_blob_gas()
192            .map(|v| BlobExcessGasAndPrice::new(v, blob_base_fee_update_fraction))
193            .unwrap_or_else(|| BlobExcessGasAndPrice::new(0, blob_base_fee_update_fraction));
194
195        BlockEnv {
196            number: U256::from(block.header.number()),
197            beneficiary: block.header.beneficiary(),
198            timestamp: U256::from(block.header.timestamp()),
199            gas_limit: block.header.gas_limit(),
200            basefee: block.header.base_fee_per_gas().unwrap_or_default(),
201            difficulty: block.header.difficulty(),
202            prevrandao: block.header.mix_hash(),
203            blob_excess_gas_and_price: Some(blob_excess_gas_and_price),
204        }
205    }
206}
207
208pub fn get_spec_id_for_block_timestamp(timestamp: u64, fork_schedule: &ForkSchedule) -> SpecId {
209    if timestamp >= fork_schedule.osaka_timestamp {
210        SpecId::OSAKA
211    } else if timestamp >= fork_schedule.prague_timestamp {
212        SpecId::PRAGUE
213    } else if timestamp >= fork_schedule.cancun_timestamp {
214        SpecId::CANCUN
215    } else if timestamp >= fork_schedule.shanghai_timestamp {
216        SpecId::SHANGHAI
217    } else if timestamp >= fork_schedule.paris_timestamp {
218        SpecId::MERGE
219    } else if timestamp >= fork_schedule.gray_glacier_timestamp {
220        SpecId::GRAY_GLACIER
221    } else if timestamp >= fork_schedule.arrow_glacier_timestamp {
222        SpecId::ARROW_GLACIER
223    } else if timestamp >= fork_schedule.london_timestamp {
224        SpecId::LONDON
225    } else if timestamp >= fork_schedule.berlin_timestamp {
226        SpecId::BERLIN
227    } else if timestamp >= fork_schedule.muir_glacier_timestamp {
228        SpecId::MUIR_GLACIER
229    } else if timestamp >= fork_schedule.istanbul_timestamp {
230        SpecId::ISTANBUL
231    } else if timestamp >= fork_schedule.petersburg_timestamp {
232        SpecId::PETERSBURG
233    } else if timestamp >= fork_schedule.constantinople_timestamp {
234        SpecId::CONSTANTINOPLE
235    } else if timestamp >= fork_schedule.byzantium_timestamp {
236        SpecId::BYZANTIUM
237    } else if timestamp >= fork_schedule.spurious_dragon_timestamp {
238        SpecId::SPURIOUS_DRAGON
239    } else if timestamp >= fork_schedule.tangerine_timestamp {
240        SpecId::TANGERINE
241    } else if timestamp >= fork_schedule.dao_timestamp {
242        SpecId::DAO_FORK
243    } else if timestamp >= fork_schedule.homestead_timestamp {
244        SpecId::HOMESTEAD
245    } else if timestamp >= fork_schedule.frontier_timestamp {
246        SpecId::FRONTIER
247    } else {
248        SpecId::default()
249    }
250}