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