Skip to main content

anvil/eth/backend/mem/
monad.rs

1//! Monad-specific execution and context helpers for the in-memory backend.
2
3use super::{
4    AnvilInspector, Backend, ClientFork, DatabaseRef, EnvelopeExecutionKind, MonadExecutionContext,
5    storage::BlockchainStorage,
6};
7use crate::eth::{
8    backend::{
9        db::MonadBlockReplayProfile,
10        executor::{
11            AnvilBlockExecutor, AnvilTxResult, ExecutedPoolTransactions, PoolTxGasConfig,
12            build_tx_env_for_pending, execute_pool_transactions,
13        },
14        replay::{
15            ExecutedHistoricalReplay, HistoricalReplayTransaction, execute_historical_replay_with,
16        },
17    },
18    error::{BlockchainError, InvalidTransactionError},
19    fees::FeeManager,
20    pool::transactions::PoolTransaction,
21};
22use alloy_consensus::{
23    BlockHeader, Header, Transaction as _, constants::EMPTY_ROOT_HASH, transaction::Recovered,
24};
25use alloy_eips::eip1559::BaseFeeParams;
26use alloy_evm::{
27    Evm, EvmEnv, EvmFactory, RecoveredTx,
28    block::{
29        BalIndexedDatabase, BlockExecutionError, BlockExecutionResult, BlockExecutor, StateDB,
30    },
31};
32use alloy_monad_evm::{MonadContext, MonadEvm, MonadEvmFactory};
33use alloy_network::{BlockResponse, Network};
34use alloy_primitives::{B256, U256};
35use alloy_rpc_types::{AccessList, BlockNumberOrTag as BlockNumber, BlockTransactions};
36use anvil_core::eth::{
37    block::Block,
38    transaction::{MaybeImpersonatedTransaction, PendingTransaction},
39};
40use eyre::{Context, Result};
41use foundry_evm::{
42    backend::DatabaseError,
43    core::{
44        FoundryChain, FromAnyRpcTransaction,
45        evm::{
46            MonadBlockParticipants, monad_block_participants, monad_context_from_participants,
47            protocol_system_call, try_transact_monad_system_replay,
48        },
49    },
50    hardfork::FoundryHardfork,
51    utils::get_blob_params,
52};
53use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope};
54use monad_revm::{MonadChainContext, MonadHardfork, instructions::monad_gas_params};
55use revm::{
56    Inspector,
57    context::{Transaction, TxEnv},
58    context_interface::{
59        block::BlobExcessGasAndPrice,
60        result::{HaltReason, InvalidTransaction, ResultAndState},
61        transaction::AuthorizationTr,
62    },
63    database_interface::WrapDatabaseRef,
64    primitives::hardfork::SpecId,
65    state::AccountInfo,
66};
67use std::sync::Arc;
68use tracing::debug;
69
70/// Resolved Monad inputs, including a transaction context for standalone calls.
71pub(super) struct PreparedExecution {
72    pub(super) context: MonadChainContext,
73    pub(super) kind: EnvelopeExecutionKind,
74    pub(super) hardfork: MonadHardfork,
75}
76
77pub(super) struct ForkReplay {
78    hardfork: FoundryHardfork,
79    context: Option<MonadChainContext>,
80    participants: MonadBlockParticipants,
81    execution_chain_id: u64,
82    source_chain_id: u64,
83    timestamp: u64,
84    inferred_hardfork: bool,
85}
86
87impl ForkReplay {
88    pub(super) const fn hardfork(&self) -> FoundryHardfork {
89        self.hardfork
90    }
91
92    pub(super) const fn take_context(&mut self) -> Option<MonadChainContext> {
93        self.context.take()
94    }
95
96    pub(super) fn store_metadata<N: Network>(
97        &mut self,
98        storage: &mut BlockchainStorage<N>,
99        block_hash: B256,
100    ) {
101        store_block_metadata(
102            storage,
103            block_hash,
104            std::mem::take(&mut self.participants),
105            self.execution_chain_id,
106            self.hardfork,
107        );
108    }
109}
110
111/// Monad execution state staged while rewinding to a retained canonical block.
112pub(super) struct MonadRollbackProfile {
113    hardfork: FoundryHardfork,
114    fees: FeeManager,
115    block_blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
116    publish_inferred_hardfork: bool,
117}
118
119impl MonadRollbackProfile {
120    /// Rewinds cfg/spec, gas params, and the retained block's fee environment.
121    pub(super) fn apply_env(&self, env: &mut EvmEnv) {
122        env.cfg_env.set_spec_and_mainnet_gas_params(SpecId::from(self.hardfork));
123        env.block_env.blob_excess_gas_and_price = self.block_blob_excess_gas_and_price;
124    }
125}
126
127pub(super) fn normalize_access_list(
128    mut access_list: AccessList,
129    hardfork: MonadHardfork,
130) -> AccessList {
131    if MonadHardfork::MonadTen.is_enabled_in(hardfork) {
132        for item in &mut access_list.0 {
133            item.storage_keys.sort_unstable();
134            item.storage_keys.dedup_by_key(|slot| {
135                monad_revm::page::page_index(U256::from_be_slice(slot.as_slice()))
136            });
137        }
138    }
139    access_list
140}
141
142/// Caches the fork blocks needed to construct the next Monad block's ancestor context.
143pub(super) async fn cache_fork_context(fork: &ClientFork) -> Result<(), BlockchainError> {
144    let block =
145        fork.block_by_hash_full(fork.block_hash()).await?.ok_or(BlockchainError::BlockNotFound)?;
146    let parent_hash = block.header().parent_hash();
147    if !parent_hash.is_zero() {
148        fork.block_by_hash_full(parent_hash).await?.ok_or(BlockchainError::BlockNotFound)?;
149    }
150    Ok(())
151}
152
153/// Executes one Monad pool candidate and restores its context if execution fails before inclusion.
154fn execute_pool_transaction<DB>(
155    executor: &mut AnvilBlockExecutor<MonadEvm<DB, AnvilInspector>>,
156    tx_env: TxEnv,
157    recovered: Recovered<FoundryTxEnvelope>,
158    is_replay: bool,
159) -> Result<AnvilTxResult<HaltReason>, BlockExecutionError>
160where
161    DB: StateDB<Error = DatabaseError> + BalIndexedDatabase,
162{
163    prepare_transaction(executor.evm_mut(), &tx_env);
164    let result = (|| {
165        if !is_replay {
166            return executor.execute_transaction_without_commit((tx_env, recovered));
167        }
168        match protocol_system_call(&tx_env) {
169            Ok(None) => return executor.execute_transaction_without_commit((tx_env, recovered)),
170            Ok(Some(_)) => {}
171            Err(err) => return Err(BlockExecutionError::msg(err)),
172        }
173        executor.execute_transaction_without_commit_with(
174            (tx_env, recovered),
175            |evm, tx_env, transaction_hash| {
176                try_transact_monad_system_replay(evm, &tx_env)
177                    .map_err(|err| {
178                        BlockExecutionError::msg(format!(
179                            "failed to replay Monad transaction {transaction_hash}: {err}"
180                        ))
181                    })?
182                    .ok_or_else(|| {
183                        BlockExecutionError::msg(format!(
184                            "Monad transaction {transaction_hash} is not a canonical replay envelope"
185                        ))
186                    })
187            },
188        )
189    })();
190    if result.is_err() {
191        rollback_transaction(executor.evm_mut());
192    }
193    result
194}
195
196/// Adds a candidate transaction to the current Monad block context.
197fn append_transaction(chain: &mut MonadChainContext, tx: &TxEnv) {
198    chain.current_tx_index = chain.current_block_senders.len();
199    chain.current_block_senders.push(tx.caller());
200    chain.current_block_authorities.push(
201        tx.authorization_list().filter_map(|authorization| authorization.authority()).collect(),
202    );
203}
204
205pub(super) fn prepare_transaction<DB: alloy_evm::Database>(
206    evm: &mut MonadEvm<DB, AnvilInspector>,
207    tx: &TxEnv,
208) {
209    append_transaction(&mut evm.ctx_mut().chain, tx);
210}
211
212pub(super) fn resolve_execution_context(
213    context: Option<MonadExecutionContext<'_>>,
214    tx: &TxEnv,
215) -> MonadChainContext {
216    match context {
217        Some(MonadExecutionContext::Exact(context)) => *context,
218        Some(MonadExecutionContext::Next(context)) => {
219            append_transaction(context, tx);
220            context.clone()
221        }
222        None => MonadChainContext::for_transaction(tx),
223    }
224}
225
226pub(super) fn advance_block_context(context: &mut Option<MonadChainContext>) {
227    let Some(context) = context else { return };
228    let current = context
229        .current_block_senders
230        .iter()
231        .copied()
232        .chain(context.current_block_authorities.iter().flatten().copied())
233        .collect();
234    context.grandparent_senders_and_authorities =
235        std::mem::replace(&mut context.parent_senders_and_authorities, current);
236    context.current_block_senders.clear();
237    context.current_block_authorities.clear();
238    context.current_tx_index = 0;
239}
240
241/// Removes a candidate transaction that failed before block inclusion.
242pub(super) fn rollback_transaction<DB: alloy_evm::Database>(
243    evm: &mut MonadEvm<DB, AnvilInspector>,
244) {
245    let chain = &mut evm.ctx_mut().chain;
246    chain.current_block_senders.pop();
247    chain.current_block_authorities.pop();
248    chain.current_tx_index = chain.current_block_senders.len();
249}
250
251impl<N: Network> Backend<N> {
252    /// Returns the remote rollback ancestor for a Monad transaction-hash fork.
253    pub(crate) async fn monad_rollback_block(
254        &self,
255        number: u64,
256    ) -> Result<Option<Block>, BlockchainError> {
257        let Some(fork) = self.get_fork().filter(|fork| {
258            fork.transaction_hash().is_some() && fork.predates_fork_inclusive(number)
259        }) else {
260            return Ok(None);
261        };
262        let Some(block) = fork.block_by_number(number).await? else {
263            return Ok(None);
264        };
265        let header = Header::try_from(block.header().inner.clone())
266            .map_err(|err| BlockchainError::Internal(err.to_string()))?;
267        Ok(Some(Block { header: header.into(), body: Default::default() }))
268    }
269
270    /// Prepares the Monad-specific inputs for replaying a historical transaction prefix.
271    pub(super) async fn prepare_monad_fork_replay(
272        &self,
273        source_chain_id: u64,
274        execution_chain_id: u64,
275        timestamp: u64,
276        parent_hash: B256,
277        transactions: &[HistoricalReplayTransaction],
278    ) -> Result<Option<ForkReplay>> {
279        if !self.is_monad() {
280            return Ok(None);
281        }
282
283        let explicit_hardfork = self.node_config.read().await.hardfork;
284        // The target block's schedule can cross a hardfork boundary after the selected parent.
285        // Explicit overrides still take precedence over timestamp inference.
286        let hardfork = explicit_hardfork
287            .or_else(|| {
288                MonadHardfork::from_chain_and_timestamp(source_chain_id, timestamp)
289                    .map(FoundryHardfork::Monad)
290            })
291            .unwrap_or_else(|| self.hardfork());
292        let context = self.monad_context_for_child_of_block_hash(parent_hash).await?;
293        let participants =
294            monad_block_participants(&self.monad_historical_replay_tx_envs(transactions));
295
296        Ok(Some(ForkReplay {
297            hardfork,
298            context: Some(context),
299            participants,
300            execution_chain_id,
301            source_chain_id,
302            timestamp,
303            inferred_hardfork: explicit_hardfork.is_none(),
304        }))
305    }
306
307    /// Applies the Monad execution rules selected for a completed fork replay.
308    pub(super) fn finalize_monad_fork_replay(&self, replay: &ForkReplay, evm_env: &mut EvmEnv) {
309        let spec_id = SpecId::from(replay.hardfork());
310        evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec_id);
311        self.fees.set_execution_rules(
312            spec_id,
313            self.networks.base_fee_params(replay.timestamp),
314            None,
315        );
316        self.fees.set_blob_params(get_blob_params(replay.source_chain_id, replay.timestamp));
317
318        if replay.inferred_hardfork {
319            *self.hardfork.write() = replay.hardfork();
320            if let Some(fork) = self.fork.read().clone() {
321                fork.config.write().hardfork = Some(replay.hardfork());
322            }
323        }
324    }
325
326    /// Stages the Monad fork protocol profile and next-block fee state for a retained block.
327    pub(super) async fn prepare_monad_rollback_profile(
328        &self,
329        common_block: &Block,
330    ) -> Option<MonadRollbackProfile> {
331        // Local nodes keep their configured profile, even when the execution chain ID changes.
332        let fork = self.get_fork()?;
333
334        let explicit_hardfork = self.node_config.read().await.hardfork;
335        let block_hash = common_block.header.hash_slow();
336        let stored_hardfork = self
337            .blockchain
338            .storage
339            .read()
340            .monad_block_replay_profiles
341            .get(&block_hash)
342            .map(|profile| profile.hardfork);
343        let endpoint_hardfork = {
344            let config = fork.config.read();
345            if config.block_number != common_block.header.number()
346                || config.block_hash != block_hash
347            {
348                None
349            } else {
350                match config.endpoint_identity.hardfork {
351                    Some(FoundryHardfork::Monad(hardfork)) => Some(hardfork),
352                    _ => None,
353                }
354            }
355        };
356        let source_chain_id = fork.chain_id();
357        let scheduled_hardfork = MonadHardfork::from_chain_and_timestamp(
358            source_chain_id,
359            common_block.header.timestamp(),
360        );
361        let hardfork = explicit_hardfork.unwrap_or_else(|| {
362            FoundryHardfork::Monad(
363                stored_hardfork
364                    .or(endpoint_hardfork)
365                    .or(scheduled_hardfork)
366                    .unwrap_or_else(|| self.monad_hardfork()),
367            )
368        });
369        let spec_id = SpecId::from(hardfork);
370        let timestamp = common_block.header.timestamp();
371        let blob_params = get_blob_params(source_chain_id, timestamp);
372        let block_base_fee = common_block.header.base_fee_per_gas.unwrap_or_default();
373        let is_fork_root = fork.block_number() == common_block.header.number();
374
375        let fees = self.fees.detached();
376        fees.set_execution_rules(spec_id, BaseFeeParams::ethereum(), None);
377        fees.set_blob_params(blob_params);
378        // The fee manager stores values for the next block. Seed it with the retained block while
379        // deriving those values so the zero-base-fee sentinel and Osaka blob target are applied to
380        // the correct parent.
381        fees.set_base_fee(block_base_fee);
382        let (next_block_base_fee, next_block_excess_blob_gas) = if is_fork_root {
383            if let Some(child) = self.get_block(common_block.header.number().saturating_add(1)) {
384                (
385                    child.header.base_fee_per_gas().unwrap_or_default(),
386                    child.header.excess_blob_gas().unwrap_or_default(),
387                )
388            } else {
389                let snapshot = self.fees.snapshot();
390                (snapshot.base_fee, snapshot.blob_excess_gas_and_price.excess_blob_gas)
391            }
392        } else {
393            (
394                fees.get_next_block_base_fee_from_header(&common_block.header),
395                blob_params.next_block_excess_blob_gas_osaka(
396                    common_block.header.excess_blob_gas.unwrap_or_default(),
397                    common_block.header.blob_gas_used.unwrap_or_default(),
398                    block_base_fee,
399                ),
400            )
401        };
402        fees.set_base_fee(next_block_base_fee);
403        fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
404            next_block_excess_blob_gas,
405            blob_params.update_fraction as u64,
406        ));
407
408        Some(MonadRollbackProfile {
409            hardfork,
410            fees,
411            block_blob_excess_gas_and_price: common_block.header.excess_blob_gas.map(|excess| {
412                BlobExcessGasAndPrice::new(excess, blob_params.update_fraction as u64)
413            }),
414            publish_inferred_hardfork: explicit_hardfork.is_none(),
415        })
416    }
417
418    /// Returns transaction fee defaults for the retained block's staged profile.
419    pub(crate) async fn monad_rollback_fee_defaults(
420        &self,
421        common_block: &Block,
422        suggested_tip: u128,
423    ) -> Option<(u128, u128)> {
424        let profile = self.prepare_monad_rollback_profile(common_block).await?;
425        let gas_price = if profile.fees.is_eip1559() {
426            let base_fee = profile.fees.base_fee() as u128;
427            if profile.fees.is_min_priority_fee_enforced() {
428                base_fee.saturating_add(suggested_tip)
429            } else {
430                base_fee
431            }
432        } else {
433            profile.fees.raw_gas_price()
434        };
435        Some((gas_price, profile.fees.get_next_block_blob_base_fee_per_gas()))
436    }
437
438    /// Rewinds state and publishes the Monad profile selected for the retained block.
439    pub(crate) async fn rollback_monad(
440        &self,
441        common_block: Block,
442        mining_guard: &tokio::sync::MutexGuard<'_, ()>,
443    ) -> Result<(), BlockchainError>
444    where
445        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
446    {
447        let profile = self.prepare_monad_rollback_profile(&common_block).await;
448        self.rollback(common_block, mining_guard).await?;
449        if let Some(profile) = profile {
450            profile.apply_env(&mut self.evm_env.write());
451            self.publish_monad_rollback_profile(profile);
452        }
453        Ok(())
454    }
455
456    /// Publishes the staged Monad fee rules and inferred hardfork after a rewind.
457    pub(super) fn publish_monad_rollback_profile(&self, profile: MonadRollbackProfile) {
458        self.fees.replace_from(&profile.fees);
459        if profile.publish_inferred_hardfork {
460            *self.hardfork.write() = profile.hardfork;
461            if let Some(fork) = self.fork.read().clone() {
462                fork.config.write().hardfork = Some(profile.hardfork);
463            }
464        }
465    }
466
467    /// Validates Monad-specific transaction type restrictions.
468    pub(super) const fn validate_monad_transaction_type(
469        &self,
470        tx: &FoundryTxEnvelope,
471    ) -> Result<(), InvalidTransactionError> {
472        if self.is_monad() && tx.is_eip4844() {
473            return Err(InvalidTransactionError::MonadBlobTransactionUnsupported);
474        }
475        Ok(())
476    }
477
478    /// Validates Monad's gas-only transaction balance requirement.
479    ///
480    /// Returns whether the transaction was validated as a Monad transaction.
481    pub(super) fn validate_monad_transaction_funds(
482        &self,
483        pending: &PendingTransaction<FoundryTxEnvelope>,
484        account: &AccountInfo,
485        evm_env: &EvmEnv,
486    ) -> Result<bool, InvalidTransactionError> {
487        if !self.is_monad() {
488            return Ok(false);
489        }
490
491        let tx = &pending.transaction;
492        let effective_gas_price = tx.effective_gas_price(Some(evm_env.block_env.basefee));
493        let required = U256::from(tx.gas_limit()) * U256::from(effective_gas_price);
494        if account.balance < required {
495            debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance, required, *pending.sender());
496            return Err(InvalidTransactionError::InsufficientFunds);
497        }
498        Ok(true)
499    }
500
501    /// Validates a forced Monad protocol transaction selected for mining.
502    ///
503    /// Returns whether the transaction was recognized and fully validated as a protocol call.
504    pub(super) fn validate_monad_mining_pool_transaction_for(
505        &self,
506        pool_tx: &PoolTransaction<FoundryTxEnvelope>,
507        account: &AccountInfo,
508        evm_env: &EvmEnv,
509    ) -> Result<bool, InvalidTransactionError> {
510        if !self.is_monad() || !pool_tx.is_replay {
511            return Ok(false);
512        }
513
514        let tx_env: TxEnv = build_tx_env_for_pending(&pool_tx.pending_transaction, self.cheats());
515        let Some(system_call) = protocol_system_call(&tx_env).map_err(|err| {
516            InvalidTransactionError::Revm(InvalidTransaction::Str(err.to_string().into()))
517        })?
518        else {
519            return Ok(false);
520        };
521        if system_call.chain_id.is_some_and(|chain_id| chain_id != evm_env.cfg_env.chain_id) {
522            return Err(InvalidTransactionError::InvalidChainId);
523        }
524        if system_call.nonce < account.nonce {
525            return Err(InvalidTransactionError::NonceTooLow);
526        }
527        if system_call.nonce > account.nonce {
528            return Err(InvalidTransactionError::NonceTooHigh);
529        }
530        if system_call.nonce == u64::MAX {
531            return Err(InvalidTransactionError::NonceMaxValue);
532        }
533        Ok(true)
534    }
535
536    /// Executes a candidate block through a concrete Monad EVM.
537    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
538    pub(super) fn execute_with_monad_block_executor<DB>(
539        &self,
540        db: DB,
541        evm_env: &EvmEnv,
542        parent_hash: B256,
543        spec_id: SpecId,
544        hardfork: MonadHardfork,
545        pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
546        gas_config: &PoolTxGasConfig,
547        inspector_tx_config: &crate::mem::inspector::InspectorTxConfig,
548        validator: &dyn Fn(
549            &PoolTransaction<FoundryTxEnvelope>,
550            &AccountInfo,
551        ) -> Result<(), InvalidTransactionError>,
552    ) -> Result<
553        (ExecutedPoolTransactions<FoundryTxEnvelope>, BlockExecutionResult<FoundryReceiptEnvelope>),
554        BlockchainError,
555    >
556    where
557        DB: StateDB<Error = DatabaseError> + BalIndexedDatabase,
558    {
559        let monad_env = Self::build_monad_evm_env(evm_env, hardfork);
560        let inspector = self.build_mining_inspector();
561        let mut evm =
562            MonadEvmFactory::default().create_evm_with_inspector(db, monad_env, inspector);
563        let transaction_context = self
564            .monad_context_for_child_of(parent_hash)
565            .expect("Monad ancestor context must be available before block execution");
566        evm.ctx_mut().chain = transaction_context;
567        self.inject_precompiles(evm.precompiles_mut(), evm_env);
568
569        let mut executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, None)
570            .with_max_blob_gas_per_block(gas_config.max_blob_gas_per_block);
571        executor
572            .apply_pre_execution_changes()
573            .map_err(|err| BlockchainError::Internal(err.to_string()))?;
574        let pool_result = execute_pool_transactions(
575            &mut executor,
576            pool_transactions,
577            gas_config,
578            inspector_tx_config,
579            self.cheats(),
580            validator,
581            &mut execute_pool_transaction,
582        );
583        let (evm, block_result) =
584            executor.finish().map_err(|err| BlockchainError::Internal(err.to_string()))?;
585        drop(evm);
586        Ok((pool_result, block_result))
587    }
588
589    /// Executes a historical transaction prefix through a concrete Monad EVM.
590    #[allow(clippy::too_many_arguments)]
591    pub(super) fn execute_with_monad_replay_block_executor<DB>(
592        &self,
593        db: DB,
594        evm_env: &EvmEnv,
595        parent_hash: B256,
596        hardfork: MonadHardfork,
597        transactions: &[HistoricalReplayTransaction],
598        inspector_tx_config: &crate::mem::inspector::InspectorTxConfig,
599        transaction_context: MonadChainContext,
600    ) -> Result<ExecutedHistoricalReplay>
601    where
602        DB: StateDB<Error = DatabaseError> + BalIndexedDatabase,
603    {
604        let monad_env = Self::build_monad_evm_env(evm_env, hardfork);
605        let inspector = self.build_mining_inspector();
606        let mut evm =
607            MonadEvmFactory::default().create_evm_with_inspector(db, monad_env, inspector);
608        evm.ctx_mut().chain = transaction_context;
609        self.inject_precompiles(evm.precompiles_mut(), evm_env);
610
611        let mut executor = AnvilBlockExecutor::new(evm, parent_hash, *evm_env.spec_id(), None)
612            .with_state_changes();
613        executor
614            .apply_pre_execution_changes()
615            .wrap_err("failed to apply replay block-start transitions")?;
616        let (transactions, transaction_infos) = execute_historical_replay_with(
617            &mut executor,
618            transactions,
619            inspector_tx_config,
620            |evm, tx_env, _transaction_hash| {
621                prepare_transaction(evm, &tx_env);
622                let result = match try_transact_monad_system_replay(evm, &tx_env)
623                    .map_err(BlockExecutionError::msg)
624                {
625                    Ok(Some(result)) => Ok(result),
626                    Ok(None) => evm.transact(tx_env).map_err(BlockExecutionError::msg),
627                    Err(err) => Err(err),
628                };
629                if result.is_err() {
630                    rollback_transaction(evm);
631                }
632                result
633            },
634        )?;
635        let state_changes = executor.take_state_changes();
636        let (evm, block_result) =
637            executor.finish().wrap_err("failed to finish replay block execution")?;
638        drop(evm);
639        Ok(ExecutedHistoricalReplay {
640            block_result,
641            transactions,
642            transaction_infos,
643            state_changes,
644        })
645    }
646
647    /// Reconstructs a locally mined transaction using its authoritative stored sender.
648    pub(super) fn monad_pending_mined_transaction_from_storage(
649        storage: &BlockchainStorage<N>,
650        transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>,
651    ) -> Result<PendingTransaction<FoundryTxEnvelope>, BlockchainError> {
652        let transaction_hash = transaction.hash();
653        let mined =
654            storage.transactions.get(&transaction_hash).ok_or(BlockchainError::DataUnavailable)?;
655        Ok(PendingTransaction::with_sender(transaction, mined.info.from))
656    }
657
658    /// Converts retained local transactions using their authoritative mined senders.
659    fn monad_tx_envs_from_storage(
660        &self,
661        storage: &BlockchainStorage<N>,
662        transactions: &[MaybeImpersonatedTransaction<FoundryTxEnvelope>],
663    ) -> Result<Vec<TxEnv>, BlockchainError> {
664        transactions
665            .iter()
666            .map(|transaction| {
667                let pending = Self::monad_pending_mined_transaction_from_storage(
668                    storage,
669                    transaction.clone(),
670                )?;
671                Ok(build_tx_env_for_pending::<FoundryTxEnvelope, TxEnv>(&pending, self.cheats()))
672            })
673            .collect()
674    }
675
676    /// Converts a historical replay prefix using its prepared authoritative senders.
677    pub(super) fn monad_historical_replay_tx_envs(
678        &self,
679        transactions: &[HistoricalReplayTransaction],
680    ) -> Vec<TxEnv> {
681        transactions
682            .iter()
683            .map(|replay| {
684                let pending = PendingTransaction::with_sender(
685                    MaybeImpersonatedTransaction::new(replay.transaction.tx().clone()),
686                    replay.transaction.signer(),
687                );
688                build_tx_env_for_pending::<FoundryTxEnvelope, TxEnv>(&pending, self.cheats())
689            })
690            .collect()
691    }
692
693    /// Returns a block's number, parent hash, and cached participants.
694    fn monad_block_participants_from_storage(
695        &self,
696        storage: &BlockchainStorage<N>,
697        hash: B256,
698    ) -> Result<(u64, B256, MonadBlockParticipants), BlockchainError> {
699        if let Some(block) = storage.blocks.get(&hash) {
700            let participants = match storage.monad_block_participants.get(&hash) {
701                Some(participants) => participants.clone(),
702                None if block.body.transactions.is_empty()
703                    && block.header.transactions_root() != EMPTY_ROOT_HASH =>
704                {
705                    return Err(BlockchainError::DataUnavailable);
706                }
707                None => monad_block_participants(
708                    &self.monad_tx_envs_from_storage(storage, &block.body.transactions)?,
709                ),
710            };
711            return Ok((block.header.number(), block.header.parent_hash, participants));
712        }
713
714        let fork = self.get_fork().ok_or(BlockchainError::BlockNotFound)?;
715        let block = fork
716            .storage
717            .read()
718            .blocks
719            .get(&hash)
720            .cloned()
721            .ok_or(BlockchainError::DataUnavailable)?;
722        let BlockTransactions::Full(transactions) = block.transactions() else {
723            return Err(BlockchainError::DataUnavailable);
724        };
725        let tx_envs = transactions
726            .iter()
727            .map(TxEnv::from_any_rpc_transaction)
728            .collect::<eyre::Result<Vec<_>>>()?;
729        Ok((
730            block.header().number(),
731            block.header().parent_hash(),
732            monad_block_participants(&tx_envs),
733        ))
734    }
735
736    fn monad_block_participants(
737        &self,
738        hash: B256,
739    ) -> Result<(u64, B256, MonadBlockParticipants), BlockchainError> {
740        self.monad_block_participants_from_storage(&self.blockchain.storage.read(), hash)
741    }
742
743    /// Rebuilds participant metadata for locally stored blocks with transaction bodies.
744    pub(super) fn rebuild_monad_block_participant_cache(
745        &self,
746        storage: &mut BlockchainStorage<N>,
747    ) -> Result<(), BlockchainError> {
748        let participants = storage
749            .blocks
750            .iter()
751            .filter(|(hash, block)| {
752                !storage.monad_block_participants.contains_key(*hash)
753                    && (!block.body.transactions.is_empty()
754                        || block.header.transactions_root() == EMPTY_ROOT_HASH)
755            })
756            .map(|(hash, block)| (*hash, block.body.transactions.clone()))
757            .map(|(hash, transactions)| {
758                let tx_envs = self.monad_tx_envs_from_storage(storage, &transactions)?;
759                Ok((hash, monad_block_participants(&tx_envs)))
760            })
761            .collect::<Result<Vec<_>, BlockchainError>>()?;
762
763        for (hash, participants) in participants {
764            if storage.blocks.contains_key(&hash) {
765                storage.monad_block_participants.insert(hash, participants);
766            }
767        }
768        Ok(())
769    }
770
771    /// Builds the initial context for a block whose parent is `parent_hash`.
772    pub(super) fn monad_context_for_child_of(
773        &self,
774        parent_hash: B256,
775    ) -> Result<MonadChainContext, BlockchainError> {
776        self.monad_context_for_child_of_in_storage(&self.blockchain.storage.read(), parent_hash)
777    }
778
779    /// Builds the initial context from staged storage.
780    pub(super) fn monad_context_for_child_of_in_storage(
781        &self,
782        storage: &BlockchainStorage<N>,
783        parent_hash: B256,
784    ) -> Result<MonadChainContext, BlockchainError> {
785        let (_, grandparent_hash, parent) =
786            self.monad_block_participants_from_storage(storage, parent_hash)?;
787        let grandparent = if grandparent_hash.is_zero() {
788            MonadBlockParticipants::default()
789        } else {
790            self.monad_block_participants_from_storage(storage, grandparent_hash)?.2
791        };
792        Ok(monad_context_from_participants(grandparent, parent, &[], 0))
793    }
794
795    async fn monad_context_for_child_of_block(
796        &self,
797        block: alloy_network::AnyRpcBlock,
798        block_hash: B256,
799    ) -> Result<MonadChainContext, BlockchainError> {
800        let parent_hash = block.header().parent_hash();
801        if !parent_hash.is_zero() {
802            self.block_by_hash_full(parent_hash).await?.ok_or(BlockchainError::DataUnavailable)?;
803        }
804        self.monad_context_for_child_of(block_hash)
805    }
806
807    /// Fetches the full blocks required to build context on top of `block_number`.
808    pub(super) async fn monad_context_for_child_of_block_number(
809        &self,
810        block_number: u64,
811    ) -> Result<MonadChainContext, BlockchainError> {
812        let block = self
813            .block_by_number_full(BlockNumber::Number(block_number))
814            .await?
815            .ok_or(BlockchainError::BlockNotFound)?;
816        let block_hash = block.header().hash;
817        self.monad_context_for_child_of_block(block, block_hash).await
818    }
819
820    /// Fetches the full blocks required to build context on top of `block_hash`.
821    pub(super) async fn monad_context_for_child_of_block_hash(
822        &self,
823        block_hash: B256,
824    ) -> Result<MonadChainContext, BlockchainError> {
825        let block =
826            self.block_by_hash_full(block_hash).await?.ok_or(BlockchainError::BlockNotFound)?;
827        self.monad_context_for_child_of_block(block, block_hash).await
828    }
829
830    fn monad_context_for_mined_transactions(
831        &self,
832        block: &Block,
833        current: &[TxEnv],
834        current_tx_index: usize,
835    ) -> Result<MonadChainContext, BlockchainError> {
836        let (_, grandparent_hash, parent) =
837            self.monad_block_participants(block.header.parent_hash)?;
838        let grandparent = if grandparent_hash.is_zero() {
839            MonadBlockParticipants::default()
840        } else {
841            self.monad_block_participants(grandparent_hash)?.2
842        };
843        Ok(monad_context_from_participants(grandparent, parent, current, current_tx_index))
844    }
845
846    fn monad_context_for_mined_block(
847        &self,
848        block: &Block,
849    ) -> Result<MonadChainContext, BlockchainError> {
850        let current = self.monad_tx_envs_from_storage(
851            &self.blockchain.storage.read(),
852            &block.body.transactions,
853        )?;
854        self.monad_context_for_mined_transactions(block, &current, 0)
855    }
856
857    pub(super) fn active_monad_context_for_mined_block(
858        &self,
859        block: &Block,
860    ) -> Result<Option<MonadChainContext>, BlockchainError> {
861        self.is_monad().then(|| self.monad_context_for_mined_block(block)).transpose()
862    }
863
864    fn monad_context_before_mined_transaction(
865        &self,
866        block: &Block,
867        current_tx_index: usize,
868    ) -> Result<MonadChainContext, BlockchainError> {
869        let current = self.monad_tx_envs_from_storage(
870            &self.blockchain.storage.read(),
871            &block.body.transactions,
872        )?;
873        if current_tx_index > current.len() {
874            return Err(BlockchainError::DataUnavailable);
875        }
876        self.monad_context_for_mined_transactions(
877            block,
878            &current[..current_tx_index],
879            current_tx_index,
880        )
881    }
882
883    pub(super) fn active_monad_context_before_mined_transaction(
884        &self,
885        block: &Block,
886        current_tx_index: usize,
887    ) -> Result<Option<MonadChainContext>, BlockchainError> {
888        self.is_monad()
889            .then(|| self.monad_context_before_mined_transaction(block, current_tx_index))
890            .transpose()
891    }
892
893    /// Builds the Monad [`EvmEnv`] (spec and gas params) from a base env.
894    pub(super) fn build_monad_evm_env(
895        evm_env: &EvmEnv,
896        hardfork: MonadHardfork,
897    ) -> EvmEnv<MonadHardfork> {
898        EvmEnv::new(
899            evm_env.cfg_env.clone().with_spec_and_gas_params(hardfork, monad_gas_params(hardfork)),
900            evm_env.block_env.clone(),
901        )
902    }
903
904    /// Monad path of [`Backend::transact_call_with_inspector_ref`].
905    pub(super) fn transact_monad_with_inspector_ref<'db, I>(
906        &self,
907        db: &'db dyn DatabaseRef,
908        evm_env: &EvmEnv,
909        inspector: &mut I,
910        tx_env: TxEnv,
911        execution: PreparedExecution,
912    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
913    where
914        I: Inspector<MonadContext<WrapDatabaseRef<&'db dyn DatabaseRef>>>,
915    {
916        let monad_env = Self::build_monad_evm_env(evm_env, execution.hardfork);
917        let factory = MonadEvmFactory::default();
918        let mut evm = factory.create_evm_with_inspector(WrapDatabaseRef(db), monad_env, inspector);
919        evm.ctx_mut().chain = execution.context;
920        self.inject_configured_precompiles(evm.precompiles_mut(), evm_env);
921        match execution.kind {
922            EnvelopeExecutionKind::Transaction => Ok(evm.transact(tx_env)?),
923            EnvelopeExecutionKind::Replay => {
924                if let Some(result) = try_transact_monad_system_replay(&mut evm, &tx_env)? {
925                    Ok(result)
926                } else {
927                    Ok(evm.transact(tx_env)?)
928                }
929            }
930        }
931    }
932}
933
934pub(super) fn store_block_metadata<N: Network>(
935    storage: &mut BlockchainStorage<N>,
936    block_hash: B256,
937    participants: MonadBlockParticipants,
938    execution_chain_id: u64,
939    hardfork: FoundryHardfork,
940) {
941    storage.monad_block_participants.insert(block_hash, participants);
942    storage.monad_block_replay_profiles.insert(
943        block_hash,
944        MonadBlockReplayProfile { execution_chain_id, hardfork: MonadHardfork::from(hardfork) },
945    );
946}