Skip to main content

anvil/eth/backend/mem/
mod.rs

1//! In-memory blockchain backend.
2use self::{in_memory_db::StateRootDb, state::trie_storage, tempo::TempoChain};
3use crate::{
4    ForkChoice, NodeConfig, PrecompileFactory,
5    config::{ForkTransactionReplay, PruneStateHistoryConfig, source_hardfork},
6    eth::{
7        backend::{
8            cheats::{CheatEcrecover, CheatsManager, NextBlockOverrides},
9            db::{
10                AnvilCacheDB, BLOCKHASH_HISTORY, Db, EmptyAsAbsentDb, MaybeFullDatabase,
11                SerializableState, StateDb, StateRef,
12            },
13            executor::{
14                AnvilBlockExecutor, BlockExecutionKind, EthereumBlockTransitions,
15                ExecutedPoolTransactions, FoundryReceiptBuilder, PoolTxGasConfig,
16                apply_ethereum_post_execution_changes, apply_ethereum_pre_execution_changes,
17                block_blob_gas_limit, build_tx_env_for_pending, execute_pool_transaction,
18                execute_pool_transactions,
19            },
20            fork::{ClientFork, ForkEndpointIdentity},
21            genesis::GenesisConfig,
22            mem::{
23                state::{
24                    StateRootCache, state_root, state_trie_witness, storage_root, trie_accounts,
25                },
26                storage::MinedTransactionReceipt,
27            },
28            notifications::{ChainNotification, ChainNotifications, NewBlockNotification},
29            replay::{
30                ExecutedHistoricalReplay, HistoricalReplayTransaction,
31                PreparedForkTransactionReplay, execute_historical_replay,
32                prepare_fork_transaction_replay,
33            },
34            tempo::AnvilStorageProvider,
35            time::{PendingBlockTimestamp, TimeManager, TimeSnapshot, utc_from_secs},
36            validate::TransactionValidator,
37        },
38        error::{BlockchainError, ErrDetail, InvalidTransactionError},
39        fees::{FeeDetails, FeeManager, FeeSnapshot, MIN_SUGGESTED_PRIORITY_FEE},
40        macros::node_info,
41        pool::transactions::PoolTransaction,
42        preserve_simulation_request_fields,
43    },
44    mem::{
45        inspector::{AnvilInspector, InspectorTxConfig},
46        storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome},
47    },
48};
49use alloy_chains::NamedChain;
50use alloy_consensus::{
51    Blob, BlockBody, BlockHeader, EnvKzgSettings, Header, Signed, Transaction as TransactionTrait,
52    TransactionEnvelope, TrieAccount, TxEip4844Variant, TxEnvelope, TxReceipt, Typed2718,
53    constants::EMPTY_WITHDRAWALS,
54    proofs::{calculate_receipt_root, calculate_transaction_root},
55    transaction::Recovered,
56};
57use alloy_eips::{
58    BlockNumHash, Encodable2718, eip2935, eip4788,
59    eip4844::{DATA_GAS_PER_BLOB, kzg_to_versioned_hash},
60    eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS,
61    eip7002, eip7251,
62    eip7685::EMPTY_REQUESTS_HASH,
63    eip7840::BlobParams,
64    eip7910::SystemContract,
65    eip7928::{BlockAccessList, EMPTY_BLOCK_ACCESS_LIST_HASH, compute_block_access_list_hash},
66};
67use alloy_evm::{
68    Database, EthEvmFactory, Evm, EvmEnv, EvmFactory, FromTxWithEncoded,
69    block::{BlockExecutionResult, BlockExecutor, StateDB},
70    eth::{EthEvm, EthEvmContext},
71    overrides::{OverrideBlockHashes, apply_state_overrides},
72    precompiles::{DynPrecompile, MovePrecompileError, Precompile, PrecompilesMap},
73};
74use alloy_genesis::Genesis;
75use alloy_network::{
76    AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType,
77    BlockResponse, Network, NetworkTransactionBuilder, ReceiptResponse, UnknownTxEnvelope,
78    UnknownTypedTransaction,
79};
80use alloy_primitives::{
81    Address, B256, Bloom, Bytes, Signature, TxKind, U256, address, hex, keccak256,
82    map::{AddressMap, B256Set, HashMap, HashSet},
83};
84use alloy_rlp::{Decodable, Encodable};
85use alloy_rpc_types::{
86    AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides,
87    BlockTransactions, EIP1186AccountProofResponse as AccountProof,
88    EIP1186StorageProof as StorageProof, Filter, Header as AlloyHeader, Index, Log, Transaction,
89    TransactionReceipt,
90    anvil::Forking,
91    debug::ExecutionWitness,
92    request::TransactionRequest,
93    serde_helpers::JsonStorageKey,
94    simulate::{
95        MAX_SIMULATE_BLOCKS, SimBlock, SimCallResult, SimulateError, SimulatePayload,
96        SimulatedBlock,
97    },
98    state::{EvmOverrides, StateOverride},
99    trace::{
100        filter::TraceFilter,
101        geth::{
102            CallConfig, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig,
103            GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
104            NoopFrame, TraceResult,
105        },
106        opcode::{BlockOpcodeGas, TransactionOpcodeGas},
107        parity::{
108            LocalizedTransactionTrace, TraceResults, TraceResultsWithTransactionHash, TraceType,
109        },
110    },
111};
112use alloy_rpc_types_eth::{AccountInfo as RpcAccountInfo, Bundle, EthCallResponse};
113use alloy_rpc_types_mev::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult};
114use alloy_serde::{OtherFields, WithOtherFields};
115use alloy_sol_types::SolCall;
116use alloy_trie::{HashBuilder, Nibbles, proof::ProofRetainer, root::state_root_ref_unhashed};
117use anvil_core::eth::{
118    block::{Block, BlockInfo, canonical_block, create_block},
119    transaction::{MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo},
120};
121use anvil_rpc::error::{ErrorCode, RpcError};
122use chrono::Datelike;
123use eyre::{Context, Result};
124use flate2::{Compression, read::GzDecoder, write::GzEncoder};
125use foundry_evm::{
126    backend::{
127        BlockchainDb, DatabaseError, DatabaseResult, EmptyDBWrapper, RevertStateSnapshotAction,
128    },
129    constants::{DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE},
130    core::precompiles::EC_RECOVER,
131    decode::RevertDecoder,
132    hardfork::{EthereumHardfork, FoundryHardfork},
133    inspectors::AccessListInspector,
134    traces::{
135        CallTraceDecoder, FourByteInspector, GethTraceBuilder, TracingInspector,
136        TracingInspectorConfig,
137    },
138    utils::{
139        StateChangeset, apply_chain_specific_tx_replay_env_changes_for_chain,
140        block_env_from_header, get_blob_base_fee_update_fraction_by_spec_id,
141        get_blob_params_by_hardfork,
142    },
143};
144use foundry_evm_networks::{NetworkConfigs, apply_bsc_p256_precompile, arbitrum};
145use foundry_primitives::{
146    FoundryHeader, FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest,
147    FoundryTxEnvelope, FoundryTxReceipt, TempoTransactionRequest,
148};
149use futures::channel::mpsc::{UnboundedSender, unbounded};
150use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
151use revm::{
152    Database as RevmDatabase, DatabaseCommit, Inspector,
153    context::{Block as RevmBlock, BlockEnv, Cfg, CfgEnv, ContextSetters, ContextTr, TxEnv},
154    context_interface::{
155        JournalTr, Transaction as _,
156        block::BlobExcessGasAndPrice,
157        result::{
158            EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output, ResultAndState,
159        },
160        transaction::TransactionType,
161    },
162    database::{
163        AccountState, CacheDB, DbAccount, WrapDatabaseRef,
164        bal::{BalDatabase, BalState},
165    },
166    handler::{
167        EthFrame, EvmTr, EvmTrError, FrameResult, FrameTr, Handler as EvmHandler, validation,
168    },
169    inspector::{InspectorEvmTr, InspectorHandler},
170    interpreter::{InstructionResult, interpreter::EthInterpreter, interpreter_action::FrameInit},
171    precompile::{PrecompileSpecId, Precompiles},
172    primitives::{eip2780, hardfork::SpecId},
173    state::{Account, AccountInfo, EvmState, EvmStorageSlot, TransactionId},
174};
175use revm_inspectors::opcode::OpcodeGasInspector;
176use std::{
177    collections::BTreeMap,
178    fmt::{self, Debug},
179    io::{Read, Write},
180    marker::PhantomData,
181    ops::Mul,
182    path::{Path, PathBuf},
183    sync::{
184        Arc,
185        atomic::{AtomicBool, Ordering},
186    },
187    time::Duration,
188};
189use storage::{Blockchain, DEFAULT_HISTORY_LIMIT, MinedTransaction};
190use tempo_evm::{TempoPoolValidationEvm, evm::TempoEvmFactory};
191use tempo_hardfork::TempoHardfork;
192use tempo_precompiles::{
193    NONCE_PRECOMPILE_ADDRESS, TIP_FEE_MANAGER_ADDRESS, extend_tempo_precompiles,
194    nonce::NonceManager,
195    storage::{Handler, StorageActions, StorageCtx},
196    tip_fee_manager::{IFeeManager, TipFeeManager},
197    tip20::{ITIP20, TIP20Token},
198    tip20_factory::TIP20Factory,
199};
200use tempo_primitives::{
201    AASigned, SignatureType, TEMPO_TX_TYPE_ID, TempoSignature,
202    transaction::{
203        Call, KeychainSignature, PrimitiveSignature, RecoveredTempoAuthorization,
204        tt_signature::{P256SignatureWithPreHash, WebAuthnSignature},
205    },
206};
207use tempo_revm::{
208    ExecutionContext, TempoBatchCallEnv, TempoBlockEnv, TempoInvalidTransaction, TempoTxEnv,
209    evm::TempoContext, gas_params::tempo_gas_params,
210};
211#[cfg(test)]
212use tokio::sync::Notify;
213use tokio::{sync::RwLock as AsyncRwLock, task::JoinSet};
214
215#[cfg(any(feature = "base", feature = "optimism"))]
216use foundry_primitives::get_deposit_tx_parts;
217#[cfg(any(feature = "base", feature = "optimism"))]
218use op_alloy_consensus::DEPOSIT_TX_TYPE_ID;
219#[cfg(any(feature = "base", feature = "optimism"))]
220use op_revm::transaction::deposit::DepositTransactionParts;
221
222#[cfg(feature = "base")]
223use alloy_hardforks::ForkCondition;
224#[cfg(feature = "base")]
225use base_common_chains::{ChainConfig, ChainUpgrades};
226#[cfg(feature = "base")]
227use base_common_consensus::{
228    BaseTransactionInfo, BaseTxEnvelope, EIP8130_REJECTION_MSG, Eip8130Constants, Predeploys,
229};
230#[cfg(feature = "base")]
231use base_common_evm::{
232    BaseContext, BaseEvmFactory, BaseSpecId, BaseTime, BaseTransaction, DEPOSIT_TRANSACTION_TYPE,
233    DepositTransactionParts as BaseDepositTransactionParts, EIP8130_TRANSACTION_TYPE,
234    Eip8130PhaseStatuses, L1BlockInfo, ensure_create2_deployer, ensure_eip8130_system_accounts,
235};
236#[cfg(feature = "base")]
237use base_common_genesis::RollupConfig;
238#[cfg(feature = "base")]
239use base_common_precompiles::NonceManagerStorage;
240#[cfg(feature = "base")]
241use base_common_rpc_types::{
242    EIP8130_PRE_ZENITH_RPC_ERROR, Eip8130Nonce, Eip8130ReceiptFields,
243    Transaction as BaseRpcTransaction,
244};
245#[cfg(feature = "base")]
246use base_execution_eip8130::{FeeCheck, IntrinsicGas, IntrinsicGasInput};
247#[cfg(feature = "base")]
248use foundry_evm::{
249    core::{constants::SYSTEM_PRECOMPILE_STUB, evm::base_code_sentinel_addresses},
250    hardfork::BaseUpgrade,
251};
252#[cfg(feature = "base")]
253use revm::inspector::NoOpInspector;
254
255#[cfg(feature = "optimism")]
256use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx};
257#[cfg(feature = "optimism")]
258use foundry_evm::hardfork::OpHardfork;
259#[cfg(feature = "optimism")]
260use foundry_evm_networks::NetworkVariant;
261#[cfg(feature = "optimism")]
262use op_alloy_consensus::POST_EXEC_TX_TYPE_ID;
263#[cfg(feature = "optimism")]
264use op_revm::OpTransaction;
265
266/// Network-specific transaction data produced by [`Backend::build_call_env_with_base`].
267#[derive(Default, Clone, Debug)]
268struct CallTransactionInfo {
269    /// OP-compatible deposit fields shared by Base and Optimism RPC requests.
270    #[cfg(any(feature = "base", feature = "optimism"))]
271    deposit: DepositTransactionParts,
272}
273
274/// Fully prepared fork replacement awaiting an atomic backend commit.
275pub(crate) struct StagedForkReset {
276    node_config: NodeConfig,
277    db: Box<dyn Db>,
278    fees: FeeManager,
279    evm_env: EvmEnv,
280    fork: ClientFork,
281    timestamp: u64,
282    discard_old_cached_state: bool,
283    invalidated_cache_namespaces: Vec<ForkCacheNamespace>,
284    flush_old_cache: bool,
285    cache_lease: StagedForkCacheLease,
286}
287
288/// Fully prepared in-memory replacement awaiting an atomic backend commit.
289pub(crate) struct StagedMemoryReset<N: Network> {
290    node_config: NodeConfig,
291    db: Box<dyn Db>,
292    fees: FeeManager,
293    evm_env: EvmEnv,
294    hardfork: FoundryHardfork,
295    storage: BlockchainStorage<N>,
296    timestamp: u64,
297    flush_old_cache: bool,
298}
299
300/// Identifies the endpoint that supplied the most recently committed fork.
301#[derive(Clone, Debug, PartialEq, Eq)]
302struct ForkCacheSource {
303    rpc_url: String,
304    endpoint_identity: ForkEndpointIdentity,
305}
306
307impl ForkCacheSource {
308    fn from_fork(fork: &ClientFork) -> Option<Self> {
309        let config = fork.config.read();
310        Some(Self {
311            rpc_url: config.eth_rpc_url()?.to_string(),
312            endpoint_identity: config.endpoint_identity,
313        })
314    }
315
316    fn authoritative_identity_changed_at_same_url(
317        &self,
318        rpc_url: &str,
319        endpoint_identity: ForkEndpointIdentity,
320    ) -> bool {
321        // `hardfork` is populated only when `anvil_nodeInfo` succeeds, which makes the complete
322        // endpoint identity authoritative. Anonymous RPC endpoints intentionally retain Foundry's
323        // existing cache behavior when reused through the same URL.
324        let authoritative =
325            self.endpoint_identity.is_authoritative() || endpoint_identity.is_authoritative();
326        self.rpc_url == rpc_url && authoritative && self.endpoint_identity != endpoint_identity
327    }
328}
329
330/// Identifies one endpoint's persisted cache files across all blocks of a source chain.
331#[derive(Clone, Debug, PartialEq, Eq)]
332struct ForkCacheNamespace {
333    chain_cache_dir: PathBuf,
334    file_name: String,
335}
336
337impl ForkCacheNamespace {
338    fn new(source_chain_id: u64, rpc_url: &str) -> Option<Self> {
339        Some(Self {
340            chain_cache_dir: foundry_config::Config::foundry_chain_cache_dir(source_chain_id)?,
341            file_name: format!("storage-{:x}.json", keccak256(rpc_url)),
342        })
343    }
344
345    fn invalidate(&self) -> Result<(), BlockchainError> {
346        let entries = match std::fs::read_dir(&self.chain_cache_dir) {
347            Ok(entries) => entries,
348            Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()),
349            Err(err) => {
350                return Err(BlockchainError::Internal(format!(
351                    "failed to inspect fork cache at {}: {err}",
352                    self.chain_cache_dir.display()
353                )));
354            }
355        };
356
357        for entry in entries {
358            let entry = entry.map_err(|err| {
359                BlockchainError::Internal(format!(
360                    "failed to inspect fork cache at {}: {err}",
361                    self.chain_cache_dir.display()
362                ))
363            })?;
364            let file_type = entry.file_type().map_err(|err| {
365                BlockchainError::Internal(format!(
366                    "failed to inspect fork cache entry at {}: {err}",
367                    entry.path().display()
368                ))
369            })?;
370            if !file_type.is_dir() {
371                continue;
372            }
373            let cache_path = entry.path().join(&self.file_name);
374            if let Err(err) = std::fs::remove_file(&cache_path)
375                && err.kind() != std::io::ErrorKind::NotFound
376            {
377                return Err(BlockchainError::Internal(format!(
378                    "failed to invalidate fork cache at {}: {err}",
379                    cache_path.display()
380                )));
381            }
382        }
383        Ok(())
384    }
385}
386
387/// Keeps a staged fork from persisting remote state unless it is committed.
388#[derive(Clone, Debug, Default)]
389struct StagedForkCacheLease(Option<Arc<StagedForkCacheLeaseInner>>);
390
391#[derive(Debug)]
392struct StagedForkCacheLeaseInner {
393    db: BlockchainDb,
394    cache_path: PathBuf,
395    armed: AtomicBool,
396}
397
398impl StagedForkCacheLease {
399    fn new(db: BlockchainDb, cache_path: Option<PathBuf>) -> Self {
400        Self(cache_path.map(|cache_path| {
401            Arc::new(StagedForkCacheLeaseInner { db, cache_path, armed: AtomicBool::new(true) })
402        }))
403    }
404
405    fn for_db(db: &BlockchainDb) -> Self {
406        Self::new(db.clone(), db.cache().cache_path().map(Path::to_path_buf))
407    }
408
409    fn disarm(&self) {
410        if let Some(inner) = &self.0 {
411            inner.armed.store(false, Ordering::Release);
412        }
413    }
414
415    fn rollback(&self) -> Result<(), BlockchainError> {
416        let Some(inner) = &self.0 else { return Ok(()) };
417        // A clone owned by an in-flight database user must outlive that user's SharedBackend.
418        // Leave cleanup armed for the final owner instead of racing its eventual cache flush.
419        if Arc::strong_count(inner) == 1 && inner.armed.load(Ordering::Acquire) {
420            inner.invalidate()?;
421            inner.armed.store(false, Ordering::Release);
422        }
423        Ok(())
424    }
425}
426
427impl StagedForkCacheLeaseInner {
428    fn invalidate(&self) -> Result<(), BlockchainError> {
429        self.db.db().clear();
430        self.db.cache().flush();
431        // Forge can store the block cache as a file, so a child endpoint cache cannot exist.
432        if let Err(err) = std::fs::remove_file(&self.cache_path)
433            && !matches!(
434                err.kind(),
435                std::io::ErrorKind::NotFound | std::io::ErrorKind::NotADirectory
436            )
437        {
438            return Err(BlockchainError::Internal(format!(
439                "failed to invalidate fork cache at {}: {err}",
440                self.cache_path.display()
441            )));
442        }
443        Ok(())
444    }
445}
446
447impl Drop for StagedForkCacheLeaseInner {
448    fn drop(&mut self) {
449        if self.armed.swap(false, Ordering::AcqRel)
450            && let Err(err) = self.invalidate()
451        {
452            warn!(target: "backend", %err, "failed to roll back staged fork cache");
453        }
454    }
455}
456
457/// Couples an asynchronous staged-database user to its rollback lease.
458///
459/// Fields drop in declaration order, so the database handle (and its SharedBackend) is released
460/// before the lease can perform final cache cleanup.
461struct StagedForkDbUser<D> {
462    db: Option<Arc<AsyncRwLock<D>>>,
463    cache_lease: StagedForkCacheLease,
464}
465
466impl<D> Clone for StagedForkDbUser<D> {
467    fn clone(&self) -> Self {
468        Self { db: self.db.clone(), cache_lease: self.cache_lease.clone() }
469    }
470}
471
472impl<D> StagedForkDbUser<D> {
473    const fn db(&self) -> &Arc<AsyncRwLock<D>> {
474        self.db.as_ref().expect("staged fork database must be present until drop")
475    }
476}
477
478impl<D> Drop for StagedForkDbUser<D> {
479    fn drop(&mut self) {
480        // Explicitly release the SharedBackend before `cache_lease` can invalidate its cache.
481        drop(self.db.take());
482    }
483}
484
485#[cfg(feature = "monad")]
486pub(crate) type MonadReplayContext = monad_revm::MonadChainContext;
487// Opaque stand-in that keeps feature-independent replay context plumbing type-stable.
488#[cfg(not(feature = "monad"))]
489#[derive(Clone)]
490pub(crate) struct MonadReplayContext;
491
492#[cfg(feature = "monad")]
493enum MonadExecutionContext<'a> {
494    Exact(Box<MonadReplayContext>),
495    Next(&'a mut MonadReplayContext),
496}
497
498#[cfg(not(feature = "monad"))]
499struct MonadExecutionContext<'a> {
500    _marker: std::marker::PhantomData<&'a mut MonadReplayContext>,
501}
502
503#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
504enum EnvelopeExecutionKind {
505    #[default]
506    Transaction,
507    Replay,
508}
509
510#[cfg_attr(not(feature = "monad"), allow(dead_code))]
511struct EnvelopeExecution<'a> {
512    monad_context: Option<MonadExecutionContext<'a>>,
513    kind: EnvelopeExecutionKind,
514    hardfork: FoundryHardfork,
515}
516
517impl<'a> EnvelopeExecution<'a> {
518    const fn transaction(
519        monad_context: Option<MonadExecutionContext<'a>>,
520        hardfork: FoundryHardfork,
521    ) -> Self {
522        Self { monad_context, kind: EnvelopeExecutionKind::Transaction, hardfork }
523    }
524
525    const fn replay(
526        monad_context: Option<MonadExecutionContext<'a>>,
527        hardfork: FoundryHardfork,
528    ) -> Self {
529        Self { monad_context, kind: EnvelopeExecutionKind::Replay, hardfork }
530    }
531}
532
533#[cfg(feature = "monad")]
534fn monad_execution_context_at(
535    context: Option<&MonadReplayContext>,
536    current_tx_index: usize,
537) -> Option<MonadExecutionContext<'static>> {
538    context.map(|context| {
539        let mut context = context.clone();
540        context.current_tx_index = current_tx_index;
541        MonadExecutionContext::Exact(Box::new(context))
542    })
543}
544
545#[cfg(not(feature = "monad"))]
546const fn monad_execution_context_at(
547    _context: Option<&MonadReplayContext>,
548    _current_tx_index: usize,
549) -> Option<MonadExecutionContext<'static>> {
550    None
551}
552
553#[cfg(feature = "monad")]
554const fn next_monad_context(context: &mut MonadReplayContext) -> MonadExecutionContext<'_> {
555    MonadExecutionContext::Next(context)
556}
557
558#[cfg(not(feature = "monad"))]
559const fn next_monad_context(_context: &mut MonadReplayContext) -> MonadExecutionContext<'_> {
560    MonadExecutionContext { _marker: std::marker::PhantomData }
561}
562
563/// Maximum cumulative gas available to one `eth_simulateV1` request.
564const SIMULATE_GAS_CAP: u64 = 50_000_000;
565const SEPOLIA_DEPOSIT_CONTRACT_ADDRESS: Address =
566    address!("7f02c3e3c98b133055b8b348b2ac625669ed295d");
567const HOLESKY_DEPOSIT_CONTRACT_ADDRESS: Address = Address::repeat_byte(0x42);
568
569/// Fixed transaction context for direct Tempo RPC simulations.
570const TEMPO_RPC_SIMULATION_CONTEXT: B256 = B256::new(*b"TEMPO_RPC_SIMULATION_MPP_CONTEXT");
571
572/// Ethereum handler that skips blob fee cap validation for non-validating calls with a zero cap.
573struct SimulationHandler<EVM, ERROR, FRAME> {
574    _phantom: PhantomData<(EVM, ERROR, FRAME)>,
575}
576
577impl<EVM, ERROR, FRAME> Default for SimulationHandler<EVM, ERROR, FRAME> {
578    fn default() -> Self {
579        Self { _phantom: PhantomData }
580    }
581}
582
583impl<EVM, ERROR, FRAME> EvmHandler for SimulationHandler<EVM, ERROR, FRAME>
584where
585    EVM: EvmTr<
586            Context: ContextTr<
587                Block = BlockEnv,
588                Tx = TxEnv,
589                Journal: JournalTr<State = EvmState>,
590            > + ContextSetters,
591            Frame = FRAME,
592        >,
593    ERROR: EvmTrError<EVM>,
594    FRAME: FrameTr<FrameResult = FrameResult, FrameInit = FrameInit>,
595{
596    type Evm = EVM;
597    type Error = ERROR;
598    type HaltReason = HaltReason;
599
600    fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
601        let skip_blob_fee_check = evm.ctx_ref().cfg().is_base_fee_check_disabled()
602            && evm.ctx_ref().tx().tx_type == 3
603            && evm.ctx_ref().tx().max_fee_per_blob_gas == 0;
604        if !skip_blob_fee_check {
605            return validation::validate_env(evm.ctx());
606        }
607
608        let block = evm.ctx_ref().block().clone();
609        let mut validation_block = block.clone();
610        if let Some(blob_gas_and_price) = &mut validation_block.blob_excess_gas_and_price {
611            blob_gas_and_price.blob_gasprice = 0;
612        }
613        evm.ctx().set_block(validation_block);
614        let result = validation::validate_env(evm.ctx());
615        evm.ctx().set_block(block);
616        result
617    }
618}
619
620impl<EVM, ERROR> InspectorHandler for SimulationHandler<EVM, ERROR, EthFrame<EthInterpreter>>
621where
622    EVM: InspectorEvmTr<
623            Context: ContextTr<
624                Block = BlockEnv,
625                Tx = TxEnv,
626                Journal: JournalTr<State = EvmState>,
627            > + ContextSetters,
628            Frame = EthFrame<EthInterpreter>,
629            Inspector: Inspector<<EVM as EvmTr>::Context, EthInterpreter>,
630        >,
631    ERROR: EvmTrError<EVM>,
632{
633    type IT = EthInterpreter;
634}
635
636#[derive(Clone)]
637enum CallTxEnv {
638    Eth(TxEnv),
639    #[cfg(feature = "base")]
640    Base(Box<BaseTransaction<TxEnv>>),
641    #[cfg(feature = "monad")]
642    Monad(TxEnv),
643    #[cfg(feature = "optimism")]
644    Op(OpTransaction<TxEnv>),
645    Tempo(TempoTxEnv),
646}
647
648impl CallTxEnv {
649    #[cfg_attr(not(feature = "js-tracer"), allow(dead_code))]
650    const fn base(&self) -> &TxEnv {
651        match self {
652            Self::Eth(tx) => tx,
653            #[cfg(feature = "base")]
654            Self::Base(tx) => &tx.base,
655            #[cfg(feature = "monad")]
656            Self::Monad(tx) => tx,
657            #[cfg(feature = "optimism")]
658            Self::Op(tx) => &tx.base,
659            Self::Tempo(tx) => &tx.inner,
660        }
661    }
662
663    const fn base_mut(&mut self) -> &mut TxEnv {
664        match self {
665            Self::Eth(tx) => tx,
666            #[cfg(feature = "base")]
667            Self::Base(tx) => &mut tx.base,
668            #[cfg(feature = "monad")]
669            Self::Monad(tx) => tx,
670            #[cfg(feature = "optimism")]
671            Self::Op(tx) => &mut tx.base,
672            Self::Tempo(tx) => &mut tx.inner,
673        }
674    }
675
676    fn into_base(self) -> TxEnv {
677        match self {
678            Self::Eth(tx) => tx,
679            #[cfg(feature = "base")]
680            Self::Base(tx) => tx.base,
681            #[cfg(feature = "monad")]
682            Self::Monad(tx) => tx,
683            #[cfg(feature = "optimism")]
684            Self::Op(tx) => tx.base,
685            Self::Tempo(tx) => tx.inner,
686        }
687    }
688
689    fn uses_protocol_call_nonce(&self) -> bool {
690        match self {
691            Self::Eth(tx) => tx.kind.is_call(),
692            #[cfg(feature = "base")]
693            Self::Base(tx) => {
694                tx.eip8130.is_none()
695                    && tx.base.tx_type != DEPOSIT_TRANSACTION_TYPE
696                    && tx.base.kind.is_call()
697            }
698            #[cfg(feature = "monad")]
699            Self::Monad(tx) => tx.kind.is_call(),
700            #[cfg(feature = "optimism")]
701            Self::Op(tx) => tx.base.kind.is_call(),
702            Self::Tempo(tx) => tx.tempo_tx_env.as_ref().map_or_else(
703                || tx.inner.kind.is_call(),
704                |aa| {
705                    aa.nonce_key.is_zero()
706                        && aa.aa_calls.first().is_some_and(|call| call.to.is_call())
707                },
708            ),
709        }
710    }
711}
712
713fn apply_tempo_envelope_identity(tx_env: &mut CallTxEnv, simulated_tx: Option<&AASigned>) {
714    if let (CallTxEnv::Tempo(tx_env), Some(simulated_tx)) = (tx_env, simulated_tx) {
715        tx_env.unique_tx_identifier = Some(simulated_tx.expiring_nonce_hash(tx_env.inner.caller));
716        if let Some(batch) = &mut tx_env.tempo_tx_env {
717            batch.tx_hash = *simulated_tx.hash();
718        }
719    }
720}
721
722struct PreparedCall {
723    evm_env: EvmEnv,
724    tx_env: CallTxEnv,
725    simulated_tempo_tx: Option<AASigned>,
726    #[cfg(feature = "base")]
727    simulated_envelope: Option<FoundryTxEnvelope>,
728}
729
730#[derive(Default)]
731struct TypedCallOverrides {
732    gas_limit: Option<u64>,
733    access_list: Option<AccessList>,
734    disable_fee_charge: bool,
735}
736
737pub(crate) struct GasEstimateCallOptions {
738    gas_limit: u64,
739    disable_fee_charge: bool,
740    monad_context: Option<MonadReplayContext>,
741}
742
743impl GasEstimateCallOptions {
744    pub(crate) const fn new(
745        gas_limit: u64,
746        disable_fee_charge: bool,
747        monad_context: Option<MonadReplayContext>,
748    ) -> Self {
749        Self { gas_limit, disable_fee_charge, monad_context }
750    }
751}
752
753/// Marker trait that abstracts over the per-network inspector trait bounds
754/// required by the in-memory backend. The OP bound is only included when the
755/// `optimism` feature is enabled.
756#[cfg(all(feature = "base", feature = "optimism", feature = "monad"))]
757pub trait BackendInspector<DB: Database>:
758    Inspector<EthEvmContext<DB>>
759    + Inspector<BaseContext<DB>>
760    + Inspector<OpEvmContext<DB>>
761    + Inspector<TempoContext<DB>>
762    + Inspector<alloy_monad_evm::MonadContext<DB>>
763{
764}
765#[cfg(all(feature = "base", feature = "optimism", feature = "monad"))]
766impl<DB: Database, T> BackendInspector<DB> for T where
767    T: Inspector<EthEvmContext<DB>>
768        + Inspector<BaseContext<DB>>
769        + Inspector<OpEvmContext<DB>>
770        + Inspector<TempoContext<DB>>
771        + Inspector<alloy_monad_evm::MonadContext<DB>>
772{
773}
774#[cfg(all(feature = "base", feature = "optimism", not(feature = "monad")))]
775pub trait BackendInspector<DB: Database>:
776    Inspector<EthEvmContext<DB>>
777    + Inspector<BaseContext<DB>>
778    + Inspector<OpEvmContext<DB>>
779    + Inspector<TempoContext<DB>>
780{
781}
782#[cfg(all(feature = "base", feature = "optimism", not(feature = "monad")))]
783impl<DB: Database, T> BackendInspector<DB> for T where
784    T: Inspector<EthEvmContext<DB>>
785        + Inspector<BaseContext<DB>>
786        + Inspector<OpEvmContext<DB>>
787        + Inspector<TempoContext<DB>>
788{
789}
790#[cfg(all(feature = "base", not(feature = "optimism"), feature = "monad"))]
791pub trait BackendInspector<DB: Database>:
792    Inspector<EthEvmContext<DB>>
793    + Inspector<BaseContext<DB>>
794    + Inspector<TempoContext<DB>>
795    + Inspector<alloy_monad_evm::MonadContext<DB>>
796{
797}
798#[cfg(all(feature = "base", not(feature = "optimism"), feature = "monad"))]
799impl<DB: Database, T> BackendInspector<DB> for T where
800    T: Inspector<EthEvmContext<DB>>
801        + Inspector<BaseContext<DB>>
802        + Inspector<TempoContext<DB>>
803        + Inspector<alloy_monad_evm::MonadContext<DB>>
804{
805}
806#[cfg(all(feature = "base", not(feature = "optimism"), not(feature = "monad")))]
807pub trait BackendInspector<DB: Database>:
808    Inspector<EthEvmContext<DB>> + Inspector<BaseContext<DB>> + Inspector<TempoContext<DB>>
809{
810}
811#[cfg(all(feature = "base", not(feature = "optimism"), not(feature = "monad")))]
812impl<DB: Database, T> BackendInspector<DB> for T where
813    T: Inspector<EthEvmContext<DB>> + Inspector<BaseContext<DB>> + Inspector<TempoContext<DB>>
814{
815}
816#[cfg(all(not(feature = "base"), feature = "optimism", feature = "monad"))]
817pub trait BackendInspector<DB: Database>:
818    Inspector<EthEvmContext<DB>>
819    + Inspector<OpEvmContext<DB>>
820    + Inspector<TempoContext<DB>>
821    + Inspector<alloy_monad_evm::MonadContext<DB>>
822{
823}
824#[cfg(all(not(feature = "base"), feature = "optimism", feature = "monad"))]
825impl<DB: Database, T> BackendInspector<DB> for T where
826    T: Inspector<EthEvmContext<DB>>
827        + Inspector<OpEvmContext<DB>>
828        + Inspector<TempoContext<DB>>
829        + Inspector<alloy_monad_evm::MonadContext<DB>>
830{
831}
832#[cfg(all(not(feature = "base"), feature = "optimism", not(feature = "monad")))]
833pub trait BackendInspector<DB: Database>:
834    Inspector<EthEvmContext<DB>> + Inspector<OpEvmContext<DB>> + Inspector<TempoContext<DB>>
835{
836}
837#[cfg(all(not(feature = "base"), feature = "optimism", not(feature = "monad")))]
838impl<DB: Database, T> BackendInspector<DB> for T where
839    T: Inspector<EthEvmContext<DB>> + Inspector<OpEvmContext<DB>> + Inspector<TempoContext<DB>>
840{
841}
842#[cfg(all(not(feature = "base"), not(feature = "optimism"), feature = "monad"))]
843pub trait BackendInspector<DB: Database>:
844    Inspector<EthEvmContext<DB>>
845    + Inspector<TempoContext<DB>>
846    + Inspector<alloy_monad_evm::MonadContext<DB>>
847{
848}
849#[cfg(all(not(feature = "base"), not(feature = "optimism"), feature = "monad"))]
850impl<DB: Database, T> BackendInspector<DB> for T where
851    T: Inspector<EthEvmContext<DB>>
852        + Inspector<TempoContext<DB>>
853        + Inspector<alloy_monad_evm::MonadContext<DB>>
854{
855}
856#[cfg(all(not(feature = "base"), not(feature = "optimism"), not(feature = "monad")))]
857pub trait BackendInspector<DB: Database>:
858    Inspector<EthEvmContext<DB>> + Inspector<TempoContext<DB>>
859{
860}
861#[cfg(all(not(feature = "base"), not(feature = "optimism"), not(feature = "monad")))]
862impl<DB: Database, T> BackendInspector<DB> for T where
863    T: Inspector<EthEvmContext<DB>> + Inspector<TempoContext<DB>>
864{
865}
866#[cfg(feature = "base")]
867mod base;
868pub mod cache;
869pub mod fork_db;
870pub mod in_memory_db;
871pub mod inspector;
872#[cfg(feature = "monad")]
873mod monad;
874#[cfg(feature = "optimism")]
875pub mod optimism;
876pub mod state;
877pub mod storage;
878mod tempo;
879
880/// Helper trait that combines revm::DatabaseRef with Debug.
881/// This is needed because alloy-evm requires Debug on Database implementations.
882/// With trait upcasting now stable, we can now upcast from this trait to revm::DatabaseRef.
883pub trait DatabaseRef: revm::DatabaseRef<Error = DatabaseError> + Debug {}
884impl<T> DatabaseRef for T where T: revm::DatabaseRef<Error = DatabaseError> + Debug {}
885
886// Gas per transaction not creating a contract.
887pub const MIN_TRANSACTION_GAS: u128 = 21000;
888// Gas per transaction creating a contract.
889pub const MIN_CREATE_GAS: u128 = 53000;
890/// Standalone Base L1 base fee, in wei.
891#[cfg(feature = "base")]
892const DEFAULT_BASE_L1_BASE_FEE: u64 = 1_000_000_000;
893/// Standalone Base L1 base-fee scalar, with six decimal places.
894#[cfg(feature = "base")]
895const DEFAULT_BASE_L1_FEE_SCALAR: u32 = 1_000_000;
896
897/// Installs the BaseTime proxy and links its Denim implementation.
898///
899/// Standalone genesis owns the proxy admin and always resets it. A fork must not rewrite an
900/// inherited deployment, so the proxy code and admin are only seeded when the account has no code.
901#[cfg(feature = "base")]
902fn ensure_base_time_predeploy(
903    mut db: &mut dyn crate::eth::backend::db::Db,
904    reset_admin: bool,
905) -> Result<(), DatabaseError> {
906    base::ensure_l1_block_predeploy(db)?;
907    let mut proxy = db.basic(Predeploys::BASE_TIME)?.unwrap_or_default();
908    let missing_code = proxy.is_empty_code_hash();
909    if missing_code {
910        let code = revm::state::Bytecode::new_raw(BaseTime::proxy_bytecode());
911        proxy.code_hash = code.hash_slow();
912        proxy.code = Some(code);
913        db.insert_account(Predeploys::BASE_TIME, proxy);
914    }
915    if reset_admin || missing_code {
916        db.set_storage_at(
917            Predeploys::BASE_TIME,
918            B256::from(BaseTime::ADMIN_SLOT.to_be_bytes::<32>()),
919            B256::from(U256::from_be_slice(Predeploys::PROXY_ADMIN.as_slice()).to_be_bytes::<32>()),
920        )?;
921    }
922    let upgrades = ChainUpgrades::new([(BaseUpgrade::Denim, ForkCondition::Timestamp(1))]);
923    BaseTime::ensure_predeploy(upgrades, 1, &mut db)
924        .map_err(|err| DatabaseError::AnyRequest(Arc::new(eyre::eyre!(err))))
925}
926
927fn tempo_nonce(
928    state: &dyn DatabaseRef,
929    caller: Address,
930    nonce_key: U256,
931) -> Result<u64, BlockchainError> {
932    if nonce_key.is_zero() {
933        return Ok(state.basic_ref(caller)?.map(|account| account.nonce).unwrap_or_default());
934    }
935    if nonce_key == U256::MAX {
936        return Ok(0);
937    }
938    let slot = NonceManager::new().nonces[caller][nonce_key].slot();
939    Ok(state.storage_ref(NONCE_PRECOMPILE_ADDRESS, slot)?.saturating_to())
940}
941
942fn mock_tempo_signature(
943    key_type: SignatureType,
944    key_data: Option<Bytes>,
945    key_id: Option<Address>,
946    caller: Address,
947    is_t1c: bool,
948) -> TempoSignature {
949    let signature = match key_type {
950        SignatureType::Secp256k1 => {
951            PrimitiveSignature::Secp256k1(Signature::new(U256::ZERO, U256::ZERO, false))
952        }
953        SignatureType::P256 => PrimitiveSignature::P256(P256SignatureWithPreHash {
954            r: B256::ZERO,
955            s: B256::ZERO,
956            pub_key_x: B256::ZERO,
957            pub_key_y: B256::ZERO,
958            pre_hash: false,
959        }),
960        SignatureType::WebAuthn => {
961            const CLIENT_JSON: &str = r#"{"type":"webauthn.get","challenge":"","origin":""}"#;
962            const AUTH_DATA_SIZE: usize = 37;
963            const MIN_SIZE: usize = AUTH_DATA_SIZE + CLIENT_JSON.len();
964            const DEFAULT_SIZE: usize = 800;
965            const MAX_SIZE: usize = 8192;
966
967            let size = key_data
968                .as_deref()
969                .and_then(|data| match data.len() {
970                    1 => Some(data[0] as usize),
971                    2 => Some(u16::from_be_bytes([data[0], data[1]]) as usize),
972                    4 => Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize),
973                    _ => None,
974                })
975                .unwrap_or(DEFAULT_SIZE)
976                .clamp(MIN_SIZE, MAX_SIZE);
977            let mut webauthn_data = vec![0u8; AUTH_DATA_SIZE];
978            webauthn_data[32] = 0x01;
979            let padding = "x".repeat(size - MIN_SIZE);
980            webauthn_data.extend_from_slice(
981                format!(r#"{{"type":"webauthn.get","challenge":"","origin":"{padding}"}}"#)
982                    .as_bytes(),
983            );
984            PrimitiveSignature::WebAuthn(WebAuthnSignature {
985                webauthn_data: webauthn_data.into(),
986                r: B256::ZERO,
987                s: B256::ZERO,
988                pub_key_x: B256::ZERO,
989                pub_key_y: B256::ZERO,
990            })
991        }
992    };
993
994    if key_id.is_some() {
995        let signature = if is_t1c {
996            KeychainSignature::new(caller, signature)
997        } else {
998            KeychainSignature::new_v1(caller, signature)
999        };
1000        TempoSignature::Keychain(signature)
1001    } else {
1002        TempoSignature::Primitive(signature)
1003    }
1004}
1005
1006fn call_config_from_tracer_config(
1007    tracer_config: GethDebugTracerConfig,
1008) -> Result<CallConfig, serde_json::Error> {
1009    let mut tracer_config = tracer_config.into_json();
1010    if let Some(config) = tracer_config.as_object_mut()
1011        && !config.contains_key("onlyTopCall")
1012        && let Some(only_top_level_call) = config.remove("onlyTopLevelCall")
1013    {
1014        config.insert("onlyTopCall".to_string(), only_top_level_call);
1015    }
1016
1017    GethDebugTracerConfig(tracer_config).into_call_config()
1018}
1019
1020/// Builds Parity trace results for `result`, with `db` as the state before the transaction.
1021///
1022/// The state diff treats empty accounts as absent, so created and newly funded accounts are
1023/// marked as added.
1024fn parity_trace_results(
1025    inspector: TracingInspector,
1026    result: &ResultAndState<HaltReason>,
1027    trace_types: &HashSet<TraceType>,
1028    db: impl revm::DatabaseRef<Error = DatabaseError>,
1029) -> Result<TraceResults, BlockchainError> {
1030    inspector
1031        .into_parity_builder()
1032        .into_trace_results_with_state(result, trace_types, EmptyAsAbsentDb(db))
1033        .map_err(Into::into)
1034}
1035
1036/// Rejects the `pending` tag for block trace methods, which only trace mined blocks.
1037///
1038/// Resolving it to the latest block would present mined transactions as the pending block.
1039fn ensure_mined_trace_block(block: BlockNumber) -> Result<(), BlockchainError> {
1040    if block.is_pending() {
1041        return Err(BlockchainError::RpcError(RpcError::invalid_params(
1042            "pending block traces are not supported",
1043        )));
1044    }
1045    Ok(())
1046}
1047
1048#[derive(Clone, Debug, Default)]
1049struct SimulationPrecompileOverrides {
1050    moves: Vec<(Address, Address)>,
1051}
1052
1053/// A block request, which includes the Pool Transactions if it's Pending
1054pub enum BlockRequest<T> {
1055    Pending(Vec<Arc<PoolTransaction<T>>>),
1056    Number(u64),
1057}
1058
1059impl<T> fmt::Debug for BlockRequest<T> {
1060    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1061        match self {
1062            Self::Pending(txs) => f.debug_tuple("Pending").field(&txs.len()).finish(),
1063            Self::Number(n) => f.debug_tuple("Number").field(n).finish(),
1064        }
1065    }
1066}
1067
1068impl<T> BlockRequest<T> {
1069    pub const fn block_number(&self) -> BlockNumber {
1070        match *self {
1071            Self::Pending(_) => BlockNumber::Pending,
1072            Self::Number(n) => BlockNumber::Number(n),
1073        }
1074    }
1075}
1076
1077struct StateSnapshot {
1078    block_number: u64,
1079    block_hash: B256,
1080    fees: FeeSnapshot,
1081    time: TimeSnapshot,
1082    next_block: NextBlockOverrides,
1083}
1084
1085#[cfg(test)]
1086#[derive(Clone)]
1087struct MiningCommitHook {
1088    reached: Arc<Notify>,
1089    resume: Arc<Notify>,
1090}
1091
1092#[cfg(test)]
1093type StorageAccessHook = Box<dyn Fn() + Send + Sync>;
1094
1095/// Gives access to the [revm::Database]
1096pub struct Backend<N: Network> {
1097    /// Access to [`revm::Database`] abstraction.
1098    ///
1099    /// This will be used in combination with [`alloy_evm::Evm`] and is responsible for feeding
1100    /// data to the evm during its execution.
1101    ///
1102    /// At time of writing, there are two different types of `Db`:
1103    ///   - [`MemDb`](crate::mem::in_memory_db::MemDb): everything is stored in memory
1104    ///   - [`ForkDb`](crate::mem::fork_db::ForkedDatabase): forks off a remote client, missing
1105    ///     data is retrieved via RPC-calls
1106    ///
1107    /// In order to commit changes to the [`revm::Database`], the [`alloy_evm::Evm`] requires
1108    /// mutable access, which requires a write-lock from this `db`. In forking mode, the time
1109    /// during which the write-lock is active depends on whether the `ForkDb` can provide all
1110    /// requested data from memory or whether it has to retrieve it via RPC calls first. This
1111    /// means that it potentially blocks for some time, even taking into account the rate
1112    /// limits of RPC endpoints. Therefore the `Db` is guarded by a `tokio::sync::RwLock` here
1113    /// so calls that need to read from it, while it's currently written to, don't block. E.g.
1114    /// a new block is currently mined and a new [`Self::set_storage_at()`] request is being
1115    /// executed.
1116    db: Arc<AsyncRwLock<Box<dyn Db>>>,
1117    /// stores all block related data in memory.
1118    blockchain: Blockchain<N>,
1119    /// Historic states of previous blocks.
1120    states: Arc<RwLock<InMemoryBlockStates>>,
1121    /// EVM environment data of the chain (block env, cfg env).
1122    evm_env: Arc<RwLock<EvmEnv>>,
1123    /// Network configuration (optimism, custom precompiles, etc.)
1124    networks: NetworkConfigs,
1125    /// Base activation-registry administrator override.
1126    #[cfg(feature = "base")]
1127    base_activation_admin: Option<Address>,
1128    /// The active hardfork.
1129    hardfork: Arc<RwLock<FoundryHardfork>>,
1130    /// This is set if this is currently forked off another client.
1131    fork: Arc<RwLock<Option<ClientFork>>>,
1132    /// The last source that supplied the live fork backend, retained across memory resets.
1133    last_fork_cache_source: Arc<RwLock<Option<ForkCacheSource>>>,
1134    /// Provides time related info, like timestamp.
1135    time: TimeManager,
1136    /// Contains state of custom overrides.
1137    cheats: CheatsManager,
1138    /// Contains fee data.
1139    fees: FeeManager,
1140    /// Initialised genesis.
1141    genesis: GenesisConfig,
1142    /// Listeners for new blocks that get notified when a new block was imported or when logs were
1143    /// removed from the canonical chain due to a reorg.
1144    new_block_listeners: Arc<Mutex<Vec<UnboundedSender<ChainNotification>>>>,
1145    /// Keeps track of active state snapshots at a specific block.
1146    active_state_snapshots: Arc<Mutex<HashMap<U256, StateSnapshot>>>,
1147    enable_steps_tracing: bool,
1148    print_logs: bool,
1149    print_traces: bool,
1150    /// Recorder used for decoding traces, used together with print_traces.
1151    call_trace_decoder: Arc<RwLock<Arc<CallTraceDecoder>>>,
1152    /// How to keep history state
1153    prune_state_history_config: PruneStateHistoryConfig,
1154    /// max number of blocks with transactions in memory
1155    transaction_block_keeper: Option<usize>,
1156    pub(crate) node_config: Arc<AsyncRwLock<NodeConfig>>,
1157    /// Slots in an epoch
1158    slots_in_an_epoch: u64,
1159    /// Precompiles to inject to the EVM.
1160    precompile_factory: Option<Arc<dyn PrecompileFactory>>,
1161    /// Prevent race conditions during mining
1162    mining: Arc<tokio::sync::Mutex<()>>,
1163    /// Test-only synchronization point after database commit and before canonical publication.
1164    #[cfg(test)]
1165    mining_commit_hook: Arc<Mutex<Option<MiningCommitHook>>>,
1166    /// Test-only observer immediately before the mined state root is computed.
1167    #[cfg(test)]
1168    mining_state_root_hook: Arc<Mutex<Option<StorageAccessHook>>>,
1169    /// Test-only observer between consensus and canonical rollback updates.
1170    #[cfg(test)]
1171    rollback_unwind_hook: Arc<Mutex<Option<StorageAccessHook>>>,
1172    /// Disable pool balance checks
1173    disable_pool_balance_checks: bool,
1174    /// Keeps startup fork-cache rollback armed until startup initialization completes.
1175    ///
1176    /// This must remain the final field so all other backend-held database references are released
1177    /// before a rejected startup invalidates its cache.
1178    startup_fork_cache_user: StagedForkDbUser<Box<dyn Db>>,
1179}
1180
1181impl<N: Network> Clone for Backend<N> {
1182    fn clone(&self) -> Self {
1183        Self {
1184            db: self.db.clone(),
1185            blockchain: self.blockchain.clone(),
1186            states: self.states.clone(),
1187            evm_env: self.evm_env.clone(),
1188            networks: self.networks,
1189            #[cfg(feature = "base")]
1190            base_activation_admin: self.base_activation_admin,
1191            hardfork: self.hardfork.clone(),
1192            fork: self.fork.clone(),
1193            last_fork_cache_source: self.last_fork_cache_source.clone(),
1194            time: self.time.clone(),
1195            cheats: self.cheats.clone(),
1196            fees: self.fees.clone(),
1197            genesis: self.genesis.clone(),
1198            new_block_listeners: self.new_block_listeners.clone(),
1199            active_state_snapshots: self.active_state_snapshots.clone(),
1200            enable_steps_tracing: self.enable_steps_tracing,
1201            print_logs: self.print_logs,
1202            print_traces: self.print_traces,
1203            call_trace_decoder: self.call_trace_decoder.clone(),
1204            prune_state_history_config: self.prune_state_history_config,
1205            transaction_block_keeper: self.transaction_block_keeper,
1206            node_config: self.node_config.clone(),
1207            slots_in_an_epoch: self.slots_in_an_epoch,
1208            precompile_factory: self.precompile_factory.clone(),
1209            mining: self.mining.clone(),
1210            #[cfg(test)]
1211            mining_commit_hook: self.mining_commit_hook.clone(),
1212            #[cfg(test)]
1213            mining_state_root_hook: self.mining_state_root_hook.clone(),
1214            #[cfg(test)]
1215            rollback_unwind_hook: self.rollback_unwind_hook.clone(),
1216            disable_pool_balance_checks: self.disable_pool_balance_checks,
1217            startup_fork_cache_user: self.startup_fork_cache_user.clone(),
1218        }
1219    }
1220}
1221
1222impl<N: Network> fmt::Debug for Backend<N> {
1223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1224        f.debug_struct("Backend").finish_non_exhaustive()
1225    }
1226}
1227
1228// Methods that are generic over any Network.
1229impl<N: Network> Backend<N> {
1230    /// Sets the account to impersonate
1231    ///
1232    /// Returns `true` if the account is already impersonated
1233    pub fn impersonate(&self, addr: Address) -> bool {
1234        if self.cheats.impersonated_accounts().contains(&addr) {
1235            return true;
1236        }
1237        // Ensure EIP-3607 is disabled
1238        self.evm_env.write().cfg_env.disable_eip3607 = true;
1239        self.cheats.impersonate(addr)
1240    }
1241
1242    /// Removes the account that from the impersonated set
1243    ///
1244    /// If the impersonated `addr` is a contract then we also reset the code here
1245    pub fn stop_impersonating(&self, addr: Address) {
1246        self.cheats.stop_impersonating(&addr);
1247    }
1248
1249    /// If set to true will make every account impersonated
1250    pub fn auto_impersonate_account(&self, enabled: bool) {
1251        self.cheats.set_auto_impersonate_account(enabled);
1252    }
1253
1254    /// Returns the configured fork, if any
1255    pub fn get_fork(&self) -> Option<ClientFork> {
1256        self.fork.read().clone()
1257    }
1258
1259    /// Marks startup fork-cache writes as belonging to the validated live backend.
1260    pub(crate) fn commit_startup_fork_cache(&self) {
1261        self.startup_fork_cache_user.cache_lease.disarm();
1262    }
1263
1264    /// Returns the database
1265    pub fn get_db(&self) -> &Arc<AsyncRwLock<Box<dyn Db>>> {
1266        &self.db
1267    }
1268
1269    /// Locks block production while a backend-wide lifecycle transition is committed.
1270    pub(crate) async fn lock_mining(&self) -> tokio::sync::MutexGuard<'_, ()> {
1271        self.mining.lock().await
1272    }
1273
1274    /// Locks block production with a guard that can be moved into a mining task.
1275    pub(crate) async fn lock_mining_owned(self: &Arc<Self>) -> tokio::sync::OwnedMutexGuard<()> {
1276        self.mining.clone().lock_owned().await
1277    }
1278
1279    /// Returns the `AccountInfo` from the database
1280    pub async fn get_account(&self, address: Address) -> DatabaseResult<AccountInfo> {
1281        Ok(self.db.read().await.basic_ref(address)?.unwrap_or_default())
1282    }
1283
1284    /// Whether we're forked off some remote client
1285    pub fn is_fork(&self) -> bool {
1286        self.fork.read().is_some()
1287    }
1288
1289    /// Writes the CREATE2 deployer code directly to the database at the address provided.
1290    pub async fn set_create2_deployer(&self, address: Address) -> DatabaseResult<()> {
1291        self.set_code(address, Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE)).await?;
1292        Ok(())
1293    }
1294
1295    /// Updates memory limits that should be more strict when auto-mine is enabled
1296    pub(crate) fn update_interval_mine_block_time(&self, block_time: Duration) {
1297        self.states.write().update_interval_mine_block_time(block_time);
1298        if let Some(states) = self.tempo_proof_states() {
1299            states.write().update_interval_mine_block_time(block_time);
1300        }
1301    }
1302
1303    /// Returns the `TimeManager` responsible for timestamps
1304    pub const fn time(&self) -> &TimeManager {
1305        &self.time
1306    }
1307
1308    /// Returns the `CheatsManager` responsible for executing cheatcodes
1309    pub const fn cheats(&self) -> &CheatsManager {
1310        &self.cheats
1311    }
1312
1313    /// Whether to skip blob validation
1314    pub fn skip_blob_validation(&self, impersonator: Option<Address>) -> bool {
1315        self.cheats().auto_impersonate_accounts()
1316            || impersonator
1317                .is_some_and(|addr| self.cheats().impersonated_accounts().contains(&addr))
1318    }
1319
1320    /// Returns the `FeeManager` that manages fee/pricings
1321    pub const fn fees(&self) -> &FeeManager {
1322        &self.fees
1323    }
1324
1325    /// The EVM environment data of the blockchain
1326    pub const fn evm_env(&self) -> &Arc<RwLock<EvmEnv>> {
1327        &self.evm_env
1328    }
1329
1330    /// Returns the current best hash of the chain
1331    pub fn best_hash(&self) -> B256 {
1332        self.blockchain.storage.read().best_hash
1333    }
1334
1335    /// Returns the current best number of the chain
1336    pub fn best_number(&self) -> u64 {
1337        self.blockchain.storage.read().best_number
1338    }
1339
1340    /// Returns the client coinbase address.
1341    pub fn coinbase(&self) -> Address {
1342        self.evm_env.read().block_env.beneficiary
1343    }
1344
1345    /// Returns the client coinbase address.
1346    pub fn chain_id(&self) -> U256 {
1347        U256::from(self.evm_env.read().cfg_env.chain_id)
1348    }
1349
1350    /// Returns the chain ID that defines protocol behavior.
1351    fn protocol_chain_id(&self) -> u64 {
1352        self.get_fork().map_or_else(|| self.evm_env.read().cfg_env.chain_id, |fork| fork.chain_id())
1353    }
1354
1355    pub fn set_chain_id(&self, chain_id: u64) {
1356        self.evm_env.write().cfg_env.chain_id = chain_id;
1357    }
1358
1359    /// Returns the genesis data for the Beacon API.
1360    pub const fn genesis_time(&self) -> u64 {
1361        self.genesis.timestamp
1362    }
1363
1364    /// Returns the configured genesis block number.
1365    pub const fn genesis_number(&self) -> u64 {
1366        self.genesis.number
1367    }
1368
1369    /// Returns balance of the given account.
1370    pub async fn current_balance(&self, address: Address) -> DatabaseResult<U256> {
1371        Ok(self.get_account(address).await?.balance)
1372    }
1373
1374    /// Returns balance of the given account.
1375    pub async fn current_nonce(&self, address: Address) -> DatabaseResult<u64> {
1376        Ok(self.get_account(address).await?.nonce)
1377    }
1378
1379    /// Sets the coinbase address
1380    pub fn set_coinbase(&self, address: Address) {
1381        self.evm_env.write().block_env.beneficiary = address;
1382    }
1383
1384    /// Sets the `prevrandao` value to use for the next mined block.
1385    ///
1386    /// This is a one-shot override that is consumed by the next block; afterwards anvil resumes its
1387    /// default per-block `prevrandao` derivation.
1388    pub fn set_next_block_prevrandao(&self, prevrandao: B256) {
1389        self.cheats.set_next_block_prevrandao(prevrandao);
1390    }
1391
1392    /// Sets the parent beacon block root to use for the next mined block.
1393    ///
1394    /// This is a one-shot override that is consumed by the next block; afterwards anvil resumes
1395    /// using the zero root.
1396    pub fn set_next_block_parent_beacon_block_root(&self, root: B256) {
1397        self.cheats.set_next_block_parent_beacon_block_root(root);
1398    }
1399
1400    /// Sets the nonce of the given address
1401    pub async fn set_nonce(&self, address: Address, nonce: U256) -> DatabaseResult<()> {
1402        self.db.write().await.set_nonce(address, nonce.try_into().unwrap_or(u64::MAX))
1403    }
1404
1405    /// Sets the balance of the given address
1406    pub async fn set_balance(&self, address: Address, balance: U256) -> DatabaseResult<()> {
1407        self.db.write().await.set_balance(address, balance)
1408    }
1409
1410    /// Increases the balance of the given address, saturating at `U256::MAX`.
1411    pub(crate) async fn add_balance(&self, address: Address, balance: U256) -> DatabaseResult<()> {
1412        let mut db = self.db.write().await;
1413        let current_balance = db.basic(address)?.unwrap_or_default().balance;
1414        db.set_balance(address, current_balance.saturating_add(balance))
1415    }
1416
1417    /// Sets the code of the given address
1418    pub async fn set_code(&self, address: Address, code: Bytes) -> DatabaseResult<()> {
1419        self.db.write().await.set_code(address, code)
1420    }
1421
1422    /// Sets the value for the given slot of the given address
1423    pub async fn set_storage_at(
1424        &self,
1425        address: Address,
1426        slot: U256,
1427        val: B256,
1428    ) -> DatabaseResult<()> {
1429        self.db.write().await.set_storage_at(address, slot.into(), val)
1430    }
1431
1432    /// Returns the configured specid
1433    pub fn spec_id(&self) -> SpecId {
1434        *self.evm_env.read().spec_id()
1435    }
1436
1437    /// Returns true for post London
1438    pub fn is_eip1559(&self) -> bool {
1439        self.spec_id().is_enabled_in(SpecId::LONDON)
1440    }
1441
1442    /// Returns true for post Merge
1443    pub fn is_eip3675(&self) -> bool {
1444        self.spec_id().is_enabled_in(SpecId::MERGE)
1445    }
1446
1447    /// Returns true for post Berlin
1448    pub fn is_eip2930(&self) -> bool {
1449        self.spec_id().is_enabled_in(SpecId::BERLIN)
1450    }
1451
1452    /// Returns true for post Cancun
1453    pub fn is_eip4844(&self) -> bool {
1454        self.spec_id().is_enabled_in(SpecId::CANCUN)
1455    }
1456
1457    /// Returns true for post Prague
1458    pub fn is_eip7702(&self) -> bool {
1459        self.spec_id().is_enabled_in(SpecId::PRAGUE)
1460    }
1461
1462    /// Returns true if op-stack deposits are active.
1463    ///
1464    /// Always `false` when built without the `optimism` feature.
1465    pub const fn is_optimism(&self) -> bool {
1466        self.networks.is_optimism()
1467    }
1468
1469    /// Returns true if Tempo network mode is active
1470    pub const fn is_tempo(&self) -> bool {
1471        self.networks.is_tempo()
1472    }
1473
1474    /// Returns whether Celo compatibility is enabled.
1475    pub const fn is_celo(&self) -> bool {
1476        self.networks.is_celo()
1477    }
1478
1479    /// Returns true if Monad network mode is active
1480    pub const fn is_monad(&self) -> bool {
1481        self.networks.is_monad()
1482    }
1483
1484    /// Returns the active execution profile name.
1485    pub const fn execution_profile_name(&self) -> &'static str {
1486        self.networks.execution_profile_name()
1487    }
1488
1489    /// Reconstructs a locally mined transaction using its authoritative stored sender.
1490    fn pending_mined_transaction(
1491        &self,
1492        transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>,
1493    ) -> Result<PendingTransaction<FoundryTxEnvelope>, BlockchainError> {
1494        #[cfg(feature = "monad")]
1495        if self.is_monad() {
1496            return Self::monad_pending_mined_transaction_from_storage(
1497                &self.blockchain.storage.read(),
1498                transaction,
1499            );
1500        }
1501        Ok(PendingTransaction::from_maybe_impersonated(transaction)?)
1502    }
1503
1504    #[cfg(not(feature = "monad"))]
1505    fn active_monad_context_for_mined_block(
1506        &self,
1507        _block: &Block,
1508    ) -> Result<Option<MonadReplayContext>, BlockchainError> {
1509        Ok(None)
1510    }
1511
1512    #[cfg(not(feature = "monad"))]
1513    fn active_monad_context_before_mined_transaction(
1514        &self,
1515        _block: &Block,
1516        _current_tx_index: usize,
1517    ) -> Result<Option<MonadReplayContext>, BlockchainError> {
1518        Ok(None)
1519    }
1520
1521    /// Returns true if Base network mode is active.
1522    #[cfg(feature = "base")]
1523    pub const fn is_base(&self) -> bool {
1524        self.networks.is_base()
1525    }
1526
1527    /// Returns the active hardfork.
1528    pub fn hardfork(&self) -> FoundryHardfork {
1529        if let Some(hardfork) =
1530            self.fork.read().as_ref().and_then(|fork| fork.config.read().hardfork)
1531        {
1532            return hardfork;
1533        }
1534        *self.hardfork.read()
1535    }
1536
1537    /// Returns canonical Ethereum transition configuration only for an Ethereum network.
1538    fn ethereum_block_transitions(
1539        &self,
1540        hardfork: FoundryHardfork,
1541        parent_beacon_block_root: Option<B256>,
1542        execution_kind: BlockExecutionKind,
1543    ) -> Option<EthereumBlockTransitions> {
1544        if self.is_optimism() || self.is_tempo() {
1545            return None;
1546        }
1547        let FoundryHardfork::Ethereum(hardfork) = hardfork else { return None };
1548        Some(EthereumBlockTransitions {
1549            hardfork,
1550            deposit_contract_address: self.ethereum_deposit_contract_address(),
1551            parent_beacon_block_root,
1552            execution_kind,
1553        })
1554    }
1555
1556    /// Returns the configured deposit contract, then the canonical address for known chains.
1557    fn ethereum_deposit_contract_address(&self) -> Address {
1558        if let Some(address) = self
1559            .genesis
1560            .genesis_init
1561            .as_ref()
1562            .and_then(|genesis| genesis.config.deposit_contract_address)
1563        {
1564            return address;
1565        }
1566
1567        match NamedChain::try_from(self.evm_env.read().cfg_env.chain_id) {
1568            Ok(NamedChain::Sepolia) => SEPOLIA_DEPOSIT_CONTRACT_ADDRESS,
1569            Ok(NamedChain::Holesky) => HOLESKY_DEPOSIT_CONTRACT_ADDRESS,
1570            // Hoodi shares the mainnet address; other chains use Alloy's mainnet fallback.
1571            _ => MAINNET_DEPOSIT_CONTRACT_ADDRESS,
1572        }
1573    }
1574
1575    /// Returns the active Tempo hardfork.
1576    pub fn tempo_hardfork(&self) -> TempoHardfork {
1577        TempoHardfork::from(self.hardfork())
1578    }
1579
1580    /// Returns the active Monad hardfork.
1581    #[cfg(feature = "monad")]
1582    pub fn monad_hardfork(&self) -> monad_revm::MonadHardfork {
1583        monad_revm::MonadHardfork::from(self.hardfork())
1584    }
1585
1586    /// Returns the active Base upgrade.
1587    #[cfg(feature = "base")]
1588    pub fn base_upgrade(&self) -> BaseUpgrade {
1589        match self.hardfork() {
1590            FoundryHardfork::Base(upgrade) => upgrade,
1591            _ => BaseUpgrade::Azul,
1592        }
1593    }
1594
1595    /// Returns the configured Base activation-registry administrator override.
1596    #[cfg(feature = "base")]
1597    pub const fn base_activation_admin(&self) -> Option<Address> {
1598        self.base_activation_admin
1599    }
1600
1601    /// Returns the Base upgrade active at `timestamp`.
1602    #[cfg(feature = "base")]
1603    pub fn base_upgrade_at_timestamp(&self, timestamp: u64) -> BaseUpgrade {
1604        if self.is_fork() {
1605            BaseUpgrade::from_chain_and_timestamp(self.chain_id().to(), timestamp)
1606                .unwrap_or_else(|| self.base_upgrade())
1607        } else {
1608            self.base_upgrade()
1609        }
1610    }
1611
1612    /// Prepares the clock for a candidate block without consuming its time controls.
1613    fn prepare_block_timestamp(
1614        &self,
1615        db: &dyn revm::DatabaseRef<Error = DatabaseError>,
1616        parent_timestamp: u64,
1617        pin_timestamp: bool,
1618    ) -> Result<PendingBlockTimestamp, DatabaseError> {
1619        #[cfg(feature = "base")]
1620        if self.is_base() && self.base_upgrade() >= BaseUpgrade::Denim {
1621            return base::prepare_block_timestamp(db, &self.time, parent_timestamp);
1622        }
1623        let _ = (db, parent_timestamp);
1624        Ok(self.time.prepare_next_timestamp(pin_timestamp))
1625    }
1626
1627    /// Builds the required deposits from the same parent state used to prepare the clock.
1628    #[cfg(feature = "base")]
1629    fn base_system_transactions(
1630        &self,
1631        db: &dyn revm::DatabaseRef<Error = DatabaseError>,
1632        block_number: u64,
1633        timestamp: u64,
1634        parent_hash: B256,
1635    ) -> Result<Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>, DatabaseError> {
1636        if self.is_base() && self.base_upgrade() >= BaseUpgrade::Denim {
1637            base::system_transactions(db, block_number, timestamp, parent_hash, self.is_fork())
1638        } else {
1639            Ok(Vec::new())
1640        }
1641    }
1642
1643    /// Returns the head timestamp snapshot used for EIP-8130 pool admission.
1644    #[cfg(feature = "base")]
1645    pub fn eip8130_pool_timestamp(&self) -> u64 {
1646        self.evm_env.read().block_env.timestamp.saturating_to()
1647    }
1648
1649    /// Returns the EIP-8130 pool admission snapshot in Unix milliseconds.
1650    ///
1651    /// EIP-8130 validity windows are millisecond timestamps evaluated against
1652    /// `block.timestamp * 1000`, so protocol validation must use this rather than the
1653    /// second-valued block timestamp.
1654    #[cfg(feature = "base")]
1655    pub fn eip8130_pool_timestamp_ms(&self) -> u64 {
1656        self.eip8130_pool_timestamp().saturating_mul(1_000)
1657    }
1658
1659    /// Returns whether a Tempo hardfork is active on this backend.
1660    pub fn is_tempo_hardfork_active(&self, hardfork: TempoHardfork) -> bool {
1661        self.is_tempo() && self.tempo_hardfork() >= hardfork
1662    }
1663
1664    /// Returns the precompiles for the current spec.
1665    pub fn precompiles(&self) -> BTreeMap<String, Address> {
1666        let spec_id = self.spec_id();
1667        let mut precompiles =
1668            PrecompilesMap::from_static(Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)));
1669        let chain_id = self.protocol_chain_id();
1670        let timestamp = self.evm_env.read().block_env.timestamp.saturating_to();
1671        apply_bsc_p256_precompile(&mut precompiles, chain_id, timestamp);
1672
1673        let mut precompiles_map = BTreeMap::<String, Address>::default();
1674        for address in precompiles.addresses() {
1675            let precompile = precompiles.get(address).expect("precompile address must resolve");
1676            precompiles_map.insert(precompile.precompile_id().name().to_string(), *address);
1677        }
1678
1679        // Extend with configured network precompiles.
1680        precompiles_map.extend(self.networks.precompiles(Some(self.hardfork())));
1681
1682        if let Some(factory) = &self.precompile_factory {
1683            for (address, precompile) in factory.precompiles() {
1684                precompiles_map.insert(precompile.precompile_id().to_string(), address);
1685            }
1686        }
1687
1688        precompiles_map
1689    }
1690
1691    /// Returns whether the address is an active precompile in the given block environment.
1692    pub fn is_precompile(&self, address: &Address, block_env: &BlockEnv) -> bool {
1693        let mut evm_env = self.evm_env.read().clone();
1694        evm_env.block_env = block_env.clone();
1695        let mut precompiles = PrecompilesMap::from_static(Precompiles::new(
1696            PrecompileSpecId::from_spec_id(self.spec_id()),
1697        ));
1698        self.inject_precompiles(&mut precompiles, &evm_env);
1699        precompiles.get(address).is_some()
1700    }
1701
1702    /// Returns the system contracts for the current spec.
1703    pub fn system_contracts(&self) -> BTreeMap<SystemContract, Address> {
1704        let mut system_contracts = BTreeMap::<SystemContract, Address>::default();
1705
1706        let spec_id = self.spec_id();
1707
1708        if spec_id >= SpecId::CANCUN {
1709            system_contracts.extend(SystemContract::cancun());
1710        }
1711
1712        if spec_id >= SpecId::PRAGUE {
1713            system_contracts.extend(SystemContract::prague(None));
1714        }
1715
1716        system_contracts
1717    }
1718
1719    /// Returns the active [`BlobParams`].
1720    pub fn blob_params(&self) -> BlobParams {
1721        self.fees.blob_params()
1722    }
1723
1724    fn simulation_blob_params_at_timestamp(&self, timestamp: u64) -> BlobParams {
1725        let configured_hardfork = self.hardfork();
1726        if let FoundryHardfork::Ethereum(
1727            configured
1728            @ (EthereumHardfork::Osaka | EthereumHardfork::Bpo1 | EthereumHardfork::Bpo2),
1729        ) = configured_hardfork
1730            && let Some(hardfork) = FoundryHardfork::from_chain_and_timestamp(
1731                self.evm_env.read().cfg_env.chain_id,
1732                timestamp,
1733            )
1734            && let FoundryHardfork::Ethereum(
1735                scheduled @ (EthereumHardfork::Osaka
1736                | EthereumHardfork::Bpo1
1737                | EthereumHardfork::Bpo2),
1738            ) = hardfork
1739        {
1740            let hardfork = match (configured, scheduled) {
1741                (EthereumHardfork::Bpo2, _) | (_, EthereumHardfork::Bpo2) => EthereumHardfork::Bpo2,
1742                (EthereumHardfork::Bpo1, _) | (_, EthereumHardfork::Bpo1) => EthereumHardfork::Bpo1,
1743                _ => EthereumHardfork::Osaka,
1744            };
1745            return get_blob_params_by_hardfork(hardfork.into());
1746        }
1747        get_blob_params_by_hardfork(configured_hardfork)
1748    }
1749
1750    #[cfg(feature = "optimism")]
1751    fn is_optimism_jovian_at_header<H: BlockHeader>(
1752        &self,
1753        header: &H,
1754        decoded: Option<bool>,
1755    ) -> bool {
1756        if !self.is_optimism() {
1757            return false;
1758        }
1759        if let Some(jovian) = decoded {
1760            return jovian;
1761        }
1762        if !header.extra_data().is_empty() {
1763            return false;
1764        }
1765        let hardfork = if self.get_fork().is_some() {
1766            NetworkVariant::Optimism
1767                .historical_hardfork(self.protocol_chain_id(), header.timestamp())
1768                .unwrap_or_else(|| self.hardfork())
1769        } else {
1770            self.hardfork()
1771        };
1772        OpHardfork::from(hardfork) >= OpHardfork::Jovian
1773    }
1774
1775    #[cfg(not(feature = "optimism"))]
1776    const fn is_optimism_jovian_at_header<H: BlockHeader>(
1777        &self,
1778        _header: &H,
1779        _decoded: Option<bool>,
1780    ) -> bool {
1781        false
1782    }
1783
1784    /// Returns an error if EIP1559 is not active (pre Berlin)
1785    pub fn ensure_eip1559_active(&self) -> Result<(), BlockchainError> {
1786        if self.is_eip1559() {
1787            return Ok(());
1788        }
1789        Err(BlockchainError::EIP1559TransactionUnsupportedAtHardfork)
1790    }
1791
1792    /// Returns an error if EIP1559 is not active (pre muirGlacier)
1793    pub fn ensure_eip2930_active(&self) -> Result<(), BlockchainError> {
1794        if self.is_eip2930() {
1795            return Ok(());
1796        }
1797        Err(BlockchainError::EIP2930TransactionUnsupportedAtHardfork)
1798    }
1799
1800    pub fn ensure_eip4844_active(&self) -> Result<(), BlockchainError> {
1801        if self.is_eip4844() {
1802            return Ok(());
1803        }
1804        Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork)
1805    }
1806
1807    pub fn ensure_eip7702_active(&self) -> Result<(), BlockchainError> {
1808        if self.is_eip7702() {
1809            return Ok(());
1810        }
1811        Err(BlockchainError::EIP7702TransactionUnsupportedAtHardfork)
1812    }
1813
1814    /// Returns an error if deposit transactions are not active.
1815    #[cfg(any(feature = "base", feature = "optimism"))]
1816    pub const fn ensure_deposits_active(&self) -> Result<(), BlockchainError> {
1817        #[cfg(feature = "base")]
1818        if self.is_base() {
1819            return Ok(());
1820        }
1821        #[cfg(feature = "optimism")]
1822        if self.is_optimism() {
1823            return Ok(());
1824        }
1825        Err(BlockchainError::DepositTransactionUnsupported)
1826    }
1827
1828    /// Returns an error if Base EIP-8130 RPC features are not active at `timestamp`.
1829    #[cfg(feature = "base")]
1830    pub fn ensure_base_eip8130_active_at(&self, timestamp: u64) -> Result<(), BlockchainError> {
1831        let upgrade = self.base_upgrade_at_timestamp(timestamp);
1832        if self.is_base() && upgrade >= BaseUpgrade::Zenith {
1833            return Ok(());
1834        }
1835        Err(BlockchainError::InvalidTransactionRequest(EIP8130_PRE_ZENITH_RPC_ERROR.to_string()))
1836    }
1837
1838    /// Returns an admission error if Base EIP-8130 submissions are not active.
1839    #[cfg(feature = "base")]
1840    pub fn ensure_base_eip8130_submission_active(&self) -> Result<(), BlockchainError> {
1841        if self.is_base() && self.base_upgrade() >= BaseUpgrade::Zenith {
1842            return Ok(());
1843        }
1844        Err(BlockchainError::Eip8130TransactionRejected(EIP8130_REJECTION_MSG.to_string()))
1845    }
1846
1847    /// Returns an error if Tempo transactions are not active
1848    pub const fn ensure_tempo_active(&self) -> Result<(), BlockchainError> {
1849        if self.is_tempo() {
1850            return Ok(());
1851        }
1852        Err(BlockchainError::TempoTransactionUnsupported)
1853    }
1854
1855    /// Returns an error unless CIP-64 native-fee compatibility is active.
1856    pub fn ensure_cip64_active(&self) -> Result<(), BlockchainError> {
1857        if !self.is_celo() {
1858            return Err(BlockchainError::InvalidTransactionRequest(
1859                "CIP-64 transactions require Celo mode (--celo)".into(),
1860            ));
1861        }
1862        self.ensure_eip1559_active()
1863    }
1864
1865    /// Builds the [`InspectorTxConfig`] from the backend's current settings.
1866    fn inspector_tx_config(&self) -> InspectorTxConfig {
1867        InspectorTxConfig {
1868            print_traces: self.print_traces,
1869            print_logs: self.print_logs,
1870            call_trace_decoder: self.call_trace_decoder(),
1871        }
1872    }
1873
1874    /// Returns a trace decoder configured for the currently resolved hardfork.
1875    fn call_trace_decoder(&self) -> Arc<CallTraceDecoder> {
1876        let hardfork = Some(self.networks.executed_hardfork(self.hardfork()));
1877        let decoder = self.call_trace_decoder.read();
1878        if decoder.hardfork() == hardfork {
1879            return Arc::clone(&decoder);
1880        }
1881        drop(decoder);
1882
1883        let mut decoder = self.call_trace_decoder.write();
1884        let mut updated = decoder.as_ref().clone();
1885        updated.set_hardfork(hardfork);
1886        *decoder = Arc::new(updated);
1887        Arc::clone(&decoder)
1888    }
1889
1890    /// Builds the [`PoolTxGasConfig`] from the given EVM environment.
1891    fn pool_tx_gas_config(&self, evm_env: &EvmEnv) -> PoolTxGasConfig {
1892        let spec_id = *evm_env.spec_id();
1893        let is_cancun = spec_id >= SpecId::CANCUN;
1894        let blob_params = self.blob_params();
1895        PoolTxGasConfig {
1896            disable_block_gas_limit: evm_env.cfg_env.disable_block_gas_limit,
1897            enforced_tx_gas_limit_cap: self.enforced_tx_gas_limit_cap(evm_env),
1898            max_blob_gas_per_block: blob_params.max_blob_gas_per_block(),
1899            is_cancun,
1900        }
1901    }
1902
1903    #[cfg(feature = "monad")]
1904    fn monad_cfg_env(&self, evm_env: &EvmEnv) -> Option<monad_revm::MonadCfgEnv> {
1905        if !self.is_monad() {
1906            return None;
1907        }
1908
1909        let hardfork = monad_revm::MonadHardfork::from(self.hardfork());
1910        Some(monad_revm::MonadCfgEnv::from(evm_env.cfg_env.clone().with_spec_and_gas_params(
1911            hardfork,
1912            monad_revm::instructions::monad_gas_params(hardfork),
1913        )))
1914    }
1915
1916    /// Returns the EIP-7825 cap enforced on a transaction's gas limit, if any.
1917    ///
1918    /// Under EIP-8037 the cap only bounds regular gas, and gas above it becomes the state gas
1919    /// reservoir, so the gas limit itself is not capped.
1920    fn enforced_tx_gas_limit_cap(&self, evm_env: &EvmEnv) -> Option<u64> {
1921        if evm_env.cfg_env.tx_gas_limit_cap.is_some()
1922            || evm_env.cfg_env.is_amsterdam_eip8037_enabled()
1923        {
1924            return None;
1925        }
1926        #[cfg(feature = "monad")]
1927        if let Some(cfg) = self.monad_cfg_env(evm_env) {
1928            return Some(cfg.tx_gas_limit_cap());
1929        }
1930        Some(evm_env.cfg_env.tx_gas_limit_cap())
1931    }
1932
1933    pub(crate) fn fallback_tx_gas_limit(&self, evm_env: &EvmEnv) -> u64 {
1934        let block_gas_limit = evm_env.block_env.gas_limit;
1935        self.enforced_tx_gas_limit_cap(evm_env)
1936            .map_or(block_gas_limit, |cap| block_gas_limit.min(cap))
1937    }
1938
1939    fn max_initcode_size(&self, evm_env: &EvmEnv) -> usize {
1940        #[cfg(feature = "monad")]
1941        if let Some(cfg) = self.monad_cfg_env(evm_env) {
1942            return cfg.max_initcode_size();
1943        }
1944        evm_env.cfg_env.max_initcode_size()
1945    }
1946
1947    /// Returns the block gas limit
1948    pub fn gas_limit(&self) -> u64 {
1949        self.evm_env.read().block_env.gas_limit
1950    }
1951
1952    /// Sets the block gas limit
1953    pub fn set_gas_limit(&self, gas_limit: u64) {
1954        self.evm_env.write().block_env.gas_limit = gas_limit;
1955    }
1956
1957    /// Returns the current base fee
1958    pub fn base_fee(&self) -> u64 {
1959        self.fees.base_fee()
1960    }
1961
1962    /// Returns whether the minimum suggested priority fee is enforced
1963    pub const fn is_min_priority_fee_enforced(&self) -> bool {
1964        self.fees.is_min_priority_fee_enforced()
1965    }
1966
1967    pub fn excess_blob_gas_and_price(&self) -> Option<BlobExcessGasAndPrice> {
1968        self.fees.excess_blob_gas_and_price()
1969    }
1970
1971    /// Sets the current basefee
1972    pub fn set_base_fee(&self, basefee: u64) {
1973        self.fees.set_base_fee(basefee)
1974    }
1975
1976    /// Sets the gas price
1977    pub fn set_gas_price(&self, price: u128) {
1978        self.fees.set_gas_price(price)
1979    }
1980
1981    pub fn elasticity(&self) -> f64 {
1982        self.fees.elasticity()
1983    }
1984
1985    /// Returns the total difficulty of the chain until this block
1986    ///
1987    /// Note: this will always be `0` in memory mode
1988    /// In forking mode this will always be the total difficulty of the forked block
1989    pub fn total_difficulty(&self) -> U256 {
1990        self.blockchain.storage.read().total_difficulty
1991    }
1992
1993    /// Creates a new `evm_snapshot` at the current height.
1994    ///
1995    /// Returns the id of the snapshot created.
1996    pub async fn create_state_snapshot(&self) -> U256 {
1997        let mut db = self.db.write().await;
1998        let num = self.best_number();
1999        let hash = self.best_hash();
2000        let id = db.snapshot_state();
2001        let proof_state = self.tempo_proof_states().and_then(|states| {
2002            // Explicit snapshots keep the sealed head independently of bounded proof history.
2003            // Live execution may already contain unmined development edits.
2004            let states = states.upgradable_read();
2005            if let Some(state) = states.get_state(&hash) {
2006                return state.maybe_as_full_db().cloned().map(Arc::new);
2007            }
2008            let mut states = RwLockUpgradableReadGuard::upgrade(states);
2009            states
2010                .get_on_disk_state(&hash)
2011                .and_then(|state| state.maybe_as_full_db().cloned())
2012                .map(Arc::new)
2013        });
2014        if let Some(tempo) = &mut self.blockchain.storage.write().tempo {
2015            tempo.snapshot(id, proof_state);
2016        }
2017        trace!(target: "backend", "creating snapshot {} at {}", id, num);
2018        self.active_state_snapshots.lock().insert(
2019            id,
2020            StateSnapshot {
2021                block_number: num,
2022                block_hash: hash,
2023                fees: self.fees.snapshot(),
2024                time: self.time.snapshot(),
2025                next_block: self.cheats.next_block_overrides(),
2026            },
2027        );
2028        id
2029    }
2030
2031    pub fn list_state_snapshots(&self) -> BTreeMap<U256, (u64, B256)> {
2032        self.active_state_snapshots
2033            .lock()
2034            .iter()
2035            .map(|(&id, snapshot)| (id, (snapshot.block_number, snapshot.block_hash)))
2036            .collect()
2037    }
2038
2039    /// Returns the EVM-visible number for a locally mined RPC block.
2040    fn evm_block_number(&self, rpc_number: u64) -> U256 {
2041        self.get_fork()
2042            .map_or_else(|| U256::from(rpc_number), |fork| fork.evm_block_number(rpc_number))
2043    }
2044
2045    /// Restores the execution number separately from the RPC header number.
2046    fn block_env_from_header(&self, header: &impl BlockHeader) -> BlockEnv {
2047        let mut block = block_env_from_header::<BlockEnv>(header);
2048        block.number = self.evm_block_number(header.number());
2049        if let Some(excess_blob_gas) = header.excess_blob_gas() {
2050            block.set_blob_excess_gas_and_price(
2051                excess_blob_gas,
2052                self.blob_params().update_fraction as u64,
2053            );
2054        }
2055        block
2056    }
2057
2058    /// Returns the base fee and blob excess gas that the child of `parent` inherits.
2059    ///
2060    /// Derives the zero-base-fee sentinel from `parent` rather than the live fee state. A local
2061    /// genesis header may not record the fees seeded for its child, so those come from the mined
2062    /// child, or from the live fee state when no child has been mined yet.
2063    pub(crate) fn next_block_fees_after(
2064        &self,
2065        parent: &impl BlockHeader,
2066    ) -> (u64, BlobExcessGasAndPrice) {
2067        let blob_params = self.fees.blob_params();
2068        let update_fraction = blob_params.update_fraction as u64;
2069        let is_fork_root =
2070            self.get_fork().is_some_and(|fork| fork.block_number() == parent.number());
2071        if is_fork_root || (!self.is_fork() && parent.number() == self.genesis_number()) {
2072            let Some(child) = self.get_block(parent.number() + 1) else {
2073                let fees = self.fees.snapshot();
2074                return (fees.base_fee, fees.blob_excess_gas_and_price);
2075            };
2076            return (
2077                child.header.base_fee_per_gas().unwrap_or_default(),
2078                BlobExcessGasAndPrice::new(
2079                    child.header.excess_blob_gas().unwrap_or_default(),
2080                    update_fraction,
2081                ),
2082            );
2083        }
2084        let base_fee = parent.base_fee_per_gas().unwrap_or_default();
2085        let excess_blob_gas = parent.excess_blob_gas().unwrap_or_default();
2086        let next_base_fee = if base_fee == 0 {
2087            0
2088        } else {
2089            self.fees.calculate_next_block_base_fee_from_header(parent)
2090        };
2091        let next_excess_blob_gas = self.networks.next_block_blob_excess_gas(
2092            blob_params,
2093            excess_blob_gas,
2094            parent.blob_gas_used().unwrap_or_default(),
2095            base_fee,
2096        );
2097        (next_base_fee, BlobExcessGasAndPrice::new(next_excess_blob_gas, update_fraction))
2098    }
2099
2100    /// Returns the environment for the next block
2101    fn next_evm_env(
2102        &self,
2103        db: &dyn revm::DatabaseRef<Error = DatabaseError>,
2104    ) -> Result<EvmEnv, DatabaseError> {
2105        let mut evm_env = self.evm_env.read().clone();
2106        // increase block number for this block
2107        evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::ONE);
2108        evm_env.block_env.basefee = self.base_fee();
2109        evm_env.block_env.blob_excess_gas_and_price = self.excess_blob_gas_and_price();
2110        let pending =
2111            self.prepare_block_timestamp(db, evm_env.block_env.timestamp.saturating_to(), false)?;
2112        evm_env.block_env.timestamp = U256::from(pending.timestamp);
2113        Ok(evm_env)
2114    }
2115
2116    /// Returns the environment for replaying transactions from a historical block.
2117    fn tx_replay_evm_env(&self, block: &Block) -> (EvmEnv, FoundryHardfork) {
2118        let mut evm_env = self.evm_env.read().clone();
2119        evm_env.block_env = self.block_env_from_header(&block.header);
2120        let hardfork = self.hardfork();
2121        #[cfg(feature = "monad")]
2122        let hardfork = if self.is_monad() {
2123            let block_hash = block.header.hash_slow();
2124            let fallback = crate::eth::backend::db::MonadBlockReplayProfile {
2125                execution_chain_id: evm_env.cfg_env.chain_id,
2126                hardfork: self.monad_hardfork(),
2127            };
2128            let profile = self
2129                .blockchain
2130                .storage
2131                .read()
2132                .monad_block_replay_profiles
2133                .get(&block_hash)
2134                .copied()
2135                .unwrap_or(fallback);
2136            evm_env.cfg_env.chain_id = profile.execution_chain_id;
2137            evm_env.cfg_env.spec = profile.hardfork.into();
2138            profile.hardfork.into()
2139        } else {
2140            hardfork
2141        };
2142        apply_chain_specific_tx_replay_env_changes_for_chain(
2143            &mut evm_env,
2144            self.protocol_chain_id(),
2145        );
2146        (evm_env, hardfork)
2147    }
2148
2149    /// Creates the database and environment for replaying a locally mined block.
2150    ///
2151    /// An empty block execution applies protocol-level pre-execution changes, such as the
2152    /// EIP-2935 parent hash system call, through the same network-specific executor used while
2153    /// mining.
2154    fn prepare_block_replay<'a>(
2155        &self,
2156        block: &Block,
2157        parent_state: StateRef<'a>,
2158    ) -> Result<(CacheDB<StateRef<'a>>, EvmEnv, FoundryHardfork), BlockchainError> {
2159        let (evm_env, hardfork) = self.tx_replay_evm_env(block);
2160        let mut cache_db = AnvilCacheDB::<StateRef<'_>>::new(parent_state, *evm_env.spec_id());
2161        let inspector_tx_config = self.inspector_tx_config();
2162        let gas_config = self.pool_tx_gas_config(&evm_env);
2163
2164        self.execute_with_block_executor(
2165            &mut cache_db,
2166            &evm_env,
2167            block.header.parent_hash,
2168            hardfork,
2169            block.header.parent_beacon_block_root,
2170            BlockExecutionKind::TransactionPrefix,
2171            &[],
2172            &gas_config,
2173            &inspector_tx_config,
2174            &|_, _| Ok(()),
2175        )?;
2176
2177        Ok((cache_db.0, evm_env, hardfork))
2178    }
2179
2180    /// Replays the stored transaction prefix `[0, end)` into an existing block overlay.
2181    fn replay_mined_transaction_prefix(
2182        &self,
2183        cache_db: &mut CacheDB<StateRef<'_>>,
2184        evm_env: &EvmEnv,
2185        hardfork: FoundryHardfork,
2186        block: &Block,
2187        end: usize,
2188    ) -> Result<(), BlockchainError> {
2189        let monad_context = self.active_monad_context_for_mined_block(block)?;
2190        for (index, transaction) in block.body.transactions[..end].iter().enumerate() {
2191            let pending = self.pending_mined_transaction(transaction.clone())?;
2192            let mut inspector = AnvilInspector::default();
2193            let transaction_context = monad_execution_context_at(monad_context.as_ref(), index);
2194            let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
2195                cache_db,
2196                evm_env,
2197                &mut inspector,
2198                &pending,
2199                EnvelopeExecution::replay(transaction_context, hardfork),
2200            )?;
2201            cache_db.commit(result.state);
2202        }
2203        Ok(())
2204    }
2205
2206    /// Builds [`Inspector`] with the configured options.
2207    fn build_inspector(&self) -> AnvilInspector {
2208        let mut inspector = AnvilInspector::default();
2209
2210        if self.print_logs {
2211            inspector = inspector.with_log_collector();
2212        }
2213        if self.print_traces {
2214            inspector = inspector.with_trace_printer();
2215        }
2216
2217        inspector
2218    }
2219
2220    /// Builds an inspector configured for block mining (tracing always enabled).
2221    fn build_mining_inspector(&self) -> AnvilInspector {
2222        let mut inspector = AnvilInspector::default().with_tracing();
2223        if self.print_logs {
2224            inspector = inspector.with_log_collector();
2225        }
2226        if self.print_traces {
2227            inspector = inspector.with_trace_printer();
2228        }
2229        inspector
2230    }
2231
2232    /// Returns a new block event stream that yields Notifications when a new block was added or
2233    /// when logs were removed from the canonical chain due to a reorg
2234    pub fn new_block_notifications(&self) -> ChainNotifications {
2235        let (tx, rx) = unbounded();
2236        self.new_block_listeners.lock().push(tx);
2237        trace!(target: "backed", "added new block listener");
2238        rx
2239    }
2240
2241    /// Returns the number of new-block listeners. Closed listeners are pruned lazily on the next
2242    /// new block notification.
2243    pub fn new_block_listeners_count(&self) -> usize {
2244        self.new_block_listeners.lock().len()
2245    }
2246
2247    /// Notifies all `new_block_listeners` about the new block
2248    fn notify_on_new_block(&self, header: Header, hash: B256) {
2249        // cleanup closed notification streams first, if the channel is closed we can remove the
2250        // sender half for the set
2251        self.new_block_listeners.lock().retain(|tx| !tx.is_closed());
2252
2253        let notification =
2254            ChainNotification::Block(NewBlockNotification { hash, header: Arc::new(header) });
2255
2256        self.new_block_listeners
2257            .lock()
2258            .retain(|tx| tx.unbounded_send(notification.clone()).is_ok());
2259    }
2260
2261    /// Notifies all `new_block_listeners` about the logs that were removed from the canonical
2262    /// chain due to a reorg.
2263    fn notify_on_removed_logs(&self, logs: Vec<Log>) {
2264        // cleanup closed notification streams first, if the channel is closed we can remove the
2265        // sender half for the set
2266        self.new_block_listeners.lock().retain(|tx| !tx.is_closed());
2267
2268        let notification = ChainNotification::RemovedLogs(Arc::new(logs));
2269
2270        self.new_block_listeners
2271            .lock()
2272            .retain(|tx| tx.unbounded_send(notification.clone()).is_ok());
2273    }
2274
2275    /// Returns the block number for the given block id
2276    pub fn convert_block_number(&self, block: Option<BlockNumber>) -> u64 {
2277        self.convert_block_number_at(block, self.best_number())
2278    }
2279
2280    /// Returns the block number for the given block id, resolving head-relative tags against
2281    /// `current`.
2282    ///
2283    /// Callers that compare against or resolve multiple tags from the head should read it once and
2284    /// pass the same snapshot, since mining can advance the head between separate reads.
2285    pub(crate) fn convert_block_number_at(&self, block: Option<BlockNumber>, current: u64) -> u64 {
2286        // The chain starts at the configured genesis number, so `earliest` and the epoch-based
2287        // tags never resolve below it.
2288        let genesis = self.genesis_number();
2289        match block.unwrap_or(BlockNumber::Latest) {
2290            BlockNumber::Latest | BlockNumber::Pending => current,
2291            BlockNumber::Earliest => genesis,
2292            BlockNumber::Number(num) => num,
2293            BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch).max(genesis),
2294            BlockNumber::Finalized => {
2295                current.saturating_sub(self.slots_in_an_epoch * 2).max(genesis)
2296            }
2297        }
2298    }
2299
2300    /// Returns the canonical hash for the given block number.
2301    pub(crate) fn block_hash_by_number(&self, number: u64) -> Option<B256> {
2302        self.blockchain.hash(BlockId::number(number), self.slots_in_an_epoch)
2303    }
2304
2305    /// Returns the block and its hash for the given id
2306    pub(crate) fn get_block_with_hash(&self, id: impl Into<BlockId>) -> Option<(Block, B256)> {
2307        let hash = self.blockchain.hash(id.into(), self.slots_in_an_epoch)?;
2308        let block = self.get_block_by_hash(hash)?;
2309        Some((block, hash))
2310    }
2311
2312    pub fn get_block(&self, id: impl Into<BlockId>) -> Option<Block> {
2313        self.get_block_with_hash(id).map(|(block, _)| block)
2314    }
2315
2316    pub fn get_block_by_hash(&self, hash: B256) -> Option<Block> {
2317        self.blockchain.get_block_by_hash(&hash)
2318    }
2319
2320    /// Returns the base fees for the block after a fee history range.
2321    ///
2322    /// Mining publishes a new canonical block before advancing the fee manager. Holding the mining
2323    /// lock makes choosing between an existing child and the current head's pending fees atomic
2324    /// with that publication sequence.
2325    pub(crate) async fn fee_history_next_fees(&self, highest: u64) -> Option<(u128, u128)> {
2326        let _mining_guard = self.mining.lock().await;
2327        let next_number = highest.checked_add(1)?;
2328        if let Some(block) = self.get_block(next_number) {
2329            Some((
2330                block.header.base_fee_per_gas.unwrap_or_default() as u128,
2331                block.header.blob_fee(self.blob_params()).unwrap_or_default(),
2332            ))
2333        } else if highest == self.best_number() {
2334            Some((self.fees().base_fee() as u128, self.fees().base_fee_per_blob_gas()))
2335        } else {
2336            None
2337        }
2338    }
2339
2340    /// Returns the traces for the given transaction
2341    pub(crate) fn mined_parity_trace_transaction(
2342        &self,
2343        hash: B256,
2344    ) -> Option<Vec<LocalizedTransactionTrace>> {
2345        self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.parity_traces())
2346    }
2347
2348    /// Returns the traces for the given block
2349    pub(crate) fn mined_parity_trace_block(
2350        &self,
2351        block: u64,
2352    ) -> Option<Vec<LocalizedTransactionTrace>> {
2353        let block = self.get_block(block)?;
2354        let mut traces = vec![];
2355        let storage = self.blockchain.storage.read();
2356        for tx in block.body.transactions {
2357            if let Some(mined_tx) = storage.transactions.get(&tx.hash()) {
2358                traces.extend(mined_tx.parity_traces());
2359            }
2360        }
2361        Some(traces)
2362    }
2363
2364    /// Returns the mined transaction for the given hash
2365    pub(crate) fn mined_transaction(&self, hash: B256) -> Option<MinedTransaction<N>> {
2366        self.blockchain.storage.read().transactions.get(&hash).cloned()
2367    }
2368
2369    /// Overrides the given signature to impersonate the specified address during ecrecover.
2370    pub async fn impersonate_signature(
2371        &self,
2372        signature: Bytes,
2373        address: Address,
2374    ) -> Result<(), BlockchainError> {
2375        self.cheats.add_recover_override(signature, address);
2376        Ok(())
2377    }
2378
2379    /// Returns code by its hash
2380    pub async fn debug_code_by_hash(
2381        &self,
2382        code_hash: B256,
2383        block_id: Option<BlockId>,
2384    ) -> Result<Option<Bytes>, BlockchainError> {
2385        if let Ok(code) = self.db.read().await.code_by_hash_ref(code_hash) {
2386            return Ok(Some(code.original_bytes()));
2387        }
2388        if let Some(fork) = self.get_fork() {
2389            return Ok(fork.debug_code_by_hash(code_hash, block_id).await?);
2390        }
2391
2392        Ok(None)
2393    }
2394
2395    /// Returns the value associated with a key from the database
2396    /// Currently only supports bytecode lookups.
2397    ///
2398    /// Based on Reth implementation: <https://github.com/paradigmxyz/reth/blob/66cfa9ed1a8c4bc2424aacf6fb2c1e67a78ee9a2/crates/rpc/rpc/src/debug.rs#L1146-L1178>
2399    ///
2400    /// Key should be: 0x63 (1-byte prefix) + 32 bytes (code_hash)
2401    /// Total key length must be 33 bytes.
2402    pub async fn debug_db_get(&self, key: String) -> Result<Option<Bytes>, BlockchainError> {
2403        let key_bytes = if key.starts_with("0x") {
2404            hex::decode(&key)
2405                .map_err(|_| BlockchainError::Message("Invalid hex key".to_string()))?
2406        } else {
2407            key.into_bytes()
2408        };
2409
2410        // Validate key length: must be 33 bytes (1 byte prefix + 32 bytes code hash)
2411        if key_bytes.len() != 33 {
2412            return Err(BlockchainError::Message(format!(
2413                "Invalid key length: expected 33 bytes, got {}",
2414                key_bytes.len()
2415            )));
2416        }
2417
2418        // Check for bytecode prefix (0x63 = 'c' in ASCII)
2419        if key_bytes[0] != 0x63 {
2420            return Err(BlockchainError::Message(
2421                "Key prefix must be 0x63 for code hash lookups".to_string(),
2422            ));
2423        }
2424
2425        let code_hash = B256::from_slice(&key_bytes[1..33]);
2426
2427        // Use the existing debug_code_by_hash method to retrieve the bytecode
2428        self.debug_code_by_hash(code_hash, None).await
2429    }
2430
2431    fn mined_block_by_hash(&self, hash: B256) -> Option<AnyRpcBlock> {
2432        let block = self.blockchain.get_block_by_hash(&hash)?;
2433        Some(self.convert_block_with_hash(block, Some(hash)))
2434    }
2435
2436    pub(crate) async fn mined_transactions_by_block_number(
2437        &self,
2438        number: BlockNumber,
2439    ) -> Option<Vec<AnyRpcTransaction>> {
2440        if let Some(block) = self.get_block(number) {
2441            return self.mined_transactions_in_block(&block);
2442        }
2443        None
2444    }
2445
2446    /// Returns all transactions given a block
2447    pub(crate) fn mined_transactions_in_block(
2448        &self,
2449        block: &Block,
2450    ) -> Option<Vec<AnyRpcTransaction>> {
2451        let mut transactions = Vec::with_capacity(block.body.transactions.len());
2452        let base_fee = block.header.base_fee_per_gas();
2453        let storage = self.blockchain.storage.read();
2454        for hash in block.body.transactions.iter().map(|tx| tx.hash()) {
2455            let info = storage.transactions.get(&hash)?.info.clone();
2456            let tx = block.body.transactions.get(info.transaction_index as usize)?.clone();
2457
2458            let tx = transaction_build(Some(hash), tx, Some(block), Some(info), base_fee);
2459            transactions.push(tx);
2460        }
2461        Some(transactions)
2462    }
2463
2464    pub fn mined_block_by_number(&self, number: BlockNumber) -> Option<AnyRpcBlock> {
2465        let (block, hash) = self.get_block_with_hash(number)?;
2466        let mut block = self.convert_block_with_hash(block, Some(hash));
2467        block.transactions.convert_to_hashes();
2468        Some(block)
2469    }
2470
2471    pub fn get_full_block(&self, id: impl Into<BlockId>) -> Option<AnyRpcBlock> {
2472        let (block, hash) = self.get_block_with_hash(id)?;
2473        let transactions = self.mined_transactions_in_block(&block)?;
2474        let mut block = self.convert_block_with_hash(block, Some(hash));
2475        block.inner.transactions = BlockTransactions::Full(transactions);
2476        Some(block)
2477    }
2478
2479    /// Takes a block as it's stored internally and returns the eth api conform block format.
2480    pub fn convert_block(&self, block: Block) -> AnyRpcBlock {
2481        self.convert_block_with_hash(block, None)
2482    }
2483
2484    /// Takes a block as it's stored internally and returns the eth api conform block format.
2485    /// If `known_hash` is provided, it will be used instead of computing `hash_slow()`.
2486    pub fn convert_block_with_hash(&self, block: Block, known_hash: Option<B256>) -> AnyRpcBlock {
2487        let transactions = block.body.transactions.iter().map(|tx| tx.hash()).collect();
2488        let block = canonical_block(block);
2489        let size = U256::from(block.length() as u32);
2490        let header = block.header;
2491
2492        let hash = known_hash.unwrap_or_else(|| header.hash_slow());
2493        let number = header.number();
2494        let withdrawals_root = header.withdrawals_root();
2495        let consensus_context = header.as_tempo().and_then(|header| header.consensus_context);
2496        let tempo_fields = header
2497            .as_tempo()
2498            .map(|header| {
2499                (
2500                    header.timestamp_millis(),
2501                    header.general_gas_limit,
2502                    header.shared_gas_limit,
2503                    header.timestamp_millis_part,
2504                )
2505            })
2506            .or_else(|| {
2507                self.is_tempo()
2508                    .then(|| (header.timestamp().saturating_mul(1000), header.gas_limit(), 0, 0))
2509            });
2510
2511        let block = AlloyBlock {
2512            header: AlloyHeader {
2513                inner: AnyHeader::from(header.into_inner()),
2514                hash,
2515                total_difficulty: Some(self.total_difficulty()),
2516                size: Some(size),
2517            },
2518            transactions: alloy_rpc_types::BlockTransactions::Hashes(transactions),
2519            uncles: vec![],
2520            withdrawals: withdrawals_root.map(|_| Default::default()),
2521        };
2522
2523        let mut block = WithOtherFields::new(block);
2524
2525        // If Arbitrum, apply chain specifics to converted block.
2526        if is_arbitrum(self.protocol_chain_id()) {
2527            // Set `l1BlockNumber` field.
2528            block.other.insert(
2529                "l1BlockNumber".to_string(),
2530                serde_json::json!(self.evm_block_number(number)),
2531            );
2532        }
2533
2534        if let Some((
2535            timestamp_millis,
2536            general_gas_limit,
2537            shared_gas_limit,
2538            timestamp_millis_part,
2539        )) = tempo_fields
2540        {
2541            block.other.insert(
2542                "timestampMillis".to_string(),
2543                serde_json::Value::String(format!("0x{timestamp_millis:x}")),
2544            );
2545            block.other.insert(
2546                "mainBlockGeneralGasLimit".to_string(),
2547                serde_json::Value::String(format!("0x{general_gas_limit:x}")),
2548            );
2549            block.other.insert(
2550                "sharedGasLimit".to_string(),
2551                serde_json::Value::String(format!("0x{shared_gas_limit:x}")),
2552            );
2553            block.other.insert(
2554                "timestampMillisPart".to_string(),
2555                serde_json::Value::String(format!("0x{timestamp_millis_part:x}")),
2556            );
2557        }
2558
2559        if let Some(consensus_context) = consensus_context {
2560            block.other.insert(
2561                "consensusContext".to_string(),
2562                serde_json::to_value(consensus_context)
2563                    .expect("Tempo consensus context serializes"),
2564            );
2565        }
2566
2567        AnyRpcBlock::from(block)
2568    }
2569
2570    pub async fn block_by_hash(&self, hash: B256) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2571        trace!(target: "backend", "get block by hash {:?}", hash);
2572        if let tx @ Some(_) = self.mined_block_by_hash(hash) {
2573            return Ok(tx);
2574        }
2575
2576        if let Some(fork) = self.get_fork() {
2577            return Ok(fork.block_by_hash(hash).await?);
2578        }
2579
2580        Ok(None)
2581    }
2582
2583    pub async fn block_by_hash_full(
2584        &self,
2585        hash: B256,
2586    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2587        trace!(target: "backend", "get block by hash {:?}", hash);
2588        if let tx @ Some(_) = self.get_full_block(hash) {
2589            return Ok(tx);
2590        }
2591
2592        if let Some(fork) = self.get_fork() {
2593            return Ok(fork.block_by_hash_full(hash).await?);
2594        }
2595
2596        Ok(None)
2597    }
2598
2599    pub async fn block_by_number(
2600        &self,
2601        number: BlockNumber,
2602    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2603        trace!(target: "backend", "get block by number {:?}", number);
2604        if let tx @ Some(_) = self.mined_block_by_number(number) {
2605            return Ok(tx);
2606        }
2607
2608        if let Some(fork) = self.get_fork() {
2609            let number = self.convert_block_number(Some(number));
2610            if fork.predates_fork_inclusive(number) {
2611                return Ok(fork.block_by_number(number).await?);
2612            }
2613        }
2614
2615        Ok(None)
2616    }
2617
2618    pub async fn block_by_number_full(
2619        &self,
2620        number: BlockNumber,
2621    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2622        trace!(target: "backend", "get block by number {:?}", number);
2623        if let tx @ Some(_) = self.get_full_block(number) {
2624            return Ok(tx);
2625        }
2626
2627        if let Some(fork) = self.get_fork() {
2628            let number = self.convert_block_number(Some(number));
2629            if fork.predates_fork_inclusive(number) {
2630                return Ok(fork.block_by_number_full(number).await?);
2631            }
2632        }
2633
2634        Ok(None)
2635    }
2636
2637    /// Converts the `BlockNumber` into a numeric value
2638    ///
2639    /// # Errors
2640    ///
2641    /// returns an error if the requested number is larger than the current height
2642    pub async fn ensure_block_number<T: Into<BlockId>>(
2643        &self,
2644        block_id: Option<T>,
2645    ) -> Result<u64, BlockchainError> {
2646        let current = self.best_number();
2647        let requested = match block_id.map(Into::into).unwrap_or(BlockId::latest()) {
2648            BlockId::Hash(hash) => {
2649                self.block_by_hash(hash.block_hash)
2650                    .await?
2651                    .ok_or(BlockchainError::BlockNotFound)?
2652                    .header
2653                    .number
2654            }
2655            BlockId::Number(num) => self.convert_block_number_at(Some(num), current),
2656        };
2657
2658        if requested > current {
2659            Err(BlockchainError::BlockOutOfRange(current, requested))
2660        } else {
2661            Ok(requested)
2662        }
2663    }
2664
2665    /// Returns the number of the block a trace method executes against.
2666    ///
2667    /// Unlike [`Self::ensure_block_number`], this reports every block that is not part of this
2668    /// chain as [`BlockchainError::BlockNotFound`]: a number past the head or below the genesis
2669    /// number, and a hash of a block that is neither stored locally nor the fork block or an
2670    /// upstream block below it.
2671    pub async fn ensure_trace_block_number(
2672        &self,
2673        block_id: BlockId,
2674    ) -> Result<u64, BlockchainError> {
2675        let number = match self.ensure_block_number(Some(block_id)).await {
2676            Err(BlockchainError::BlockOutOfRange(_, _)) => {
2677                return Err(BlockchainError::BlockNotFound);
2678            }
2679            result => result?,
2680        };
2681        let known = match block_id {
2682            BlockId::Hash(hash) => {
2683                self.get_block_by_hash(hash.block_hash).is_some()
2684                    || self.get_fork().is_some_and(|fork| {
2685                        fork.predates_fork(number) || hash.block_hash == fork.block_hash()
2686                    })
2687            }
2688            BlockId::Number(_) => {
2689                self.get_block(number).is_some()
2690                    || self.get_fork().is_some_and(|fork| fork.predates_fork_inclusive(number))
2691            }
2692        };
2693        if known { Ok(number) } else { Err(BlockchainError::BlockNotFound) }
2694    }
2695
2696    /// Returns the timestamp represented by a block request.
2697    #[cfg(feature = "base")]
2698    pub async fn block_request_timestamp(
2699        &self,
2700        block_request: &BlockRequest<FoundryTxEnvelope>,
2701    ) -> Result<u64, BlockchainError> {
2702        match block_request {
2703            BlockRequest::Pending(_) => Ok(self.time.current_call_timestamp()),
2704            BlockRequest::Number(number) => self
2705                .block_by_number(BlockNumber::Number(*number))
2706                .await?
2707                .map(|block| block.header.timestamp())
2708                .ok_or(BlockchainError::BlockNotFound),
2709        }
2710    }
2711
2712    /// Injects all configured precompiles into the given precompile map.
2713    ///
2714    /// This applies five layers:
2715    /// 1. Network-specific precompiles (e.g. Tempo, OP)
2716    /// 2. Chain- and timestamp-specific precompiles
2717    /// 3. User-provided precompiles via [`PrecompileFactory`]
2718    /// 4. Cheatcode ecrecover overrides (if active)
2719    /// 5. Block-specific precompiles (e.g. ArbSys)
2720    fn inject_precompiles(&self, precompiles: &mut PrecompilesMap, evm_env: &EvmEnv) {
2721        self.inject_configured_precompiles(precompiles, evm_env);
2722
2723        if let Some(block_number) = self.arbitrum_block_number(evm_env) {
2724            self.inject_arbitrum_precompile_at_block(precompiles, block_number);
2725        }
2726    }
2727
2728    fn inject_configured_precompiles(&self, precompiles: &mut PrecompilesMap, evm_env: &EvmEnv) {
2729        self.networks.inject_precompiles(precompiles);
2730        apply_bsc_p256_precompile(
2731            precompiles,
2732            self.protocol_chain_id(),
2733            evm_env.block_env.timestamp.saturating_to(),
2734        );
2735
2736        if let Some(factory) = &self.precompile_factory {
2737            factory.install(precompiles);
2738        }
2739
2740        let cheats = Arc::new(self.cheats.clone());
2741        if cheats.has_recover_overrides() {
2742            let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats));
2743            precompiles.apply_precompile(&EC_RECOVER, move |_| {
2744                Some(DynPrecompile::new_stateful(
2745                    cheat_ecrecover.precompile_id().clone(),
2746                    move |input| cheat_ecrecover.call(input),
2747                ))
2748            });
2749        }
2750    }
2751
2752    fn inject_arbitrum_precompile_at_block(
2753        &self,
2754        precompiles: &mut PrecompilesMap,
2755        block_number: u64,
2756    ) {
2757        precompiles.apply_precompile(&arbitrum::ARB_SYS_ADDRESS, move |_| {
2758            Some(arbitrum::arb_sys_precompile(block_number))
2759        });
2760    }
2761
2762    fn simulation_precompile_overrides(
2763        &self,
2764        state_overrides: Option<&StateOverride>,
2765        evm_env: &EvmEnv,
2766    ) -> Result<SimulationPrecompileOverrides, BlockchainError> {
2767        let mut moves = state_overrides
2768            .into_iter()
2769            .flatten()
2770            .filter_map(|(source, account)| {
2771                account.move_precompile_to.map(|destination| (*source, destination))
2772            })
2773            .collect::<Vec<_>>();
2774        moves.sort_unstable();
2775        if moves.is_empty() {
2776            return Ok(SimulationPrecompileOverrides::default());
2777        }
2778        #[cfg(feature = "base")]
2779        if self.is_base() {
2780            return Err(simulate_rpc_error(
2781                -32000,
2782                "precompile moves are not supported on this network",
2783            ));
2784        }
2785        if self.is_optimism() || self.is_tempo() || self.is_monad() {
2786            return Err(simulate_rpc_error(
2787                -32000,
2788                "precompile moves are not supported on this network",
2789            ));
2790        }
2791
2792        let mut precompiles = PrecompilesMap::from_static(Precompiles::new(
2793            PrecompileSpecId::from_spec_id(*evm_env.spec_id()),
2794        ));
2795        self.inject_precompiles(&mut precompiles, evm_env);
2796        let precompile_addresses = precompiles.addresses().copied().collect::<HashSet<_>>();
2797
2798        // Validate every source first so invalid-source errors take precedence over the more
2799        // specific move errors below.
2800        for (source, _) in &moves {
2801            if !precompile_addresses.contains(source) {
2802                return Err(simulate_rpc_error(
2803                    -32000,
2804                    format!("account {source} is not a precompile"),
2805                ));
2806            }
2807        }
2808        for (source, destination) in &moves {
2809            if source == destination {
2810                return Err(simulate_rpc_error(
2811                    -38022,
2812                    format!("cannot move precompile {source} to itself"),
2813                ));
2814            }
2815        }
2816        let mut destinations = Vec::with_capacity(moves.len());
2817        for (_, destination) in &moves {
2818            if destinations.contains(destination) {
2819                return Err(simulate_rpc_error(
2820                    -38023,
2821                    format!("multiple precompiles moved to {destination}"),
2822                ));
2823            }
2824            destinations.push(*destination);
2825        }
2826
2827        Ok(SimulationPrecompileOverrides { moves })
2828    }
2829
2830    fn apply_simulation_precompile_overrides(
2831        &self,
2832        precompiles: &mut PrecompilesMap,
2833        overrides: &SimulationPrecompileOverrides,
2834    ) -> Result<alloy_primitives::map::AddressSet, BlockchainError> {
2835        let warm_addresses = precompiles.addresses().copied().collect();
2836        precompiles.move_precompiles(overrides.moves.iter().copied()).map_err(
2837            |MovePrecompileError::NotAPrecompile(address)| {
2838                simulate_rpc_error(-32000, format!("account {address} is not a precompile"))
2839            },
2840        )?;
2841
2842        // A dynamic lookup must not restore a precompile removed from its protocol address.
2843        let moved_sources =
2844            Arc::new(overrides.moves.iter().map(|(source, _)| *source).collect::<HashSet<_>>());
2845        precompiles.map_precompile_lookup(move |address, previous| {
2846            if moved_sources.contains(address) {
2847                None
2848            } else {
2849                previous.and_then(|lookup| lookup.lookup(address))
2850            }
2851        });
2852        Ok(warm_addresses)
2853    }
2854
2855    fn inject_tempo_precompiles<DB, I>(
2856        &self,
2857        evm: &mut tempo_evm::evm::TempoEvm<DB, I>,
2858        evm_env: &EvmEnv,
2859    ) where
2860        DB: Database,
2861        I: Inspector<TempoContext<DB>>,
2862    {
2863        self.inject_configured_precompiles(evm.precompiles_mut(), evm_env);
2864        // Re-extend Tempo precompiles, preserving shared non-creditable slots.
2865        let cfg = evm.ctx().cfg.clone();
2866        let non_creditable_slots = evm.non_creditable_slots();
2867        extend_tempo_precompiles(
2868            evm.precompiles_mut(),
2869            &cfg,
2870            StorageActions::disabled(),
2871            non_creditable_slots,
2872        );
2873    }
2874
2875    /// Executes a call with the Ethereum EVM.
2876    ///
2877    /// Creates an Ethereum EVM, injects precompiles, and transacts with a
2878    /// plain [`TxEnv`].
2879    fn transact_eth_with_inspector_ref<'db, I>(
2880        &self,
2881        db: &'db dyn DatabaseRef,
2882        evm_env: &EvmEnv,
2883        inspector: &mut I,
2884        tx_env: TxEnv,
2885    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2886    where
2887        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db dyn DatabaseRef>>>,
2888    {
2889        self.transact_eth_with_inspector_ref_and_precompile_overrides(
2890            db,
2891            evm_env,
2892            inspector,
2893            tx_env,
2894            &SimulationPrecompileOverrides::default(),
2895        )
2896    }
2897
2898    fn transact_eth_with_inspector_ref_and_precompile_overrides<'db, I>(
2899        &self,
2900        db: &'db dyn DatabaseRef,
2901        evm_env: &EvmEnv,
2902        inspector: &mut I,
2903        tx_env: TxEnv,
2904        overrides: &SimulationPrecompileOverrides,
2905    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2906    where
2907        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db dyn DatabaseRef>>>,
2908    {
2909        let mut evm = self.prepare_eth_evm(db, evm_env, inspector, overrides)?;
2910        Ok(evm.transact(tx_env)?)
2911    }
2912
2913    fn transact_eth_simulation_with_inspector_ref<'db, I>(
2914        &self,
2915        db: &'db dyn DatabaseRef,
2916        evm_env: &EvmEnv,
2917        inspector: &mut I,
2918        tx_env: TxEnv,
2919        overrides: &SimulationPrecompileOverrides,
2920    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2921    where
2922        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db dyn DatabaseRef>>>,
2923    {
2924        let evm = self.prepare_eth_evm(db, evm_env, inspector, overrides)?;
2925        let mut evm = evm.into_inner();
2926        ContextSetters::set_tx(evm.ctx_mut(), tx_env);
2927        let mut handler = SimulationHandler::<
2928            _,
2929            revm::context::result::EVMError<DatabaseError>,
2930            EthFrame<EthInterpreter>,
2931        >::default();
2932        let result = handler.inspect_run(&mut evm)?;
2933        let state = evm.ctx_mut().journal_mut().finalize();
2934        Ok(ResultAndState { result, state })
2935    }
2936
2937    /// Creates an Ethereum EVM with the active precompiles and simulation overrides.
2938    fn prepare_eth_evm<'db, 'inspector, I>(
2939        &self,
2940        db: &'db dyn DatabaseRef,
2941        evm_env: &EvmEnv,
2942        inspector: &'inspector mut I,
2943        overrides: &SimulationPrecompileOverrides,
2944    ) -> Result<
2945        EthEvm<WrapDatabaseRef<&'db dyn DatabaseRef>, &'inspector mut I, PrecompilesMap>,
2946        BlockchainError,
2947    >
2948    where
2949        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db dyn DatabaseRef>>>,
2950    {
2951        let mut evm = EthEvmFactory::default().create_evm_with_inspector(
2952            WrapDatabaseRef(db),
2953            evm_env.clone(),
2954            inspector,
2955        );
2956        self.inject_precompiles(evm.precompiles_mut(), evm_env);
2957        if !overrides.moves.is_empty() {
2958            let warm_addresses =
2959                self.apply_simulation_precompile_overrides(evm.precompiles_mut(), overrides)?;
2960            // EIP-2929 warms protocol precompile addresses, not simulation-only destinations.
2961            evm.ctx_mut().journal_mut().warm_precompiles(&warm_addresses);
2962        }
2963        Ok(evm)
2964    }
2965
2966    /// Executes an envelope through the active network EVM with optional Monad block context.
2967    ///
2968    /// Returns both the execution result and the base [`TxEnv`].
2969    fn transact_envelope_with_inspector_ref_and_context<'db, I>(
2970        &self,
2971        db: &'db dyn DatabaseRef,
2972        evm_env: &EvmEnv,
2973        inspector: &mut I,
2974        pending: &PendingTransaction<FoundryTxEnvelope>,
2975        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
2976            MonadExecutionContext<'_>,
2977        >,
2978    ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
2979    where
2980        I: BackendInspector<WrapDatabaseRef<&'db dyn DatabaseRef>>,
2981    {
2982        self.transact_envelope_with_inspector_ref_and_context_kind(
2983            db,
2984            evm_env,
2985            inspector,
2986            pending,
2987            EnvelopeExecution::transaction(monad_context, self.hardfork()),
2988        )
2989    }
2990
2991    /// Replays a mined envelope through the active network's canonical replay entry point.
2992    fn replay_envelope_with_inspector_ref_and_context<'db, I>(
2993        &self,
2994        db: &'db dyn DatabaseRef,
2995        evm_env: &EvmEnv,
2996        inspector: &mut I,
2997        pending: &PendingTransaction<FoundryTxEnvelope>,
2998        execution: EnvelopeExecution<'_>,
2999    ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
3000    where
3001        I: BackendInspector<WrapDatabaseRef<&'db dyn DatabaseRef>>,
3002    {
3003        self.transact_envelope_with_inspector_ref_and_context_kind(
3004            db, evm_env, inspector, pending, execution,
3005        )
3006    }
3007
3008    fn transact_envelope_with_inspector_ref_and_context_kind<'db, I>(
3009        &self,
3010        db: &'db dyn DatabaseRef,
3011        evm_env: &EvmEnv,
3012        inspector: &mut I,
3013        pending: &PendingTransaction<FoundryTxEnvelope>,
3014        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] execution: EnvelopeExecution<
3015            '_,
3016        >,
3017    ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
3018    where
3019        I: BackendInspector<WrapDatabaseRef<&'db dyn DatabaseRef>>,
3020    {
3021        #[cfg(any(feature = "base", feature = "optimism"))]
3022        let (tx, sender) = (pending.transaction.as_ref(), *pending.sender());
3023        #[cfg(feature = "base")]
3024        if self.is_base() {
3025            let base_tx: BaseTransaction<TxEnv> =
3026                FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
3027            let base = base_tx.base.clone();
3028            let result = self.transact_base_with_inspector_ref(db, evm_env, inspector, base_tx)?;
3029            return Ok((result, base));
3030        }
3031        if self.is_tempo() {
3032            let tx_env: TempoTxEnv = build_tx_env_for_pending(pending, self.cheats());
3033            let base = tx_env.inner.clone();
3034            let result = self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)?;
3035            return Ok((result, base));
3036        }
3037        #[cfg(feature = "optimism")]
3038        if self.is_optimism() {
3039            let op_tx: OpTransaction<TxEnv> =
3040                FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
3041            let base = op_tx.base.clone();
3042            let spec = self.hardfork().into();
3043            let result =
3044                self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx, spec)?;
3045            return Ok((result, base));
3046        }
3047        let tx_env: TxEnv = build_tx_env_for_pending(pending, self.cheats());
3048        let base = tx_env.clone();
3049        #[cfg(feature = "monad")]
3050        let result = if self.is_monad() {
3051            let context = monad::resolve_execution_context(execution.monad_context, &tx_env);
3052            self.transact_monad_with_inspector_ref(
3053                db,
3054                evm_env,
3055                inspector,
3056                tx_env,
3057                monad::PreparedExecution {
3058                    context,
3059                    kind: execution.kind,
3060                    hardfork: monad_revm::MonadHardfork::from(execution.hardfork),
3061                },
3062            )?
3063        } else {
3064            self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?
3065        };
3066        #[cfg(not(feature = "monad"))]
3067        let result = self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?;
3068        Ok((result, base))
3069    }
3070
3071    /// Builds the Tempo [`EvmEnv`] (spec, gas params, [`TempoBlockEnv`]) from a base
3072    /// env.
3073    fn build_tempo_evm_env(
3074        evm_env: &EvmEnv,
3075        hardfork: TempoHardfork,
3076    ) -> EvmEnv<TempoHardfork, TempoBlockEnv> {
3077        EvmEnv::new(
3078            evm_env.cfg_env.clone().with_spec_and_gas_params(hardfork, tempo_gas_params(hardfork)),
3079            TempoBlockEnv {
3080                inner: evm_env.block_env.clone(),
3081                timestamp_millis_part: 0,
3082                ..Default::default()
3083            },
3084        )
3085    }
3086
3087    /// Creates a Tempo EVM, injects precompiles, and transacts with a native [`TempoTxEnv`].
3088    fn transact_tempo_with_inspector_ref<'db, I>(
3089        &self,
3090        db: &'db dyn DatabaseRef,
3091        evm_env: &EvmEnv,
3092        inspector: &mut I,
3093        tx_env: TempoTxEnv,
3094    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
3095    where
3096        I: Inspector<TempoContext<WrapDatabaseRef<&'db dyn DatabaseRef>>>,
3097    {
3098        let tempo_env = Self::build_tempo_evm_env(evm_env, self.tempo_hardfork());
3099        let mut evm = TempoEvmFactory::default().create_evm_with_inspector(
3100            WrapDatabaseRef(db),
3101            tempo_env,
3102            inspector,
3103        );
3104        self.inject_tempo_precompiles(&mut evm, evm_env);
3105        Ok(evm.transact(tx_env)?)
3106    }
3107
3108    /// Creates a concrete EVM + [`AnvilBlockExecutor`], runs pre-execution changes, and
3109    /// executes pool transactions. Returns the execution results and drops the EVM.
3110    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
3111    fn execute_with_block_executor(
3112        &self,
3113        db: &mut AnvilCacheDB<StateRef<'_>>,
3114        evm_env: &EvmEnv,
3115        parent_hash: B256,
3116        hardfork: FoundryHardfork,
3117        parent_beacon_block_root: Option<B256>,
3118        execution_kind: BlockExecutionKind,
3119        pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
3120        gas_config: &PoolTxGasConfig,
3121        inspector_tx_config: &InspectorTxConfig,
3122        validator: &dyn Fn(
3123            &PoolTransaction<FoundryTxEnvelope>,
3124            &AccountInfo,
3125        ) -> Result<(), InvalidTransactionError>,
3126    ) -> Result<
3127        (ExecutedPoolTransactions<FoundryTxEnvelope>, BlockExecutionResult<FoundryReceiptEnvelope>),
3128        BlockchainError,
3129    > {
3130        let spec_id = *evm_env.spec_id();
3131        #[cfg(feature = "monad")]
3132        if self.is_monad() {
3133            return self.execute_with_monad_block_executor(
3134                db,
3135                evm_env,
3136                parent_hash,
3137                spec_id,
3138                hardfork.into(),
3139                pool_transactions,
3140                gas_config,
3141                inspector_tx_config,
3142                validator,
3143            );
3144        }
3145
3146        let inspector = self.build_mining_inspector();
3147        let transitions =
3148            self.ethereum_block_transitions(hardfork, parent_beacon_block_root, execution_kind);
3149
3150        macro_rules! execute {
3151            ($executor:expr) => {{
3152                let mut executor = $executor;
3153                executor
3154                    .apply_pre_execution_changes()
3155                    .map_err(|err| BlockchainError::Internal(err.to_string()))?;
3156                let pool_result = execute_pool_transactions(
3157                    &mut executor,
3158                    pool_transactions,
3159                    gas_config,
3160                    inspector_tx_config,
3161                    self.cheats(),
3162                    validator,
3163                    &mut execute_pool_transaction,
3164                );
3165                let (_, block_result) =
3166                    executor.finish().map_err(|err| BlockchainError::Internal(err.to_string()))?;
3167                Ok((pool_result, block_result))
3168            }};
3169        }
3170
3171        #[cfg(feature = "base")]
3172        if self.is_base() {
3173            let configured = match hardfork {
3174                FoundryHardfork::Base(upgrade) => upgrade,
3175                _ => BaseUpgrade::Azul,
3176            };
3177            let upgrade = if self.is_fork() {
3178                BaseUpgrade::from_chain_and_timestamp(
3179                    evm_env.cfg_env.chain_id,
3180                    evm_env.block_env.timestamp.saturating_to(),
3181                )
3182                .unwrap_or(configured)
3183            } else {
3184                configured
3185            };
3186            let cfg =
3187                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(BaseSpecId::new(upgrade));
3188            let activation_admin = self.base_activation_admin().or_else(|| {
3189                ChainConfig::activation_admin_address_for_upgrade_by_chain_id(cfg.chain_id, upgrade)
3190            });
3191            let base_env = EvmEnv::new(cfg, evm_env.block_env.clone());
3192            let mut evm = BaseEvmFactory::new(activation_admin)
3193                .create_evm_with_inspector(db, base_env, inspector);
3194            evm.ctx_mut().cfg.tx_chain_id_check = true;
3195            self.inject_precompiles(evm.precompiles_mut(), evm_env);
3196            let mut executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
3197                .with_max_blob_gas_per_block(gas_config.max_blob_gas_per_block);
3198            executor.set_base_upgrade(upgrade);
3199            return execute!(executor);
3200        }
3201
3202        #[cfg(feature = "optimism")]
3203        if self.is_optimism() {
3204            let op_env = EvmEnv::new(
3205                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(hardfork.into()),
3206                evm_env.block_env.clone(),
3207            );
3208            let mut evm =
3209                OpEvmFactory::<OpTx>::default().create_evm_with_inspector(db, op_env, inspector);
3210            self.inject_precompiles(evm.precompiles_mut(), evm_env);
3211            let mut executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
3212                .with_max_blob_gas_per_block(gas_config.max_blob_gas_per_block);
3213            executor.set_optimism_hardfork(hardfork);
3214            return execute!(executor);
3215        }
3216
3217        if self.is_tempo() {
3218            let tempo_env = Self::build_tempo_evm_env(evm_env, self.tempo_hardfork());
3219            let mut evm =
3220                TempoEvmFactory::default().create_evm_with_inspector(db, tempo_env, inspector);
3221            self.inject_precompiles(evm.precompiles_mut(), evm_env);
3222            let executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
3223                .with_max_blob_gas_per_block(gas_config.max_blob_gas_per_block);
3224            return execute!(executor);
3225        }
3226        let mut evm =
3227            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
3228        self.inject_precompiles(evm.precompiles_mut(), evm_env);
3229        let executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
3230            .with_max_blob_gas_per_block(gas_config.max_blob_gas_per_block);
3231        execute!(executor)
3232    }
3233
3234    /// Applies Ethereum block-start transitions to a disposable simulation candidate.
3235    fn apply_simulation_pre_execution_changes<DB>(
3236        &self,
3237        db: DB,
3238        evm_env: &EvmEnv,
3239        parent_hash: B256,
3240        transitions: EthereumBlockTransitions,
3241    ) -> Result<(), BlockchainError>
3242    where
3243        DB: StateDB,
3244    {
3245        let inspector = self.build_mining_inspector();
3246        let mut evm =
3247            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
3248        self.inject_precompiles(evm.precompiles_mut(), evm_env);
3249        apply_ethereum_pre_execution_changes(&mut evm, parent_hash, transitions)
3250            .map_err(|err| BlockchainError::Internal(err.to_string()))
3251    }
3252
3253    /// Applies Ethereum post-block transitions to a disposable simulation candidate.
3254    fn apply_simulation_post_execution_changes<DB>(
3255        &self,
3256        db: DB,
3257        evm_env: &EvmEnv,
3258        transitions: EthereumBlockTransitions,
3259        receipts: &[FoundryReceiptEnvelope],
3260    ) -> Result<alloy_eips::eip7685::Requests, BlockchainError>
3261    where
3262        DB: StateDB,
3263    {
3264        let inspector = self.build_mining_inspector();
3265        let mut evm =
3266            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
3267        self.inject_precompiles(evm.precompiles_mut(), evm_env);
3268        apply_ethereum_post_execution_changes(&mut evm, transitions, receipts)
3269            .map_err(|err| BlockchainError::Internal(err.to_string()))
3270    }
3271
3272    /// ## EVM settings
3273    ///
3274    /// This modifies certain EVM settings to mirror geth's `SkipAccountChecks` when transacting requests, see also: <https://github.com/ethereum/go-ethereum/blob/380688c636a654becc8f114438c2a5d93d2db032/core/state_transition.go#L145-L148>:
3275    ///
3276    ///  - `disable_eip3607` is set to `true`
3277    ///  - the base fee is zero and its check disabled for a zero-fee call, while a priced call must
3278    ///    pay at least the base fee
3279    ///  - the blob base fee is zero for a blob call without a blob fee cap
3280    ///  - `tx_gas_limit_cap` is set to `Some(u64::MAX)` indicating no gas limit cap, except under
3281    ///    EIP-8037
3282    ///  - `nonce` check is skipped
3283    fn build_call_env_with_base(
3284        &self,
3285        request: WithOtherFields<TransactionRequest>,
3286        fee_details: FeeDetails,
3287        block_env: BlockEnv,
3288        base_evm_env: Option<&EvmEnv>,
3289    ) -> (EvmEnv, TxEnv, CallTransactionInfo) {
3290        let tx_type = request.minimal_tx_type() as u8;
3291        #[cfg(any(feature = "base", feature = "optimism"))]
3292        let mut transaction_info = CallTransactionInfo::default();
3293        #[cfg(not(any(feature = "base", feature = "optimism")))]
3294        let transaction_info = CallTransactionInfo::default();
3295
3296        let WithOtherFields::<TransactionRequest> {
3297            inner:
3298                TransactionRequest {
3299                    from,
3300                    to,
3301                    gas,
3302                    value,
3303                    input,
3304                    access_list,
3305                    blob_versioned_hashes,
3306                    authorization_list,
3307                    nonce,
3308                    sidecar: _,
3309                    chain_id,
3310                    .. // Rest of the gas fees related fields are taken from `fee_details`
3311                },
3312            other,
3313        } = request;
3314
3315        let FeeDetails {
3316            gas_price,
3317            max_fee_per_gas,
3318            max_priority_fee_per_gas,
3319            max_fee_per_blob_gas,
3320        } = fee_details;
3321
3322        let gas_limit = gas.unwrap_or(block_env.gas_limit);
3323        let mut evm_env = base_evm_env.cloned().unwrap_or_else(|| self.evm_env.read().clone());
3324        evm_env.block_env = block_env;
3325        // we want to disable this in eth_call, since this is common practice used by other node
3326        // impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
3327        evm_env.cfg_env.disable_block_gas_limit = true;
3328        // Under EIP-8037 the cap stays, because gas above it becomes the state gas reservoir.
3329        if !evm_env.cfg_env.is_amsterdam_eip8037_enabled() {
3330            evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX);
3331        }
3332
3333        // Disable nonce check in revm
3334        evm_env.cfg_env.disable_nonce_check = true;
3335
3336        let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| {
3337            self.fees().raw_gas_price().saturating_add(MIN_SUGGESTED_PRIORITY_FEE)
3338        });
3339        // A zero-fee call runs with a zero base fee, as in geth's eth_call, so BASEFEE never
3340        // exceeds the price the call pays. A priced call keeps the base fee check, so a price
3341        // below the base fee is rejected as it is for a transaction.
3342        if gas_price == 0 {
3343            evm_env.block_env.basefee = 0;
3344        }
3345        // Set the check on every call, because a cloned base environment can carry a disabled
3346        // check. Pre-London blocks have no protocol base fee, so they skip the check too.
3347        evm_env.cfg_env.disable_base_fee =
3348            evm_env.block_env.basefee == 0 || evm_env.cfg_env.spec < SpecId::LONDON;
3349        let caller = from.unwrap_or_default();
3350        let to = to.as_ref().and_then(TxKind::to);
3351        let blob_hashes = blob_versioned_hashes.unwrap_or_default();
3352        // As in geth's eth_call, a blob call without a blob fee cap runs at a zero blob base fee
3353        // and pays no blob fee, while other calls keep the block's blob base fee.
3354        let is_blob_call = !blob_hashes.is_empty() || max_fee_per_blob_gas.is_some();
3355        let max_fee_per_blob_gas = max_fee_per_blob_gas.unwrap_or_default();
3356        if is_blob_call
3357            && max_fee_per_blob_gas == 0
3358            && let Some(blob) = evm_env.block_env.blob_excess_gas_and_price.as_mut()
3359        {
3360            blob.blob_gasprice = 0;
3361        }
3362        let mut tx_env = TxEnv {
3363            caller,
3364            gas_limit,
3365            gas_price,
3366            gas_priority_fee: max_priority_fee_per_gas,
3367            max_fee_per_blob_gas,
3368            kind: match to {
3369                Some(addr) => TxKind::Call(*addr),
3370                None => TxKind::Create,
3371            },
3372            tx_type,
3373            value: value.unwrap_or_default(),
3374            data: input.into_input().unwrap_or_default(),
3375            chain_id: Some(chain_id.unwrap_or(evm_env.cfg_env.chain_id)),
3376            access_list: access_list.unwrap_or_default(),
3377            blob_hashes,
3378            ..Default::default()
3379        };
3380        tx_env.set_signed_authorization(authorization_list.unwrap_or_default());
3381
3382        if let Some(nonce) = nonce {
3383            tx_env.nonce = nonce;
3384        }
3385
3386        // Deposit transaction? (only valid when a deposit-capable network is active)
3387        #[cfg(any(feature = "base", feature = "optimism"))]
3388        {
3389            transaction_info.deposit = if self.ensure_deposits_active().is_ok()
3390                && let Ok(deposit) = get_deposit_tx_parts(&other)
3391            {
3392                deposit
3393            } else {
3394                DepositTransactionParts::default()
3395            };
3396        }
3397        #[cfg(not(any(feature = "base", feature = "optimism")))]
3398        {
3399            // `other` carries OP-only deposit fields; consumed only when feature is enabled.
3400            let _ = &other;
3401        }
3402
3403        (evm_env, tx_env, transaction_info)
3404    }
3405
3406    fn prepare_call_env(
3407        &self,
3408        state: &dyn DatabaseRef,
3409        request: WithOtherFields<TransactionRequest>,
3410        fee_details: FeeDetails,
3411        block_env: BlockEnv,
3412    ) -> Result<PreparedCall, BlockchainError> {
3413        self.prepare_call_env_from_base(state, request, fee_details, block_env, None)
3414    }
3415
3416    fn prepare_call_env_from_base(
3417        &self,
3418        state: &dyn DatabaseRef,
3419        request: WithOtherFields<TransactionRequest>,
3420        fee_details: FeeDetails,
3421        block_env: BlockEnv,
3422        base_evm_env: Option<&EvmEnv>,
3423    ) -> Result<PreparedCall, BlockchainError> {
3424        let gas_omitted = request.gas.is_none();
3425        let request = self.parse_transaction_request(request)?;
3426        let mut prepared = self.prepare_typed_call_env_with_base(
3427            state,
3428            request,
3429            fee_details,
3430            block_env,
3431            base_evm_env,
3432        )?;
3433        // Without a gas limit, a priced call gets at most the gas its sender can pay for, rather
3434        // than failing the funds check for the default limit. Tempo pays fees in tokens instead,
3435        // and OP deposits mint funds before paying for execution.
3436        let cap_by_balance = match &prepared.tx_env {
3437            CallTxEnv::Tempo(_) => false,
3438            #[cfg(feature = "optimism")]
3439            CallTxEnv::Op(tx) => tx.tx_type() != DEPOSIT_TX_TYPE_ID,
3440            _ => true,
3441        };
3442        let min_gas = if prepared.evm_env.cfg_env.enable_amsterdam_eip2780 {
3443            eip2780::TX_BASE_COST
3444        } else {
3445            MIN_TRANSACTION_GAS as u64
3446        };
3447        let tx_env = prepared.tx_env.base_mut();
3448        if gas_omitted && cap_by_balance && tx_env.gas_price > 0 {
3449            let balance =
3450                state.basic_ref(tx_env.caller)?.map(|info| info.balance).unwrap_or_default();
3451            let upfront = tx_env.value.saturating_add(tx_env.calc_max_data_fee());
3452            // A sender that cannot pay for the cheapest transaction keeps the default limit, so
3453            // the call fails the funds check.
3454            if let Some(allowance) = balance
3455                .checked_sub(upfront)
3456                .map(|available| available / U256::from(tx_env.gas_price))
3457                && allowance >= U256::from(min_gas)
3458            {
3459                tx_env.gas_limit = tx_env.gas_limit.min(allowance.saturating_to());
3460            }
3461        }
3462        Ok(prepared)
3463    }
3464
3465    const fn base_call_tx_env(&self, tx_env: TxEnv) -> CallTxEnv {
3466        #[cfg(feature = "monad")]
3467        if self.is_monad() {
3468            return CallTxEnv::Monad(tx_env);
3469        }
3470        CallTxEnv::Eth(tx_env)
3471    }
3472
3473    fn prepare_base_call_env_with_base(
3474        &self,
3475        request: WithOtherFields<TransactionRequest>,
3476        fee_details: FeeDetails,
3477        block_env: BlockEnv,
3478        base_evm_env: Option<&EvmEnv>,
3479    ) -> Result<PreparedCall, BlockchainError> {
3480        let (evm_env, tx_env, transaction_info) =
3481            self.build_call_env_with_base(request, fee_details, block_env, base_evm_env);
3482
3483        #[cfg(feature = "base")]
3484        if self.is_base() {
3485            let mut base_tx = BaseTransaction::new(tx_env);
3486            if base_tx.base.tx_type == DEPOSIT_TRANSACTION_TYPE {
3487                let deposit = &transaction_info.deposit;
3488                base_tx.deposit = BaseDepositTransactionParts::new(
3489                    deposit.source_hash,
3490                    deposit.mint,
3491                    deposit.is_system_transaction,
3492                );
3493            } else {
3494                base_tx.enveloped_tx = Some(Bytes::new());
3495            }
3496            return Ok(PreparedCall {
3497                evm_env,
3498                tx_env: CallTxEnv::Base(Box::new(base_tx)),
3499                simulated_tempo_tx: None,
3500                simulated_envelope: None,
3501            });
3502        }
3503
3504        #[cfg(feature = "optimism")]
3505        let tx_env = if self.is_optimism() {
3506            CallTxEnv::Op(OpTransaction {
3507                base: tx_env,
3508                deposit: transaction_info.deposit,
3509                enveloped_tx: Some(Bytes::new()),
3510            })
3511        } else if self.is_tempo() {
3512            CallTxEnv::Tempo(TempoTxEnv::from(tx_env))
3513        } else {
3514            self.base_call_tx_env(tx_env)
3515        };
3516        #[cfg(not(feature = "optimism"))]
3517        let tx_env = {
3518            let _ = transaction_info;
3519            if self.is_tempo() {
3520                CallTxEnv::Tempo(TempoTxEnv::from(tx_env))
3521            } else {
3522                self.base_call_tx_env(tx_env)
3523            }
3524        };
3525        Ok(PreparedCall {
3526            evm_env,
3527            tx_env,
3528            simulated_tempo_tx: None,
3529            #[cfg(feature = "base")]
3530            simulated_envelope: None,
3531        })
3532    }
3533
3534    /// Classifies an RPC request according to the active network.
3535    pub(crate) fn parse_transaction_request(
3536        &self,
3537        request: WithOtherFields<TransactionRequest>,
3538    ) -> Result<FoundryTransactionRequest, BlockchainError> {
3539        request
3540            .input
3541            .unique_input()
3542            .map_err(|err| BlockchainError::RpcError(RpcError::invalid_params(err.to_string())))?;
3543        // No transaction carries both fee styles, so reject the request rather than pick one.
3544        if request.gas_price.is_some()
3545            && (request.max_fee_per_gas.is_some() || request.max_priority_fee_per_gas.is_some())
3546        {
3547            return Err(BlockchainError::ConflictingFeeFields);
3548        }
3549        let transaction_type = request.transaction_type;
3550        if transaction_type == Some(foundry_primitives::CIP64_TX_TYPE)
3551            || request.other.get("feeCurrency").is_some_and(|value| !value.is_null())
3552        {
3553            self.ensure_cip64_active()?;
3554            return FoundryTransactionRequest::new(request)
3555                .map_err(|err| BlockchainError::InvalidTransactionRequest(err.to_string()));
3556        }
3557        #[cfg(feature = "base")]
3558        if self.is_base() {
3559            let parsed = FoundryTransactionRequest::try_from(request.clone()).map_err(
3560                |err: serde_json::Error| {
3561                    BlockchainError::InvalidTransactionRequest(err.to_string())
3562                },
3563            )?;
3564            if parsed.is_base() {
3565                return Ok(parsed);
3566            }
3567            if transaction_type == Some(DEPOSIT_TX_TYPE_ID)
3568                || get_deposit_tx_parts(&request.other).is_ok()
3569            {
3570                return Ok(FoundryTransactionRequest::Op(request));
3571            }
3572            return Ok(FoundryTransactionRequest::Ethereum(request.into_inner()));
3573        }
3574        #[cfg(feature = "base")]
3575        if transaction_type == Some(foundry_primitives::FoundryTxType::Eip8130.into())
3576            || matches!(
3577                FoundryTransactionRequest::try_from(request.clone()),
3578                Ok(FoundryTransactionRequest::Base(_))
3579            )
3580        {
3581            return Err(BlockchainError::BaseTransactionUnsupported);
3582        }
3583        if !self.is_tempo() && transaction_type != Some(TEMPO_TX_TYPE_ID) {
3584            #[cfg(feature = "optimism")]
3585            if transaction_type == Some(DEPOSIT_TX_TYPE_ID)
3586                || transaction_type == Some(POST_EXEC_TX_TYPE_ID)
3587                || get_deposit_tx_parts(&request.other).is_ok()
3588            {
3589                return Ok(FoundryTransactionRequest::Op(request));
3590            }
3591            return Ok(FoundryTransactionRequest::Ethereum(request.into_inner()));
3592        }
3593
3594        let parsed: FoundryTransactionRequest =
3595            FoundryTransactionRequest::new(request).map_err(|err: serde_json::Error| {
3596                BlockchainError::InvalidTransactionRequest(err.to_string())
3597            })?;
3598        if parsed.is_tempo() {
3599            self.ensure_tempo_active()?;
3600        }
3601        if parsed.is_tempo()
3602            && self.is_tempo()
3603            && transaction_type.is_some_and(|ty| ty != TEMPO_TX_TYPE_ID)
3604        {
3605            return Err(BlockchainError::FailedToDecodeTransaction);
3606        }
3607        Ok(parsed)
3608    }
3609
3610    fn build_tempo_request_env(
3611        request: TempoTransactionRequest,
3612        mut base: TxEnv,
3613        hardfork: TempoHardfork,
3614    ) -> Result<(TempoTxEnv, AASigned), BlockchainError> {
3615        let fee_payer = request.fee_payer_signature.map(|_| {
3616            request.clone().build_aa().ok().and_then(|tx| tx.recover_fee_payer(base.caller).ok())
3617        });
3618
3619        // Build the response representation separately so the mocked execution signature does not
3620        // leak into RPC output.
3621        let mut response_request = request.clone();
3622        response_request.inner.from = Some(base.caller);
3623        response_request.inner.gas = Some(base.gas_limit);
3624        response_request.inner.nonce = Some(base.nonce);
3625        response_request.inner.chain_id = base.chain_id;
3626        response_request.inner.max_fee_per_gas = Some(base.gas_price);
3627        response_request.inner.max_priority_fee_per_gas =
3628            Some(base.gas_priority_fee.unwrap_or_default());
3629        response_request.inner.access_list = Some(base.access_list.clone());
3630        if response_request.calls.is_empty()
3631            && response_request.inner.to.is_none()
3632            && !base.data.is_empty()
3633        {
3634            response_request.inner.to = Some(base.kind);
3635        }
3636        let response_tx = response_request
3637            .build_aa()
3638            .map_err(|err| BlockchainError::InvalidTransactionRequest(err.to_string()))?;
3639        let response_tx = response_tx.into_signed(TempoSignature::default());
3640        let key_type = request.key_type.unwrap_or(SignatureType::Secp256k1);
3641        let key_data = request.key_data.clone();
3642        let key_id = request.key_id;
3643        let signature =
3644            mock_tempo_signature(key_type, key_data, key_id, base.caller, hardfork.is_t1c());
3645        let mut calls = request.calls;
3646        if let Some(to) = request.inner.to {
3647            calls.push(Call {
3648                to,
3649                value: request.inner.value.unwrap_or_default(),
3650                input: request.inner.input.into_input().unwrap_or_default(),
3651            });
3652        } else if calls.is_empty() && !base.data.is_empty() {
3653            // Alloy represents an omitted top-level `to` as `None`; preserve Ethereum CREATE
3654            // semantics by materializing it as the final Tempo call.
3655            calls.push(Call { to: base.kind, value: base.value, input: base.data.clone() });
3656        }
3657        if let Some(first_call) = calls.first() {
3658            base.kind = first_call.to;
3659            base.value = first_call.value;
3660            base.data = first_call.input.clone();
3661        }
3662        let tx_env = TempoTxEnv {
3663            fee_token: request.fee_token,
3664            is_system_tx: false,
3665            execution_context: ExecutionContext::Simulation,
3666            unique_tx_identifier: Some(TEMPO_RPC_SIMULATION_CONTEXT),
3667            fee_payer,
3668            tempo_tx_env: Some(Box::new(TempoBatchCallEnv {
3669                aa_calls: calls,
3670                signature,
3671                tempo_authorization_list: request
3672                    .tempo_authorization_list
3673                    .into_iter()
3674                    .map(RecoveredTempoAuthorization::new)
3675                    .collect(),
3676                nonce_key: request.nonce_key.unwrap_or_default(),
3677                key_authorization: request.key_authorization,
3678                signature_hash: B256::ZERO,
3679                tx_hash: B256::ZERO,
3680                valid_before: request.valid_before.map(|value| value.get()),
3681                valid_after: request.valid_after.map(|value| value.get()),
3682                override_key_id: key_id,
3683                expiring_nonce_idx: None,
3684            })),
3685            inner: base,
3686        };
3687        Ok((tx_env, response_tx))
3688    }
3689
3690    fn prepare_typed_call_env(
3691        &self,
3692        state: &dyn DatabaseRef,
3693        request: FoundryTransactionRequest,
3694        fee_details: FeeDetails,
3695        block_env: BlockEnv,
3696    ) -> Result<PreparedCall, BlockchainError> {
3697        self.prepare_typed_call_env_with_base(state, request, fee_details, block_env, None)
3698    }
3699
3700    fn prepare_typed_call_env_with_base(
3701        &self,
3702        state: &dyn DatabaseRef,
3703        request: FoundryTransactionRequest,
3704        fee_details: FeeDetails,
3705        block_env: BlockEnv,
3706        base_evm_env: Option<&EvmEnv>,
3707    ) -> Result<PreparedCall, BlockchainError> {
3708        match request {
3709            FoundryTransactionRequest::Tempo(tempo_request) => {
3710                self.ensure_tempo_active()?;
3711                let mut tempo_request = *tempo_request;
3712                if tempo_request.inner.nonce.is_none() {
3713                    let caller = tempo_request.inner.from.unwrap_or_default();
3714                    tempo_request.inner.nonce = Some(tempo_nonce(
3715                        state,
3716                        caller,
3717                        tempo_request.nonce_key.unwrap_or_default(),
3718                    )?);
3719                }
3720                let inner = WithOtherFields::new(tempo_request.inner.clone());
3721                let (evm_env, base, _) =
3722                    self.build_call_env_with_base(inner, fee_details, block_env, base_evm_env);
3723                let (tx_env, simulated_tempo_tx) =
3724                    Self::build_tempo_request_env(tempo_request, base, self.tempo_hardfork())?;
3725                Ok(PreparedCall {
3726                    evm_env,
3727                    tx_env: CallTxEnv::Tempo(tx_env),
3728                    simulated_tempo_tx: Some(simulated_tempo_tx),
3729                    #[cfg(feature = "base")]
3730                    simulated_envelope: None,
3731                })
3732            }
3733            #[cfg(feature = "base")]
3734            FoundryTransactionRequest::Base(request) => {
3735                self.ensure_base_eip8130_active_at(block_env.timestamp.saturating_to())?;
3736                let gas_limit = block_env.gas_limit;
3737                let (evm_env, _, _) = self.build_call_env_with_base(
3738                    WithOtherFields::new(request.as_ref().clone()),
3739                    fee_details,
3740                    block_env,
3741                    base_evm_env,
3742                );
3743                let tx = request
3744                    .to_eip8130_simulation_tx(self.chain_id().to(), gas_limit)
3745                    .ok_or_else(|| {
3746                        BlockchainError::InvalidTransactionRequest(
3747                            "invalid EIP-8130 simulation request: missing or conflicting sender, \
3748                             oversized authentication data, or invalid payer authenticator"
3749                                .to_string(),
3750                        )
3751                    })?;
3752                let simulated_envelope = tx
3753                    .eip8130
3754                    .as_ref()
3755                    .map(|parts| FoundryTxEnvelope::Eip8130(parts.signed.clone()));
3756                Ok(PreparedCall {
3757                    evm_env,
3758                    tx_env: CallTxEnv::Base(Box::new(tx)),
3759                    simulated_tempo_tx: None,
3760                    simulated_envelope,
3761                })
3762            }
3763            FoundryTransactionRequest::Ethereum(mut request)
3764            | FoundryTransactionRequest::Celo(foundry_primitives::Cip64TransactionRequest {
3765                inner: mut request,
3766                ..
3767            }) => {
3768                // Account-creation gas depends on the nonce: EIP-8037 checks the address a
3769                // creation would deploy to, and Tempo charges for nonce 0. So an omitted nonce
3770                // must not default to 0. revm rejects a transaction nonce of `u64::MAX` even
3771                // without the nonce check, so such a sender keeps the default outside Tempo,
3772                // where falling back to 0 would incorrectly charge account-creation gas.
3773                if request.nonce.is_none() {
3774                    let nonce = tempo_nonce(state, request.from.unwrap_or_default(), U256::ZERO)?;
3775                    request.nonce = (self.is_tempo() || nonce != u64::MAX).then_some(nonce);
3776                }
3777                self.prepare_base_call_env_with_base(
3778                    WithOtherFields::new(request),
3779                    fee_details,
3780                    block_env,
3781                    base_evm_env,
3782                )
3783            }
3784            #[cfg(any(feature = "base", feature = "optimism"))]
3785            FoundryTransactionRequest::Op(request) => {
3786                self.prepare_base_call_env_with_base(request, fee_details, block_env, base_evm_env)
3787            }
3788        }
3789    }
3790
3791    fn transact_call_with_inspector_ref<'db, I>(
3792        &self,
3793        db: &'db dyn DatabaseRef,
3794        evm_env: &EvmEnv,
3795        inspector: &mut I,
3796        tx_env: CallTxEnv,
3797        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
3798            MonadExecutionContext<'_>,
3799        >,
3800    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
3801    where
3802        I: BackendInspector<WrapDatabaseRef<&'db dyn DatabaseRef>>,
3803    {
3804        self.transact_call_with_inspector_ref_at_hardfork(
3805            db,
3806            evm_env,
3807            inspector,
3808            tx_env,
3809            monad_context,
3810            self.hardfork(),
3811        )
3812    }
3813
3814    fn transact_call_with_inspector_ref_at_hardfork<'db, I>(
3815        &self,
3816        db: &'db dyn DatabaseRef,
3817        evm_env: &EvmEnv,
3818        inspector: &mut I,
3819        tx_env: CallTxEnv,
3820        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
3821            MonadExecutionContext<'_>,
3822        >,
3823        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] hardfork: FoundryHardfork,
3824    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
3825    where
3826        I: BackendInspector<WrapDatabaseRef<&'db dyn DatabaseRef>>,
3827    {
3828        match tx_env {
3829            CallTxEnv::Eth(tx_env) => {
3830                self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)
3831            }
3832            #[cfg(feature = "base")]
3833            CallTxEnv::Base(tx_env) => {
3834                self.transact_base_with_inspector_ref(db, evm_env, inspector, *tx_env)
3835            }
3836            #[cfg(feature = "monad")]
3837            CallTxEnv::Monad(tx_env) => {
3838                let context = monad::resolve_execution_context(monad_context, &tx_env);
3839                self.transact_monad_with_inspector_ref(
3840                    db,
3841                    evm_env,
3842                    inspector,
3843                    tx_env,
3844                    monad::PreparedExecution {
3845                        context,
3846                        kind: EnvelopeExecutionKind::Transaction,
3847                        hardfork: monad_revm::MonadHardfork::from(hardfork),
3848                    },
3849                )
3850            }
3851            #[cfg(feature = "optimism")]
3852            CallTxEnv::Op(tx_env) => {
3853                let spec = self.hardfork().into();
3854                self.transact_op_with_inspector_ref(db, evm_env, inspector, tx_env, spec)
3855            }
3856            CallTxEnv::Tempo(tx_env) => {
3857                self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)
3858            }
3859        }
3860    }
3861
3862    pub(crate) fn call_with_state_and_context(
3863        &self,
3864        state: &dyn DatabaseRef,
3865        request: WithOtherFields<TransactionRequest>,
3866        fee_details: FeeDetails,
3867        block_env: BlockEnv,
3868        mut monad_context: Option<MonadReplayContext>,
3869    ) -> Result<(InstructionResult, Option<Output>, u128, StateChangeset), BlockchainError> {
3870        let mut inspector = self.build_inspector();
3871        let PreparedCall { evm_env, tx_env, .. } =
3872            self.prepare_call_env(state, request, fee_details, block_env)?;
3873        let ResultAndState { result, state } = self.transact_call_with_inspector_ref(
3874            state,
3875            &evm_env,
3876            &mut inspector,
3877            tx_env,
3878            monad_context.as_mut().map(next_monad_context),
3879        )?;
3880
3881        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3882        inspector.print_logs();
3883
3884        if self.print_traces {
3885            inspector.into_print_traces(self.call_trace_decoder());
3886        }
3887
3888        Ok((exit_reason, out, gas_used as u128, state))
3889    }
3890
3891    pub(crate) fn call_with_state_typed_gas_limit(
3892        &self,
3893        state: &dyn DatabaseRef,
3894        request: FoundryTransactionRequest,
3895        fee_details: FeeDetails,
3896        block_env: BlockEnv,
3897        options: GasEstimateCallOptions,
3898    ) -> Result<(InstructionResult, Option<Output>, u128, StateChangeset), BlockchainError> {
3899        let GasEstimateCallOptions { gas_limit, disable_fee_charge, monad_context } = options;
3900        self.call_with_state_typed_inner(
3901            state,
3902            request,
3903            fee_details,
3904            block_env,
3905            TypedCallOverrides {
3906                gas_limit: Some(gas_limit),
3907                disable_fee_charge,
3908                ..Default::default()
3909            },
3910            monad_context,
3911        )
3912    }
3913
3914    pub(crate) fn call_with_state_typed_access_list(
3915        &self,
3916        state: &dyn DatabaseRef,
3917        request: FoundryTransactionRequest,
3918        fee_details: FeeDetails,
3919        block_env: BlockEnv,
3920        access_list: AccessList,
3921        monad_context: Option<MonadReplayContext>,
3922    ) -> Result<(InstructionResult, Option<Output>, u128, StateChangeset), BlockchainError> {
3923        self.call_with_state_typed_inner(
3924            state,
3925            request,
3926            fee_details,
3927            block_env,
3928            TypedCallOverrides { access_list: Some(access_list), ..Default::default() },
3929            monad_context,
3930        )
3931    }
3932
3933    fn call_with_state_typed_inner(
3934        &self,
3935        state: &dyn DatabaseRef,
3936        request: FoundryTransactionRequest,
3937        fee_details: FeeDetails,
3938        block_env: BlockEnv,
3939        overrides: TypedCallOverrides,
3940        mut monad_context: Option<MonadReplayContext>,
3941    ) -> Result<(InstructionResult, Option<Output>, u128, StateChangeset), BlockchainError> {
3942        // Estimation probes do not return or print traces, but still collect console logs.
3943        let mut inspector = AnvilInspector::default();
3944        if self.print_logs {
3945            inspector = inspector.with_log_collector();
3946        }
3947        let block_fees = (block_env.basefee, block_env.blob_excess_gas_and_price);
3948        let PreparedCall { mut evm_env, mut tx_env, .. } =
3949            self.prepare_typed_call_env(state, request, fee_details, block_env)?;
3950        evm_env.cfg_env.disable_fee_charge = overrides.disable_fee_charge;
3951        if let Some(gas_limit) = overrides.gas_limit {
3952            tx_env.base_mut().gas_limit = gas_limit;
3953        }
3954        if let Some(access_list) = overrides.access_list {
3955            let tx_env = tx_env.base_mut();
3956            Self::use_block_fees(&mut evm_env, tx_env, block_fees);
3957            tx_env.access_list = access_list;
3958            if tx_env.tx_type == TransactionType::Legacy as u8 {
3959                tx_env.tx_type = TransactionType::Eip2930 as u8;
3960            }
3961        }
3962        let ResultAndState { result, state } = self.transact_call_with_inspector_ref(
3963            state,
3964            &evm_env,
3965            &mut inspector,
3966            tx_env,
3967            monad_context.as_mut().map(next_monad_context),
3968        )?;
3969        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3970        inspector.print_logs();
3971        Ok((exit_reason, out, gas_used as u128, state))
3972    }
3973
3974    pub(crate) fn build_access_list_with_state_and_context(
3975        &self,
3976        state: &dyn DatabaseRef,
3977        request: WithOtherFields<TransactionRequest>,
3978        fee_details: FeeDetails,
3979        block_env: BlockEnv,
3980        mut monad_context: Option<MonadReplayContext>,
3981    ) -> Result<(InstructionResult, Option<Output>, u64, AccessList), BlockchainError> {
3982        let mut inspector =
3983            AccessListInspector::new(request.access_list.clone().unwrap_or_default());
3984
3985        let block_fees = (block_env.basefee, block_env.blob_excess_gas_and_price);
3986        let PreparedCall { mut evm_env, mut tx_env, .. } =
3987            self.prepare_call_env(state, request, fee_details, block_env)?;
3988        Self::use_block_fees(&mut evm_env, tx_env.base_mut(), block_fees);
3989        let ResultAndState { result, state: _ } = self.transact_call_with_inspector_ref(
3990            state,
3991            &evm_env,
3992            &mut inspector,
3993            tx_env,
3994            monad_context.as_mut().map(next_monad_context),
3995        )?;
3996        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3997        let access_list = inspector.access_list();
3998        #[cfg(feature = "monad")]
3999        let access_list = if self.is_monad() {
4000            monad::normalize_access_list(access_list, self.monad_hardfork())
4001        } else {
4002            access_list
4003        };
4004        Ok((exit_reason, out, gas_used, access_list))
4005    }
4006
4007    /// Restores the block's base and blob base fees that a zero-fee call env clears. Like geth,
4008    /// access lists are built at the block's fees.
4009    fn use_block_fees(
4010        evm_env: &mut EvmEnv,
4011        tx_env: &mut TxEnv,
4012        (basefee, blob_excess_gas_and_price): (u64, Option<BlobExcessGasAndPrice>),
4013    ) {
4014        evm_env.block_env.basefee = basefee;
4015        evm_env.block_env.blob_excess_gas_and_price = blob_excess_gas_and_price;
4016        if !tx_env.blob_hashes.is_empty() && tx_env.max_fee_per_blob_gas == 0 {
4017            tx_env.max_fee_per_blob_gas = evm_env.block_env.blob_gasprice().unwrap_or_default();
4018        }
4019    }
4020
4021    fn arbitrum_block_number(&self, evm_env: &EvmEnv) -> Option<u64> {
4022        if !arbitrum::is_arbitrum_chain(self.protocol_chain_id()) {
4023            return None;
4024        }
4025
4026        let env_block = evm_env.block_env.number;
4027        Some(
4028            self.get_fork()
4029                .map_or_else(|| env_block.saturating_to(), |fork| fork.rpc_block_number(env_block)),
4030        )
4031    }
4032
4033    pub fn get_code_with_state(
4034        &self,
4035        state: &dyn DatabaseRef,
4036        address: Address,
4037    ) -> Result<Bytes, BlockchainError> {
4038        trace!(target: "backend", "get code for {:?}", address);
4039        let account = state.basic_ref(address)?.unwrap_or_default();
4040        if account.is_empty_code_hash() {
4041            // if the code hash is `KECCAK_EMPTY`, we check no further
4042            return Ok(Default::default());
4043        }
4044        let code = if let Some(code) = account.code {
4045            code
4046        } else {
4047            state.code_by_hash_ref(account.code_hash())?
4048        };
4049        Ok(code.original_bytes())
4050    }
4051
4052    pub fn get_balance_with_state<D>(
4053        &self,
4054        state: D,
4055        address: Address,
4056    ) -> Result<U256, BlockchainError>
4057    where
4058        D: DatabaseRef,
4059    {
4060        trace!(target: "backend", "get balance for {:?}", address);
4061        Ok(state.basic_ref(address)?.unwrap_or_default().balance)
4062    }
4063
4064    pub async fn transaction_by_block_number_and_index(
4065        &self,
4066        number: BlockNumber,
4067        index: Index,
4068    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
4069        if let Some(block) = self.mined_block_by_number(number) {
4070            return Ok(self.mined_transaction_by_block_hash_and_index(block.header.hash, index));
4071        }
4072
4073        if let Some(fork) = self.get_fork() {
4074            let number = self.convert_block_number(Some(number));
4075            if fork.predates_fork(number) {
4076                return Ok(fork
4077                    .transaction_by_block_number_and_index(number, index.into())
4078                    .await?);
4079            }
4080        }
4081
4082        Ok(None)
4083    }
4084
4085    pub async fn transaction_by_block_hash_and_index(
4086        &self,
4087        hash: B256,
4088        index: Index,
4089    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
4090        if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) {
4091            return Ok(tx);
4092        }
4093
4094        if let Some(fork) = self.get_fork() {
4095            return Ok(fork.transaction_by_block_hash_and_index(hash, index.into()).await?);
4096        }
4097
4098        Ok(None)
4099    }
4100
4101    pub fn mined_transaction_by_block_hash_and_index(
4102        &self,
4103        block_hash: B256,
4104        index: Index,
4105    ) -> Option<AnyRpcTransaction> {
4106        let (info, block, tx) = {
4107            let storage = self.blockchain.storage.read();
4108            let block = storage.blocks.get(&block_hash).cloned()?;
4109            let index: usize = index.into();
4110            let tx = block.body.transactions.get(index)?.clone();
4111            let info = storage.transactions.get(&tx.hash())?.info.clone();
4112            (info, block, tx)
4113        };
4114
4115        Some(transaction_build(
4116            Some(info.transaction_hash),
4117            tx,
4118            Some(&block),
4119            Some(info),
4120            block.header.base_fee_per_gas(),
4121        ))
4122    }
4123
4124    pub async fn transaction_by_hash(
4125        &self,
4126        hash: B256,
4127    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
4128        trace!(target: "backend", "transaction_by_hash={:?}", hash);
4129        if let tx @ Some(_) = self.mined_transaction_by_hash(hash) {
4130            return Ok(tx);
4131        }
4132
4133        if let Some(fork) = self.get_fork() {
4134            return fork
4135                .transaction_by_hash(hash)
4136                .await
4137                .map_err(BlockchainError::AlloyForkProvider);
4138        }
4139
4140        Ok(None)
4141    }
4142
4143    pub fn mined_transaction_by_hash(&self, hash: B256) -> Option<AnyRpcTransaction> {
4144        let (info, block) = {
4145            let storage = self.blockchain.storage.read();
4146            let MinedTransaction { info, block_hash, .. } =
4147                storage.transactions.get(&hash)?.clone();
4148            let block = storage.blocks.get(&block_hash).cloned()?;
4149            (info, block)
4150        };
4151        let tx = block.body.transactions.get(info.transaction_index as usize)?.clone();
4152
4153        Some(transaction_build(
4154            Some(info.transaction_hash),
4155            tx,
4156            Some(&block),
4157            Some(info),
4158            block.header.base_fee_per_gas(),
4159        ))
4160    }
4161
4162    /// Returns the traces for the given transaction
4163    pub async fn trace_transaction(
4164        &self,
4165        hash: B256,
4166    ) -> Result<Option<Vec<LocalizedTransactionTrace>>, BlockchainError> {
4167        if let Some(traces) = self.mined_parity_trace_transaction(hash) {
4168            return Ok(Some(traces));
4169        }
4170
4171        if let Some(fork) = self.get_fork() {
4172            return Ok(fork.trace_transaction(hash).await?);
4173        }
4174
4175        Ok(None)
4176    }
4177
4178    /// Returns the transaction trace at the given trace address, where `[]` is the root call.
4179    pub async fn trace_get(
4180        &self,
4181        hash: B256,
4182        indices: Vec<Index>,
4183    ) -> Result<Option<LocalizedTransactionTrace>, BlockchainError> {
4184        if let Some(traces) = self.mined_parity_trace_transaction(hash) {
4185            let trace_address = indices.iter().copied().map(usize::from).collect::<Vec<_>>();
4186            return Ok(traces.into_iter().find(|trace| trace.trace.trace_address == trace_address));
4187        }
4188
4189        if let Some(fork) = self.get_fork() {
4190            return Ok(fork.trace_get(hash, indices).await?);
4191        }
4192
4193        Ok(None)
4194    }
4195
4196    /// Returns the traces for the given block
4197    pub async fn trace_block(
4198        &self,
4199        block: BlockNumber,
4200    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
4201        ensure_mined_trace_block(block)?;
4202        let number = self.convert_block_number(Some(block));
4203        if let Some(traces) = self.mined_parity_trace_block(number) {
4204            return Ok(traces);
4205        }
4206
4207        if let Some(fork) = self.get_fork()
4208            && fork.predates_fork_inclusive(number)
4209        {
4210            return Ok(fork.trace_block(number).await?);
4211        }
4212
4213        Err(BlockchainError::BlockNotFound)
4214    }
4215
4216    /// Executes a transaction call and returns requested parity trace results.
4217    pub async fn trace_call(
4218        &self,
4219        request: WithOtherFields<TransactionRequest>,
4220        fee_details: FeeDetails,
4221        trace_types: HashSet<TraceType>,
4222        block_request: BlockRequest<FoundryTxEnvelope>,
4223        block_id: BlockId,
4224    ) -> Result<TraceResults, BlockchainError>
4225    where
4226        Self: TransactionValidator<FoundryTxEnvelope>,
4227        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
4228    {
4229        if let BlockRequest::Number(number) = &block_request
4230            && let Some(fork) = self.get_fork()
4231            && fork.predates_fork(*number)
4232        {
4233            // Delegate the resolved block number so tags (`latest`/`pending`/`safe`/
4234            // `finalized`) are resolved against the fork's head instead of drifting with
4235            // the upstream chain. Hashes are forwarded unchanged.
4236            let resolved = match block_id {
4237                BlockId::Hash(_) => block_id,
4238                _ => BlockId::number(*number),
4239            };
4240            return Ok(fork.trace_call(request, trace_types, resolved).await?);
4241        }
4242
4243        self.with_database_at_and_context(Some(block_request), |state, block, mut monad_context| {
4244            let cache_db = CacheDB::new(&*state);
4245            let mut inspector =
4246                TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
4247            let PreparedCall { evm_env, tx_env, .. } =
4248                self.prepare_call_env(&cache_db, request, fee_details, block)?;
4249            let result = self.transact_call_with_inspector_ref(
4250                &cache_db,
4251                &evm_env,
4252                &mut inspector,
4253                tx_env,
4254                monad_context.as_mut().map(next_monad_context),
4255            )?;
4256
4257            parity_trace_results(inspector, &result, &trace_types, &cache_db)
4258        })
4259        .await
4260    }
4261
4262    /// Replays all transactions in a block and returns the requested traces for each transaction.
4263    pub async fn trace_replay_block_transactions(
4264        &self,
4265        block: BlockNumber,
4266        trace_types: HashSet<TraceType>,
4267    ) -> Result<Vec<TraceResultsWithTransactionHash>, BlockchainError> {
4268        ensure_mined_trace_block(block)?;
4269        let block_number = self.convert_block_number(Some(block));
4270
4271        // Try mined blocks first
4272        if let Some(results) =
4273            self.mined_parity_trace_replay_block_transactions(block_number, &trace_types)?
4274        {
4275            return Ok(results);
4276        }
4277
4278        // Fallback to fork if block predates fork
4279        if let Some(fork) = self.get_fork()
4280            && fork.predates_fork_inclusive(block_number)
4281        {
4282            return Ok(fork.trace_replay_block_transactions(block_number, trace_types).await?);
4283        }
4284
4285        Err(BlockchainError::BlockNotFound)
4286    }
4287
4288    /// Replays a mined transaction and returns the requested traces, or `None` if the transaction
4289    /// is unknown.
4290    pub async fn trace_replay_transaction(
4291        &self,
4292        hash: B256,
4293        trace_types: HashSet<TraceType>,
4294    ) -> Result<Option<TraceResultsWithTransactionHash>, BlockchainError> {
4295        let mined = self.blockchain.storage.read().transactions.contains_key(&hash);
4296
4297        // If the transaction was mined locally, replay it locally. Do not fall
4298        // through to the fork when the local replay fails; that would misreport
4299        // a local data problem as an upstream transaction lookup.
4300        let full_trace = if mined {
4301            let inspector =
4302                TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
4303            self.replay_tx_with_inspector(hash, inspector, |result, cache_db, inspector, _, _| {
4304                parity_trace_results(inspector, &result, &trace_types, &cache_db)
4305            })??
4306        } else if let Some(fork) = self.get_fork() {
4307            // Not known locally: forward to the fork if present.
4308            let Some(full_trace) = fork.trace_replay_transaction(hash, trace_types).await? else {
4309                return Ok(None);
4310            };
4311            full_trace
4312        } else {
4313            return Ok(None);
4314        };
4315
4316        Ok(Some(TraceResultsWithTransactionHash { transaction_hash: hash, full_trace }))
4317    }
4318
4319    /// Traces a raw transaction without committing it to the chain state or mempool.
4320    pub async fn trace_raw_transaction(
4321        &self,
4322        pending_transaction: PendingTransaction<FoundryTxEnvelope>,
4323        trace_types: HashSet<TraceType>,
4324        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4325    ) -> Result<TraceResults, BlockchainError>
4326    where
4327        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
4328    {
4329        let trace_config = TracingInspectorConfig::from_parity_config(&trace_types);
4330
4331        self.with_database_at_and_context(block_request, |state, block_env, mut monad_context| {
4332            let cache_db = CacheDB::new(&*state);
4333            let mut evm_env = self.evm_env.read().clone();
4334            evm_env.block_env = block_env;
4335            // A signed transaction cannot be impersonated, so reject senders with code that is not
4336            // an EIP-7702 delegation, as block execution would.
4337            evm_env.cfg_env.disable_eip3607 = false;
4338
4339            let mut inspector = TracingInspector::new(trace_config);
4340            let (result, _) = self.transact_envelope_with_inspector_ref_and_context(
4341                &cache_db,
4342                &evm_env,
4343                &mut inspector,
4344                &pending_transaction,
4345                monad_context.as_mut().map(next_monad_context),
4346            )?;
4347
4348            parity_trace_results(inspector, &result, &trace_types, &cache_db)
4349        })
4350        .await
4351    }
4352
4353    /// Traces calls sequentially against a shared in-memory state.
4354    pub async fn trace_call_many(
4355        &self,
4356        calls: Vec<(WithOtherFields<TransactionRequest>, HashSet<TraceType>)>,
4357        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4358    ) -> Result<Vec<TraceResults>, BlockchainError>
4359    where
4360        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
4361    {
4362        self.with_database_at_and_context(block_request, |state, block_env, mut monad_context| {
4363            let mut cache_db = CacheDB::new(&*state);
4364            let mut results = Vec::with_capacity(calls.len());
4365            let mut calls = calls.into_iter().peekable();
4366
4367            while let Some((request, trace_types)) = calls.next() {
4368                let fee_details = FeeDetails::new(
4369                    request.gas_price,
4370                    request.max_fee_per_gas,
4371                    request.max_priority_fee_per_gas,
4372                    request.max_fee_per_blob_gas,
4373                )?
4374                .or_zero_fees();
4375                let PreparedCall { evm_env, mut tx_env, simulated_tempo_tx, .. } =
4376                    self.prepare_call_env(&cache_db, request, fee_details, block_env.clone())?;
4377                apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
4378
4379                let trace_config = TracingInspectorConfig::from_parity_config(&trace_types);
4380                let mut inspector = TracingInspector::new(trace_config);
4381                let result = self.transact_call_with_inspector_ref(
4382                    &cache_db,
4383                    &evm_env,
4384                    &mut inspector,
4385                    tx_env,
4386                    monad_context.as_mut().map(next_monad_context),
4387                )?;
4388
4389                let trace_result =
4390                    parity_trace_results(inspector, &result, &trace_types, &cache_db)?;
4391                results.push(trace_result);
4392
4393                if calls.peek().is_some() {
4394                    cache_db.commit(result.state);
4395                }
4396            }
4397
4398            Ok(results)
4399        })
4400        .await
4401    }
4402
4403    /// Returns the trace results for all transactions in a mined block by replaying them
4404    fn mined_parity_trace_replay_block_transactions(
4405        &self,
4406        block_number: u64,
4407        trace_types: &HashSet<TraceType>,
4408    ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, BlockchainError> {
4409        let Some(block) = self.get_block(block_number) else { return Ok(None) };
4410
4411        // Blocks without transactions, such as genesis, have nothing to replay.
4412        if block.body.transactions.is_empty() {
4413            return Ok(Some(Vec::new()));
4414        }
4415
4416        // Execute this in the context of the parent state
4417        let parent_hash = block.header.parent_hash;
4418        let trace_config = TracingInspectorConfig::from_parity_config(trace_types);
4419
4420        let read_guard = self.states.upgradable_read();
4421        if let Some(state) = read_guard.get_state(&parent_hash) {
4422            self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types)
4423                .map(Some)
4424        } else {
4425            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
4426            let Some(state) = write_guard.get_on_disk_state(&parent_hash) else {
4427                return Err(BlockchainError::HistoricalStateUnavailable(block.header.number()));
4428            };
4429            self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types)
4430                .map(Some)
4431        }
4432    }
4433
4434    /// Replays all transactions in a block with the tracing inspector to generate TraceResults
4435    fn replay_block_transactions_with_inspector(
4436        &self,
4437        block: &Block,
4438        parent_state: &StateDb,
4439        trace_config: TracingInspectorConfig,
4440        trace_types: &HashSet<TraceType>,
4441    ) -> Result<Vec<TraceResultsWithTransactionHash>, BlockchainError> {
4442        let (mut cache_db, evm_env, hardfork) = self.prepare_block_replay(block, parent_state)?;
4443        let mut results = Vec::new();
4444        let monad_context = self.active_monad_context_for_mined_block(block)?;
4445
4446        // Execute each transaction in the block with tracing
4447        for tx_envelope in &block.body.transactions {
4448            let tx_hash = tx_envelope.hash();
4449
4450            // Create a fresh inspector for this transaction
4451            let mut inspector = TracingInspector::new(trace_config);
4452
4453            // Prepare transaction environment and execute
4454            let pending_tx = self.pending_mined_transaction(tx_envelope.clone())?;
4455            let transaction_context =
4456                monad_execution_context_at(monad_context.as_ref(), results.len());
4457            let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
4458                &cache_db,
4459                &evm_env,
4460                &mut inspector,
4461                &pending_tx,
4462                EnvelopeExecution::replay(transaction_context, hardfork),
4463            )?;
4464
4465            // Build TraceResults from the inspector and execution result
4466            let full_trace = parity_trace_results(inspector, &result, trace_types, &cache_db)?;
4467
4468            results.push(TraceResultsWithTransactionHash { transaction_hash: tx_hash, full_trace });
4469
4470            // Commit the state changes for the next transaction
4471            cache_db.commit(result.state);
4472        }
4473
4474        Ok(results)
4475    }
4476
4477    fn replay_tx_with_inspector<I, F, T>(
4478        &self,
4479        hash: B256,
4480        mut inspector: I,
4481        f: F,
4482    ) -> Result<T, BlockchainError>
4483    where
4484        for<'a> I: BackendInspector<WrapDatabaseRef<&'a dyn DatabaseRef>> + 'a,
4485        for<'a> F: FnOnce(ResultAndState<HaltReason>, CacheDB<StateRef<'a>>, I, TxEnv, EvmEnv) -> T,
4486    {
4487        let block = {
4488            let storage = self.blockchain.storage.read();
4489            let MinedTransaction { block_hash, .. } =
4490                storage.transactions.get(&hash).ok_or(BlockchainError::TransactionNotFound)?;
4491
4492            storage.blocks.get(block_hash).cloned().ok_or(BlockchainError::BlockNotFound)?
4493        };
4494
4495        let index = block
4496            .body
4497            .transactions
4498            .iter()
4499            .position(|tx| tx.hash() == hash)
4500            .expect("transaction not found in block");
4501
4502        let trace = |parent_state: &StateDb| -> Result<T, BlockchainError> {
4503            let (mut cache_db, evm_env, hardfork) =
4504                self.prepare_block_replay(&block, parent_state)?;
4505            self.replay_mined_transaction_prefix(&mut cache_db, &evm_env, hardfork, &block, index)?;
4506
4507            let target_tx = block.body.transactions[index].clone();
4508            let target_tx = self.pending_mined_transaction(target_tx)?;
4509            let monad_context = self.active_monad_context_for_mined_block(&block)?;
4510            let transaction_context = monad_execution_context_at(monad_context.as_ref(), index);
4511            let (result, base_tx_env) = self.replay_envelope_with_inspector_ref_and_context(
4512                &cache_db,
4513                &evm_env,
4514                &mut inspector,
4515                &target_tx,
4516                EnvelopeExecution::replay(transaction_context, hardfork),
4517            )?;
4518
4519            Ok(f(result, cache_db, inspector, base_tx_env, evm_env))
4520        };
4521
4522        let read_guard = self.states.upgradable_read();
4523        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
4524            trace(state)
4525        } else {
4526            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
4527            let state = write_guard
4528                .get_on_disk_state(&block.header.parent_hash)
4529                .ok_or(BlockchainError::HistoricalStateUnavailable(block.header.number()))?;
4530            trace(state)
4531        }
4532    }
4533
4534    // Returns the traces matching a given filter
4535    pub async fn trace_filter(
4536        &self,
4537        filter: TraceFilter,
4538    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
4539        let matcher = filter.matcher();
4540        let best_number = self.best_number();
4541        let start = filter.from_block.unwrap_or(best_number);
4542        let end = filter.to_block.unwrap_or(best_number);
4543
4544        if start > best_number || end > best_number {
4545            return Err(BlockchainError::BlockNotFound);
4546        }
4547        if start > end {
4548            return Err(BlockchainError::RpcError(RpcError::invalid_params(
4549                "invalid block range, ensure that to block is greater than from block".to_string(),
4550            )));
4551        }
4552
4553        let dist = end - start;
4554        if dist > 300 {
4555            return Err(BlockchainError::RpcError(RpcError::invalid_params(
4556                "block range too large, currently limited to 300".to_string(),
4557            )));
4558        }
4559
4560        // Accumulate tasks for block range
4561        let mut trace_tasks = vec![];
4562        for num in start..=end {
4563            trace_tasks.push(self.trace_block(num.into()));
4564        }
4565
4566        // Execute tasks and filter traces
4567        let traces = futures::future::try_join_all(trace_tasks).await?;
4568        let filtered_traces =
4569            traces.into_iter().flatten().filter(|trace| matcher.matches(&trace.trace));
4570
4571        // Apply after and count
4572        let filtered_traces: Vec<_> = if let Some(after) = filter.after {
4573            filtered_traces.skip(after as usize).collect()
4574        } else {
4575            filtered_traces.collect()
4576        };
4577
4578        let filtered_traces: Vec<_> = if let Some(count) = filter.count {
4579            filtered_traces.into_iter().take(count as usize).collect()
4580        } else {
4581            filtered_traces
4582        };
4583
4584        Ok(filtered_traces)
4585    }
4586
4587    pub fn get_blobs_by_block_id(
4588        &self,
4589        id: impl Into<BlockId>,
4590        versioned_hashes: Vec<B256>,
4591    ) -> Result<Option<Vec<alloy_consensus::Blob>>> {
4592        Ok(self.get_block(id).map(|block| {
4593            block
4594                .body
4595                .transactions
4596                .iter()
4597                .filter_map(|tx| tx.as_ref().sidecar())
4598                .flat_map(|sidecar| {
4599                    sidecar.sidecar.blobs().iter().zip(sidecar.sidecar.commitments().iter())
4600                })
4601                .filter(|(_, commitment)| {
4602                    // Filter blobs by versioned_hashes if provided
4603                    versioned_hashes.is_empty()
4604                        || versioned_hashes.contains(&kzg_to_versioned_hash(commitment.as_slice()))
4605                })
4606                .map(|(blob, _)| *blob)
4607                .collect()
4608        }))
4609    }
4610
4611    #[allow(clippy::large_stack_frames)]
4612    pub fn get_blob_by_versioned_hash(&self, hash: B256) -> Result<Option<Blob>> {
4613        let storage = self.blockchain.storage.read();
4614        for block in storage.blocks.values() {
4615            for tx in &block.body.transactions {
4616                let typed_tx = tx.as_ref();
4617                if let Some(sidecar) = typed_tx.sidecar() {
4618                    for versioned_hash in sidecar.sidecar.versioned_hashes() {
4619                        if versioned_hash == hash
4620                            && let Some(index) =
4621                                sidecar.sidecar.commitments().iter().position(|commitment| {
4622                                    kzg_to_versioned_hash(commitment.as_slice()) == *hash
4623                                })
4624                            && let Some(blob) = sidecar.sidecar.blobs().get(index)
4625                        {
4626                            return Ok(Some(*blob));
4627                        }
4628                    }
4629                }
4630            }
4631        }
4632        Ok(None)
4633    }
4634
4635    /// Initialises the balance of the given accounts
4636    #[expect(clippy::too_many_arguments)]
4637    pub async fn with_genesis(
4638        db: Arc<AsyncRwLock<Box<dyn Db>>>,
4639        env: Arc<RwLock<EvmEnv>>,
4640        networks: NetworkConfigs,
4641        genesis: GenesisConfig,
4642        fees: FeeManager,
4643        fork: Arc<RwLock<Option<ClientFork>>>,
4644        enable_steps_tracing: bool,
4645        print_logs: bool,
4646        print_traces: bool,
4647        call_trace_decoder: Arc<CallTraceDecoder>,
4648        prune_state_history_config: PruneStateHistoryConfig,
4649        max_persisted_states: Option<usize>,
4650        transaction_block_keeper: Option<usize>,
4651        automine_block_time: Option<Duration>,
4652        cache_path: Option<PathBuf>,
4653        node_config: Arc<AsyncRwLock<NodeConfig>>,
4654    ) -> Result<Self> {
4655        let last_fork_cache_source = fork.read().as_ref().and_then(ForkCacheSource::from_fork);
4656        // if this is a fork then adjust the blockchain storage
4657        let blockchain = if let Some(fork) = fork.read().as_ref() {
4658            trace!(target: "backend", "using forked blockchain at {}", fork.block_number());
4659            Blockchain::forked(fork.block_number(), fork.block_hash(), fork.total_difficulty())
4660        } else {
4661            let header = if let Some(genesis) = genesis.genesis_init.as_ref() {
4662                genesis_json_header(genesis)
4663            } else {
4664                genesis_header(
4665                    &env.read(),
4666                    fees.is_eip1559().then(|| fees.base_fee()),
4667                    genesis.timestamp,
4668                    genesis.number,
4669                )
4670            };
4671            Blockchain::new(foundry_header(&networks, header))
4672        };
4673
4674        // Sync EVM block.number with genesis for non-fork mode.
4675        // Fork mode syncs in setup_fork_db_config() instead.
4676        if fork.read().is_none() {
4677            env.write().block_env.number = U256::from(genesis.number);
4678
4679            // The genesis block keeps its base fee, but the next block must already follow Tempo's
4680            // rules (e.g. T7 clamps the seed down to the cap). Fork mode seeds this from the fork
4681            // block instead.
4682            if fees.tempo_hardfork().is_some() {
4683                let env = env.read();
4684                let next_base_fee = fees.get_next_block_base_fee_per_gas(
4685                    0,
4686                    env.block_env.gas_limit,
4687                    env.block_env.basefee,
4688                );
4689                drop(env);
4690                fees.set_base_fee(next_base_fee);
4691            }
4692        }
4693
4694        let start_timestamp = if let Some(fork) = fork.read().as_ref() {
4695            fork.timestamp()
4696        } else {
4697            genesis.timestamp
4698        };
4699
4700        let new_states = || {
4701            let mut states = if prune_state_history_config.is_config_enabled() {
4702                // if prune state history is enabled, configure the state cache only for memory
4703                prune_state_history_config
4704                    .max_memory_history
4705                    .map(|limit| InMemoryBlockStates::new(limit, 0))
4706                    .unwrap_or_default()
4707                    .memory_only()
4708            } else if max_persisted_states.is_some() {
4709                max_persisted_states
4710                    .map(|limit| InMemoryBlockStates::new(DEFAULT_HISTORY_LIMIT, limit))
4711                    .unwrap_or_default()
4712            } else {
4713                Default::default()
4714            };
4715
4716            if let Some(cache_path) = &cache_path {
4717                states = states.disk_path(cache_path.clone());
4718            }
4719
4720            states
4721        };
4722        let states = new_states();
4723
4724        let (slots_in_an_epoch, precompile_factory, disable_pool_balance_checks, hardfork) = {
4725            let cfg = node_config.read().await;
4726            (
4727                cfg.slots_in_an_epoch,
4728                cfg.precompile_factory.clone(),
4729                cfg.disable_pool_balance_checks,
4730                cfg.get_hardfork(),
4731            )
4732        };
4733        if networks.is_tempo() {
4734            let cfg = node_config.read().await;
4735            let mut storage = blockchain.storage.write();
4736            let mut tempo = TempoChain::new(
4737                cfg.tempo_consensus_seed(),
4738                cfg.tempo_epoch_length,
4739                storage.best_number,
4740            );
4741            let mut proof_states = new_states();
4742            if prune_state_history_config.is_state_history_supported() {
4743                // Execution history counts past states; proof history also retains the head.
4744                proof_states = proof_states.with_current_state();
4745            } else {
4746                proof_states.set_cache_limit(1);
4747            }
4748            tempo.proof_states = Arc::new(RwLock::new(proof_states));
4749            storage.tempo = Some(tempo);
4750        }
4751        let startup_cache_lease = if fork.read().is_some() {
4752            let db = db.read().await;
4753            let inner = db
4754                .maybe_inner()
4755                .map_err(|err| eyre::eyre!("fork database is missing its cache backend: {err}"))?;
4756            StagedForkCacheLease::for_db(inner)
4757        } else {
4758            StagedForkCacheLease::default()
4759        };
4760        let startup_fork_cache_user =
4761            StagedForkDbUser { db: Some(Arc::clone(&db)), cache_lease: startup_cache_lease };
4762        #[cfg(feature = "base")]
4763        let base_activation_admin = node_config.read().await.base_activation_admin;
4764
4765        let backend = Self {
4766            db,
4767            blockchain,
4768            states: Arc::new(RwLock::new(states)),
4769            evm_env: env,
4770            networks,
4771            #[cfg(feature = "base")]
4772            base_activation_admin,
4773            hardfork: Arc::new(RwLock::new(hardfork)),
4774            fork,
4775            last_fork_cache_source: Arc::new(RwLock::new(last_fork_cache_source)),
4776            time: TimeManager::new(start_timestamp),
4777            cheats: Default::default(),
4778            new_block_listeners: Default::default(),
4779            fees,
4780            genesis,
4781            active_state_snapshots: Arc::new(Mutex::new(Default::default())),
4782            enable_steps_tracing,
4783            print_logs,
4784            print_traces,
4785            call_trace_decoder: Arc::new(RwLock::new(call_trace_decoder)),
4786            prune_state_history_config,
4787            transaction_block_keeper,
4788            node_config,
4789            slots_in_an_epoch,
4790            precompile_factory,
4791            mining: Arc::new(tokio::sync::Mutex::new(())),
4792            #[cfg(test)]
4793            mining_commit_hook: Default::default(),
4794            #[cfg(test)]
4795            mining_state_root_hook: Default::default(),
4796            #[cfg(test)]
4797            rollback_unwind_hook: Default::default(),
4798            disable_pool_balance_checks,
4799            startup_fork_cache_user,
4800        };
4801
4802        #[cfg(feature = "monad")]
4803        let monad_fork =
4804            if backend.networks.is_monad() { backend.fork.read().clone() } else { None };
4805        #[cfg(feature = "monad")]
4806        if let Some(fork) = monad_fork {
4807            monad::cache_fork_context(&fork).await?;
4808        }
4809
4810        if let Some(interval_block_time) = automine_block_time {
4811            backend.update_interval_mine_block_time(interval_block_time);
4812        }
4813
4814        // Note: this can only fail in forking mode, in which case we can't recover
4815        backend.apply_genesis().await.wrap_err("failed to create genesis")?;
4816        Ok(backend)
4817    }
4818
4819    /// Applies the configured genesis settings
4820    ///
4821    /// This will fund, create the genesis accounts
4822    async fn apply_genesis(&self) -> Result<(), DatabaseError> {
4823        trace!(target: "backend", "setting genesis balances");
4824
4825        if self.fork.read().is_some() {
4826            let parent_env = self.evm_env.read().clone();
4827            return self
4828                .apply_fork_genesis(
4829                    Arc::clone(&self.db),
4830                    self.startup_fork_cache_user.cache_lease.clone(),
4831                    &parent_env,
4832                    self.best_hash(),
4833                    self.hardfork(),
4834                )
4835                .await;
4836        }
4837
4838        let mut db = self.db.write().await;
4839        for (account, info) in self.genesis.account_infos() {
4840            db.insert_account(account, info);
4841        }
4842
4843        // insert the new genesis hash to the database so it's available for the next block in
4844        // the evm
4845        db.insert_block_hash(U256::from(self.best_number()), self.best_hash());
4846
4847        if let Some(transitions) =
4848            self.ethereum_block_transitions(self.hardfork(), None, BlockExecutionKind::Complete)
4849        {
4850            if transitions.hardfork >= EthereumHardfork::Cancun {
4851                db.set_code(eip4788::BEACON_ROOTS_ADDRESS, eip4788::BEACON_ROOTS_CODE.clone())?;
4852            }
4853            if transitions.hardfork >= EthereumHardfork::Prague {
4854                db.set_code(
4855                    eip2935::HISTORY_STORAGE_ADDRESS,
4856                    eip2935::HISTORY_STORAGE_CODE.clone(),
4857                )?;
4858                db.set_code(
4859                    eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
4860                    eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone(),
4861                )?;
4862                db.set_code(
4863                    eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
4864                    eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_CODE.clone(),
4865                )?;
4866            }
4867        }
4868        // apply the genesis.json alloc
4869        self.genesis.apply_genesis_json_alloc(&mut **db)?;
4870        drop(db);
4871        self.apply_funded_accounts(&self.db).await?;
4872
4873        // Seed Base protocol accounts and one-time state transitions for standalone nodes.
4874        #[cfg(feature = "base")]
4875        if self.is_base() && !self.is_fork() {
4876            let upgrade = self.base_upgrade();
4877            Self::apply_base_genesis(&mut **self.db.write().await, upgrade)?;
4878        }
4879
4880        // Initialize Tempo precompiles and fee tokens when in Tempo mode (not in fork mode).
4881        // In fork mode, precompiles are inherited from the forked origin.
4882        if self.networks.is_tempo() && !self.is_fork() {
4883            let chain_id = self.evm_env.read().cfg_env.chain_id;
4884            let timestamp = self.genesis.timestamp;
4885            let test_accounts: Vec<Address> = self.genesis.accounts.clone();
4886            let hardfork = self.tempo_hardfork();
4887            let mut db = self.db.write().await;
4888            crate::eth::backend::tempo::initialize_tempo_precompiles(
4889                &mut **db,
4890                chain_id,
4891                timestamp,
4892                &test_accounts,
4893                hardfork,
4894            )
4895            .map_err(|e| {
4896                tracing::error!(target: "backend", "failed to initialize Tempo precompiles: {e}");
4897                DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))
4898            })?;
4899            trace!(target: "backend", "initialized Tempo precompiles and fee tokens for {} accounts", test_accounts.len());
4900        }
4901
4902        trace!(target: "backend", "set genesis balances");
4903
4904        Ok(())
4905    }
4906
4907    /// Applies genesis allocations to a fork database before it becomes live.
4908    async fn apply_fork_genesis(
4909        &self,
4910        db: Arc<AsyncRwLock<Box<dyn Db>>>,
4911        cache_lease: StagedForkCacheLease,
4912        parent_env: &EvmEnv,
4913        parent_hash: B256,
4914        hardfork: FoundryHardfork,
4915    ) -> Result<(), DatabaseError> {
4916        let user = StagedForkDbUser { db: Some(db), cache_lease };
4917        let mut genesis_accounts = JoinSet::new();
4918        for address in self.genesis.accounts.iter().copied() {
4919            let task_user = StagedForkDbUser {
4920                db: Some(Arc::clone(user.db())),
4921                cache_lease: user.cache_lease.clone(),
4922            };
4923
4924            // The fork database can fetch independent accounts concurrently.
4925            genesis_accounts.spawn(async move {
4926                let db = task_user.db().read().await;
4927                let info = db.basic_ref(address)?.unwrap_or_default();
4928                Ok::<_, DatabaseError>((address, info))
4929            });
4930        }
4931
4932        let mut account_infos = Vec::with_capacity(self.genesis.accounts.len());
4933        while let Some(result) = genesis_accounts.join_next().await {
4934            match result {
4935                Ok(Ok(account)) => account_infos.push(account),
4936                Ok(Err(err)) => {
4937                    genesis_accounts.shutdown().await;
4938                    return Err(err);
4939                }
4940                Err(err) => {
4941                    genesis_accounts.shutdown().await;
4942                    return Err(DatabaseError::AnyRequest(Arc::new(eyre::eyre!(
4943                        "fork genesis account task failed: {err}"
4944                    ))));
4945                }
4946            }
4947        }
4948        let mut db_guard = user.db().write().await;
4949        for (address, mut info) in account_infos {
4950            info.balance = self.genesis.balance;
4951            db_guard.insert_account(address, info);
4952        }
4953        self.genesis.apply_genesis_json_alloc(&mut **db_guard)?;
4954        // A fork taken before the remote chain activated Denim has no BaseTime deployment, but
4955        // Denim blocks mined locally still send the BaseTime deposit. Existing deployments and
4956        // their admin are preserved.
4957        #[cfg(feature = "base")]
4958        if matches!(hardfork, FoundryHardfork::Base(upgrade) if upgrade >= BaseUpgrade::Denim) {
4959            ensure_base_time_predeploy(&mut **db_guard, false)?;
4960            self.ensure_fork_accepts_system_transactions(
4961                &**db_guard,
4962                parent_env,
4963                parent_hash,
4964                hardfork,
4965            )?;
4966        }
4967        #[cfg(not(feature = "base"))]
4968        let _ = (parent_env, parent_hash, hardfork);
4969        drop(db_guard);
4970        self.apply_funded_accounts(user.db()).await
4971    }
4972
4973    /// Applies explicit `--fund` balances while preserving account metadata inherited from a fork.
4974    async fn apply_funded_accounts(
4975        &self,
4976        db: &Arc<AsyncRwLock<Box<dyn Db>>>,
4977    ) -> Result<(), DatabaseError> {
4978        let funded_accounts = self.node_config.read().await.funded_accounts.clone();
4979        let mut accounts = Vec::with_capacity(funded_accounts.len());
4980        for (address, balance) in funded_accounts {
4981            let mut info = db.read().await.basic_ref(address)?.unwrap_or_default();
4982            info.balance = balance;
4983            accounts.push((address, info));
4984        }
4985        let mut db = db.write().await;
4986        for (address, info) in accounts {
4987            db.insert_account(address, info);
4988        }
4989        Ok(())
4990    }
4991
4992    /// Seeds Base protocol accounts and one-time state transitions for a standalone node.
4993    #[cfg(feature = "base")]
4994    fn apply_base_genesis(mut db: &mut dyn Db, upgrade: BaseUpgrade) -> Result<(), DatabaseError> {
4995        for address in [
4996            Predeploys::L1_BLOCK_INFO,
4997            Predeploys::SEQUENCER_FEE_VAULT,
4998            Predeploys::BASE_FEE_VAULT,
4999            Predeploys::L1_FEE_VAULT,
5000            Predeploys::OPERATOR_FEE_VAULT,
5001            Predeploys::BASE_TIME,
5002        ] {
5003            if db.basic(address)?.is_none() {
5004                db.insert_account(address, AccountInfo::default());
5005            }
5006        }
5007
5008        db.set_storage_at(
5009            Predeploys::L1_BLOCK_INFO,
5010            L1BlockInfo::L1_BASE_FEE_SLOT.into(),
5011            U256::from(DEFAULT_BASE_L1_BASE_FEE).into(),
5012        )?;
5013        db.set_storage_at(
5014            Predeploys::L1_BLOCK_INFO,
5015            L1BlockInfo::ECOTONE_L1_BLOB_BASE_FEE_SLOT.into(),
5016            B256::with_last_byte(1),
5017        )?;
5018        let mut l1_fee_scalars = [0u8; 32];
5019        l1_fee_scalars
5020            [L1BlockInfo::BASE_FEE_SCALAR_OFFSET..L1BlockInfo::BASE_FEE_SCALAR_OFFSET + 4]
5021            .copy_from_slice(&DEFAULT_BASE_L1_FEE_SCALAR.to_be_bytes());
5022        db.set_storage_at(
5023            Predeploys::L1_BLOCK_INFO,
5024            L1BlockInfo::ECOTONE_L1_FEE_SCALARS_SLOT.into(),
5025            B256::from(l1_fee_scalars),
5026        )?;
5027
5028        if upgrade >= BaseUpgrade::Canyon {
5029            let upgrades = ChainUpgrades::new([(BaseUpgrade::Canyon, ForkCondition::Timestamp(1))]);
5030            ensure_create2_deployer(upgrades, 1, &mut db)?;
5031        }
5032        if upgrade >= BaseUpgrade::Denim {
5033            ensure_base_time_predeploy(&mut *db, true)?;
5034        }
5035        if upgrade >= BaseUpgrade::Zenith {
5036            // Zenith is genesis-only and is not stored by ChainUpgrades.
5037            let mut upgrades = RollupConfig::default();
5038            upgrades.set_upgrade_activation_timestamp(BaseUpgrade::Zenith, 0);
5039            ensure_eip8130_system_accounts(upgrades, 1, &mut db)?;
5040        }
5041
5042        // Give the installed Base precompiles a sentinel byte so Solidity's `extcodesize`
5043        // check on high-level calls to functions without return data does not revert in the
5044        // caller. `ensure_eip8130_system_accounts` only covers the Zenith nonce manager.
5045        for address in base_code_sentinel_addresses(upgrade) {
5046            let mut account = db.basic(address)?.unwrap_or_default();
5047            if account.code.as_ref().is_none_or(|code| code.is_empty()) {
5048                let code =
5049                    revm::state::Bytecode::new_legacy(Bytes::from_static(SYSTEM_PRECOMPILE_STUB));
5050                account.set_code(code);
5051                db.insert_account(address, account);
5052            }
5053        }
5054
5055        Ok(())
5056    }
5057
5058    /// Populates a detached in-memory database from explicit reset inputs.
5059    #[allow(clippy::too_many_arguments)]
5060    fn populate_memory_db(
5061        db: &mut dyn Db,
5062        genesis: &GenesisConfig,
5063        funded_accounts: &HashMap<Address, U256>,
5064        hardfork: FoundryHardfork,
5065        chain_id: u64,
5066        is_tempo: bool,
5067        tempo_hardfork: Option<TempoHardfork>,
5068        genesis_hash: B256,
5069        install_create2_deployer: bool,
5070    ) -> Result<(), DatabaseError> {
5071        for (account, info) in genesis.account_infos() {
5072            db.insert_account(account, info);
5073        }
5074        db.insert_block_hash(U256::from(genesis.number), genesis_hash);
5075
5076        if let FoundryHardfork::Ethereum(hardfork) = hardfork {
5077            if hardfork >= EthereumHardfork::Cancun {
5078                db.set_code(eip4788::BEACON_ROOTS_ADDRESS, eip4788::BEACON_ROOTS_CODE.clone())?;
5079            }
5080            if hardfork >= EthereumHardfork::Prague {
5081                db.set_code(
5082                    eip2935::HISTORY_STORAGE_ADDRESS,
5083                    eip2935::HISTORY_STORAGE_CODE.clone(),
5084                )?;
5085                db.set_code(
5086                    eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
5087                    eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone(),
5088                )?;
5089                db.set_code(
5090                    eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
5091                    eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_CODE.clone(),
5092                )?;
5093            }
5094        }
5095
5096        genesis.apply_genesis_json_alloc(db)?;
5097        for (&address, &balance) in funded_accounts {
5098            let mut info = db.basic_ref(address)?.unwrap_or_default();
5099            info.balance = balance;
5100            db.insert_account(address, info);
5101        }
5102
5103        if is_tempo {
5104            let hardfork = tempo_hardfork.ok_or_else(|| {
5105                DatabaseError::AnyRequest(Arc::new(eyre::eyre!(
5106                    "missing Tempo hardfork during memory reset"
5107                )))
5108            })?;
5109            crate::eth::backend::tempo::initialize_tempo_precompiles(
5110                db,
5111                chain_id,
5112                genesis.timestamp,
5113                &genesis.accounts,
5114                hardfork,
5115            )
5116            .map_err(|err| DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{err}"))))?;
5117        }
5118
5119        if install_create2_deployer {
5120            db.set_code(
5121                DEFAULT_CREATE2_DEPLOYER,
5122                Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE),
5123            )?;
5124        }
5125
5126        Ok(())
5127    }
5128
5129    /// Prepares a fresh fork without mutating the live backend.
5130    pub(crate) async fn prepare_fork_reset(
5131        &self,
5132        forking: Forking,
5133        serving_instance_id: B256,
5134    ) -> Result<StagedForkReset, BlockchainError> {
5135        let previous_fork = self.get_fork();
5136        let previous_source = self
5137            .last_fork_cache_source
5138            .read()
5139            .clone()
5140            .or_else(|| previous_fork.as_ref().and_then(ForkCacheSource::from_fork));
5141        let configured_rpc_urls = self.node_config.read().await.fork_urls.clone();
5142        let rpc_url_was_provided = forking.json_rpc_url.is_some();
5143        let target_rpc_urls = if let Some(url) = forking.json_rpc_url {
5144            vec![url]
5145        } else if !configured_rpc_urls.is_empty() {
5146            configured_rpc_urls
5147        } else {
5148            previous_fork
5149                .as_ref()
5150                .map(|fork| fork.config.read().fork_urls.clone())
5151                .filter(|urls| !urls.is_empty())
5152                .ok_or_else(|| {
5153                    RpcError::invalid_params(
5154                        "Forking not enabled and RPC URL not provided to start forking",
5155                    )
5156                })?
5157        };
5158        let flush_old_cache = previous_fork.is_some();
5159        if flush_old_cache {
5160            // Staging opens a separate BlockchainDb from disk. Persist the live remote cache first
5161            // so an unchanged source and block can reuse it without copying locally modified state.
5162            self.db.write().await.maybe_flush_cache().map_err(BlockchainError::Internal)?;
5163        }
5164
5165        for _ in 0..3 {
5166            if let Some(staged) = self
5167                .stage_fork_reset(
5168                    &target_rpc_urls,
5169                    forking.block_number,
5170                    serving_instance_id,
5171                    previous_source.clone(),
5172                    flush_old_cache,
5173                    rpc_url_was_provided,
5174                )
5175                .await?
5176            {
5177                return Ok(staged);
5178            }
5179        }
5180        Err(BlockchainError::Internal(
5181            "fork endpoint changed while the replacement was being staged".to_string(),
5182        ))
5183    }
5184
5185    /// Builds and validates one complete fork replacement without mutating the live backend.
5186    async fn stage_fork_reset(
5187        &self,
5188        target_rpc_urls: &[String],
5189        block_number: Option<u64>,
5190        serving_instance_id: B256,
5191        previous_source: Option<ForkCacheSource>,
5192        flush_old_cache: bool,
5193        rpc_url_was_provided: bool,
5194    ) -> Result<Option<StagedForkReset>, BlockchainError> {
5195        let target_rpc_url = target_rpc_urls.first().ok_or_else(|| {
5196            BlockchainError::Internal("at least one fork URL is required".to_string())
5197        })?;
5198        let mut staged_config = self.node_config.read().await.clone();
5199        if rpc_url_was_provided {
5200            staged_config.fork_chain_id = None;
5201        }
5202        let configured_endpoint_is_anvil = staged_config.fork_endpoint_is_anvil
5203            && staged_config.fork_urls.contains(target_rpc_url);
5204        let cached_endpoint_is_anvil = previous_source.as_ref().is_some_and(|source| {
5205            source.rpc_url == *target_rpc_url && source.endpoint_identity.is_authoritative()
5206        });
5207        staged_config.fork_endpoint_is_anvil =
5208            configured_endpoint_is_anvil || cached_endpoint_is_anvil;
5209        staged_config.fork_urls = target_rpc_urls.to_vec();
5210        staged_config.fork_choice = block_number.map(|number| ForkChoice::Block(number as i128));
5211        let mut staged_env = self.evm_env.read().clone();
5212        staged_config.apply_tempo_fork_beneficiary_default(&mut staged_env);
5213        let staged_fees = self.fees.detached();
5214        let (mut staged_db, staged_client_config) = staged_config
5215            .setup_fork_db_config_for_reset(
5216                target_rpc_url.clone(),
5217                &mut staged_env,
5218                &staged_fees,
5219                self.networks,
5220            )
5221            .await?;
5222        let cache_lease = StagedForkCacheLease::for_db(staged_db.inner());
5223        let cache_identity_changed = previous_source.as_ref().is_some_and(|source| {
5224            source.authoritative_identity_changed_at_same_url(
5225                target_rpc_url,
5226                staged_client_config.endpoint_identity,
5227            )
5228        });
5229        if cache_identity_changed {
5230            staged_db.clear_into_state_snapshot();
5231            staged_db.insert_block_hash(
5232                U256::from(staged_client_config.block_number),
5233                staged_client_config.block_hash,
5234            );
5235        }
5236        if !staged_config.no_bal && !staged_config.no_fork_node_info {
5237            staged_client_config.prefill_cache(staged_db.inner()).await;
5238        }
5239        let mut invalidated_cache_namespaces = Vec::new();
5240        if cache_identity_changed && !staged_config.no_storage_caching {
5241            if let Some(source) = &previous_source
5242                && let Some(namespace) = ForkCacheNamespace::new(
5243                    source.endpoint_identity.source_chain_id,
5244                    target_rpc_url,
5245                )
5246            {
5247                invalidated_cache_namespaces.push(namespace);
5248            }
5249            if let Some(namespace) =
5250                ForkCacheNamespace::new(staged_client_config.chain_id, target_rpc_url)
5251                && !invalidated_cache_namespaces.contains(&namespace)
5252            {
5253                invalidated_cache_namespaces.push(namespace);
5254            }
5255        }
5256        let discard_old_cached_state =
5257            cache_identity_changed && flush_old_cache && !invalidated_cache_namespaces.is_empty();
5258        let staged_db: Arc<AsyncRwLock<Box<dyn Db>>> =
5259            Arc::new(AsyncRwLock::new(Box::new(staged_db)));
5260        let staged_fork = ClientFork::new(staged_client_config.clone(), Arc::clone(&staged_db));
5261        let attempt = async {
5262            let target_networks =
5263                staged_client_config.endpoint_identity.network_profile.unwrap_or_default();
5264            if !staged_config.has_explicit_network_selection()
5265                && !self.networks.supports_fork_source(&target_networks)
5266            {
5267                return Err(RpcError::invalid_params(format!(
5268                    "cannot reset Anvil across network families ({} -> {}); start a new \
5269                     instance with matching network configuration",
5270                    self.execution_profile_name(),
5271                    target_networks.execution_profile_name()
5272                ))
5273                .into());
5274            }
5275            if staged_client_config.endpoint_identity.instance_id == Some(serving_instance_id) {
5276                return Err(
5277                    RpcError::invalid_params("cannot reset Anvil to its own RPC endpoint").into()
5278                );
5279            }
5280            let fork_block = staged_fork
5281                .block_by_number(staged_fork.block_number())
5282                .await?
5283                .ok_or(BlockchainError::BlockNotFound)?;
5284            if fork_block.header.hash != staged_client_config.block_hash {
5285                return Ok(None);
5286            }
5287            self.apply_fork_genesis(
5288                Arc::clone(&staged_db),
5289                cache_lease.clone(),
5290                &staged_env,
5291                staged_client_config.block_hash,
5292                staged_client_config.hardfork.unwrap_or_else(|| staged_config.get_hardfork()),
5293            )
5294            .await?;
5295
5296            #[cfg(feature = "monad")]
5297            if self.is_monad() {
5298                monad::cache_fork_context(&staged_fork).await?;
5299            }
5300
5301            if !staged_config
5302                .fork_urls_match_context(
5303                    target_rpc_urls,
5304                    staged_client_config.endpoint_identity,
5305                    staged_client_config.block_number,
5306                    staged_client_config.block_hash,
5307                )
5308                .await?
5309            {
5310                return Ok(None);
5311            }
5312
5313            Ok(Some((fork_block, staged_fork.storage.read().clone())))
5314        }
5315        .await;
5316        drop(staged_fork);
5317        let (fork_block, staged_storage) = match attempt {
5318            Ok(Some(staged)) => staged,
5319            Ok(None) => {
5320                drop(staged_db);
5321                self.rollback_staged_fork_cache(cache_lease, flush_old_cache).await?;
5322                return Ok(None);
5323            }
5324            Err(err) => {
5325                drop(staged_db);
5326                self.rollback_staged_fork_cache(cache_lease, flush_old_cache).await?;
5327                return Err(err);
5328            }
5329        };
5330        let staged_db = match Arc::try_unwrap(staged_db) {
5331            Ok(staged_db) => staged_db.into_inner(),
5332            Err(staged_db) => {
5333                drop(staged_db);
5334                self.rollback_staged_fork_cache(cache_lease, flush_old_cache).await?;
5335                return Err(BlockchainError::Internal(
5336                    "staged fork database still has active references".to_string(),
5337                ));
5338            }
5339        };
5340
5341        let fork = ClientFork::new(staged_client_config, Arc::clone(&self.db));
5342        *fork.storage.write() = staged_storage;
5343        Ok(Some(StagedForkReset {
5344            node_config: staged_config,
5345            db: staged_db,
5346            fees: staged_fees,
5347            evm_env: staged_env,
5348            fork,
5349            timestamp: fork_block.header.timestamp(),
5350            discard_old_cached_state,
5351            invalidated_cache_namespaces,
5352            flush_old_cache,
5353            cache_lease,
5354        }))
5355    }
5356
5357    async fn rollback_staged_fork_cache(
5358        &self,
5359        cache_lease: StagedForkCacheLease,
5360        restore_live_cache: bool,
5361    ) -> Result<(), BlockchainError> {
5362        let rollback_err = cache_lease.rollback().err();
5363        // If immediate invalidation failed, dropping the final lease retries cleanup before the
5364        // live cache is restored at a potentially shared path.
5365        drop(cache_lease);
5366        let restore_err =
5367            if restore_live_cache { self.db.read().await.maybe_flush_cache().err() } else { None };
5368        match (rollback_err, restore_err) {
5369            (None, None) => Ok(()),
5370            (Some(err), None) => Err(err),
5371            (None, Some(err)) => Err(BlockchainError::Internal(format!(
5372                "failed to restore the live fork cache after staged reset rollback: {err}"
5373            ))),
5374            (Some(rollback), Some(restore)) => Err(BlockchainError::Internal(format!(
5375                "{rollback}; restoring the live fork cache also failed: {restore}"
5376            ))),
5377        }
5378    }
5379
5380    /// Atomically publishes a fully prepared fork replacement.
5381    pub(crate) async fn commit_fork_reset(
5382        &self,
5383        staged: StagedForkReset,
5384    ) -> Result<(), BlockchainError> {
5385        let fork_block_number = staged.fork.block_number();
5386        let fork_block_hash = staged.fork.block_hash();
5387        let fork_total_difficulty = staged.fork.total_difficulty();
5388        let fork_cache_source = ForkCacheSource::from_fork(&staged.fork);
5389
5390        // Acquire asynchronous write guards before flushing and replacing the live database so no
5391        // old-context request can populate its cache between those operations.
5392        let mut node_config = self.node_config.write().await;
5393        let mut db = self.db.write().await;
5394        if staged.flush_old_cache {
5395            db.maybe_flush_cache().map_err(BlockchainError::Internal)?;
5396        }
5397        if let Err(err) =
5398            staged.invalidated_cache_namespaces.iter().try_for_each(ForkCacheNamespace::invalidate)
5399        {
5400            // Prevent the rejected staged backend from flushing partial target state, then restore
5401            // the still-live cache after the endpoint namespace cleanup failed.
5402            let StagedForkReset { db: staged_db, cache_lease, flush_old_cache, .. } = staged;
5403            drop(staged_db);
5404            let rollback_err = cache_lease.rollback().err();
5405            drop(cache_lease);
5406            let restore_err = if flush_old_cache { db.maybe_flush_cache().err() } else { None };
5407            let mut message = err.to_string();
5408            if let Some(err) = rollback_err {
5409                message.push_str(&format!("; staged cache rollback also failed: {err}"));
5410            }
5411            if let Some(err) = restore_err {
5412                message.push_str(&format!("; restoring the live fork cache also failed: {err}"));
5413            }
5414            return Err(BlockchainError::Internal(message));
5415        }
5416        if staged.discard_old_cached_state {
5417            db.clear_into_state_snapshot();
5418        }
5419        staged.cache_lease.disarm();
5420        let StagedForkReset {
5421            node_config: staged_node_config,
5422            db: staged_db,
5423            fees,
5424            evm_env,
5425            fork,
5426            timestamp,
5427            ..
5428        } = staged;
5429        *node_config = staged_node_config;
5430        *db = staged_db;
5431        self.fees.replace_from(&fees);
5432        *self.evm_env.write() = evm_env;
5433        *self.fork.write() = Some(fork);
5434        *self.last_fork_cache_source.write() = fork_cache_source;
5435        {
5436            let mut storage = self.blockchain.storage.write();
5437            let mut replacement = BlockchainStorage::forked(
5438                fork_block_number,
5439                fork_block_hash,
5440                fork_total_difficulty,
5441            );
5442            replacement.tempo = storage.tempo.take();
5443            if let Some(tempo) = &mut replacement.tempo {
5444                tempo.reanchor(fork_block_number);
5445            }
5446            *storage = replacement;
5447        }
5448        self.states.write().clear();
5449        self.active_state_snapshots.lock().clear();
5450        self.time.reset(timestamp);
5451        self.cheats.clear_next_block_overrides();
5452
5453        trace!(target: "backend", "reset fork");
5454        Ok(())
5455    }
5456
5457    /// Builds a complete in-memory replacement without mutating the live backend.
5458    pub(crate) async fn prepare_memory_reset(
5459        &self,
5460    ) -> Result<StagedMemoryReset<N>, BlockchainError> {
5461        let reset_from_fork = self.is_fork();
5462        let genesis_timestamp = self.genesis.timestamp;
5463        let genesis_number = self.genesis.number;
5464        let mut staged_config = self.node_config.read().await.clone();
5465        staged_config.fork_source_chain_id = None;
5466        staged_config.fork_execution_chain_id = None;
5467        staged_config.fork_endpoint_is_anvil = false;
5468        staged_config.restore_fork_overrides();
5469        let local_chain_id = staged_config.get_chain_id();
5470        staged_config.update_wallet_chain_id(local_chain_id);
5471        let (
5472            local_gas_limit,
5473            local_base_fee,
5474            local_base_fee_is_explicit,
5475            local_gas_price,
5476            local_blob_params,
5477            local_blob_excess_gas_and_price,
5478            local_beneficiary,
5479            install_create2_deployer,
5480        ) = {
5481            (
5482                staged_config.gas_limit(),
5483                staged_config.get_base_fee(),
5484                staged_config.base_fee.is_some()
5485                    || staged_config
5486                        .genesis
5487                        .as_ref()
5488                        .is_some_and(|genesis| genesis.base_fee_per_gas.is_some()),
5489                staged_config.get_gas_price(),
5490                staged_config.get_blob_params(),
5491                staged_config.get_blob_excess_gas_and_price(),
5492                staged_config.genesis.as_ref().map(|genesis| genesis.coinbase).unwrap_or_default(),
5493                !staged_config.disable_default_create2_deployer,
5494            )
5495        };
5496
5497        let local_hardfork = staged_config.get_hardfork();
5498        let local_spec = SpecId::from(local_hardfork);
5499        let local_tempo_hardfork =
5500            self.networks.is_tempo().then(|| TempoHardfork::from(local_hardfork));
5501        let staged_fees = self.fees.detached();
5502        staged_fees.set_execution_rules(
5503            local_spec,
5504            self.networks.base_fee_params(genesis_timestamp),
5505            local_tempo_hardfork,
5506        );
5507        #[cfg(feature = "optimism")]
5508        if self.networks.is_optimism() {
5509            staged_fees.set_optimism_hardfork(local_hardfork.into());
5510        }
5511        staged_fees.set_blob_params(local_blob_params);
5512        staged_fees.set_blob_excess_gas_and_price(local_blob_excess_gas_and_price);
5513
5514        // Explicit local configuration always wins, while Tempo configuration uses the
5515        // hardfork's own seed. An implicit in-memory Ethereum chain otherwise keeps its live base
5516        // fee in the reset genesis; returning from a fork restores the local default instead of
5517        // leaking remote fee state. Compute this up front so the env, storage, and fee manager all
5518        // agree.
5519        let preserve_live_base_fee =
5520            !reset_from_fork && !local_base_fee_is_explicit && local_tempo_hardfork.is_none();
5521        let genesis_base_fee =
5522            if preserve_live_base_fee { staged_fees.base_fee() } else { local_base_fee };
5523
5524        let mut staged_cfg = CfgEnv::new();
5525        staged_config.set_cfg_spec(&mut staged_cfg, local_spec);
5526        staged_cfg.chain_id = local_chain_id;
5527        staged_cfg.limit_contract_code_size = staged_config.code_size_limit;
5528        staged_cfg.disable_eip3607 = true;
5529        staged_cfg.disable_block_gas_limit = staged_config.disable_block_gas_limit;
5530        if let Some(memory_limit) = staged_config.memory_limit {
5531            staged_cfg.memory_limit = memory_limit;
5532        }
5533        let staged_env = EvmEnv::new(
5534            staged_cfg,
5535            BlockEnv {
5536                number: U256::from(genesis_number),
5537                beneficiary: local_beneficiary,
5538                timestamp: U256::from(genesis_timestamp),
5539                gas_limit: local_gas_limit,
5540                basefee: genesis_base_fee,
5541                prevrandao: Some(B256::ZERO),
5542                ..Default::default()
5543            },
5544        );
5545
5546        let base_fee = staged_fees.is_eip1559().then_some(genesis_base_fee);
5547        let header = genesis_header(&staged_env, base_fee, genesis_timestamp, genesis_number);
5548        let staged_storage = BlockchainStorage::new(foundry_header(&self.networks, header));
5549
5550        // Seed the next block's fee state. Tempo always advances through its hardfork rule, an
5551        // implicit in-memory Ethereum reset restores Anvil's default, and explicit or
5552        // fork-to-memory Ethereum resets retain the local configured value.
5553        staged_fees.set_base_fee(genesis_base_fee);
5554        if staged_fees.is_eip1559() {
5555            let next_base_fee = if staged_fees.tempo_hardfork().is_some() {
5556                staged_fees.get_next_block_base_fee_per_gas(
5557                    0,
5558                    staged_env.block_env.gas_limit,
5559                    genesis_base_fee,
5560                )
5561            } else if preserve_live_base_fee {
5562                crate::eth::fees::INITIAL_BASE_FEE
5563            } else {
5564                genesis_base_fee
5565            };
5566            staged_fees.set_base_fee(next_base_fee);
5567        }
5568        staged_fees.set_gas_price(local_gas_price);
5569
5570        let mut staged_db: Box<dyn Db> = Box::new(StateRootDb::new(
5571            self.prune_state_history_config.is_state_history_supported(),
5572        ));
5573        Self::populate_memory_db(
5574            &mut *staged_db,
5575            &self.genesis,
5576            &staged_config.funded_accounts,
5577            local_hardfork,
5578            local_chain_id,
5579            self.networks.is_tempo(),
5580            local_tempo_hardfork,
5581            staged_storage.genesis_hash,
5582            install_create2_deployer,
5583        )?;
5584        #[cfg(feature = "base")]
5585        if self.is_base() {
5586            let upgrade = match local_hardfork {
5587                FoundryHardfork::Base(upgrade) => upgrade,
5588                _ => BaseUpgrade::Azul,
5589            };
5590            Self::apply_base_genesis(&mut *staged_db, upgrade)?;
5591        }
5592
5593        Ok(StagedMemoryReset {
5594            node_config: staged_config,
5595            db: staged_db,
5596            fees: staged_fees,
5597            evm_env: staged_env,
5598            hardfork: local_hardfork,
5599            storage: staged_storage,
5600            timestamp: genesis_timestamp,
5601            flush_old_cache: reset_from_fork,
5602        })
5603    }
5604
5605    /// Atomically publishes a fully prepared in-memory replacement.
5606    pub(crate) async fn commit_memory_reset(
5607        &self,
5608        staged: StagedMemoryReset<N>,
5609    ) -> Result<(), BlockchainError> {
5610        let StagedMemoryReset {
5611            node_config,
5612            db,
5613            fees,
5614            evm_env,
5615            hardfork,
5616            mut storage,
5617            timestamp,
5618            flush_old_cache,
5619        } = staged;
5620        let mut live_config = self.node_config.write().await;
5621        let mut live_db = self.db.write().await;
5622        if flush_old_cache {
5623            live_db.maybe_flush_cache().map_err(BlockchainError::Internal)?;
5624        }
5625
5626        *live_config = node_config;
5627        *live_db = db;
5628        self.fees.replace_from(&fees);
5629        *self.evm_env.write() = evm_env;
5630        *self.hardfork.write() = hardfork;
5631        *self.fork.write() = None;
5632        {
5633            let mut live_storage = self.blockchain.storage.write();
5634            storage.tempo = live_storage.tempo.take();
5635            if let Some(tempo) = &mut storage.tempo {
5636                tempo.reanchor(storage.best_number);
5637            }
5638            *live_storage = storage;
5639        }
5640        self.states.write().clear();
5641        self.active_state_snapshots.lock().clear();
5642        self.time.reset(timestamp);
5643        self.cheats.clear_next_block_overrides();
5644        trace!(target: "backend", "reset to fresh in-memory state");
5645        Ok(())
5646    }
5647
5648    /// Reverts the state to the state snapshot identified by the given `id`.
5649    pub async fn revert_state_snapshot(&self, id: U256) -> Result<bool, BlockchainError>
5650    where
5651        N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
5652    {
5653        let Some((num, hash, fees, time, next_block)) =
5654            self.active_state_snapshots.lock().get(&id).map(|snapshot| {
5655                (
5656                    snapshot.block_number,
5657                    snapshot.block_hash,
5658                    snapshot.fees,
5659                    snapshot.time,
5660                    snapshot.next_block,
5661                )
5662            })
5663        else {
5664            return Ok(false);
5665        };
5666        let block = self.block_by_hash(hash).await?.ok_or(BlockchainError::BlockNotFound)?;
5667        let removed_logs = self.removed_logs_since(num);
5668        let mut db = self.db.write().await;
5669        if !db.revert_state(id, RevertStateSnapshotAction::RevertRemove) {
5670            return Ok(false);
5671        }
5672        {
5673            let mut snapshots = self.active_state_snapshots.lock();
5674            snapshots.remove(&id);
5675            snapshots.retain(|snapshot_id, _| *snapshot_id < id);
5676        }
5677        // Revert the storage that's newer than the snapshot.
5678        let (removed_blocks, proof_state) = {
5679            let mut storage = self.blockchain.storage.write();
5680            let proof_state = storage.tempo.as_mut().and_then(|tempo| {
5681                tempo.restore(id, num).map(|state| (Arc::clone(&tempo.proof_states), state))
5682            });
5683            (storage.unwind_to(num, hash), proof_state)
5684        };
5685        if let Some((states, accounts)) = proof_state {
5686            let mut state = CacheDB::<EmptyDBWrapper>::new(Default::default());
5687            state.cache.accounts = (*accounts).clone();
5688            let mut states = states.write();
5689            states.remove_block_states(&[hash]);
5690            states.insert(hash, StateDb::new(state));
5691        }
5692        let removed_hashes: Vec<_> = removed_blocks.iter().map(|b| b.header.hash_slow()).collect();
5693        self.states.write().remove_block_states(&removed_hashes);
5694        if let Some(states) = self.tempo_proof_states() {
5695            states.write().remove_block_states(&removed_hashes);
5696        }
5697        if !removed_logs.is_empty() {
5698            self.notify_on_removed_logs(removed_logs);
5699        }
5700
5701        self.time.restore(time);
5702        self.cheats.restore_next_block_overrides(&next_block);
5703
5704        {
5705            let mut env = self.evm_env.write();
5706            env.block_env = BlockEnv {
5707                number: self.evm_block_number(num),
5708                timestamp: U256::from(block.header.timestamp()),
5709                difficulty: block.header.difficulty(),
5710                // ensures prevrandao is set
5711                prevrandao: Some(block.header.mix_hash().unwrap_or_default()),
5712                gas_limit: block.header.gas_limit(),
5713                // Keep previous `beneficiary` and `basefee` value
5714                beneficiary: env.block_env.beneficiary,
5715                basefee: env.block_env.basefee,
5716                ..Default::default()
5717            };
5718        }
5719        self.fees.restore(fees);
5720        Ok(true)
5721    }
5722
5723    fn tempo_proof_states(&self) -> Option<Arc<RwLock<InMemoryBlockStates>>> {
5724        self.blockchain.storage.read().tempo.as_ref().map(|tempo| Arc::clone(&tempo.proof_states))
5725    }
5726}
5727
5728impl<N: Network> Backend<N>
5729where
5730    N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
5731{
5732    /// Appends all `Log`s mined by the node that were emitted in the `block` and match the
5733    /// `Filter`.
5734    fn mined_logs_for_block(
5735        storage: &BlockchainStorage<N>,
5736        filter: &Filter,
5737        block: &Block,
5738        block_hash: B256,
5739        all_logs: &mut Vec<Log>,
5740    ) {
5741        if !filter.matches_bloom(block.header.logs_bloom) {
5742            return;
5743        }
5744
5745        let mut block_log_index = 0u32;
5746        for tx in &block.body.transactions {
5747            let Some(tx) = storage.transactions.get(&tx.hash()) else {
5748                continue;
5749            };
5750
5751            let logs = tx.receipt.logs();
5752            let transaction_hash = tx.info.transaction_hash;
5753
5754            for log in logs {
5755                if filter.matches(log) {
5756                    all_logs.push(Log {
5757                        inner: log.clone(),
5758                        block_hash: Some(block_hash),
5759                        block_number: Some(block.header.number()),
5760                        block_timestamp: Some(block.header.timestamp()),
5761                        transaction_hash: Some(transaction_hash),
5762                        transaction_index: Some(tx.info.transaction_index),
5763                        log_index: Some(block_log_index as u64),
5764                        removed: false,
5765                    });
5766                }
5767                block_log_index += 1;
5768            }
5769        }
5770    }
5771
5772    /// Returns all logs of the blocks with a number greater than `block_number`, marked as
5773    /// removed.
5774    ///
5775    /// This captures the logs of blocks that are about to be unwound before their transactions
5776    /// and receipts are cleared from storage, so they can be re-delivered to log subscriptions
5777    /// and filters with `removed: true`.
5778    fn removed_logs_since(&self, block_number: u64) -> Vec<Log> {
5779        let storage = self.blockchain.storage.read();
5780        let mut all_logs = Vec::new();
5781
5782        for num in (block_number + 1)..=storage.best_number {
5783            if let Some(hash) = storage.hashes.get(&num)
5784                && let Some(block) = storage.blocks.get(hash)
5785            {
5786                let mut block_log_index = 0u64;
5787                for tx in &block.body.transactions {
5788                    if let Some(tx) = storage.transactions.get(&tx.hash()) {
5789                        for log in tx.receipt.logs() {
5790                            all_logs.push(Log {
5791                                inner: log.clone(),
5792                                block_hash: Some(*hash),
5793                                block_number: Some(num),
5794                                block_timestamp: Some(block.header.timestamp()),
5795                                transaction_hash: Some(tx.info.transaction_hash),
5796                                transaction_index: Some(tx.info.transaction_index),
5797                                log_index: Some(block_log_index),
5798                                removed: true,
5799                            });
5800                            block_log_index += 1;
5801                        }
5802                    }
5803                }
5804            }
5805        }
5806
5807        all_logs
5808    }
5809
5810    /// Returns the logs of the block that match the filter
5811    async fn logs_for_block(
5812        &self,
5813        filter: Filter,
5814        hash: B256,
5815    ) -> Result<Vec<Log>, BlockchainError> {
5816        {
5817            let storage = self.blockchain.storage.read();
5818            if let Some(block) = storage.blocks.get(&hash) {
5819                let mut logs = Vec::new();
5820                Self::mined_logs_for_block(&storage, &filter, block, hash, &mut logs);
5821                return Ok(logs);
5822            }
5823        }
5824
5825        if let Some(fork) = self.get_fork() {
5826            return Ok(fork.logs(&filter).await?);
5827        }
5828
5829        Err(BlockchainError::UnknownBlock)
5830    }
5831
5832    /// Returns the logs that match the filter in the given range of blocks
5833    async fn logs_for_range(
5834        &self,
5835        filter: &Filter,
5836        mut from: u64,
5837        to: u64,
5838    ) -> Result<Vec<Log>, BlockchainError> {
5839        let mut all_logs = Vec::new();
5840
5841        // get the range that predates the fork if any
5842        if let Some(fork) = self.get_fork() {
5843            let to_on_fork = if fork.predates_fork(to) {
5844                to
5845            } else {
5846                // adjust the ranges
5847                fork.block_number()
5848            };
5849
5850            if fork.predates_fork_inclusive(from) {
5851                // this data is only available on the forked client
5852                let filter = filter.clone().from_block(from).to_block(to_on_fork);
5853                all_logs = fork.logs(&filter).await?;
5854
5855                // update the range
5856                from = fork.block_number() + 1;
5857            }
5858        }
5859
5860        let storage = self.blockchain.storage.read();
5861        for number in from..=to {
5862            if let Some(&hash) = storage.hashes.get(&number)
5863                && let Some(block) = storage.blocks.get(&hash)
5864            {
5865                Self::mined_logs_for_block(&storage, filter, block, hash, &mut all_logs);
5866            }
5867        }
5868
5869        Ok(all_logs)
5870    }
5871
5872    /// Returns the logs according to the filter
5873    pub async fn logs(&self, filter: Filter) -> Result<Vec<Log>, BlockchainError> {
5874        trace!(target: "backend", "get logs [{:?}]", filter);
5875        if let Some(hash) = filter.get_block_hash() {
5876            self.logs_for_block(filter, hash).await
5877        } else {
5878            let best = self.best_number();
5879            let to_block = self
5880                .convert_block_number_at(filter.block_option.get_to_block().copied(), best)
5881                .min(best);
5882            let from_block =
5883                self.convert_block_number_at(filter.block_option.get_from_block().copied(), best);
5884            if from_block > best {
5885                return Err(BlockchainError::BlockOutOfRange(best, from_block));
5886            }
5887            if from_block > to_block {
5888                return Err(BlockchainError::RpcError(RpcError::invalid_params(
5889                    "invalid block range params",
5890                )));
5891            }
5892
5893            self.logs_for_range(&filter, from_block, to_block).await
5894        }
5895    }
5896
5897    /// Returns all receipts of the block
5898    pub fn mined_receipts(&self, hash: B256) -> Option<Vec<N::ReceiptEnvelope>> {
5899        let storage = self.blockchain.storage.read();
5900        let block = storage.blocks.get(&hash)?;
5901        block
5902            .body
5903            .transactions
5904            .iter()
5905            .map(|transaction| {
5906                storage.transactions.get(&transaction.hash()).map(|tx| tx.receipt.clone())
5907            })
5908            .collect()
5909    }
5910
5911    /// Returns the EIP-7928 block access list stored for a locally mined block hash.
5912    pub fn block_access_list_by_hash(&self, hash: B256) -> Option<BlockAccessList> {
5913        self.blockchain.storage.read().block_access_lists.get(&hash).cloned()
5914    }
5915
5916    /// Returns the EIP-7928 block access list stored for a locally mined block number.
5917    pub fn block_access_list_by_number(&self, number: u64) -> Option<BlockAccessList> {
5918        let storage = self.blockchain.storage.read();
5919        storage.hashes.get(&number).and_then(|hash| storage.block_access_lists.get(hash)).cloned()
5920    }
5921}
5922
5923// Mining methods — generic over N: Network, with Foundry-associated-type bounds for now.
5924impl<N: Network> Backend<N>
5925where
5926    Self: TransactionValidator<FoundryTxEnvelope>,
5927    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
5928{
5929    /// Mines a new block and stores it.
5930    ///
5931    /// this will execute all transaction in the order they come in and return all the markers they
5932    /// provide.
5933    pub async fn mine_block(
5934        &self,
5935        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
5936    ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
5937        self.do_mine_block(pool_transactions).await
5938    }
5939
5940    /// Mines a new block while the caller holds the mining lock.
5941    pub(crate) async fn mine_block_locked(
5942        &self,
5943        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
5944        pin_timestamp: bool,
5945    ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
5946        self.do_mine_block_locked(pool_transactions, pin_timestamp).await
5947    }
5948
5949    /// Replays a transaction-hash fork prefix before the live pool and miner are created.
5950    pub(crate) async fn apply_fork_transaction_replay(
5951        &self,
5952        replay: ForkTransactionReplay,
5953    ) -> Result<()> {
5954        let source_chain_id = self.protocol_chain_id();
5955        let arbitrum_block_numbers = is_arbitrum(source_chain_id).then(|| {
5956            (
5957                replay.source_block.header().number(),
5958                arbitrum_replay_block_number(&replay.source_block),
5959            )
5960        });
5961        let prepared = prepare_fork_transaction_replay(replay, self.is_monad(), self.is_celo())?;
5962        let fallback_execution_chain_id = self
5963            .get_fork()
5964            .map(|fork| fork.execution_chain_id())
5965            .unwrap_or_else(|| self.chain_id().to());
5966        let execution_chain_id = prepared.execution_chain_id(fallback_execution_chain_id)?;
5967        let PreparedForkTransactionReplay {
5968            transactions,
5969            beneficiary,
5970            timestamp,
5971            parent_beacon_block_root,
5972        } = prepared;
5973        eyre::ensure!(!transactions.is_empty(), "fork transaction replay prefix is empty");
5974        let next_timestamp = timestamp.checked_add(1).ok_or_else(|| {
5975            eyre::eyre!("fork transaction replay timestamp cannot be incremented")
5976        })?;
5977
5978        let _mining_guard = self.mining.lock().await;
5979        let current_base_fee = self.base_fee();
5980        let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price();
5981        let mut evm_env = self.evm_env.read().clone();
5982        if evm_env.block_env.basefee == 0 {
5983            evm_env.cfg_env.disable_base_fee = true;
5984        }
5985
5986        let best_number = self.blockchain.storage.read().best_number;
5987        let block_number = best_number.saturating_add(1);
5988        evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::ONE);
5989        evm_env.block_env.basefee = current_base_fee;
5990        evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price;
5991        evm_env.block_env.timestamp = U256::from(timestamp);
5992
5993        let best_hash = self.blockchain.storage.read().best_hash;
5994        let mut prevrandao_input = [0u8; 40];
5995        prevrandao_input[..32].copy_from_slice(best_hash.as_slice());
5996        prevrandao_input[32..].copy_from_slice(&block_number.to_le_bytes());
5997        evm_env.block_env.prevrandao = Some(
5998            self.cheats.take_next_block_prevrandao().unwrap_or_else(|| keccak256(prevrandao_input)),
5999        );
6000
6001        let mut replay_env = evm_env.clone();
6002        // Execute the prefix with the source block's coinbase so priority fees, COINBASE, and the
6003        // header miner match the source. Later blocks keep the local beneficiary.
6004        replay_env.block_env.beneficiary = beneficiary;
6005        let arbitrum_rpc_block_number = arbitrum_block_numbers.map(|(rpc, evm)| {
6006            replay_env.block_env.number = evm;
6007            rpc
6008        });
6009        replay_env.cfg_env.chain_id = execution_chain_id;
6010        apply_chain_specific_tx_replay_env_changes_for_chain(&mut replay_env, source_chain_id);
6011        let inspector_tx_config = self.inspector_tx_config();
6012
6013        let scheduled_hardfork =
6014            source_hardfork(self.networks.execution_network(), source_chain_id, timestamp);
6015        #[cfg(feature = "monad")]
6016        let mut monad_replay = self
6017            .prepare_monad_fork_replay(
6018                source_chain_id,
6019                execution_chain_id,
6020                timestamp,
6021                best_hash,
6022                &transactions,
6023            )
6024            .await?;
6025        #[cfg(feature = "monad")]
6026        let hardfork = monad_replay
6027            .as_ref()
6028            .map(monad::ForkReplay::hardfork)
6029            .unwrap_or_else(|| scheduled_hardfork.unwrap_or_else(|| self.hardfork()));
6030        #[cfg(not(feature = "monad"))]
6031        let hardfork = scheduled_hardfork.unwrap_or_else(|| self.hardfork());
6032        if !self.is_optimism() && !self.is_tempo() {
6033            replay_env.cfg_env.spec = SpecId::from(hardfork);
6034        }
6035        let replay_spec = SpecId::from(hardfork);
6036        // Cancun-family execution requires blob excess gas even for non-blob transactions. Some
6037        // OP Stack source headers omit it, so use the neutral value for historical replay.
6038        if !self.is_tempo()
6039            && replay_spec >= SpecId::CANCUN
6040            && replay_env.block_env.blob_excess_gas_and_price.is_none()
6041        {
6042            replay_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(
6043                0,
6044                get_blob_base_fee_update_fraction_by_spec_id(replay_spec),
6045            ));
6046        }
6047
6048        #[cfg(feature = "monad")]
6049        let monad_context = monad_replay.as_mut().and_then(monad::ForkReplay::take_context);
6050        #[cfg(not(feature = "monad"))]
6051        let monad_context = None;
6052
6053        let (block_info, state_changes, block_hash) = {
6054            let db = self.db.read().await;
6055            let mut overlay = AnvilCacheDB::<StateRef<'_>>::new(&**db, *replay_env.spec_id());
6056            let ExecutedHistoricalReplay {
6057                block_result,
6058                transactions,
6059                transaction_infos,
6060                state_changes,
6061            } = self.execute_with_replay_block_executor(
6062                &mut overlay,
6063                &replay_env,
6064                best_hash,
6065                arbitrum_rpc_block_number,
6066                hardfork,
6067                parent_beacon_block_root,
6068                &transactions,
6069                &inspector_tx_config,
6070                monad_context,
6071            )?;
6072            let state_root = overlay.maybe_state_root().unwrap_or_default();
6073            let block_info = self.build_block_info(
6074                &replay_env,
6075                best_hash,
6076                block_number,
6077                state_root,
6078                block_result,
6079                transactions,
6080                transaction_infos,
6081                parent_beacon_block_root,
6082                None,
6083            );
6084            let block_hash = block_info.block.header.hash_slow();
6085            (block_info, state_changes, block_hash)
6086        };
6087
6088        if self.prune_state_history_config.is_state_history_supported() {
6089            let state = self.db.read().await.current_state();
6090            self.states.write().insert(best_hash, state);
6091        }
6092
6093        let mut db = self.db.write().await;
6094        for state in state_changes {
6095            db.commit(state);
6096        }
6097        db.insert_block_hash(U256::from(block_number), block_hash);
6098
6099        let BlockInfo { block, transactions, receipts } = block_info;
6100        let header = block.header.clone();
6101        {
6102            let mut storage = self.blockchain.storage.write();
6103            storage.best_number = block_number;
6104            storage.best_hash = block_hash;
6105            if let Some(tempo) = &mut storage.tempo {
6106                tempo.reanchor(block_number);
6107            }
6108            if !self.is_eip3675() {
6109                storage.total_difficulty =
6110                    storage.total_difficulty.saturating_add(header.difficulty);
6111            }
6112            storage.blocks.insert(block_hash, block);
6113            storage.hashes.insert(block_number, block_hash);
6114            #[cfg(feature = "monad")]
6115            if let Some(replay) = &mut monad_replay {
6116                replay.store_metadata(&mut storage, block_hash);
6117            }
6118            for (info, receipt) in transactions.into_iter().zip(receipts) {
6119                let mined_tx = MinedTransaction { info, receipt, block_hash, block_number };
6120                storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx);
6121            }
6122
6123            if let Some(transaction_block_keeper) = self.transaction_block_keeper
6124                && storage.blocks.len() > transaction_block_keeper
6125            {
6126                let to_clear = block_number
6127                    .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX));
6128                storage.remove_block_transactions_by_number(to_clear)
6129            }
6130        }
6131
6132        #[cfg(feature = "monad")]
6133        if let Some(replay) = &monad_replay {
6134            self.finalize_monad_fork_replay(replay, &mut evm_env);
6135        }
6136
6137        evm_env.block_env.difficulty = U256::ZERO;
6138        *self.evm_env.write() = evm_env;
6139        self.time.reset(timestamp);
6140        self.time.set_next_block_timestamp(next_timestamp)?;
6141
6142        #[cfg(feature = "optimism")]
6143        if self.is_optimism() {
6144            self.fees.set_optimism_base_fee_rules(header.extra_data());
6145        }
6146        let next_block_base_fee = self.fees.get_next_block_base_fee_from_header(&header);
6147        let blob_params = self.fees.blob_params();
6148        let next_block_excess_blob_gas = self.networks.next_block_blob_excess_gas(
6149            blob_params,
6150            header.excess_blob_gas.unwrap_or_default(),
6151            header.blob_gas_used.unwrap_or_default(),
6152            header.base_fee_per_gas.unwrap_or_default(),
6153        );
6154        self.fees.set_base_fee(next_block_base_fee);
6155        self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
6156            next_block_excess_blob_gas,
6157            blob_params.update_fraction as u64,
6158        ));
6159        drop(db);
6160        self.notify_on_new_block(header.into_inner(), block_hash);
6161
6162        Ok(())
6163    }
6164
6165    #[allow(clippy::too_many_arguments)]
6166    fn execute_with_replay_block_executor(
6167        &self,
6168        db: &mut AnvilCacheDB<StateRef<'_>>,
6169        evm_env: &EvmEnv,
6170        parent_hash: B256,
6171        arbitrum_rpc_block_number: Option<u64>,
6172        hardfork: FoundryHardfork,
6173        parent_beacon_block_root: Option<B256>,
6174        transactions: &[HistoricalReplayTransaction],
6175        inspector_tx_config: &InspectorTxConfig,
6176        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
6177            MonadReplayContext,
6178        >,
6179    ) -> Result<ExecutedHistoricalReplay> {
6180        #[cfg(feature = "monad")]
6181        if self.is_monad() {
6182            return self.execute_with_monad_replay_block_executor(
6183                db,
6184                evm_env,
6185                parent_hash,
6186                hardfork.into(),
6187                transactions,
6188                inspector_tx_config,
6189                monad_context
6190                    .ok_or_else(|| eyre::eyre!("Monad replay ancestor context is unavailable"))?,
6191            );
6192        }
6193
6194        let inspector = self.build_mining_inspector();
6195        let spec_id = *evm_env.spec_id();
6196        let transitions = self.ethereum_block_transitions(
6197            hardfork,
6198            parent_beacon_block_root,
6199            BlockExecutionKind::TransactionPrefix,
6200        );
6201
6202        macro_rules! execute {
6203            ($executor:expr) => {{
6204                let mut executor = $executor;
6205                executor
6206                    .apply_pre_execution_changes()
6207                    .wrap_err("failed to apply replay block-start transitions")?;
6208                let (stored_transactions, transaction_infos) =
6209                    execute_historical_replay(&mut executor, transactions, inspector_tx_config)?;
6210                let state_changes = executor.take_state_changes();
6211                let (_, block_result) =
6212                    executor.finish().wrap_err("failed to finish replay block execution")?;
6213                Ok(ExecutedHistoricalReplay {
6214                    block_result,
6215                    transactions: stored_transactions,
6216                    transaction_infos,
6217                    state_changes,
6218                })
6219            }};
6220        }
6221
6222        #[cfg(feature = "base")]
6223        if self.is_base() {
6224            let upgrade =
6225                self.base_upgrade_at_timestamp(evm_env.block_env.timestamp.saturating_to());
6226            let cfg =
6227                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(BaseSpecId::new(upgrade));
6228            let activation_admin = self.base_activation_admin().or_else(|| {
6229                ChainConfig::activation_admin_address_for_upgrade_by_chain_id(cfg.chain_id, upgrade)
6230            });
6231            let base_env = EvmEnv::new(cfg, evm_env.block_env.clone());
6232            let mut evm = BaseEvmFactory::new(activation_admin)
6233                .create_evm_with_inspector(db, base_env, inspector);
6234            evm.ctx_mut().cfg.tx_chain_id_check = true;
6235            self.inject_precompiles(evm.precompiles_mut(), evm_env);
6236            if let Some(block_number) = arbitrum_rpc_block_number {
6237                self.inject_arbitrum_precompile_at_block(evm.precompiles_mut(), block_number);
6238            }
6239            let mut executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
6240                .with_state_changes();
6241            executor.set_base_upgrade(upgrade);
6242            return execute!(executor);
6243        }
6244
6245        #[cfg(feature = "optimism")]
6246        if self.is_optimism() {
6247            let op_env = EvmEnv::new(
6248                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(hardfork.into()),
6249                evm_env.block_env.clone(),
6250            );
6251            let mut evm =
6252                OpEvmFactory::<OpTx>::default().create_evm_with_inspector(db, op_env, inspector);
6253            self.inject_precompiles(evm.precompiles_mut(), evm_env);
6254            if let Some(block_number) = arbitrum_rpc_block_number {
6255                self.inject_arbitrum_precompile_at_block(evm.precompiles_mut(), block_number);
6256            }
6257            // Historical replay has no local blob budget. OP still configures its Jovian DA budget.
6258            let mut executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
6259                .with_state_changes();
6260            executor.set_optimism_hardfork(hardfork);
6261            return execute!(executor);
6262        }
6263
6264        if self.is_tempo() {
6265            let tempo_env = Self::build_tempo_evm_env(evm_env, self.tempo_hardfork());
6266            let mut evm =
6267                TempoEvmFactory::default().create_evm_with_inspector(db, tempo_env, inspector);
6268            self.inject_precompiles(evm.precompiles_mut(), evm_env);
6269            if let Some(block_number) = arbitrum_rpc_block_number {
6270                self.inject_arbitrum_precompile_at_block(evm.precompiles_mut(), block_number);
6271            }
6272            let executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
6273                .with_state_changes();
6274            return execute!(executor);
6275        }
6276
6277        let mut evm =
6278            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
6279        self.inject_precompiles(evm.precompiles_mut(), evm_env);
6280        if let Some(block_number) = arbitrum_rpc_block_number {
6281            self.inject_arbitrum_precompile_at_block(evm.precompiles_mut(), block_number);
6282        }
6283        let executor =
6284            AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions).with_state_changes();
6285        execute!(executor)
6286    }
6287
6288    /// Builds a [`BlockInfo`] from the EVM environment, execution results, and transactions.
6289    #[allow(clippy::too_many_arguments)]
6290    fn build_block_info(
6291        &self,
6292        evm_env: &EvmEnv,
6293        parent_hash: B256,
6294        number: u64,
6295        state_root: B256,
6296        block_result: BlockExecutionResult<FoundryReceiptEnvelope>,
6297        transactions: Vec<MaybeImpersonatedTransaction<FoundryTxEnvelope>>,
6298        transaction_infos: Vec<TransactionInfo>,
6299        parent_beacon_block_root: Option<B256>,
6300        block_access_list: Option<&BlockAccessList>,
6301    ) -> BlockInfo<N> {
6302        let spec_id = *evm_env.spec_id();
6303        let is_shanghai = spec_id >= SpecId::SHANGHAI;
6304        let is_cancun = spec_id >= SpecId::CANCUN;
6305        let is_prague = spec_id >= SpecId::PRAGUE;
6306
6307        let receipts_root = calculate_receipt_root(&block_result.receipts);
6308        let cumulative_blob_gas_used = is_cancun.then_some(block_result.blob_gas_used);
6309        let bloom = block_result.receipts.iter().fold(Bloom::default(), |mut b, r| {
6310            b.accrue_bloom(r.logs_bloom());
6311            b
6312        });
6313
6314        let header = Header {
6315            parent_hash,
6316            ommers_hash: Default::default(),
6317            beneficiary: evm_env.block_env.beneficiary,
6318            state_root,
6319            transactions_root: Default::default(),
6320            receipts_root,
6321            logs_bloom: bloom,
6322            difficulty: evm_env.block_env.difficulty,
6323            number,
6324            gas_limit: evm_env.block_env.gas_limit,
6325            gas_used: block_result.gas_used,
6326            timestamp: evm_env.block_env.timestamp.saturating_to(),
6327            extra_data: self.fees.base_fee_extra_data(),
6328            mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(),
6329            nonce: Default::default(),
6330            base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(evm_env.block_env.basefee),
6331            parent_beacon_block_root: is_cancun
6332                .then(|| parent_beacon_block_root.unwrap_or_default()),
6333            blob_gas_used: cumulative_blob_gas_used,
6334            excess_blob_gas: if is_cancun { evm_env.block_env.blob_excess_gas() } else { None },
6335            withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
6336            requests_hash: is_prague.then(|| block_result.requests.requests_hash()),
6337            block_access_list_hash: block_access_list
6338                .map(|bal| compute_block_access_list_hash(bal.as_slice())),
6339            slot_number: None,
6340        };
6341
6342        let block = create_block(foundry_header(&self.networks, header), transactions);
6343        BlockInfo { block, transactions: transaction_infos, receipts: block_result.receipts }
6344    }
6345
6346    async fn do_mine_block(
6347        &self,
6348        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
6349    ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
6350        let _mining_guard = self.mining.lock().await;
6351        self.do_mine_block_locked(pool_transactions, false).await
6352    }
6353
6354    /// Mines a block while the caller holds the mining lock.
6355    async fn do_mine_block_locked(
6356        &self,
6357        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
6358        pin_timestamp: bool,
6359    ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
6360        trace!(target: "backend", "creating new block with {} transactions", pool_transactions.len());
6361
6362        let (outcome, header, block_hash) = {
6363            let current_base_fee = self.base_fee();
6364            let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price();
6365
6366            let mut evm_env = self.evm_env.read().clone();
6367            #[cfg(feature = "monad")]
6368            let execution_chain_id = evm_env.cfg_env.chain_id;
6369            let hardfork = self.hardfork();
6370
6371            if evm_env.block_env.basefee == 0 {
6372                // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee`
6373                // 0 is only possible if it's manually set
6374                evm_env.cfg_env.disable_base_fee = true;
6375            }
6376
6377            let block_number = self.blockchain.storage.read().best_number.saturating_add(1);
6378
6379            // Advance the EVM number independently of the RPC header number. On Arbitrum
6380            // forks this preserves the L1 number read by NUMBER while the header tracks L2.
6381            evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::ONE);
6382
6383            evm_env.block_env.basefee = current_base_fee;
6384            evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price;
6385
6386            let best_hash = self.blockchain.storage.read().best_hash;
6387
6388            let mut input = [0u8; 40];
6389            input[..32].copy_from_slice(best_hash.as_slice());
6390            input[32..].copy_from_slice(&block_number.to_le_bytes());
6391            // Use the values set via `anvil_setNextBlockPrevRandao` and
6392            // `anvil_setNextBlockParentBeaconBlockRoot` for this block if provided, otherwise
6393            // derive `prevrandao` from the parent hash and block number and use the zero root.
6394            // The overrides are consumed once the block is committed so they only apply to it.
6395            let next_block = self.cheats.next_block_overrides();
6396            evm_env.block_env.prevrandao =
6397                Some(next_block.prevrandao.value.unwrap_or_else(|| keccak256(input)));
6398            let parent_beacon_block_root =
6399                Some(next_block.parent_beacon_block_root.value.unwrap_or_default());
6400
6401            let (
6402                db_guard,
6403                block_info,
6404                included,
6405                stale,
6406                invalid,
6407                not_yet_valid,
6408                block_hash,
6409                parent_state,
6410                next_block_base_fee,
6411                next_block_blob_fees,
6412                block_access_list,
6413            ) = {
6414                let mut db = self.db.write().await;
6415
6416                // finally set the next block timestamp, this is done just before execution, because
6417                // there can be concurrent requests that can delay acquiring the db lock and we want
6418                // to ensure the timestamp is as close as possible to the actual execution.
6419                let pending_timestamp = self.prepare_block_timestamp(
6420                    &**db,
6421                    evm_env.block_env.timestamp.saturating_to(),
6422                    pin_timestamp,
6423                )?;
6424                let block_timestamp = pending_timestamp.timestamp;
6425                evm_env.block_env.timestamp = U256::from(block_timestamp);
6426
6427                // Forced historical transactions bypass pool admission and are replayed while
6428                // mining. Keep this exception local to the disposable mining environment.
6429                let mut mining_evm_env = evm_env.clone();
6430                if pool_transactions.iter().any(|tx| tx.is_replay) {
6431                    apply_chain_specific_tx_replay_env_changes_for_chain(
6432                        &mut mining_evm_env,
6433                        self.protocol_chain_id(),
6434                    );
6435                }
6436
6437                #[cfg(feature = "base")]
6438                let (protocol_transactions, system_transaction_count) = {
6439                    let mut transactions = self.base_system_transactions(
6440                        &**db,
6441                        block_number,
6442                        block_timestamp,
6443                        best_hash,
6444                    )?;
6445                    let system_transaction_count = transactions.len();
6446                    transactions.extend(pool_transactions);
6447                    (transactions, system_transaction_count)
6448                };
6449                #[cfg(not(feature = "base"))]
6450                let protocol_transactions = pool_transactions;
6451
6452                let inspector_tx_config = self.inspector_tx_config();
6453                let gas_config = self.pool_tx_gas_config(&mining_evm_env);
6454
6455                let mut candidate_db =
6456                    AnvilCacheDB::<StateRef<'_>>::new(&**db, *mining_evm_env.spec_id());
6457                if matches!(
6458                    hardfork,
6459                    FoundryHardfork::Ethereum(hardfork) if hardfork >= EthereumHardfork::Amsterdam
6460                ) {
6461                    candidate_db.enable_bal_recording();
6462                }
6463                let (pool_result, block_result) = self.execute_with_block_executor(
6464                    &mut candidate_db,
6465                    &mining_evm_env,
6466                    best_hash,
6467                    hardfork,
6468                    parent_beacon_block_root,
6469                    BlockExecutionKind::Complete,
6470                    &protocol_transactions,
6471                    &gas_config,
6472                    &inspector_tx_config,
6473                    &|pool_tx, account| {
6474                        let validation_env =
6475                            if pool_tx.is_replay { &mining_evm_env } else { &evm_env };
6476                        self.validate_mining_pool_transaction_for(pool_tx, account, validation_env)
6477                    },
6478                )?;
6479                let block_access_list = candidate_db.take_block_access_list();
6480
6481                #[cfg(feature = "base")]
6482                base::validate_system_transactions(
6483                    &protocol_transactions[..system_transaction_count],
6484                    &pool_result,
6485                )?;
6486
6487                #[cfg_attr(not(feature = "base"), allow(unused_mut))]
6488                let mut included = pool_result.included;
6489                // System deposits are generated per block rather than taken from the pool. Report
6490                // only pool transactions so the pool does not treat every block as progress.
6491                #[cfg(feature = "base")]
6492                included.drain(..system_transaction_count);
6493                let stale = pool_result.stale;
6494                let invalid = pool_result.invalid;
6495                let not_yet_valid = pool_result.not_yet_valid;
6496
6497                let CacheDB { cache, db: _ } = candidate_db.0;
6498                let parent_state = self
6499                    .prune_state_history_config
6500                    .is_state_history_supported()
6501                    .then(|| db.current_state());
6502                let mut block_info = self.build_block_info(
6503                    &mining_evm_env,
6504                    best_hash,
6505                    block_number,
6506                    B256::ZERO,
6507                    block_result,
6508                    pool_result.txs,
6509                    pool_result.tx_info,
6510                    parent_beacon_block_root,
6511                    block_access_list.as_ref(),
6512                );
6513
6514                if let FoundryHeader::Tempo(header) = &mut block_info.block.header {
6515                    let storage = self.blockchain.storage.read();
6516                    let tempo = storage.tempo.as_ref().expect("Tempo chain is initialized");
6517                    tempo.prepare(header, TempoHardfork::from(hardfork))?;
6518                }
6519
6520                // The database guard protects the prepared consensus state until publication.
6521                // Keep the expensive execution commit and state root outside the storage lock.
6522                commit_cache(&mut **db, cache)?;
6523                #[cfg(test)]
6524                if let Some(hook) = self.mining_state_root_hook.lock().as_ref() {
6525                    hook();
6526                }
6527                let proof_state = (matches!(block_info.block.header, FoundryHeader::Tempo(_))
6528                    && self.get_fork().is_some())
6529                .then(|| {
6530                    let mut state = CacheDB::<EmptyDBWrapper>::new(Default::default());
6531                    state.cache.accounts = db
6532                        .maybe_as_full_db()
6533                        .expect("a fork database exposes its cached accounts")
6534                        .clone();
6535                    state
6536                });
6537                let state_root = proof_state.as_ref().map_or_else(
6538                    || db.maybe_state_root().unwrap_or_default(),
6539                    |state| {
6540                        let proof_root = Arc::clone(
6541                            &self
6542                                .blockchain
6543                                .storage
6544                                .read()
6545                                .tempo
6546                                .as_ref()
6547                                .expect("Tempo chain is initialized")
6548                                .proof_root,
6549                        );
6550                        let (cache, previous) = &mut *proof_root.lock();
6551                        // Rehash only the paths that changed since the previous seal.
6552                        cache.record_diff(&state.cache.accounts, previous);
6553                        let root = cache.root(&state.cache.accounts);
6554                        previous.clone_from(&state.cache.accounts);
6555                        root
6556                    },
6557                );
6558                match &mut block_info.block.header {
6559                    FoundryHeader::Tempo(header) => header.inner.state_root = state_root,
6560                    FoundryHeader::Ethereum(header) => header.state_root = state_root,
6561                }
6562
6563                // Update the new blockhash in the db itself.
6564                let block_hash = block_info.block.header.hash_slow();
6565                if let Some(proof_state) = proof_state {
6566                    let proof_states =
6567                        self.tempo_proof_states().expect("Tempo chain is initialized");
6568                    let mut proof_states = proof_states.write();
6569                    // Re-mining after a rewind can produce an already cached hash.
6570                    proof_states.remove_block_states(&[block_hash]);
6571                    proof_states.insert(block_hash, StateDb::new(proof_state));
6572                }
6573                db.insert_block_hash(U256::from(block_info.block.header.number()), block_hash);
6574                self.time.commit_next_timestamp(pending_timestamp);
6575                self.cheats.consume_next_block_overrides(&next_block);
6576
6577                let header = &block_info.block.header;
6578                let next_block_base_fee = self.fees.get_next_block_base_fee_from_header(header);
6579                let next_block_excess_blob_gas = self.networks.next_block_blob_excess_gas(
6580                    self.fees.blob_params(),
6581                    header.excess_blob_gas.unwrap_or_default(),
6582                    header.blob_gas_used.unwrap_or_default(),
6583                    header.base_fee_per_gas.unwrap_or_default(),
6584                );
6585                let next_block_blob_fees = BlobExcessGasAndPrice::new(
6586                    next_block_excess_blob_gas,
6587                    self.fees.blob_params().update_fraction as u64,
6588                );
6589
6590                (
6591                    db,
6592                    block_info,
6593                    included,
6594                    stale,
6595                    invalid,
6596                    not_yet_valid,
6597                    block_hash,
6598                    parent_state,
6599                    next_block_base_fee,
6600                    next_block_blob_fees,
6601                    block_access_list,
6602                )
6603            };
6604
6605            // create the new block with the current timestamp
6606            let BlockInfo { block, transactions, receipts } = block_info;
6607
6608            let header = block.header.clone();
6609            #[cfg(feature = "monad")]
6610            let monad_participants = self.is_monad().then(|| {
6611                let tx_envs = included
6612                    .iter()
6613                    .map(|pool_tx| {
6614                        build_tx_env_for_pending::<FoundryTxEnvelope, TxEnv>(
6615                            &pool_tx.pending_transaction,
6616                            self.cheats(),
6617                        )
6618                    })
6619                    .collect::<Vec<_>>();
6620                foundry_evm::core::evm::monad_block_participants(&tx_envs)
6621            });
6622
6623            if let Some(parent_state) = parent_state {
6624                self.states.write().insert(best_hash, parent_state);
6625            }
6626
6627            trace!(
6628                target: "backend",
6629                "Mined block {} with {} tx {:?}",
6630                block_number,
6631                transactions.len(),
6632                transactions.iter().map(|tx| tx.transaction_hash).collect::<Vec<_>>()
6633            );
6634            #[cfg(test)]
6635            let mining_commit_hook = { self.mining_commit_hook.lock().clone() };
6636            #[cfg(test)]
6637            if let Some(hook) = mining_commit_hook {
6638                hook.reached.notify_one();
6639                hook.resume.notified().await;
6640            }
6641            let mut storage = self.blockchain.storage.write();
6642            if let FoundryHeader::Tempo(header) = &header {
6643                storage
6644                    .tempo
6645                    .as_mut()
6646                    .expect("Tempo chain is initialized")
6647                    .finalize(header, TempoHardfork::from(hardfork));
6648            }
6649            // update block metadata
6650            storage.best_number = block_number;
6651            storage.best_hash = block_hash;
6652            // Difficulty is removed and not used after Paris (aka TheMerge). Value is replaced with
6653            // prevrandao. https://github.com/bluealloy/revm/blob/1839b3fce8eaeebb85025576f2519b80615aca1e/crates/interpreter/src/instructions/host_env.rs#L27
6654            if !self.is_eip3675() {
6655                storage.total_difficulty =
6656                    storage.total_difficulty.saturating_add(header.difficulty);
6657            }
6658
6659            if let Some(tempo) = &mut storage.tempo {
6660                tempo.publish(&block);
6661            }
6662            storage.blocks.insert(block_hash, block);
6663            storage.hashes.insert(block_number, block_hash);
6664            if let Some(block_access_list) = block_access_list {
6665                storage.block_access_lists.insert(block_hash, block_access_list);
6666            }
6667            #[cfg(feature = "monad")]
6668            if let Some(participants) = monad_participants {
6669                monad::store_block_metadata(
6670                    &mut storage,
6671                    block_hash,
6672                    participants,
6673                    execution_chain_id,
6674                    hardfork,
6675                );
6676            }
6677
6678            node_info!("");
6679            // insert all transactions
6680            for (info, receipt) in transactions.into_iter().zip(receipts) {
6681                // log some tx info
6682                node_info!("    Transaction: {:?}", info.transaction_hash);
6683                if let Some(contract) = &info.contract_address {
6684                    node_info!("    Contract created: {contract}");
6685                }
6686                node_info!("    Gas used: {}", info.gas_used);
6687                if !info.exit.is_ok() {
6688                    let r = RevertDecoder::new().decode(
6689                        info.out.as_ref().map(|b| &b[..]).unwrap_or_default(),
6690                        Some(info.exit),
6691                    );
6692                    node_info!("    Error: reverted with: {r}");
6693                }
6694                node_info!("");
6695
6696                let mined_tx = MinedTransaction { info, receipt, block_hash, block_number };
6697                storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx);
6698            }
6699
6700            // remove old transactions that exceed the transaction block keeper
6701            if let Some(transaction_block_keeper) = self.transaction_block_keeper
6702                && storage.blocks.len() > transaction_block_keeper
6703            {
6704                let to_clear = block_number
6705                    .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX));
6706                storage.remove_block_transactions_by_number(to_clear)
6707            }
6708
6709            // Live execution readers acquire the database read lock before cloning the block
6710            // environment, next-block fee state, and canonical head. Publish the complete snapshot
6711            // while retaining the database write lock so readers observe either the parent or newly
6712            // mined state. The database is always the outer lock, and no path holds a storage,
6713            // environment, or fee guard while waiting for it, so these acquisitions cannot form a
6714            // lock cycle.
6715            evm_env.block_env.difficulty = U256::ZERO;
6716            *self.evm_env.write() = evm_env;
6717            self.fees.set_next_block_fees(next_block_base_fee, next_block_blob_fees);
6718            drop(storage);
6719            drop(db_guard);
6720
6721            self.time.mark_block_created();
6722
6723            let timestamp = utc_from_secs(header.timestamp);
6724
6725            node_info!("    Block Number: {}", block_number);
6726            node_info!("    Block Hash: {:?}", block_hash);
6727            if timestamp.year() > 9999 {
6728                // rf2822 panics with more than 4 digits
6729                node_info!("    Block Time: {:?}\n", timestamp.to_rfc3339());
6730            } else {
6731                node_info!("    Block Time: {:?}\n", timestamp.to_rfc2822());
6732            }
6733
6734            let outcome =
6735                MinedBlockOutcome { block_number, included, stale, invalid, not_yet_valid };
6736
6737            (outcome, header, block_hash)
6738        };
6739        // notify all listeners
6740        self.notify_on_new_block(header.into_inner(), block_hash);
6741
6742        Ok(outcome)
6743    }
6744
6745    /// Mines replacement blocks after the caller has rewound the chain for a reorg.
6746    ///
6747    /// The caller must hold the mining lock across ancestor selection, rollback, and this method.
6748    pub async fn reorg(
6749        &self,
6750        depth: u64,
6751        tx_pairs: HashMap<u64, Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>>,
6752        _mining_guard: &tokio::sync::MutexGuard<'_, ()>,
6753    ) -> Result<(), BlockchainError> {
6754        // Create the new reorged chain, filling the blocks with transactions if supplied
6755        for i in 0..depth {
6756            let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new);
6757            let outcome = self.do_mine_block_locked(to_be_mined, false).await?;
6758            node_info!(
6759                "    Mined reorg block number {}. With {} valid txs and with invalid {} txs",
6760                outcome.block_number,
6761                outcome.included.len(),
6762                outcome.invalid.len()
6763            );
6764        }
6765
6766        Ok(())
6767    }
6768
6769    /// Creates the pending block
6770    ///
6771    /// This will execute all transaction in the order they come but will not mine the block
6772    pub async fn pending_block(
6773        &self,
6774        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
6775    ) -> Result<BlockInfo<N>, BlockchainError> {
6776        self.with_pending_block(pool_transactions, |_, block| block).await
6777    }
6778
6779    /// Creates the pending block
6780    ///
6781    /// This will execute all transaction in the order they come but will not mine the block
6782    pub async fn with_pending_block<F, T>(
6783        &self,
6784        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
6785        f: F,
6786    ) -> Result<T, BlockchainError>
6787    where
6788        F: FnOnce(Box<dyn MaybeFullDatabase + '_>, BlockInfo<N>) -> T,
6789    {
6790        let db = self.db.read().await;
6791        let evm_env = self.next_evm_env(&**db)?;
6792
6793        let mut cache_db = AnvilCacheDB::<StateRef<'_>>::new(&**db, *evm_env.spec_id());
6794
6795        let parent_hash = self.blockchain.storage.read().best_hash;
6796        let block_number = self.best_number().saturating_add(1);
6797
6798        #[cfg(feature = "base")]
6799        let (pool_transactions, system_transaction_count) = {
6800            let mut transactions = self.base_system_transactions(
6801                &**db,
6802                block_number,
6803                evm_env.block_env.timestamp.saturating_to(),
6804                parent_hash,
6805            )?;
6806            let system_transaction_count = transactions.len();
6807            transactions.extend(pool_transactions);
6808            (transactions, system_transaction_count)
6809        };
6810        // Read the next block's beacon root override without consuming it, so the pending block
6811        // sees the root the next mined block will use.
6812        let parent_beacon_block_root = Some(
6813            self.cheats.next_block_overrides().parent_beacon_block_root.value.unwrap_or_default(),
6814        );
6815
6816        let inspector_tx_config = self.inspector_tx_config();
6817        let gas_config = self.pool_tx_gas_config(&evm_env);
6818
6819        let (pool_result, block_result) = self.execute_with_block_executor(
6820            &mut cache_db,
6821            &evm_env,
6822            parent_hash,
6823            self.hardfork(),
6824            parent_beacon_block_root,
6825            BlockExecutionKind::Complete,
6826            &pool_transactions,
6827            &gas_config,
6828            &inspector_tx_config,
6829            &|pool_tx, account| {
6830                self.validate_pool_transaction_for(&pool_tx.pending_transaction, account, &evm_env)
6831            },
6832        )?;
6833
6834        #[cfg(feature = "base")]
6835        base::validate_system_transactions(
6836            &pool_transactions[..system_transaction_count],
6837            &pool_result,
6838        )?;
6839
6840        // Extract inner CacheDB (which implements MaybeFullDatabase)
6841        let cache_db = cache_db.0;
6842
6843        let state_root = cache_db.maybe_state_root().unwrap_or_default();
6844        let block_info = self.build_block_info(
6845            &evm_env,
6846            parent_hash,
6847            block_number,
6848            state_root,
6849            block_result,
6850            pool_result.txs,
6851            pool_result.tx_info,
6852            parent_beacon_block_root,
6853            None,
6854        );
6855
6856        Ok(f(Box::new(cache_db), block_info))
6857    }
6858
6859    /// Returns the account used to sponsor Tempo fee-payer requests handled by this node.
6860    ///
6861    /// Returns `None` on non-Tempo networks.
6862    pub async fn tempo_fee_payer(&self) -> Option<Address> {
6863        if !self.is_tempo() {
6864            return None;
6865        }
6866        self.node_config.read().await.tempo_fee_payer_address()
6867    }
6868
6869    /// Returns the fee token an account pays with, as stored in the Tempo fee manager.
6870    ///
6871    /// Falls back to PathUSD when the account has no stored preference or the lookup fails.
6872    pub async fn tempo_user_fee_token(&self, account: Address) -> Result<Address, BlockchainError> {
6873        let calldata = IFeeManager::userTokensCall { user: account }.abi_encode();
6874
6875        let request = WithOtherFields::new(TransactionRequest {
6876            from: Some(Address::ZERO),
6877            to: Some(TxKind::Call(TIP_FEE_MANAGER_ADDRESS)),
6878            input: calldata.into(),
6879            ..Default::default()
6880        });
6881
6882        let (exit, out, _, _) =
6883            self.call(request, FeeDetails::zero(), None, Default::default()).await?;
6884
6885        let token = if exit == InstructionResult::Return
6886            && let Some(Output::Call(data)) = out
6887        {
6888            IFeeManager::userTokensCall::abi_decode_returns(&data).unwrap_or(Address::ZERO)
6889        } else {
6890            Address::ZERO
6891        };
6892
6893        if token.is_zero() {
6894            return Ok(foundry_evm::core::tempo::PATH_USD_ADDRESS);
6895        }
6896        Ok(token)
6897    }
6898
6899    /// Executes the [TransactionRequest] without writing to the DB
6900    ///
6901    /// # Errors
6902    ///
6903    /// Returns an error if the `block_number` is greater than the current height
6904    pub async fn call(
6905        &self,
6906        request: WithOtherFields<TransactionRequest>,
6907        fee_details: FeeDetails,
6908        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6909        overrides: EvmOverrides,
6910    ) -> Result<(InstructionResult, Option<Output>, u128, StateChangeset), BlockchainError> {
6911        self.with_database_at_and_context(block_request, |state, mut block, monad_context| {
6912            let block_number = block.number;
6913            let (exit, out, gas, state) = {
6914                let mut cache_db = CacheDB::new(&*state);
6915                if let Some(state_overrides) = overrides.state {
6916                    apply_state_overrides(state_overrides.into_iter().collect(), &mut cache_db)?;
6917                }
6918                if let Some(block_overrides) = overrides.block {
6919                    cache_db.apply_block_overrides(*block_overrides, &mut block);
6920                }
6921                self.call_with_state_and_context(
6922                    &cache_db,
6923                    request,
6924                    fee_details,
6925                    block,
6926                    monad_context,
6927                )
6928            }?;
6929            trace!(target: "backend", "call return {:?} out: {:?} gas {} on block {}", exit, out, gas, block_number);
6930            Ok((exit, out, gas, state))
6931        })
6932        .await
6933    }
6934
6935    pub async fn call_with_tracing(
6936        &self,
6937        request: WithOtherFields<TransactionRequest>,
6938        fee_details: FeeDetails,
6939        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6940        opts: GethDebugTracingCallOptions,
6941    ) -> Result<GethTrace, BlockchainError> {
6942        let GethDebugTracingCallOptions {
6943            tracing_options,
6944            block_overrides,
6945            state_overrides,
6946            tx_index,
6947        } = opts;
6948
6949        if let Some(tx_index) = tx_index {
6950            return self
6951                .call_with_tracing_at_tx_index(
6952                    request,
6953                    fee_details,
6954                    block_request,
6955                    tx_index,
6956                    tracing_options,
6957                    state_overrides,
6958                    block_overrides,
6959                )
6960                .await;
6961        }
6962
6963        self.with_database_at_and_context(block_request, |state, block, monad_context| {
6964            let cache_db = CacheDB::new(&*state);
6965            self.trace_call_with_state(
6966                request,
6967                fee_details,
6968                block,
6969                cache_db,
6970                tracing_options,
6971                state_overrides,
6972                block_overrides,
6973                monad_context,
6974                None,
6975            )
6976        })
6977        .await
6978    }
6979
6980    #[allow(clippy::too_many_arguments)]
6981    async fn call_with_tracing_at_tx_index(
6982        &self,
6983        request: WithOtherFields<TransactionRequest>,
6984        fee_details: FeeDetails,
6985        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6986        tx_index: u64,
6987        tracing_options: GethDebugTracingOptions,
6988        state_overrides: Option<StateOverride>,
6989        block_overrides: Option<BlockOverrides>,
6990    ) -> Result<GethTrace, BlockchainError> {
6991        let tx_index = usize::try_from(tx_index).map_err(|_| {
6992            BlockchainError::RpcError(RpcError::invalid_params(format!(
6993                "tx_index {tx_index} does not fit in usize"
6994            )))
6995        })?;
6996        let block_number = match block_request {
6997            Some(BlockRequest::Pending(_)) => {
6998                return Err(BlockchainError::RpcError(RpcError::invalid_params(
6999                    "tx_index is not supported for pending blocks".to_string(),
7000                )));
7001            }
7002            Some(BlockRequest::Number(number)) => number,
7003            None => self.best_number(),
7004        };
7005        let block_id = BlockId::number(block_number);
7006
7007        if let Some(block) = self.get_block(block_id) {
7008            return self.mined_trace_call_at_tx_index(
7009                request,
7010                fee_details,
7011                &block,
7012                tx_index,
7013                tracing_options,
7014                state_overrides,
7015                block_overrides,
7016            );
7017        }
7018
7019        if let Some(fork) = self.get_fork()
7020            && fork.predates_fork_inclusive(block_number)
7021        {
7022            let opts = GethDebugTracingCallOptions {
7023                tracing_options,
7024                state_overrides,
7025                block_overrides,
7026                tx_index: Some(tx_index as u64),
7027            };
7028            return Ok(fork.debug_trace_call(request, block_id, opts).await?);
7029        }
7030
7031        Err(BlockchainError::BlockNotFound)
7032    }
7033
7034    #[allow(clippy::too_many_arguments)]
7035    fn mined_trace_call_at_tx_index(
7036        &self,
7037        request: WithOtherFields<TransactionRequest>,
7038        fee_details: FeeDetails,
7039        block: &Block,
7040        tx_index: usize,
7041        tracing_options: GethDebugTracingOptions,
7042        state_overrides: Option<StateOverride>,
7043        block_overrides: Option<BlockOverrides>,
7044    ) -> Result<GethTrace, BlockchainError> {
7045        let transaction_count = block.body.transactions.len();
7046        if tx_index >= transaction_count {
7047            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
7048                "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
7049            ))));
7050        }
7051
7052        let trace = |parent_state: &StateDb| -> Result<GethTrace, BlockchainError> {
7053            let (mut cache_db, evm_env, hardfork) =
7054                self.prepare_block_replay(block, parent_state)?;
7055            self.replay_mined_transaction_prefix(
7056                &mut cache_db,
7057                &evm_env,
7058                hardfork,
7059                block,
7060                tx_index,
7061            )?;
7062            self.trace_call_with_state(
7063                request,
7064                fee_details,
7065                evm_env.block_env.clone(),
7066                cache_db,
7067                tracing_options,
7068                state_overrides,
7069                block_overrides,
7070                self.active_monad_context_before_mined_transaction(block, tx_index)?,
7071                Some((evm_env, hardfork)),
7072            )
7073        };
7074
7075        let read_guard = self.states.upgradable_read();
7076        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
7077            trace(state)
7078        } else {
7079            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
7080            let state = write_guard
7081                .get_on_disk_state(&block.header.parent_hash)
7082                .ok_or(BlockchainError::HistoricalStateUnavailable(block.header.number()))?;
7083            trace(state)
7084        }
7085    }
7086
7087    #[allow(clippy::too_many_arguments)]
7088    fn trace_call_with_state(
7089        &self,
7090        request: WithOtherFields<TransactionRequest>,
7091        fee_details: FeeDetails,
7092        mut block: BlockEnv,
7093        mut cache_db: CacheDB<StateRef<'_>>,
7094        tracing_options: GethDebugTracingOptions,
7095        state_overrides: Option<StateOverride>,
7096        block_overrides: Option<BlockOverrides>,
7097        mut monad_context: Option<MonadReplayContext>,
7098        historical_execution: Option<(EvmEnv, FoundryHardfork)>,
7099    ) -> Result<GethTrace, BlockchainError> {
7100        let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options;
7101        let block_number = block.number;
7102        let base_evm_env = historical_execution.as_ref().map(|(evm_env, _)| evm_env);
7103        let hardfork = historical_execution
7104            .as_ref()
7105            .map(|(_, hardfork)| *hardfork)
7106            .unwrap_or_else(|| self.hardfork());
7107
7108        if let Some(state_overrides) = state_overrides {
7109            apply_state_overrides(state_overrides, &mut cache_db)?;
7110        }
7111        if let Some(block_overrides) = block_overrides {
7112            cache_db.apply_block_overrides(block_overrides, &mut block);
7113        }
7114
7115        if let Some(tracer) = tracer {
7116            return match tracer {
7117                GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
7118                    GethDebugBuiltInTracerType::CallTracer => {
7119                        let call_config = call_config_from_tracer_config(tracer_config)
7120                            .map_err(|e| RpcError::invalid_params(e.to_string()))?;
7121
7122                        let mut inspector = self.build_inspector().with_tracing_config(
7123                            TracingInspectorConfig::from_geth_call_config(&call_config),
7124                        );
7125
7126                        let PreparedCall { evm_env, tx_env, .. } = self
7127                            .prepare_call_env_from_base(
7128                                &cache_db,
7129                                request,
7130                                fee_details,
7131                                block,
7132                                base_evm_env,
7133                            )?;
7134                        let ResultAndState { result, state: _ } = self
7135                            .transact_call_with_inspector_ref_at_hardfork(
7136                                &cache_db,
7137                                &evm_env,
7138                                &mut inspector,
7139                                tx_env,
7140                                monad_context.as_mut().map(next_monad_context),
7141                                hardfork,
7142                            )?;
7143
7144                        inspector.print_logs();
7145                        if self.print_traces {
7146                            inspector.print_traces(self.call_trace_decoder());
7147                        }
7148
7149                        let tracing_inspector = inspector.tracer.expect("tracer disappeared");
7150
7151                        Ok(tracing_inspector
7152                            .into_geth_builder()
7153                            .geth_call_traces(call_config, result.tx_gas_used())
7154                            .into())
7155                    }
7156                    GethDebugBuiltInTracerType::PreStateTracer => {
7157                        let pre_state_config = tracer_config
7158                            .into_pre_state_config()
7159                            .map_err(|e| RpcError::invalid_params(e.to_string()))?;
7160
7161                        let mut inspector = TracingInspector::new(
7162                            TracingInspectorConfig::from_geth_prestate_config(&pre_state_config),
7163                        );
7164
7165                        let PreparedCall { evm_env, tx_env, .. } = self
7166                            .prepare_call_env_from_base(
7167                                &cache_db,
7168                                request,
7169                                fee_details,
7170                                block,
7171                                base_evm_env,
7172                            )?;
7173                        let result = self.transact_call_with_inspector_ref_at_hardfork(
7174                            &cache_db,
7175                            &evm_env,
7176                            &mut inspector,
7177                            tx_env,
7178                            monad_context.as_mut().map(next_monad_context),
7179                            hardfork,
7180                        )?;
7181
7182                        Ok(inspector
7183                            .into_geth_builder()
7184                            .geth_prestate_traces(&result, &pre_state_config, cache_db)?
7185                            .into())
7186                    }
7187                    GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()),
7188                    GethDebugBuiltInTracerType::FourByteTracer
7189                    | GethDebugBuiltInTracerType::MuxTracer
7190                    | GethDebugBuiltInTracerType::FlatCallTracer
7191                    | GethDebugBuiltInTracerType::Erc7562Tracer
7192                    | GethDebugBuiltInTracerType::StateGasTracer => {
7193                        Err(RpcError::invalid_params("unsupported tracer type").into())
7194                    }
7195                },
7196                #[cfg(not(feature = "js-tracer"))]
7197                GethDebugTracerType::JsTracer(_) => {
7198                    Err(RpcError::invalid_params("unsupported tracer type").into())
7199                }
7200                #[cfg(feature = "js-tracer")]
7201                GethDebugTracerType::JsTracer(code) => {
7202                    let config = tracer_config.into_json();
7203                    let mut inspector =
7204                        revm_inspectors::tracing::js::JsInspector::new(code, config)
7205                            .map_err(|err| BlockchainError::Message(err.to_string()))?;
7206
7207                    let PreparedCall { evm_env, tx_env, .. } = self.prepare_call_env_from_base(
7208                        &cache_db,
7209                        request,
7210                        fee_details,
7211                        block.clone(),
7212                        base_evm_env,
7213                    )?;
7214                    let result = self.transact_call_with_inspector_ref_at_hardfork(
7215                        &cache_db,
7216                        &evm_env,
7217                        &mut inspector,
7218                        tx_env.clone(),
7219                        monad_context.as_mut().map(next_monad_context),
7220                        hardfork,
7221                    )?;
7222                    let res = inspector
7223                        .json_result(result, tx_env.base(), &block, &cache_db)
7224                        .map_err(|err| BlockchainError::Message(err.to_string()))?;
7225
7226                    Ok(GethTrace::JS(res))
7227                }
7228            };
7229        }
7230
7231        // defaults to StructLog tracer used since no tracer is specified
7232        let mut inspector = self
7233            .build_inspector()
7234            .with_tracing_config(TracingInspectorConfig::from_geth_config(&config));
7235
7236        let PreparedCall { evm_env, tx_env, .. } =
7237            self.prepare_call_env_from_base(&cache_db, request, fee_details, block, base_evm_env)?;
7238        let ResultAndState { result, state: _ } = self
7239            .transact_call_with_inspector_ref_at_hardfork(
7240                &cache_db,
7241                &evm_env,
7242                &mut inspector,
7243                tx_env,
7244                monad_context.as_mut().map(next_monad_context),
7245                hardfork,
7246            )?;
7247
7248        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
7249
7250        let tracing_inspector = inspector.tracer.expect("tracer disappeared");
7251        let return_value = out.as_ref().map(|o| o.data()).cloned().unwrap_or_default();
7252
7253        trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call");
7254
7255        let res = tracing_inspector
7256            .into_geth_builder()
7257            .geth_traces(gas_used, return_value, config)
7258            .into();
7259
7260        Ok(res)
7261    }
7262
7263    /// Helper function to execute a closure with the database at a specific block
7264    pub async fn with_database_at<F, T>(
7265        &self,
7266        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7267        f: F,
7268    ) -> Result<T, BlockchainError>
7269    where
7270        F: FnOnce(Box<dyn MaybeFullDatabase + '_>, BlockEnv) -> T,
7271    {
7272        let block_number = match block_request {
7273            Some(BlockRequest::Pending(pool_transactions)) => {
7274                let result = self
7275                    .with_pending_block(pool_transactions, |state, block| {
7276                        let block = block.block;
7277                        f(state, self.block_env_from_header(&block.header))
7278                    })
7279                    .await?;
7280                return Ok(result);
7281            }
7282            Some(BlockRequest::Number(bn)) => Some(BlockNumber::Number(bn)),
7283            None => None,
7284        };
7285        let block_number = self.convert_block_number(block_number);
7286        let current_number = self.best_number();
7287
7288        // Reject requests for future blocks that don't exist yet
7289        if block_number > current_number {
7290            return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
7291        }
7292
7293        // The head can advance while waiting for the database lock. Serve the live state only if
7294        // the requested block is still the head or no state history is kept. Otherwise hold the
7295        // lock so the requested block's state cannot be pruned before it is read below.
7296        let _head_guard = if block_number == current_number {
7297            let db = self.db.read().await;
7298            if self.best_number() == block_number
7299                || !self.prune_state_history_config.is_state_history_supported()
7300            {
7301                let block = self.evm_env.read().block_env.clone();
7302                return Ok(f(Box::new(&**db), block));
7303            }
7304            Some(db)
7305        } else {
7306            None
7307        };
7308
7309        if let Some((block_hash, block)) = self
7310            .block_by_number(BlockNumber::Number(block_number))
7311            .await?
7312            .map(|block| (block.header.hash, block))
7313        {
7314            let read_guard = self.states.upgradable_read();
7315            if let Some(state_db) = read_guard.get_state(&block_hash) {
7316                return Ok(f(Box::new(state_db), self.block_env_from_header(&block.header)));
7317            }
7318
7319            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
7320            if let Some(state) = write_guard.get_on_disk_state(&block_hash) {
7321                return Ok(f(Box::new(state), self.block_env_from_header(&block.header)));
7322            }
7323        }
7324
7325        warn!(target: "backend", "Not historic state found for block={}", block_number);
7326        Err(BlockchainError::BlockOutOfRange(self.best_number(), block_number))
7327    }
7328
7329    /// Executes a closure with both state and network context at a specific block.
7330    pub(crate) async fn with_database_at_and_context<F, T>(
7331        &self,
7332        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7333        f: F,
7334    ) -> Result<T, BlockchainError>
7335    where
7336        F: FnOnce(
7337            Box<dyn MaybeFullDatabase + '_>,
7338            BlockEnv,
7339            Option<MonadReplayContext>,
7340        ) -> Result<T, BlockchainError>,
7341    {
7342        let block_number = match block_request {
7343            Some(BlockRequest::Pending(pool_transactions)) => {
7344                return self
7345                    .with_pending_block(pool_transactions, |state, block_info| {
7346                        let context = self.active_monad_context_before_mined_transaction(
7347                            &block_info.block,
7348                            block_info.block.body.transactions.len(),
7349                        )?;
7350                        let block_env = self.block_env_from_header(&block_info.block.header);
7351                        f(state, block_env, context)
7352                    })
7353                    .await?;
7354            }
7355            Some(BlockRequest::Number(number)) => Some(BlockNumber::Number(number)),
7356            None => None,
7357        };
7358        let block_number = self.convert_block_number(block_number);
7359        let current_number = self.best_number();
7360
7361        if block_number > current_number {
7362            return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
7363        }
7364
7365        #[cfg(feature = "monad")]
7366        let context = if self.is_monad() {
7367            Some(self.monad_context_for_child_of_block_number(block_number).await?)
7368        } else {
7369            None
7370        };
7371        #[cfg(not(feature = "monad"))]
7372        let context = None;
7373
7374        // See `with_database_at`: re-check the head under the read guard.
7375        let _head_guard = if block_number == current_number {
7376            let db = self.db.read().await;
7377            if self.best_number() == block_number
7378                || !self.prune_state_history_config.is_state_history_supported()
7379            {
7380                let block = self.evm_env.read().block_env.clone();
7381                return f(Box::new(&**db), block, context);
7382            }
7383            Some(db)
7384        } else {
7385            None
7386        };
7387
7388        if let Some((block_hash, block)) = self
7389            .block_by_number(BlockNumber::Number(block_number))
7390            .await?
7391            .map(|block| (block.header.hash, block))
7392        {
7393            let read_guard = self.states.upgradable_read();
7394            if let Some(state_db) = read_guard.get_state(&block_hash) {
7395                return f(Box::new(state_db), self.block_env_from_header(&block.header), context);
7396            }
7397
7398            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
7399            if let Some(state) = write_guard.get_on_disk_state(&block_hash) {
7400                return f(Box::new(state), self.block_env_from_header(&block.header), context);
7401            }
7402        }
7403
7404        warn!(target: "backend", "Not historic state found for block={}", block_number);
7405        Err(BlockchainError::BlockOutOfRange(self.best_number(), block_number))
7406    }
7407
7408    pub async fn storage_at(
7409        &self,
7410        address: Address,
7411        index: U256,
7412        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7413    ) -> Result<B256, BlockchainError> {
7414        self.with_database_at(block_request, |db, _| {
7415            trace!(target: "backend", "get storage for {:?} at {:?}", address, index);
7416            let val = db.storage_ref(address, index)?;
7417            Ok(val.into())
7418        })
7419        .await?
7420    }
7421
7422    pub async fn tempo_nonce(
7423        &self,
7424        caller: Address,
7425        nonce_key: U256,
7426        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7427    ) -> Result<u64, BlockchainError> {
7428        self.with_database_at(block_request, |state, _| tempo_nonce(&state, caller, nonce_key))
7429            .await?
7430    }
7431
7432    /// Returns storage values for multiple accounts and slots in a single call.
7433    pub async fn storage_values(
7434        &self,
7435        requests: HashMap<Address, Vec<B256>>,
7436        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7437    ) -> Result<HashMap<Address, Vec<B256>>, BlockchainError> {
7438        self.with_database_at(block_request, |db, _| {
7439            trace!(target: "backend", "get storage values for {} addresses", requests.len());
7440            let mut result: HashMap<Address, Vec<B256>> = HashMap::default();
7441            for (address, slots) in &requests {
7442                let mut values = Vec::with_capacity(slots.len());
7443                for slot in slots {
7444                    let val = db.storage_ref(*address, (*slot).into())?;
7445                    values.push(val.into());
7446                }
7447                result.insert(*address, values);
7448            }
7449            Ok(result)
7450        })
7451        .await?
7452    }
7453
7454    /// Returns the code of the address
7455    ///
7456    /// If the code is not present and fork mode is enabled then this will try to fetch it from the
7457    /// forked client
7458    pub async fn get_code(
7459        &self,
7460        address: Address,
7461        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7462    ) -> Result<Bytes, BlockchainError> {
7463        self.with_database_at(block_request, |db, _| self.get_code_with_state(&db, address)).await?
7464    }
7465
7466    /// Returns the balance of the address
7467    ///
7468    /// If the requested number predates the fork then this will fetch it from the endpoint
7469    pub async fn get_balance(
7470        &self,
7471        address: Address,
7472        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7473    ) -> Result<U256, BlockchainError> {
7474        self.with_database_at(block_request, |db, _| self.get_balance_with_state(db, address))
7475            .await?
7476    }
7477
7478    pub async fn get_account_at_block(
7479        &self,
7480        address: Address,
7481        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7482    ) -> Result<TrieAccount, BlockchainError> {
7483        self.with_database_at(block_request, |block_db, _| {
7484            let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
7485            let account = db.get(&address).cloned().unwrap_or_default();
7486            let storage_root = storage_root(&account.storage);
7487            let code_hash = account.info.code_hash();
7488            let balance = account.info.balance;
7489            let nonce = account.info.nonce;
7490            Ok(TrieAccount { balance, nonce, code_hash, storage_root })
7491        })
7492        .await?
7493    }
7494
7495    pub async fn get_account_info_at_block(
7496        &self,
7497        address: Address,
7498        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7499    ) -> Result<RpcAccountInfo, BlockchainError> {
7500        self.with_database_at(block_request, |db, _| {
7501            let account = db.basic_ref(address)?.unwrap_or_default();
7502            let code = self.get_code_with_state(&db, address)?;
7503            Ok(RpcAccountInfo { balance: account.balance, nonce: account.nonce, code })
7504        })
7505        .await?
7506    }
7507
7508    /// Returns the nonce of the address
7509    ///
7510    /// If the requested number predates the fork then this will fetch it from the endpoint
7511    pub async fn get_nonce(
7512        &self,
7513        address: Address,
7514        block_request: BlockRequest<FoundryTxEnvelope>,
7515    ) -> Result<u64, BlockchainError> {
7516        if let BlockRequest::Pending(pool_transactions) = &block_request
7517            && let Some(value) = get_pool_transactions_nonce(pool_transactions, address)
7518        {
7519            return Ok(value);
7520        }
7521        let final_block_request = match block_request {
7522            BlockRequest::Pending(_) => BlockRequest::Number(self.best_number()),
7523            BlockRequest::Number(bn) => BlockRequest::Number(bn),
7524        };
7525
7526        self.with_database_at(Some(final_block_request), |db, _| {
7527            trace!(target: "backend", "get nonce for {:?}", address);
7528            Ok(db.basic_ref(address)?.unwrap_or_default().nonce)
7529        })
7530        .await?
7531    }
7532
7533    /// Traces the transaction with the js tracer
7534    #[cfg(feature = "js-tracer")]
7535    pub async fn trace_tx_with_js_tracer(
7536        &self,
7537        hash: B256,
7538        code: String,
7539        opts: GethDebugTracingOptions,
7540    ) -> Result<GethTrace, BlockchainError> {
7541        let GethDebugTracingOptions { tracer_config, .. } = opts;
7542        let config = tracer_config.into_json();
7543        let inspector = revm_inspectors::tracing::js::JsInspector::new(code, config)
7544            .map_err(|err| BlockchainError::Message(err.to_string()))?;
7545        let trace = self.replay_tx_with_inspector(
7546            hash,
7547            inspector,
7548            |result, cache_db, mut inspector, tx_env, evm_env| {
7549                inspector
7550                    .json_result(
7551                        result,
7552                        &alloy_evm::IntoTxEnv::into_tx_env(tx_env),
7553                        evm_env.block_env(),
7554                        &cache_db,
7555                    )
7556                    .map_err(|e| BlockchainError::Message(e.to_string()))
7557            },
7558        )??;
7559        Ok(GethTrace::JS(trace))
7560    }
7561
7562    /// Prove an account's existence or nonexistence in the state trie.
7563    ///
7564    /// Returns a merkle proof of the account's trie node, `account_key` == keccak(address)
7565    pub async fn prove_account_at(
7566        &self,
7567        address: Address,
7568        keys: Vec<B256>,
7569        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7570    ) -> Result<AccountProof, BlockchainError> {
7571        let block_number = block_request.as_ref().map(|r| r.block_number());
7572        if !matches!(block_request, Some(BlockRequest::Pending(_))) {
7573            let height = self.convert_block_number(block_number);
7574            if let Some(proof) = self.prove_tempo_account_at(address, &keys, height).await? {
7575                return Ok(proof);
7576            }
7577        }
7578
7579        self.with_database_at(block_request, |block_db, _| {
7580            trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number);
7581            let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
7582            Ok(prove_account(db, address, &keys))
7583        })
7584        .await?
7585    }
7586}
7587
7588impl<N: Network> Backend<N>
7589where
7590    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
7591{
7592    /// Returns opcode gas usage for the given transaction.
7593    pub async fn trace_transaction_opcode_gas(
7594        &self,
7595        hash: B256,
7596    ) -> Result<Option<TransactionOpcodeGas>, BlockchainError> {
7597        match self.replay_tx_with_inspector(
7598            hash,
7599            OpcodeGasInspector::new(),
7600            move |_, _, inspector, _, _| TransactionOpcodeGas {
7601                transaction_hash: hash,
7602                opcode_gas: inspector.opcode_gas_iter().collect(),
7603            },
7604        ) {
7605            Ok(trace) => Ok(Some(trace)),
7606            Err(BlockchainError::TransactionNotFound) => {
7607                if let Some(fork) = self.get_fork() {
7608                    return Ok(fork.trace_transaction_opcode_gas(hash).await?);
7609                }
7610
7611                Ok(None)
7612            }
7613            Err(err) => Err(err),
7614        }
7615    }
7616
7617    /// Returns opcode gas usage for all transactions in the given block.
7618    pub async fn trace_block_opcode_gas(
7619        &self,
7620        block_id: BlockId,
7621    ) -> Result<Option<BlockOpcodeGas>, BlockchainError> {
7622        if let Some((block, block_hash)) = self.get_block_with_hash(block_id) {
7623            return self.mined_block_opcode_gas(&block, block_hash).map(Some);
7624        }
7625
7626        if let Some(fork) = self.get_fork() {
7627            let number = self.ensure_block_number(Some(block_id)).await?;
7628            if fork.predates_fork_inclusive(number) {
7629                // Delegate the resolved block number so tags (`latest`/`pending`/`safe`/
7630                // `finalized`) are resolved against the fork's head instead of drifting with
7631                // the upstream chain. Hashes are forwarded unchanged.
7632                let resolved = match block_id {
7633                    BlockId::Hash(_) => block_id,
7634                    _ => BlockId::number(number),
7635                };
7636                return Ok(fork.trace_block_opcode_gas(resolved).await?);
7637            }
7638        }
7639
7640        Err(BlockchainError::BlockNotFound)
7641    }
7642
7643    fn mined_block_opcode_gas(
7644        &self,
7645        block: &Block,
7646        block_hash: B256,
7647    ) -> Result<BlockOpcodeGas, BlockchainError> {
7648        // Genesis has no parent state or protocol pre-execution to replay.
7649        if block.header.number() == self.genesis_number() {
7650            return Ok(BlockOpcodeGas {
7651                block_hash,
7652                block_number: block.header.number(),
7653                transactions: Vec::new(),
7654            });
7655        }
7656
7657        let parent_hash = block.header.parent_hash;
7658
7659        let trace = |parent_state: &StateDb| -> Result<Vec<TransactionOpcodeGas>, BlockchainError> {
7660            let (mut cache_db, evm_env, hardfork) =
7661                self.prepare_block_replay(block, parent_state)?;
7662            let mut transactions = Vec::with_capacity(block.body.transactions.len());
7663            let monad_context = self.active_monad_context_for_mined_block(block)?;
7664
7665            for tx_envelope in &block.body.transactions {
7666                let mut inspector = OpcodeGasInspector::new();
7667                let pending_tx = self.pending_mined_transaction(tx_envelope.clone())?;
7668                let transaction_context =
7669                    monad_execution_context_at(monad_context.as_ref(), transactions.len());
7670                let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
7671                    &cache_db,
7672                    &evm_env,
7673                    &mut inspector,
7674                    &pending_tx,
7675                    EnvelopeExecution::replay(transaction_context, hardfork),
7676                )?;
7677
7678                transactions.push(TransactionOpcodeGas {
7679                    transaction_hash: tx_envelope.hash(),
7680                    opcode_gas: inspector.opcode_gas_iter().collect(),
7681                });
7682
7683                cache_db.commit(result.state);
7684            }
7685
7686            Ok(transactions)
7687        };
7688
7689        let read_guard = self.states.upgradable_read();
7690        let transactions = if let Some(state) = read_guard.get_state(&parent_hash) {
7691            trace(state)?
7692        } else {
7693            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
7694            let state = write_guard
7695                .get_on_disk_state(&parent_hash)
7696                .ok_or(BlockchainError::HistoricalStateUnavailable(block.header.number()))?;
7697            trace(state)?
7698        };
7699
7700        Ok(BlockOpcodeGas { block_hash, block_number: block.header.number(), transactions })
7701    }
7702
7703    /// Returns a best-effort execution witness for the given block, in the same format as reth's
7704    /// `debug_executionWitness`.
7705    ///
7706    /// Anvil does not track which state a block's execution actually touched, so this returns a
7707    /// witness for the entire parent state instead: the RLP encoding of every node of the parent
7708    /// state trie (including all storage tries), all contract codes, and the preimages of all
7709    /// account addresses and storage slots. This is a strict superset of the minimal witness, so
7710    /// stateless re-execution of the block against it works, but the witness size grows with the
7711    /// total state instead of the state accessed by the block.
7712    ///
7713    /// Limitations:
7714    /// - Not supported while forking: only remotely accessed accounts are known locally, and the
7715    ///   locally computed state roots do not match the remote chain's roots.
7716    /// - The parent block's state must still be available in the state history, i.e. it must not
7717    ///   have been discarded via `--prune-history`.
7718    /// - The genesis block has no witness since it has no parent state.
7719    /// - `headers` contains the ancestor headers within the 256 block `BLOCKHASH` window that are
7720    ///   known locally, which may be fewer than 256.
7721    pub async fn debug_execution_witness(
7722        &self,
7723        block: BlockNumber,
7724    ) -> Result<ExecutionWitness, BlockchainError> {
7725        let number = self.convert_block_number(Some(block));
7726        let best = self.best_number();
7727        if number > best {
7728            return Err(BlockchainError::BlockOutOfRange(best, number));
7729        }
7730        let genesis = if self.is_fork() { 0 } else { self.genesis_number() };
7731        let Some(parent) = (number > genesis).then(|| number - 1) else {
7732            return Err(BlockchainError::Message(
7733                "genesis block has no parent state to build a witness from".to_string(),
7734            ));
7735        };
7736
7737        let mut headers = Vec::new();
7738        for ancestor in (number.saturating_sub(BLOCKHASH_HISTORY)..number).rev() {
7739            let Some(block) = self.get_block(ancestor) else { break };
7740            headers.push(alloy_rlp::encode(&block.header).into());
7741        }
7742
7743        self.with_database_at(Some(BlockRequest::Number(parent)), |state, _| {
7744            let Some(accounts) = state.maybe_full_db() else {
7745                return Err(BlockchainError::Message(
7746                    "debug_executionWitness is not supported while forking".to_string(),
7747                ));
7748            };
7749
7750            let (_, nodes) = state_trie_witness(&accounts);
7751            let mut codes = Vec::new();
7752            let mut seen_codes = B256Set::default();
7753            let mut keys = Vec::new();
7754            for (address, account) in &accounts {
7755                keys.push(Bytes::copy_from_slice(address.as_slice()));
7756                for slot in account.storage.keys() {
7757                    keys.push(Bytes::from(slot.to_be_bytes::<32>()));
7758                }
7759                if !account.info.is_empty_code_hash() && seen_codes.insert(account.info.code_hash())
7760                {
7761                    let code = match &account.info.code {
7762                        Some(code) => code.original_bytes(),
7763                        None => state.code_by_hash_ref(account.info.code_hash())?.original_bytes(),
7764                    };
7765                    codes.push(code);
7766                }
7767            }
7768            keys.sort_unstable();
7769            keys.dedup();
7770
7771            Ok(ExecutionWitness { state: nodes, codes, keys, headers })
7772        })
7773        .await?
7774    }
7775
7776    /// Returns account information after replaying a block through the transaction at `tx_index`.
7777    pub async fn debug_account_info_at(
7778        &self,
7779        block_id: BlockId,
7780        tx_index: Index,
7781        address: Address,
7782    ) -> Result<Option<RpcAccountInfo>, BlockchainError> {
7783        if let Some((block, _)) = self.get_block_with_hash(block_id) {
7784            return self.mined_debug_account_info_at(&block, tx_index, address).map(Some);
7785        }
7786
7787        if let Some(fork) = self.get_fork() {
7788            let number = self.ensure_block_number(Some(block_id)).await?;
7789            if fork.predates_fork_inclusive(number) {
7790                // Delegate the resolved block number so tags (`latest`/`pending`/`safe`/
7791                // `finalized`) are resolved against the fork's head instead of drifting with
7792                // the upstream chain. Hashes are forwarded unchanged.
7793                let resolved = match block_id {
7794                    BlockId::Hash(_) => block_id,
7795                    _ => BlockId::number(number),
7796                };
7797                return Ok(fork.debug_account_info_at(resolved, tx_index, address).await?);
7798            }
7799        }
7800
7801        Err(BlockchainError::BlockNotFound)
7802    }
7803
7804    fn mined_debug_account_info_at(
7805        &self,
7806        block: &Block,
7807        tx_index: Index,
7808        address: Address,
7809    ) -> Result<RpcAccountInfo, BlockchainError> {
7810        let tx_index = tx_index.0;
7811        let transaction_count = block.body.transactions.len();
7812        if tx_index >= transaction_count {
7813            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
7814                "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
7815            ))));
7816        }
7817
7818        let trace = |parent_state: &StateDb| -> Result<RpcAccountInfo, BlockchainError> {
7819            let (mut cache_db, evm_env, hardfork) =
7820                self.prepare_block_replay(block, parent_state)?;
7821            self.replay_mined_transaction_prefix(
7822                &mut cache_db,
7823                &evm_env,
7824                hardfork,
7825                block,
7826                tx_index + 1,
7827            )?;
7828            let account = revm::DatabaseRef::basic_ref(&cache_db, address)?.unwrap_or_default();
7829            let code = self.get_code_with_state(&cache_db, address)?;
7830            Ok(RpcAccountInfo { balance: account.balance, nonce: account.nonce, code })
7831        };
7832
7833        let read_guard = self.states.upgradable_read();
7834        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
7835            trace(state)
7836        } else {
7837            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
7838            let state = write_guard
7839                .get_on_disk_state(&block.header.parent_hash)
7840                .ok_or(BlockchainError::HistoricalStateUnavailable(block.header.number()))?;
7841            trace(state)
7842        }
7843    }
7844
7845    /// Rollback the chain to a common height.
7846    ///
7847    /// The state of the chain is rewound using `rewind` to the common block, including the db,
7848    /// storage, env, and next-block fees. Pending next-block overrides, including a base fee set
7849    /// with `anvil_setNextBlockBaseFeePerGas`, are discarded.
7850    pub async fn rollback(
7851        &self,
7852        common_block: Block,
7853        _mining_guard: &tokio::sync::MutexGuard<'_, ()>,
7854    ) -> Result<(), BlockchainError> {
7855        let hash = common_block.header.hash_slow();
7856
7857        // Get the database at the common block
7858        let (common_state, replace_accounts) = {
7859            let return_state_or_throw_err =
7860                |db: Option<&StateDb>| -> Result<AddressMap<DbAccount>, BlockchainError> {
7861                    let state_db = db.ok_or(BlockchainError::DataUnavailable)?;
7862                    let db_full =
7863                        state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
7864                    Ok(db_full.clone())
7865                };
7866
7867            let read_guard = self.states.upgradable_read();
7868            if let Some(db) = read_guard.get_state(&hash) {
7869                (return_state_or_throw_err(Some(db))?, true)
7870            } else {
7871                let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
7872                (return_state_or_throw_err(write_guard.get_on_disk_state(&hash))?, false)
7873            }
7874        };
7875
7876        // Acquire the only awaited mutation lock before publishing any part of the rewind.
7877        let (rollback_gas_limit, rollback_base_fee) = if self
7878            .get_fork()
7879            .is_some_and(|fork| fork.block_number() == common_block.header.number())
7880        {
7881            let node_config = self.node_config.read().await;
7882            (
7883                self.get_block(common_block.header.number().saturating_add(1))
7884                    .map(|child| child.header.gas_limit())
7885                    .unwrap_or_else(|| node_config.gas_limit()),
7886                node_config.base_fee.unwrap_or_default(),
7887            )
7888        } else {
7889            (
7890                common_block.header.gas_limit(),
7891                common_block.header.base_fee_per_gas().unwrap_or_default(),
7892            )
7893        };
7894        let mut db = self.db.write().await;
7895        // Read the next-block fees while the retained block's child is still canonical.
7896        let (next_base_fee, next_blob_fees) = self.next_block_fees_after(&common_block.header);
7897        {
7898            // Collect the logs of the blocks that are about to be removed from the canonical
7899            // chain, while their transactions and receipts are still in storage
7900            let removed_logs = self.removed_logs_since(common_block.header.number());
7901
7902            // Unwind the storage back to the common ancestor first
7903            let removed_blocks = {
7904                let mut storage = self.blockchain.storage.write();
7905                if let Some(tempo) = &mut storage.tempo {
7906                    tempo.unwind(&common_block)?;
7907                }
7908                #[cfg(test)]
7909                if let Some(hook) = self.rollback_unwind_hook.lock().as_ref() {
7910                    hook();
7911                }
7912                storage.unwind_to(common_block.header.number(), hash)
7913            };
7914
7915            // Clean up in-memory and on-disk states for removed blocks
7916            let removed_hashes: Vec<_> =
7917                removed_blocks.iter().map(|b| b.header.hash_slow()).collect();
7918            self.states.write().remove_block_states(&removed_hashes);
7919            if let Some(states) = self.tempo_proof_states() {
7920                states.write().remove_block_states(&removed_hashes);
7921            }
7922
7923            // Notify all log subscriptions and filters about the removed logs, so they receive
7924            // them again marked as removed, before any new chain notifications are emitted
7925            if !removed_logs.is_empty() {
7926                self.notify_on_removed_logs(removed_logs);
7927            }
7928
7929            // Set environment back to common block
7930            let mut env = self.evm_env.write();
7931            env.block_env.number = self.evm_block_number(common_block.header.number());
7932            env.block_env.timestamp = U256::from(common_block.header.timestamp());
7933            env.block_env.gas_limit = rollback_gas_limit;
7934            env.block_env.difficulty = common_block.header.difficulty();
7935            env.block_env.prevrandao = common_block.header.mix_hash();
7936            env.block_env.basefee = rollback_base_fee;
7937            // Keep the blob environment set whenever Cancun is active, even for fork blocks whose
7938            // headers omit blob fields, because the EVM requires it.
7939            env.block_env.blob_excess_gas_and_price = self.fees.is_eip4844().then(|| {
7940                BlobExcessGasAndPrice::new(
7941                    common_block.header.excess_blob_gas().unwrap_or_default(),
7942                    self.fees.blob_params().update_fraction as u64,
7943                )
7944            });
7945            self.fees.set_next_block_fees(next_base_fee, next_blob_fees);
7946
7947            self.time.reset(env.block_env.timestamp.saturating_to());
7948            // drop any pending next-block overrides so they do not leak into a block
7949            self.cheats.clear_next_block_overrides();
7950        }
7951
7952        // Only collect the last 256 block hashes since that's all BLOCKHASH can access.
7953        let block_hashes: Vec<_> = {
7954            let storage = self.blockchain.storage.read();
7955            let min_block = common_block.header.number().saturating_sub(256);
7956            storage
7957                .hashes
7958                .iter()
7959                .filter(|(num, _)| **num >= min_block)
7960                .map(|(&num, &hash)| (num, hash))
7961                .collect()
7962        };
7963
7964        db.clear();
7965
7966        if replace_accounts && let Some(accounts) = db.maybe_as_full_db_mut() {
7967            *accounts = common_state;
7968        } else {
7969            // Insert account info before storage to prevent fork-mode RPC fetches after clear.
7970            for (address, acc) in common_state {
7971                db.insert_account(address, acc.info);
7972                for (key, value) in acc.storage {
7973                    db.set_storage_at(address, key.into(), value.into())?;
7974                }
7975            }
7976        }
7977
7978        // Restore block hashes from blockchain storage (now unwound, contains only valid blocks).
7979        for (block_num, hash) in block_hashes {
7980            db.insert_block_hash(U256::from(block_num), hash);
7981        }
7982
7983        Ok(())
7984    }
7985
7986    /// Returns the traces for the given transaction
7987    pub async fn debug_trace_transaction(
7988        &self,
7989        hash: B256,
7990        opts: GethDebugTracingOptions,
7991    ) -> Result<GethTrace, BlockchainError> {
7992        #[cfg(feature = "js-tracer")]
7993        if let Some(tracer_type) = opts.tracer.as_ref()
7994            && tracer_type.is_js()
7995        {
7996            return self
7997                .trace_tx_with_js_tracer(hash, tracer_type.as_str().to_string(), opts.clone())
7998                .await;
7999        }
8000
8001        if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()).await {
8002            return trace;
8003        }
8004
8005        if let Some(fork) = self.get_fork() {
8006            return Ok(fork.debug_trace_transaction(hash, opts).await?);
8007        }
8008
8009        Err(BlockchainError::TransactionNotFound)
8010    }
8011
8012    /// Returns geth-style traces for all transactions in an RLP-encoded block.
8013    pub async fn debug_trace_block(
8014        &self,
8015        rlp_block: Bytes,
8016        opts: GethDebugTracingOptions,
8017    ) -> Result<Vec<TraceResult>, BlockchainError> {
8018        let mut rlp = rlp_block.as_ref();
8019        let block = Block::<FoundryTxEnvelope>::decode(&mut rlp).map_err(|err| {
8020            BlockchainError::RpcError(RpcError::invalid_params(format!(
8021                "failed to decode block: {err}"
8022            )))
8023        })?;
8024        if !rlp.is_empty() {
8025            return Err(BlockchainError::RpcError(RpcError::invalid_params(
8026                "failed to decode block: trailing bytes".to_string(),
8027            )));
8028        }
8029
8030        self.debug_trace_block_by_hash(block.header.hash_slow(), opts).await
8031    }
8032
8033    /// Returns geth-style traces for all transactions in a block by hash.
8034    pub async fn debug_trace_block_by_hash(
8035        &self,
8036        block_hash: B256,
8037        opts: GethDebugTracingOptions,
8038    ) -> Result<Vec<TraceResult>, BlockchainError> {
8039        if let Some(block) = self.blockchain.get_block_by_hash(&block_hash) {
8040            return Ok(self.debug_trace_mined_block(&block, opts).await);
8041        }
8042
8043        if let Some(fork) = self.get_fork() {
8044            return Ok(fork.debug_trace_block_by_hash(block_hash, opts).await?);
8045        }
8046
8047        Err(BlockchainError::BlockNotFound)
8048    }
8049
8050    /// Returns geth-style traces for all transactions in a block by number.
8051    pub async fn debug_trace_block_by_number(
8052        &self,
8053        block_number: BlockNumber,
8054        opts: GethDebugTracingOptions,
8055    ) -> Result<Vec<TraceResult>, BlockchainError> {
8056        let number = self.convert_block_number(Some(block_number));
8057
8058        if let Some(block) = self.get_block(BlockId::number(number)) {
8059            return Ok(self.debug_trace_mined_block(&block, opts).await);
8060        }
8061
8062        if let Some(fork) = self.get_fork() {
8063            return Ok(fork.debug_trace_block_by_number(number, opts).await?);
8064        }
8065
8066        Err(BlockchainError::BlockNotFound)
8067    }
8068
8069    async fn debug_trace_mined_block(
8070        &self,
8071        block: &Block,
8072        opts: GethDebugTracingOptions,
8073    ) -> Vec<TraceResult> {
8074        if block.body.transactions.is_empty() {
8075            return Vec::new();
8076        }
8077
8078        // A single transaction has no prefix replay to share.
8079        if block.body.transactions.len() > 1 {
8080            let traces = match &opts.tracer {
8081                Some(GethDebugTracerType::BuiltInTracer(
8082                    GethDebugBuiltInTracerType::CallTracer,
8083                )) => {
8084                    call_config_from_tracer_config(opts.tracer_config.clone()).ok().map(|config| {
8085                        self.replay_block_geth_traces(
8086                            block,
8087                            TracingInspectorConfig::from_geth_call_config(&config),
8088                            |inspector, gas_used, _| {
8089                                inspector.geth_builder().geth_call_traces(config, gas_used).into()
8090                            },
8091                        )
8092                    })
8093                }
8094                // Without steps tracing, struct logs come from the traces stored while mining.
8095                None if self.enable_steps_tracing => Some(self.replay_block_geth_traces(
8096                    block,
8097                    TracingInspectorConfig::from_geth_config(&opts.config),
8098                    |inspector, gas_used, output| {
8099                        inspector.geth_builder().geth_traces(gas_used, output, opts.config).into()
8100                    },
8101                )),
8102                _ => None,
8103            };
8104            if let Some(Ok(traces)) = traces {
8105                return traces;
8106            }
8107        }
8108
8109        // Preserve per-transaction errors and fork lookup when sequential replay is unavailable.
8110        let mut traces = Vec::with_capacity(block.body.transactions.len());
8111        for tx in &block.body.transactions {
8112            let tx_hash = tx.hash();
8113            traces.push(match self.debug_trace_transaction(tx_hash, opts.clone()).await {
8114                Ok(result) => TraceResult::Success { result, tx_hash: Some(tx_hash) },
8115                Err(error) => {
8116                    TraceResult::Error { error: error.to_string(), tx_hash: Some(tx_hash) }
8117                }
8118            });
8119        }
8120        traces
8121    }
8122
8123    /// Replays a local block once, keeping each transaction's committed state for the next call.
8124    fn replay_block_geth_traces(
8125        &self,
8126        block: &Block,
8127        tracing_config: TracingInspectorConfig,
8128        build_trace: impl Fn(&TracingInspector, u64, Bytes) -> GethTrace,
8129    ) -> Result<Vec<TraceResult>, BlockchainError> {
8130        let outcomes = {
8131            let storage = self.blockchain.storage.read();
8132            let block_hash = block.header.hash_slow();
8133            block
8134                .body
8135                .transactions
8136                .iter()
8137                .enumerate()
8138                .map(|(index, tx)| {
8139                    let hash = tx.hash();
8140                    let mined = storage
8141                        .transactions
8142                        .get(&hash)
8143                        .ok_or(BlockchainError::TransactionNotFound)?;
8144                    if mined.block_hash != block_hash
8145                        || mined.info.transaction_index as usize != index
8146                        || mined.info.transaction_hash != hash
8147                    {
8148                        return Err(BlockchainError::TransactionNotFound);
8149                    }
8150                    Ok((mined.info.gas_used, mined.info.out.clone().unwrap_or_default()))
8151                })
8152                .collect::<Result<Vec<_>, BlockchainError>>()?
8153        };
8154
8155        let trace = |parent_state: &StateDb| -> Result<Vec<TraceResult>, BlockchainError> {
8156            let (mut cache_db, evm_env, hardfork) =
8157                self.prepare_block_replay(block, parent_state)?;
8158            let monad_context = self.active_monad_context_for_mined_block(block)?;
8159            let mut traces = Vec::with_capacity(block.body.transactions.len());
8160            for (index, (tx, (gas_used, output))) in
8161                block.body.transactions.iter().zip(&outcomes).enumerate()
8162            {
8163                let mut inspector = TracingInspector::new(tracing_config);
8164                let pending = self.pending_mined_transaction(tx.clone())?;
8165                let transaction_context = monad_execution_context_at(monad_context.as_ref(), index);
8166                let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
8167                    &cache_db,
8168                    &evm_env,
8169                    &mut inspector,
8170                    &pending,
8171                    EnvelopeExecution::replay(transaction_context, hardfork),
8172                )?;
8173                traces.push(TraceResult::Success {
8174                    result: build_trace(&inspector, *gas_used, output.clone()),
8175                    tx_hash: Some(tx.hash()),
8176                });
8177                cache_db.commit(result.state);
8178            }
8179            Ok(traces)
8180        };
8181
8182        let read_guard = self.states.upgradable_read();
8183        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
8184            trace(state)
8185        } else {
8186            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
8187            let state = write_guard
8188                .get_on_disk_state(&block.header.parent_hash)
8189                .ok_or(BlockchainError::HistoricalStateUnavailable(block.header.number()))?;
8190            trace(state)
8191        }
8192    }
8193
8194    fn geth_trace(
8195        &self,
8196        tx: &MinedTransaction<N>,
8197        opts: GethDebugTracingOptions,
8198    ) -> Result<GethTrace, BlockchainError> {
8199        let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts;
8200
8201        if let Some(tracer) = tracer {
8202            match tracer {
8203                GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
8204                    GethDebugBuiltInTracerType::FourByteTracer => {
8205                        let inspector = FourByteInspector::default();
8206                        let res = self.replay_tx_with_inspector(
8207                            tx.info.transaction_hash,
8208                            inspector,
8209                            |_, _, inspector, _, _| FourByteFrame::from(inspector).into(),
8210                        )?;
8211                        return Ok(res);
8212                    }
8213                    GethDebugBuiltInTracerType::CallTracer => {
8214                        return match call_config_from_tracer_config(tracer_config) {
8215                            Ok(call_config) => {
8216                                let inspector = TracingInspector::new(
8217                                    TracingInspectorConfig::from_geth_call_config(&call_config),
8218                                );
8219                                let frame = self.replay_tx_with_inspector(
8220                                    tx.info.transaction_hash,
8221                                    inspector,
8222                                    |_, _, inspector, _, _| {
8223                                        inspector
8224                                            .geth_builder()
8225                                            .geth_call_traces(call_config, tx.info.gas_used)
8226                                            .into()
8227                                    },
8228                                )?;
8229                                Ok(frame)
8230                            }
8231                            Err(e) => Err(RpcError::invalid_params(e.to_string()).into()),
8232                        };
8233                    }
8234                    GethDebugBuiltInTracerType::PreStateTracer => {
8235                        return match tracer_config.into_pre_state_config() {
8236                            Ok(pre_state_config) => {
8237                                let inspector = TracingInspector::new(
8238                                    TracingInspectorConfig::from_geth_prestate_config(
8239                                        &pre_state_config,
8240                                    ),
8241                                );
8242                                let frame = self.replay_tx_with_inspector(
8243                                    tx.info.transaction_hash,
8244                                    inspector,
8245                                    |state, db, inspector, _, _| {
8246                                        inspector.geth_builder().geth_prestate_traces(
8247                                            &state,
8248                                            &pre_state_config,
8249                                            db,
8250                                        )
8251                                    },
8252                                )??;
8253                                Ok(frame.into())
8254                            }
8255                            Err(e) => Err(RpcError::invalid_params(e.to_string()).into()),
8256                        };
8257                    }
8258                    GethDebugBuiltInTracerType::NoopTracer
8259                    | GethDebugBuiltInTracerType::MuxTracer
8260                    | GethDebugBuiltInTracerType::Erc7562Tracer
8261                    | GethDebugBuiltInTracerType::FlatCallTracer
8262                    | GethDebugBuiltInTracerType::StateGasTracer => {}
8263                },
8264                GethDebugTracerType::JsTracer(_code) => {}
8265            }
8266
8267            return Ok(NoopFrame::default().into());
8268        }
8269
8270        // default structlog tracer
8271        let return_value = tx.info.out.clone().unwrap_or_default();
8272
8273        // Steps are not recorded when mining because they would be kept for every transaction, so
8274        // replay the transaction to record only what this request asks for.
8275        if self.enable_steps_tracing {
8276            let inspector =
8277                TracingInspector::new(TracingInspectorConfig::from_geth_config(&config));
8278            return self.replay_tx_with_inspector(
8279                tx.info.transaction_hash,
8280                inspector,
8281                |_, _, inspector, _, _| {
8282                    inspector
8283                        .geth_builder()
8284                        .geth_traces(tx.info.gas_used, return_value, config)
8285                        .into()
8286                },
8287            );
8288        }
8289
8290        Ok(GethTraceBuilder::new_borrowed(&tx.info.traces)
8291            .geth_traces(tx.info.gas_used, return_value, config)
8292            .into())
8293    }
8294
8295    async fn mined_geth_trace_transaction(
8296        &self,
8297        hash: B256,
8298        opts: GethDebugTracingOptions,
8299    ) -> Option<Result<GethTrace, BlockchainError>> {
8300        self.blockchain.storage.read().transactions.get(&hash).map(|tx| self.geth_trace(tx, opts))
8301    }
8302
8303    pub async fn transaction_receipt(
8304        &self,
8305        hash: B256,
8306    ) -> Result<Option<FoundryTxReceipt>, BlockchainError> {
8307        if let Some(receipt) = self.mined_transaction_receipt(hash) {
8308            return Ok(Some(receipt.inner));
8309        }
8310
8311        if let Some(fork) = self.get_fork() {
8312            let receipt = fork.transaction_receipt(hash).await?;
8313            let number = self.convert_block_number(
8314                receipt.clone().and_then(|r| r.block_number()).map(BlockNumber::from),
8315            );
8316
8317            if fork.predates_fork_inclusive(number) {
8318                return Ok(receipt);
8319            }
8320        }
8321
8322        Ok(None)
8323    }
8324
8325    /// Returns all transaction receipts of the block
8326    pub fn mined_block_receipts(&self, id: impl Into<BlockId>) -> Option<Vec<FoundryTxReceipt>> {
8327        let storage = self.blockchain.storage.read();
8328        let hash = match id.into() {
8329            BlockId::Hash(hash) => hash.block_hash,
8330            BlockId::Number(number) => storage.hash(number, self.slots_in_an_epoch)?,
8331        };
8332        let block = storage.blocks.get(&hash)?.clone();
8333
8334        if block.body.transactions.iter().enumerate().any(|(index, transaction)| {
8335            storage.transactions.get(&transaction.hash()).is_none_or(|transaction| {
8336                transaction.block_hash != hash
8337                    || transaction.info.transaction_index as usize != index
8338            })
8339        }) {
8340            drop(storage);
8341            return block
8342                .body
8343                .transactions
8344                .into_iter()
8345                .map(|transaction| {
8346                    self.mined_transaction_receipt(transaction.hash()).map(|receipt| receipt.inner)
8347                })
8348                .collect();
8349        }
8350
8351        let mut receipts = Vec::with_capacity(block.body.transactions.len());
8352        let mut next_log_index = 0;
8353
8354        for block_transaction in &block.body.transactions {
8355            let transaction = storage.transactions.get(&block_transaction.hash())?;
8356            let log_count = transaction.receipt.logs().len();
8357            let receipt = self.build_mined_transaction_receipt(
8358                &transaction.info,
8359                transaction.receipt.clone(),
8360                transaction.block_hash,
8361                &block,
8362                next_log_index,
8363            );
8364            receipts.push(receipt.inner);
8365            next_log_index += log_count;
8366        }
8367
8368        Some(receipts)
8369    }
8370
8371    /// Returns the transaction receipt for the given hash
8372    pub(crate) fn mined_transaction_receipt(
8373        &self,
8374        hash: B256,
8375    ) -> Option<MinedTransactionReceipt<FoundryNetwork>> {
8376        let storage = self.blockchain.storage.read();
8377        let transaction = storage.transactions.get(&hash)?;
8378
8379        let index = transaction.info.transaction_index as usize;
8380        let block = storage.blocks.get(&transaction.block_hash)?;
8381        let mut next_log_index = 0;
8382        for block_transaction in &block.body.transactions[..index] {
8383            next_log_index +=
8384                storage.transactions.get(&block_transaction.hash())?.receipt.logs().len();
8385        }
8386
8387        Some(self.build_mined_transaction_receipt(
8388            &transaction.info,
8389            transaction.receipt.clone(),
8390            transaction.block_hash,
8391            block,
8392            next_log_index,
8393        ))
8394    }
8395
8396    fn build_mined_transaction_receipt(
8397        &self,
8398        info: &TransactionInfo,
8399        tx_receipt: FoundryReceiptEnvelope,
8400        block_hash: B256,
8401        block: &Block,
8402        next_log_index: usize,
8403    ) -> MinedTransactionReceipt<FoundryNetwork> {
8404        let transaction = block.body.transactions[info.transaction_index as usize].clone();
8405
8406        // Cancun specific
8407        let blob_gas_used = transaction.blob_gas_used();
8408        let blob_gas_price = blob_gas_used.and_then(|_| block.header.blob_fee(self.blob_params()));
8409
8410        let effective_gas_price = transaction.effective_gas_price(block.header.base_fee_per_gas());
8411
8412        #[cfg(feature = "base")]
8413        let eip8130_phase_statuses = tx_receipt.eip8130_phase_statuses().to_vec();
8414        let tx_receipt = tx_receipt.convert_logs_rpc(
8415            BlockNumHash::new(block.header.number(), block_hash),
8416            block.header.timestamp(),
8417            info.transaction_hash,
8418            info.transaction_index,
8419            next_log_index,
8420        );
8421
8422        let receipt = TransactionReceipt {
8423            inner: tx_receipt,
8424            transaction_hash: info.transaction_hash,
8425            transaction_index: Some(info.transaction_index),
8426            block_number: Some(block.header.number()),
8427            gas_used: info.gas_used,
8428            contract_address: info.contract_address,
8429            effective_gas_price,
8430            block_hash: Some(block_hash),
8431            from: info.from,
8432            to: info.to,
8433            blob_gas_price,
8434            blob_gas_used,
8435        };
8436
8437        // Include timestamp in receipt to avoid extra block lookups (e.g., in Otterscan API)
8438        let mut inner = FoundryTxReceipt::with_timestamp(receipt, block.header.timestamp());
8439        if let FoundryTxEnvelope::Celo(tx) = &*transaction {
8440            inner
8441                .0
8442                .other
8443                .insert("feeCurrency".into(), serde_json::to_value(tx.tx().fee_currency).unwrap());
8444        }
8445        if self.is_tempo() {
8446            let fee_payer = match &*transaction {
8447                FoundryTxEnvelope::Tempo(tx) => match tx.tx().recover_fee_payer(info.from) {
8448                    Ok(fee_payer) => fee_payer,
8449                    Err(error) => {
8450                        warn!(
8451                            target: "backend",
8452                            %error,
8453                            tx_hash = ?info.transaction_hash,
8454                            "failed to recover Tempo fee payer for mined receipt"
8455                        );
8456                        info.from
8457                    }
8458                },
8459                _ => info.from,
8460            };
8461            inner = inner.with_fee_payer(fee_payer);
8462
8463            // Match Tempo's receipt conversion: the final log of every non-free
8464            // transaction is the fee token transfer to TIPFeeManager.
8465            if inner.effective_gas_price() > 0
8466                && inner.gas_used() > 0
8467                && let Some(fee_token) = inner.0.inner.logs().last().map(|log| log.address())
8468            {
8469                inner = inner.with_fee_token(fee_token);
8470            }
8471        }
8472        #[cfg(feature = "base")]
8473        if self.is_base()
8474            && let FoundryTxEnvelope::Eip8130(tx) = transaction.as_ref()
8475        {
8476            let fields = Eip8130ReceiptFields {
8477                payer: Some(tx.tx().payer.unwrap_or(info.from)),
8478                phase_statuses: eip8130_phase_statuses,
8479                metadata: (!tx.tx().metadata.is_empty()).then(|| tx.tx().metadata.clone()),
8480            };
8481            inner
8482                .0
8483                .other
8484                .extend(OtherFields::try_from(fields).expect("EIP-8130 receipt fields serialize"));
8485        }
8486        MinedTransactionReceipt { inner, out: info.out.clone() }
8487    }
8488
8489    /// Executes the pending block and returns its transaction receipts.
8490    pub async fn pending_block_receipts(
8491        &self,
8492        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
8493    ) -> Result<Vec<FoundryTxReceipt>, BlockchainError> {
8494        let BlockInfo { block, transactions, receipts } =
8495            self.pending_block(pool_transactions).await?;
8496        let block_hash = block.header.hash_slow();
8497        let mut pending_receipts = Vec::with_capacity(receipts.len());
8498        let mut next_log_index = 0;
8499
8500        for (info, receipt) in transactions.iter().zip(receipts) {
8501            let log_count = receipt.logs().len();
8502            let receipt = self.build_mined_transaction_receipt(
8503                info,
8504                receipt,
8505                block_hash,
8506                &block,
8507                next_log_index,
8508            );
8509            pending_receipts.push(receipt.inner);
8510            next_log_index += log_count;
8511        }
8512
8513        Ok(pending_receipts)
8514    }
8515
8516    /// Returns the blocks receipts for the given number
8517    pub async fn block_receipts(
8518        &self,
8519        number: BlockId,
8520    ) -> Result<Option<Vec<FoundryTxReceipt>>, BlockchainError> {
8521        if let Some(receipts) = self.mined_block_receipts(number) {
8522            return Ok(Some(receipts));
8523        }
8524
8525        if let Some(fork) = self.get_fork() {
8526            let number = match self.ensure_block_number(Some(number)).await {
8527                Err(_) => return Ok(None),
8528                Ok(n) => n,
8529            };
8530
8531            if fork.predates_fork_inclusive(number) {
8532                let receipts = fork.block_receipts(number).await?;
8533
8534                return Ok(receipts);
8535            }
8536        }
8537
8538        Ok(None)
8539    }
8540}
8541
8542impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> Backend<N> {
8543    /// Get the current state.
8544    pub async fn serialized_state(
8545        &self,
8546        preserve_historical_states: bool,
8547    ) -> Result<SerializableState, BlockchainError> {
8548        // Keep account state and head metadata coherent across mining and state replacement.
8549        let _mining_guard = self.mining.lock().await;
8550        let at = self.evm_env.read().block_env.clone();
8551        #[cfg(feature = "monad")]
8552        let mut monad_block_participants = BTreeMap::new();
8553        #[cfg(feature = "monad")]
8554        let mut monad_block_replay_profiles = BTreeMap::new();
8555        let (best_number, blocks, transactions) = {
8556            let storage = self.blockchain.storage.read();
8557            #[cfg(feature = "monad")]
8558            if self.is_monad() {
8559                monad_block_participants = storage
8560                    .monad_block_participants
8561                    .iter()
8562                    .filter(|(hash, _)| storage.blocks.contains_key(*hash))
8563                    .map(|(hash, participants)| (*hash, participants.iter().copied().collect()))
8564                    .collect();
8565                monad_block_replay_profiles = storage
8566                    .monad_block_replay_profiles
8567                    .iter()
8568                    .filter(|(hash, _)| storage.blocks.contains_key(*hash))
8569                    .map(|(hash, profile)| (*hash, *profile))
8570                    .collect();
8571            }
8572            (storage.best_number, storage.serialized_blocks(), storage.serialized_transactions())
8573        };
8574        let historical_states =
8575            preserve_historical_states.then(|| self.states.write().serialized_states());
8576
8577        let state = self
8578            .db
8579            .read()
8580            .await
8581            .dump_state(at, best_number, blocks, transactions, historical_states)?
8582            .ok_or_else(|| {
8583                BlockchainError::RpcError(RpcError::invalid_params(
8584                    "Dumping state not supported with the current configuration",
8585                ))
8586            })?;
8587        #[cfg(feature = "monad")]
8588        let state = {
8589            let mut state = state;
8590            state.monad_block_participants = monad_block_participants;
8591            state.monad_block_replay_profiles = monad_block_replay_profiles;
8592            state
8593        };
8594        Ok(state)
8595    }
8596
8597    /// Write all chain data to serialized bytes buffer
8598    pub async fn dump_state(
8599        &self,
8600        preserve_historical_states: bool,
8601    ) -> Result<Bytes, BlockchainError> {
8602        let state = self.serialized_state(preserve_historical_states).await?;
8603        let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
8604        encoder
8605            .write_all(&serde_json::to_vec(&state).unwrap_or_default())
8606            .map_err(|_| BlockchainError::DataUnavailable)?;
8607        Ok(encoder.finish().unwrap_or_default().into())
8608    }
8609
8610    /// Apply [SerializableState] data to the backend storage.
8611    pub async fn load_state(&self, mut state: SerializableState) -> Result<bool, BlockchainError> {
8612        let _mining_guard = self.mining.lock().await;
8613        let mut block_env = state.block.take();
8614        let mut fork_block_boundary = None;
8615        let mut selected_head = None;
8616        let mut selected_header = None;
8617        let mut checkpoint = None;
8618        let fork_head = self.get_fork().map(|f| (f.block_number(), f.block_hash(), f.timestamp()));
8619        if let Some(block) = &mut block_env {
8620            if self.is_tempo() && self.is_fork() && block.beneficiary.is_zero() {
8621                block.beneficiary = TIP_FEE_MANAGER_ADDRESS;
8622            }
8623            // Set the current best block number.
8624            // Defaults to block number for compatibility with existing state files.
8625            let best_number = state.best_block_number.unwrap_or(block.number.saturating_to());
8626            let (selected_best_number, selected_best_hash) = if let Some((number, hash, _)) =
8627                fork_head
8628            {
8629                trace!(target: "backend", state_block_number=?best_number, fork_block_number=?number);
8630                // If the state.block_number is greater than the fork block number, set best number
8631                // to the state block number.
8632                // Ref: https://github.com/foundry-rs/foundry/issues/9539
8633                if best_number > number {
8634                    (best_number, None)
8635                } else {
8636                    // If loading state file on a fork, set best number to the fork block number.
8637                    // Ref: https://github.com/foundry-rs/foundry/pull/9215#issue-2618681838
8638                    fork_block_boundary = Some(number);
8639                    (number, Some(hash))
8640                }
8641            } else {
8642                (best_number, None)
8643            };
8644
8645            let best_hash = if let Some(hash) = selected_best_hash {
8646                selected_header = state
8647                    .blocks
8648                    .iter()
8649                    .rev()
8650                    .find(|block| block.header.hash_slow() == hash)
8651                    .map(|block| block.header.clone());
8652                hash
8653            } else if state.blocks.is_empty() {
8654                let spec_id = self.spec_id();
8655                let is_cancun = spec_id >= SpecId::CANCUN;
8656                let parent_hash = selected_best_number
8657                    .checked_sub(1)
8658                    .and_then(|number| self.blockchain.storage.read().hashes.get(&number).copied())
8659                    .unwrap_or_default();
8660                let header = Header {
8661                    parent_hash,
8662                    beneficiary: block.beneficiary,
8663                    difficulty: block.difficulty,
8664                    number: selected_best_number,
8665                    gas_limit: block.gas_limit,
8666                    timestamp: block.timestamp.saturating_to(),
8667                    mix_hash: block.prevrandao.unwrap_or_default(),
8668                    base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(block.basefee),
8669                    parent_beacon_block_root: is_cancun.then_some(Default::default()),
8670                    blob_gas_used: is_cancun.then_some(0),
8671                    excess_blob_gas: if is_cancun { block.blob_excess_gas() } else { None },
8672                    withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
8673                    requests_hash: (spec_id >= SpecId::PRAGUE).then_some(EMPTY_REQUESTS_HASH),
8674                    ..Default::default()
8675                };
8676                let header = foundry_header(&self.networks, header);
8677                let best_hash = header.hash_slow();
8678                selected_header = Some(header.clone());
8679                checkpoint = Some(create_block(
8680                    header,
8681                    Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
8682                ));
8683                warn!(
8684                    target: "backend",
8685                    block_number = selected_best_number,
8686                    "state dump has no block history; created a synthetic checkpoint block"
8687                );
8688                best_hash
8689            } else if let Some(header) = state
8690                .blocks
8691                .iter()
8692                .rev()
8693                .find(|block| block.header.number() == selected_best_number)
8694                .map(|block| block.header.clone())
8695            {
8696                let best_hash = header.hash_slow();
8697                selected_header = Some(header);
8698                best_hash
8699            } else {
8700                return Err(BlockchainError::RpcError(RpcError::internal_error_with(format!(
8701                    "Best hash not found for best number {selected_best_number}",
8702                ))));
8703            };
8704
8705            selected_head = Some((selected_best_number, best_hash));
8706        }
8707
8708        // Stage the complete chain update first. Besides keeping blocks and transactions atomic for
8709        // concurrent readers, this ensures validation failures cannot leave a partially loaded
8710        // chain behind.
8711        let blocks = std::mem::take(&mut state.blocks);
8712        let transactions = std::mem::take(&mut state.transactions);
8713        let mut storage = self.blockchain.storage.read().clone();
8714        storage.load_blocks(blocks, fork_block_boundary);
8715        if !self.is_celo()
8716            && storage.blocks.values().any(|block| {
8717                block
8718                    .body
8719                    .transactions
8720                    .iter()
8721                    .any(|tx| matches!(tx.as_ref(), FoundryTxEnvelope::Celo(_)))
8722            })
8723        {
8724            self.ensure_cip64_active()?;
8725        }
8726        storage.load_transactions(transactions, fork_block_boundary);
8727        if let Some(checkpoint) = checkpoint {
8728            storage.insert_block(checkpoint);
8729        }
8730        if let Some((number, hash)) = selected_head {
8731            storage.hashes.insert(number, hash);
8732            storage.best_number = number;
8733            storage.best_hash = hash;
8734            storage.hashes.retain(|block_number, _| *block_number <= number);
8735        }
8736
8737        #[cfg(feature = "monad")]
8738        if self.is_monad() {
8739            for (hash, profile) in &state.monad_block_replay_profiles {
8740                if storage.blocks.contains_key(hash) {
8741                    storage.monad_block_replay_profiles.insert(*hash, *profile);
8742                }
8743            }
8744            for (hash, participants) in &state.monad_block_participants {
8745                if storage.blocks.contains_key(hash) {
8746                    storage
8747                        .monad_block_participants
8748                        .insert(*hash, participants.iter().copied().collect());
8749                }
8750            }
8751            self.rebuild_monad_block_participant_cache(&mut storage)?;
8752            // Reject state that cannot supply the ancestor metadata required by the next block
8753            // before changing the live chain, EVM environment, or database.
8754            self.monad_context_for_child_of_in_storage(&storage, storage.best_hash)?;
8755        }
8756
8757        // Re-anchor block time to the canonical head selected above so the next blocks continue
8758        // its timeline: the saved one when the loaded head stays canonical, the fork's when the
8759        // state file is at or below the fork block. Resolve the timestamp from staged storage so a
8760        // later validation or database failure cannot modify the live clock.
8761        let canonical_timestamp = match fork_head {
8762            Some((_, fork_hash, fork_timestamp)) if storage.best_hash == fork_hash => {
8763                Some(fork_timestamp)
8764            }
8765            _ => storage.blocks.get(&storage.best_hash).map(|block| block.header.timestamp),
8766        };
8767
8768        if let Some(block) = block_env.as_mut() {
8769            // Keep NUMBER aligned with the canonical local head chosen above. Arbitrum state dumps
8770            // can intentionally keep BlockEnv.number distinct from the best L2 block number.
8771            if !is_arbitrum(self.protocol_chain_id())
8772                && let Some((number, _)) = selected_head
8773            {
8774                block.number = U256::from(number);
8775            }
8776        }
8777
8778        let next_fees = selected_header.as_ref().map(|header| {
8779            let parent_fees = self.fees.get_parent_header_fees(header);
8780            let blob_params = self.fees.blob_params();
8781            let next_block_excess_blob_gas = self.networks.next_block_blob_excess_gas(
8782                blob_params,
8783                header.excess_blob_gas().unwrap_or_default(),
8784                header.blob_gas_used().unwrap_or_default(),
8785                header.base_fee_per_gas().unwrap_or_default(),
8786            );
8787            let blob_excess_gas_and_price = BlobExcessGasAndPrice::new(
8788                next_block_excess_blob_gas,
8789                blob_params.update_fraction as u64,
8790            );
8791            (parent_fees, blob_excess_gas_and_price)
8792        });
8793
8794        let historical_states = state.historical_states.take();
8795        let mut db = self.db.write().await;
8796        let db_snapshot = db.snapshot_state();
8797        let load_result = (|| -> Result<(), BlockchainError> {
8798            if !db.load_state(state)? {
8799                return Err(RpcError::invalid_params(
8800                    "Loading state not supported with the current configuration",
8801                )
8802                .into());
8803            }
8804            // A pre-Denim dump has no BaseTime deployment, but Denim blocks still send its
8805            // deposit.
8806            #[cfg(feature = "base")]
8807            if self.is_base() && self.base_upgrade() >= BaseUpgrade::Denim {
8808                ensure_base_time_predeploy(&mut **db, false)?;
8809            }
8810            Ok(())
8811        })();
8812        if let Err(err) = load_result {
8813            db.revert_state(db_snapshot, RevertStateSnapshotAction::RevertRemove);
8814            return Err(err);
8815        }
8816        db.delete_state_snapshot(db_snapshot);
8817
8818        // Backfill the EVM-level block hash cache from the freshly loaded blocks so that the
8819        // BLOCKHASH opcode stays consistent after loading state. Reuses the hashes already
8820        // computed by `load_blocks` above. Only collect the last 256 blocks since that's all
8821        // BLOCKHASH can access.
8822        let block_hashes = {
8823            let min_block = storage.best_number.saturating_sub(256);
8824            storage
8825                .hashes
8826                .iter()
8827                .filter(|(num, _)| (min_block..=storage.best_number).contains(*num))
8828                .map(|(&num, &hash)| (U256::from(num), hash))
8829                .collect()
8830        };
8831
8832        let reanchor_tempo = {
8833            // The database guard keeps chain identity stable until publication. Compare under a
8834            // read guard so existing chain readers need not wait for a large history comparison.
8835            let live_storage = self.blockchain.storage.read();
8836            live_storage.tempo.is_some()
8837                && (storage.best_number != live_storage.best_number
8838                    || storage.best_hash != live_storage.best_hash
8839                    || storage.genesis_number != live_storage.genesis_number
8840                    || storage.genesis_hash != live_storage.genesis_hash
8841                    || storage.hashes != live_storage.hashes)
8842        };
8843        {
8844            let mut live_storage = self.blockchain.storage.write();
8845            // Keep the live consensus, including snapshots or rotations created since staging.
8846            storage.tempo = live_storage.tempo.take();
8847            if reanchor_tempo && let Some(tempo) = &mut storage.tempo {
8848                tempo.reanchor(storage.best_number);
8849            }
8850            *live_storage = storage;
8851        }
8852        if let Some(timestamp) = canonical_timestamp {
8853            self.time.reset(timestamp);
8854        }
8855        if let Some(block_env) = block_env {
8856            self.evm_env.write().block_env = block_env;
8857        }
8858        if let Some((parent_fees, blob_excess_gas_and_price)) = next_fees {
8859            #[cfg(feature = "optimism")]
8860            if self.is_optimism() {
8861                self.fees.set_optimism_base_fee_rules(&parent_fees.extra_data);
8862            }
8863            self.fees.set_base_fee(parent_fees.base_fee);
8864            self.fees.set_blob_excess_gas_and_price(blob_excess_gas_and_price);
8865        }
8866
8867        db.set_block_hashes(block_hashes);
8868
8869        if let Some(historical_states) = historical_states {
8870            let mut states = self.states.write();
8871            for (hash, state_snapshot) in historical_states {
8872                states.insert(hash, db.historical_state(state_snapshot));
8873            }
8874        }
8875
8876        Ok(true)
8877    }
8878
8879    /// Deserialize and add all chain data to the backend storage
8880    pub async fn load_state_bytes(&self, buf: Bytes) -> Result<bool, BlockchainError> {
8881        let orig_buf = &buf.0[..];
8882        let mut decoder = GzDecoder::new(orig_buf);
8883        let mut decoded_data = Vec::new();
8884
8885        let state: SerializableState = serde_json::from_slice(if decoder.header().is_some() {
8886            decoder
8887                .read_to_end(decoded_data.as_mut())
8888                .map_err(|_| BlockchainError::FailedToDecodeStateDump)?;
8889            &decoded_data
8890        } else {
8891            &buf.0
8892        })
8893        .map_err(|_| BlockchainError::FailedToDecodeStateDump)?;
8894
8895        self.load_state(state).await
8896    }
8897}
8898
8899impl Backend<FoundryNetwork> {
8900    /// Simulates a bundle of signed transactions and returns Flashbots-compatible results.
8901    pub async fn call_bundle(
8902        &self,
8903        bundle: EthCallBundle,
8904        transactions: Vec<PendingTransaction<FoundryTxEnvelope>>,
8905        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
8906    ) -> Result<EthCallBundleResponse, BlockchainError> {
8907        let EthCallBundle {
8908            block_number,
8909            coinbase,
8910            timestamp,
8911            gas_limit,
8912            difficulty,
8913            base_fee,
8914            ..
8915        } = bundle;
8916
8917        let blob_gas_used = transactions
8918            .iter()
8919            .filter_map(|transaction| transaction.transaction.blob_gas_used())
8920            .sum::<u64>();
8921        let max_blob_gas = self.blob_params().max_blob_gas_per_block();
8922        if blob_gas_used > max_blob_gas {
8923            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
8924                "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
8925            ))));
8926        }
8927
8928        self.with_database_at_and_context(
8929            block_request,
8930            |state, mut block_env, mut monad_context| {
8931                let state_block_number = block_env.number.to::<u64>();
8932                block_env.number = U256::from(block_number);
8933                block_env.timestamp = timestamp
8934                    .map(U256::from)
8935                    .unwrap_or_else(|| block_env.timestamp.saturating_add(U256::from(12)));
8936                if let Some(coinbase) = coinbase {
8937                    block_env.beneficiary = coinbase;
8938                }
8939                if let Some(gas_limit) = gas_limit {
8940                    block_env.gas_limit = gas_limit;
8941                }
8942                if let Some(difficulty) = difficulty {
8943                    block_env.difficulty = difficulty;
8944                }
8945                if let Some(base_fee) = base_fee {
8946                    block_env.basefee = base_fee.try_into().unwrap_or(u64::MAX);
8947                }
8948
8949                let mut evm_env = self.evm_env.read().clone();
8950                evm_env.block_env = block_env;
8951                let coinbase = evm_env.block_env.beneficiary;
8952                let base_fee = evm_env.block_env.basefee;
8953                let mut cache_db = CacheDB::new(&*state);
8954                let initial_coinbase = revm::DatabaseRef::basic_ref(&cache_db, coinbase)?
8955                    .map(|account| account.balance)
8956                    .unwrap_or_default();
8957                let mut coinbase_balance_before_tx = initial_coinbase;
8958                let mut coinbase_balance_after_tx = initial_coinbase;
8959                let mut total_gas_used = 0u64;
8960                let mut total_gas_fees = U256::ZERO;
8961                let mut bundle_hash = alloy_primitives::Keccak256::new();
8962                let mut results = Vec::with_capacity(transactions.len());
8963
8964                for transaction in transactions {
8965                    let sender = *transaction.sender();
8966                    let tx = transaction.transaction.as_ref();
8967                    let tx_hash = tx.hash();
8968                    bundle_hash.update(tx_hash);
8969
8970                    let mut inspector = self.build_inspector();
8971                    let (ResultAndState { result, state }, _) = self
8972                        .transact_envelope_with_inspector_ref_and_context(
8973                            &cache_db,
8974                            &evm_env,
8975                            &mut inspector,
8976                            &transaction,
8977                            monad_context.as_mut().map(next_monad_context),
8978                        )?;
8979
8980                    let gas_price = tx.effective_tip_per_gas(base_fee).unwrap_or_default();
8981                    let gas_used = result.tx_gas_used();
8982                    let gas_fees = U256::from(gas_used) * U256::from(gas_price);
8983                    total_gas_used += gas_used;
8984                    total_gas_fees += gas_fees;
8985
8986                    coinbase_balance_after_tx = state
8987                        .get(&coinbase)
8988                        .map(|account| account.info.balance)
8989                        .unwrap_or(coinbase_balance_before_tx);
8990                    let coinbase_diff =
8991                        coinbase_balance_after_tx.saturating_sub(coinbase_balance_before_tx);
8992                    let eth_sent_to_coinbase = coinbase_diff.saturating_sub(gas_fees);
8993                    coinbase_balance_before_tx = coinbase_balance_after_tx;
8994
8995                    let output = result.output().cloned().unwrap_or_default();
8996                    let (value, revert) = if result.is_success() {
8997                        (Some(output), None)
8998                    } else {
8999                        (None, Some(output))
9000                    };
9001
9002                    results.push(EthCallBundleTransactionResult {
9003                        coinbase_diff,
9004                        eth_sent_to_coinbase,
9005                        from_address: sender,
9006                        gas_fees,
9007                        gas_price: U256::from(gas_price),
9008                        gas_used,
9009                        to_address: tx.to(),
9010                        tx_hash,
9011                        value,
9012                        revert,
9013                    });
9014                    cache_db.commit(state);
9015                }
9016
9017                let coinbase_diff = coinbase_balance_after_tx.saturating_sub(initial_coinbase);
9018                let eth_sent_to_coinbase = coinbase_diff.saturating_sub(total_gas_fees);
9019                let bundle_gas_price =
9020                    coinbase_diff.checked_div(U256::from(total_gas_used)).unwrap_or_default();
9021
9022                Ok(EthCallBundleResponse {
9023                    bundle_hash: bundle_hash.finalize(),
9024                    bundle_gas_price,
9025                    coinbase_diff,
9026                    eth_sent_to_coinbase,
9027                    gas_fees: total_gas_fees,
9028                    results,
9029                    state_block_number,
9030                    total_gas_used,
9031                })
9032            },
9033        )
9034        .await
9035    }
9036
9037    /// Executes bundles of call requests and returns each call output.
9038    pub async fn call_many(
9039        &self,
9040        bundles: Vec<Bundle<WithOtherFields<TransactionRequest>>>,
9041        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
9042        state_override: Option<alloy_rpc_types::state::StateOverride>,
9043    ) -> Result<Vec<Vec<EthCallResponse>>, BlockchainError> {
9044        if bundles.is_empty() {
9045            return Err(BlockchainError::RpcError(RpcError::invalid_params(
9046                "bundles are empty.".to_string(),
9047            )));
9048        }
9049
9050        self.with_database_at_and_context(
9051            block_request,
9052            |state, mut block_env, mut monad_context| {
9053                let mut cache_db = CacheDB::new(&*state);
9054                if let Some(state_override) = state_override {
9055                    apply_state_overrides(state_override, &mut cache_db)?;
9056                }
9057
9058                let mut results = Vec::with_capacity(bundles.len());
9059                for bundle in bundles {
9060                    let Bundle { transactions, block_override } = bundle;
9061                    if let Some(block_override) = block_override {
9062                        cache_db.apply_block_overrides(block_override, &mut block_env);
9063                    }
9064
9065                    let mut bundle_results = Vec::with_capacity(transactions.len());
9066                    for request in transactions {
9067                        let fee_details = FeeDetails::new(
9068                            request.gas_price,
9069                            request.max_fee_per_gas,
9070                            request.max_priority_fee_per_gas,
9071                            request.max_fee_per_blob_gas,
9072                        )?
9073                        .or_zero_fees();
9074                        let PreparedCall { evm_env, mut tx_env, simulated_tempo_tx, .. } = self
9075                            .prepare_call_env(&cache_db, request, fee_details, block_env.clone())?;
9076                        apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
9077
9078                        let mut inspector = self.build_inspector();
9079                        let ResultAndState { result, state } = self
9080                            .transact_call_with_inspector_ref(
9081                                &cache_db,
9082                                &evm_env,
9083                                &mut inspector,
9084                                tx_env,
9085                                monad_context.as_mut().map(next_monad_context),
9086                            )?;
9087
9088                        let output = result.output().cloned().unwrap_or_default();
9089                        let response = if result.is_success() {
9090                            EthCallResponse { value: Some(output), error: None }
9091                        } else {
9092                            let error = RevertDecoder::new()
9093                                .maybe_decode(&output, None)
9094                                .unwrap_or_else(|| "execution failed".to_string());
9095                            EthCallResponse { value: None, error: Some(error) }
9096                        };
9097
9098                        cache_db.commit(state);
9099                        bundle_results.push(response);
9100                    }
9101
9102                    results.push(bundle_results);
9103                    block_env.number = block_env.number.saturating_add(U256::ONE);
9104                    block_env.timestamp = block_env.timestamp.saturating_add(U256::ONE);
9105                    #[cfg(feature = "monad")]
9106                    self::monad::advance_block_context(&mut monad_context);
9107                }
9108
9109                Ok(results)
9110            },
9111        )
9112        .await
9113    }
9114
9115    /// Simulates the payload by executing the calls in request.
9116    pub async fn simulate(
9117        &self,
9118        request: SimulatePayload,
9119        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
9120        block_interval: u64,
9121    ) -> Result<Vec<SimulatedBlock<AnyRpcBlock>>, BlockchainError> {
9122        self.simulate_raw(
9123            preserve_simulation_request_fields(request),
9124            block_request,
9125            block_interval,
9126        )
9127        .await
9128    }
9129
9130    /// Simulates a payload while preserving transaction extension fields.
9131    pub(crate) async fn simulate_raw(
9132        &self,
9133        request: SimulatePayload<WithOtherFields<TransactionRequest>>,
9134        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
9135        block_interval: u64,
9136    ) -> Result<Vec<SimulatedBlock<AnyRpcBlock>>, BlockchainError> {
9137        let simulate_at = |state: Box<dyn MaybeFullDatabase + '_>,
9138                           base_block_env: BlockEnv,
9139                           base_number,
9140                           base_timestamp,
9141                           base_hash,
9142                           base_fee,
9143                           mut monad_context: Option<MonadReplayContext>,
9144                           base_base_fee_per_gas,
9145                           base_excess_blob_gas,
9146                           base_blob_gas_used,
9147                           base_fee_extra_data: Bytes,
9148                           optimism_jovian: bool| {
9149            let SimulatePayload {
9150                block_state_calls,
9151                trace_transfers,
9152                validation,
9153                return_full_transactions,
9154            } = request;
9155            let block_state_calls = sanitize_simulation_blocks(
9156                block_state_calls,
9157                base_number,
9158                base_timestamp,
9159                block_interval,
9160            )?;
9161            let mut cache_db = BalDatabase::new(CacheDB::new(&*state));
9162            cache_db.cache.block_hashes.insert(U256::from(base_number), base_hash);
9163            let mut block_res = Vec::with_capacity(block_state_calls.len());
9164            // A single block cannot amortize building an incremental trie.
9165            let cache_state_roots = block_state_calls.len() > 1;
9166            let mut state_root_cache = None::<(StateRootCache, AddressMap<DbAccount>)>;
9167            let mut parent_hash = base_hash;
9168            let mut next_base_fee = base_fee;
9169            let mut inherited_block_env = base_block_env;
9170            let (is_merge, is_cancun, is_amsterdam, tx_gas_limit_cap) = {
9171                let cfg_env = &self.evm_env.read().cfg_env;
9172                (
9173                    cfg_env.spec >= SpecId::MERGE,
9174                    cfg_env.spec >= SpecId::CANCUN,
9175                    cfg_env.spec >= SpecId::AMSTERDAM,
9176                    cfg_env.tx_gas_limit_cap(),
9177                )
9178            };
9179            let mut parent_base_fee_per_gas = base_base_fee_per_gas;
9180            let mut parent_excess_blob_gas = base_excess_blob_gas;
9181            let mut parent_blob_gas_used = base_blob_gas_used;
9182            let mut rpc_gas_budget = SIMULATE_GAS_CAP;
9183
9184            // execute the blocks
9185            for block in block_state_calls {
9186                let SimBlock { block_overrides, state_overrides, calls } = block;
9187                let mut block_env = inherited_block_env.clone();
9188                let overridden_beacon_root =
9189                    block_overrides.as_ref().and_then(|overrides| overrides.beacon_root);
9190                let block_timestamp = block_overrides
9191                    .as_ref()
9192                    .and_then(|overrides| overrides.time)
9193                    .unwrap_or_else(|| block_env.timestamp.saturating_to());
9194                let blob_params = self.simulation_blob_params_at_timestamp(block_timestamp);
9195                if is_cancun {
9196                    let excess_blob_gas = self.networks.next_block_blob_excess_gas(
9197                        blob_params,
9198                        parent_excess_blob_gas,
9199                        parent_blob_gas_used,
9200                        parent_base_fee_per_gas,
9201                    );
9202                    block_env.set_blob_excess_gas_and_price(
9203                        excess_blob_gas,
9204                        blob_params.update_fraction as u64,
9205                    );
9206                } else {
9207                    block_env.blob_excess_gas_and_price = None;
9208                }
9209                block_env.basefee = if validation { next_base_fee } else { 0 };
9210                block_env.prevrandao = Some(B256::ZERO);
9211                if is_merge && !is_arbitrum(self.protocol_chain_id()) {
9212                    block_env.difficulty = U256::ZERO;
9213                }
9214                let mut call_res = Vec::with_capacity(calls.len());
9215                let mut log_index = 0;
9216                let mut cumulative_gas_used = 0;
9217                let mut block_regular_gas_used = 0;
9218                let mut block_state_gas_used = 0;
9219                let mut block_blob_gas_used = 0u64;
9220                let mut transactions = Vec::with_capacity(calls.len());
9221                let mut transaction_envelopes = Vec::with_capacity(calls.len());
9222                let mut receipts = Vec::with_capacity(calls.len());
9223                let overridden_block_hashes = block_overrides
9224                    .as_ref()
9225                    .and_then(|overrides| overrides.block_hash.as_ref())
9226                    .map(|overrides| {
9227                        overrides
9228                            .keys()
9229                            .map(|number| {
9230                                let number = U256::from(*number);
9231                                (number, cache_db.cache.block_hashes.get(&number).copied())
9232                            })
9233                            .collect::<Vec<_>>()
9234                    })
9235                    .unwrap_or_default();
9236
9237                if let Some(block_overrides) = block_overrides {
9238                    cache_db.apply_block_overrides(block_overrides, &mut block_env);
9239                }
9240                // Simulation overrides and RPC results use L2 numbers, while Arbitrum
9241                // execution reads the corresponding L1 number through NUMBER.
9242                let rpc_block_number = block_env.number.saturating_to();
9243                block_env.number = self.evm_block_number(rpc_block_number);
9244                let simulation_evm_env =
9245                    EvmEnv::new(self.evm_env.read().cfg_env.clone(), block_env.clone());
9246                let spec_id = *simulation_evm_env.spec_id();
9247                let ethereum_transitions = self
9248                    .ethereum_block_transitions(self.hardfork(), None, BlockExecutionKind::Complete)
9249                    .map(|mut transitions| {
9250                        transitions.parent_beacon_block_root = (transitions.hardfork
9251                            >= EthereumHardfork::Cancun)
9252                            .then_some(overridden_beacon_root.unwrap_or_default());
9253                        transitions
9254                    });
9255                let precompile_overrides = self.simulation_precompile_overrides(
9256                    state_overrides.as_ref(),
9257                    &simulation_evm_env,
9258                )?;
9259
9260                // Apply state overrides after validating precompile moves against this block's
9261                // active precompile set.
9262                if let Some(mut state_overrides) = state_overrides {
9263                    state_overrides.retain(|_, account| {
9264                        account.balance.is_some()
9265                            || account.nonce.is_some()
9266                            || account.code.is_some()
9267                            || account.state.is_some()
9268                            || account
9269                                .state_diff
9270                                .as_ref()
9271                                .is_some_and(|state_diff| !state_diff.is_empty())
9272                    });
9273                    let previously_deleted = previously_deleted_accounts(
9274                        &cache_db.cache.accounts,
9275                        state_overrides.keys().copied(),
9276                    );
9277                    apply_state_overrides(state_overrides, &mut cache_db.db)?;
9278                    preserve_deleted_storage(&mut cache_db.cache.accounts, previously_deleted);
9279                }
9280
9281                // Overrides define the starting state, so only execution contributes BAL writes.
9282                if ethereum_transitions
9283                    .is_some_and(|transitions| transitions.hardfork >= EthereumHardfork::Amsterdam)
9284                {
9285                    cache_db.bal_state = BalState::new().with_bal_builder();
9286                }
9287
9288                if let Some(transitions) = ethereum_transitions {
9289                    self.apply_simulation_pre_execution_changes(
9290                        &mut cache_db,
9291                        &simulation_evm_env,
9292                        parent_hash,
9293                        transitions,
9294                    )?;
9295                }
9296
9297                cache_db.bump_bal_index();
9298
9299                // execute all calls in that block
9300                for (req_idx, mut request) in calls.into_iter().enumerate() {
9301                    #[cfg(feature = "base")]
9302                    if request.transaction_type == Some(EIP8130_TRANSACTION_TYPE) {
9303                        return Err(BlockchainError::InvalidTransactionRequest(
9304                            "eth_simulateV1 does not accept EIP-8130 transaction requests"
9305                                .to_string(),
9306                        ));
9307                    }
9308
9309                    let classified_request = self.parse_transaction_request(request.clone())?;
9310                    #[cfg(feature = "base")]
9311                    if classified_request.is_base() {
9312                        return Err(BlockchainError::InvalidTransactionRequest(
9313                            "eth_simulateV1 does not accept EIP-8130 transaction requests"
9314                                .to_string(),
9315                        ));
9316                    }
9317                    let is_ethereum_request = classified_request.is_ethereum();
9318                    let mut parsed_request = self.is_tempo().then_some(classified_request);
9319                    if is_ethereum_request {
9320                        request.populate_blob_hashes();
9321                        let preferred_type = request.preferred_type();
9322                        request.transaction_type = Some(preferred_type as u8);
9323                        request.trim_conflicting_keys();
9324                        request.populate_blob_hashes();
9325                    }
9326                    let request_blob_gas_used = if is_ethereum_request && !optimism_jovian {
9327                        u64::try_from(request.blob_versioned_hashes.as_ref().map_or(0, Vec::len))
9328                            .unwrap_or(u64::MAX)
9329                            .saturating_mul(DATA_GAS_PER_BLOB)
9330                    } else {
9331                        0
9332                    };
9333                    let max_blob_gas = block_blob_gas_limit(
9334                        optimism_jovian,
9335                        block_env.gas_limit,
9336                        blob_params.max_blob_gas_per_block(),
9337                    );
9338                    if !optimism_jovian
9339                        && block_blob_gas_used.saturating_add(request_blob_gas_used) > max_blob_gas
9340                    {
9341                        return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
9342                            "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
9343                        ))));
9344                    }
9345                    block_blob_gas_used = block_blob_gas_used.saturating_add(request_blob_gas_used);
9346
9347                    let inner = request.as_ref();
9348                    let remaining_regular_gas =
9349                        block_env.gas_limit.saturating_sub(block_regular_gas_used);
9350                    let remaining_state_gas =
9351                        block_env.gas_limit.saturating_sub(block_state_gas_used);
9352                    let remaining_gas = if is_amsterdam {
9353                        remaining_regular_gas.min(remaining_state_gas)
9354                    } else {
9355                        block_env.gas_limit.saturating_sub(cumulative_gas_used)
9356                    };
9357                    let requested_gas = inner.gas.unwrap_or(remaining_gas);
9358                    let exceeds_gas_limit = if is_amsterdam {
9359                        let requested_regular_gas = requested_gas.min(tx_gas_limit_cap);
9360                        requested_regular_gas > remaining_regular_gas
9361                            || requested_gas > remaining_state_gas
9362                    } else {
9363                        requested_gas > remaining_gas
9364                    };
9365                    if exceeds_gas_limit {
9366                        return Err(BlockchainError::RpcError(RpcError {
9367                            code: ErrorCode::ServerError(-38015),
9368                            message: format!(
9369                                "block gas limit exceeded: remaining {remaining_gas}, requested {requested_gas}"
9370                            )
9371                            .into(),
9372                            data: None,
9373                        }));
9374                    }
9375                    let execution_gas_limit = requested_gas.min(rpc_gas_budget);
9376                    let preserve_signed_gas = matches!(
9377                        &parsed_request,
9378                        Some(FoundryTransactionRequest::Tempo(request))
9379                            if request.fee_payer_signature.is_some()
9380                    );
9381                    request.gas = Some(execution_gas_limit);
9382                    if !preserve_signed_gas && let Some(parsed_request) = &mut parsed_request {
9383                        parsed_request.as_mut().gas = Some(execution_gas_limit);
9384                    }
9385
9386                    let caller = request.from.unwrap_or_default();
9387                    let caller_nonce = RevmDatabase::basic(&mut cache_db.db, caller)?
9388                        .map(|account| account.nonce)
9389                        .unwrap_or_default();
9390                    let tempo_nonce_key =
9391                        parsed_request.as_ref().and_then(|request| match request {
9392                            FoundryTransactionRequest::Tempo(request) => request.nonce_key,
9393                            _ => None,
9394                        });
9395                    if request.nonce.is_none() {
9396                        let nonce = tempo_nonce_key.map_or(Ok(caller_nonce), |nonce_key| {
9397                            tempo_nonce(&cache_db.db, caller, nonce_key)
9398                        })?;
9399                        request.nonce = Some(nonce);
9400                        if let Some(parsed_request) = &mut parsed_request {
9401                            parsed_request.as_mut().nonce = Some(nonce);
9402                        }
9403                    }
9404
9405                    if is_ethereum_request {
9406                        let mut canonical_request =
9407                            FoundryTransactionRequest::Ethereum(request.inner.clone());
9408                        canonical_request.prep_for_submission();
9409                        request.inner = canonical_request.as_ref().clone();
9410                        if let Some(parsed_request) = &mut parsed_request {
9411                            *parsed_request = canonical_request;
9412                        }
9413                    }
9414
9415                    let fee_details = FeeDetails::new(
9416                        request.gas_price,
9417                        request.max_fee_per_gas,
9418                        request.max_priority_fee_per_gas,
9419                        request.max_fee_per_blob_gas,
9420                    )?
9421                    .or_zero_fees();
9422
9423                    let PreparedCall {
9424                        mut evm_env,
9425                        mut tx_env,
9426                        simulated_tempo_tx,
9427                        #[cfg(feature = "base")]
9428                        simulated_envelope,
9429                    } = if let Some(parsed_request) = parsed_request {
9430                        self.prepare_typed_call_env(
9431                            &cache_db.db,
9432                            parsed_request,
9433                            fee_details,
9434                            block_env.clone(),
9435                        )?
9436                    } else {
9437                        self.prepare_call_env(
9438                            &cache_db.db,
9439                            request.clone(),
9440                            fee_details,
9441                            block_env.clone(),
9442                        )?
9443                    };
9444                    tx_env.base_mut().gas_limit = execution_gas_limit;
9445                    apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
9446                    if !validation
9447                        && tempo_nonce_key.is_none_or(|key| key.is_zero())
9448                        && request.nonce == Some(u64::MAX)
9449                    {
9450                        tx_env.base_mut().nonce = 0;
9451                    }
9452                    let uses_protocol_call_nonce = tx_env.uses_protocol_call_nonce();
9453                    #[cfg(feature = "base")]
9454                    let simulated_envelope = simulated_envelope
9455                        .or_else(|| simulated_tempo_tx.map(FoundryTxEnvelope::Tempo));
9456                    #[cfg(not(feature = "base"))]
9457                    let simulated_envelope = simulated_tempo_tx.map(FoundryTxEnvelope::Tempo);
9458
9459                    if is_amsterdam {
9460                        // Ensure simulated Amsterdam calls use EIP-8037's split gas schedule.
9461                        let spec = evm_env.cfg_env.spec;
9462                        evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec);
9463                    }
9464
9465                    // Always disable EIP-3607
9466                    evm_env.cfg_env.disable_eip3607 = true;
9467
9468                    // Simulated blocks keep their own base and blob base fees, which are what
9469                    // BASEFEE and BLOBBASEFEE read and what validation checks fees against.
9470                    evm_env.block_env.basefee = block_env.basefee;
9471                    evm_env.block_env.blob_excess_gas_and_price =
9472                        block_env.blob_excess_gas_and_price;
9473                    // Without validation, calls run at any price against the simulated block's
9474                    // fees.
9475                    evm_env.cfg_env.disable_base_fee = !validation;
9476                    if validation {
9477                        evm_env.cfg_env.disable_nonce_check = false;
9478                        evm_env.cfg_env.disable_block_gas_limit = false;
9479                    }
9480
9481                    let mut inspector = self.build_inspector();
9482
9483                    // transact
9484                    inspector = inspector.with_simulation_logs(trace_transfers);
9485                    trace!(target: "backend", env=?evm_env, spec=?evm_env.spec_id(),"simulate evm env");
9486                    let execution_result = match tx_env {
9487                        CallTxEnv::Eth(tx_env)
9488                            if !validation
9489                                && tx_env.tx_type == 3
9490                                && tx_env.max_fee_per_blob_gas == 0 =>
9491                        {
9492                            self.transact_eth_simulation_with_inspector_ref(
9493                                &cache_db.db,
9494                                &evm_env,
9495                                &mut inspector,
9496                                tx_env,
9497                                &precompile_overrides,
9498                            )
9499                        }
9500                        tx_env if precompile_overrides.moves.is_empty() => self
9501                            .transact_call_with_inspector_ref(
9502                                &cache_db.db,
9503                                &evm_env,
9504                                &mut inspector,
9505                                tx_env,
9506                                monad_context.as_mut().map(next_monad_context),
9507                            ),
9508                        tx_env => self.transact_eth_with_inspector_ref_and_precompile_overrides(
9509                            &cache_db.db,
9510                            &evm_env,
9511                            &mut inspector,
9512                            tx_env.into_base(),
9513                            &precompile_overrides,
9514                        ),
9515                    };
9516                    let ResultAndState { result, mut state } = match execution_result {
9517                        Err(BlockchainError::InvalidTransaction(error)) => {
9518                            return Err(simulate_transaction_error(error));
9519                        }
9520                        result => result?,
9521                    };
9522                    if !validation
9523                        && caller_nonce == u64::MAX
9524                        && uses_protocol_call_nonce
9525                        && let Some(account) = state.get_mut(&caller)
9526                    {
9527                        account.info.nonce = 0;
9528                    }
9529                    trace!(target: "backend", ?result, ?request, "simulate call");
9530
9531                    let canonical_logs = result.clone().into_logs();
9532                    let (response_logs, attempted_log_count) = inspector
9533                        .take_simulation_logs(&canonical_logs, result.is_success())
9534                        .expect("simulation log collector is installed");
9535                    inspector.print_logs();
9536                    if self.print_traces {
9537                        inspector.into_print_traces(self.call_trace_decoder());
9538                    }
9539
9540                    // REVM turns a previously deleted account into `Touched` when a later call
9541                    // recreates it without storage. Preserve the cleared-storage provenance so
9542                    // subsequent calls and the recursively merged state cannot reload old slots.
9543                    let previously_deleted = previously_deleted_accounts(
9544                        &cache_db.cache.accounts,
9545                        state.keys().copied(),
9546                    );
9547
9548                    rpc_gas_budget = rpc_gas_budget.saturating_sub(result.tx_gas_used());
9549                    cumulative_gas_used = cumulative_gas_used.saturating_add(result.tx_gas_used());
9550                    block_regular_gas_used = block_regular_gas_used
9551                        .saturating_add(result.gas().block_regular_gas_used());
9552                    block_state_gas_used =
9553                        block_state_gas_used.saturating_add(result.gas().block_state_gas_used());
9554
9555                    // create the transaction from a request
9556                    let from = caller;
9557                    request.sidecar = None;
9558                    let tx = if let Some(envelope) = simulated_envelope {
9559                        MaybeImpersonatedTransaction::impersonated(envelope, from)
9560                    } else {
9561                        if request.to.is_none() {
9562                            request.to = Some(TxKind::Create);
9563                        }
9564                        let mut request = self.parse_transaction_request(request)?;
9565                        request.prep_for_submission();
9566                        let typed_tx = request.build_unsigned().map_err(|e| {
9567                            BlockchainError::InvalidTransactionRequest(e.to_string())
9568                        })?;
9569                        MaybeImpersonatedTransaction::impersonated(
9570                            typed_tx.into_impersonated(),
9571                            from,
9572                        )
9573                    };
9574                    let tx_hash = tx.as_ref().hash();
9575                    #[cfg(feature = "optimism")]
9576                    if optimism_jovian {
9577                        let tx_blob_gas = crate::eth::backend::executor::optimism::blob_gas_used(
9578                            &mut cache_db.db,
9579                            tx.as_ref(),
9580                            true,
9581                        )
9582                        .map_err(|err| BlockchainError::Internal(err.to_string()))?;
9583                        if block_blob_gas_used.saturating_add(tx_blob_gas) > max_blob_gas {
9584                            return Err(BlockchainError::RpcError(RpcError::invalid_params(
9585                                format!(
9586                                    "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
9587                                ),
9588                            )));
9589                        }
9590                        block_blob_gas_used = block_blob_gas_used.saturating_add(tx_blob_gas);
9591                    }
9592
9593                    // Commit after calculating the footprint so the scalar comes from pre-tx
9594                    // state, matching the upstream OP block executor.
9595                    cache_db.commit(state);
9596                    cache_db.bump_bal_index();
9597                    preserve_deleted_storage(&mut cache_db.cache.accounts, previously_deleted);
9598                    #[cfg(any(feature = "base", feature = "optimism"))]
9599                    let receipt = if tx.as_ref().is_deposit() {
9600                        crate::eth::backend::executor::optimism::build_simulated_deposit_receipt(
9601                            self.hardfork(),
9602                            caller_nonce,
9603                            &result,
9604                            canonical_logs.clone(),
9605                            cumulative_gas_used,
9606                        )
9607                    } else {
9608                        FoundryReceiptBuilder::build_simulated_receipt(
9609                            tx.as_ref().tx_type(),
9610                            &result,
9611                            canonical_logs.clone(),
9612                            cumulative_gas_used,
9613                        )
9614                    };
9615                    #[cfg(not(any(feature = "base", feature = "optimism")))]
9616                    let receipt = FoundryReceiptBuilder::build_simulated_receipt(
9617                        tx.as_ref().tx_type(),
9618                        &result,
9619                        canonical_logs.clone(),
9620                        cumulative_gas_used,
9621                    );
9622                    receipts.push(receipt);
9623                    transaction_envelopes.push(tx.as_ref().clone());
9624                    let rpc_tx =
9625                        transaction_build(Some(tx_hash), tx, None, None, Some(block_env.basefee));
9626                    transactions.push(rpc_tx);
9627
9628                    let return_data = if result.is_success() {
9629                        result.output().cloned().unwrap_or_default()
9630                    } else {
9631                        Bytes::new()
9632                    };
9633                    let sim_res = SimCallResult {
9634                        return_data,
9635                        gas_used: result.tx_gas_used(),
9636                        max_used_gas: Some(
9637                            result.gas().total_gas_spent().max(result.gas().floor_gas()),
9638                        ),
9639                        status: result.is_success(),
9640                        error: match &result {
9641                            ExecutionResult::Success { .. } => None,
9642                            ExecutionResult::Revert { output, .. } => {
9643                                let message = RevertDecoder::new()
9644                                    .maybe_decode(output, None)
9645                                    .map(|reason| format!("execution reverted: {reason}"))
9646                                    .unwrap_or_else(|| "execution reverted".to_string());
9647                                Some(SimulateError {
9648                                    code: SimulateError::EXECUTION_REVERTED_CODE,
9649                                    message,
9650                                    data: Some(output.clone()),
9651                                })
9652                            }
9653                            ExecutionResult::Halt { reason, .. } => Some(SimulateError {
9654                                code: SimulateError::VM_EXECUTION_ERROR_CODE,
9655                                message: if matches!(reason, HaltReason::OutOfGas(_)) {
9656                                    "out of gas".to_string()
9657                                } else {
9658                                    format!("vm execution error: {reason}")
9659                                },
9660                                data: None,
9661                            }),
9662                        },
9663                        logs: response_logs
9664                            .into_iter()
9665                            .map(|(idx, log)| Log {
9666                                inner: log,
9667                                block_number: Some(rpc_block_number),
9668                                block_timestamp: Some(block_env.timestamp.saturating_to()),
9669                                transaction_index: Some(req_idx as u64),
9670                                log_index: Some(idx + log_index),
9671                                removed: false,
9672
9673                                block_hash: None,
9674                                transaction_hash: Some(tx_hash),
9675                            })
9676                            .collect(),
9677                    };
9678                    log_index += attempted_log_count;
9679                    call_res.push(sim_res);
9680                }
9681
9682                for (number, hash) in overridden_block_hashes {
9683                    if let Some(hash) = hash {
9684                        cache_db.cache.block_hashes.insert(number, hash);
9685                    } else {
9686                        cache_db.cache.block_hashes.remove(&number);
9687                    }
9688                }
9689
9690                let gas_used = if is_amsterdam {
9691                    block_regular_gas_used.max(block_state_gas_used)
9692                } else {
9693                    cumulative_gas_used
9694                };
9695                let requests = if let Some(transitions) = ethereum_transitions {
9696                    self.apply_simulation_post_execution_changes(
9697                        &mut cache_db,
9698                        &simulation_evm_env,
9699                        transitions,
9700                        &receipts,
9701                    )?
9702                } else {
9703                    Default::default()
9704                };
9705
9706                // Fork databases are partial, so their synthetic blocks use a zero state root.
9707                let incremental = state_root_cache.as_mut().is_some_and(|(cache, previous)| {
9708                    cache.record_overlay(&cache_db.cache.accounts, previous)
9709                });
9710                let state_root = if incremental {
9711                    let (cache, previous) = state_root_cache.as_mut().unwrap();
9712                    let root = cache.root(&cache_db.cache.accounts);
9713                    previous.clone_from(&cache_db.cache.accounts);
9714                    root
9715                } else {
9716                    cache_db
9717                        .maybe_full_db()
9718                        .map(|accounts| {
9719                            // A deleted base account can retain storage written by
9720                            // anvil_setStorageAt. A later touch exposes it in the full merge,
9721                            // although it has no trie leaf. Keep the full rebuild in that case.
9722                            if cache_state_roots
9723                                && accounts.values().all(|account| {
9724                                    account.account_state != AccountState::NotExisting
9725                                        || account.storage.is_empty()
9726                                })
9727                            {
9728                                let (cache, previous) =
9729                                    state_root_cache.get_or_insert_with(Default::default);
9730                                let root = cache.root(&accounts);
9731                                previous.clone_from(&cache_db.cache.accounts);
9732                                root
9733                            } else {
9734                                state_root_cache = None;
9735                                state_root(&accounts)
9736                            }
9737                        })
9738                        .unwrap_or_default()
9739                };
9740                let block_access_list_hash = cache_db
9741                    .bal_state
9742                    .take_built_alloy_bal()
9743                    .map(|bal| compute_block_access_list_hash(bal.as_slice()));
9744                let header = Header {
9745                    block_access_list_hash,
9746                    logs_bloom: receipts.iter().fold(Bloom::ZERO, |mut bloom, receipt| {
9747                        bloom.accrue_bloom(receipt.logs_bloom());
9748                        bloom
9749                    }),
9750                    transactions_root: calculate_transaction_root(&transaction_envelopes),
9751                    receipts_root: calculate_receipt_root(&receipts),
9752                    parent_hash,
9753                    beneficiary: block_env.beneficiary,
9754                    state_root,
9755                    difficulty: block_env.difficulty,
9756                    number: rpc_block_number,
9757                    gas_limit: block_env.gas_limit,
9758                    gas_used,
9759                    timestamp: block_env.timestamp.saturating_to(),
9760                    extra_data: base_fee_extra_data.clone(),
9761                    mix_hash: block_env.prevrandao.unwrap_or_default(),
9762                    nonce: Default::default(),
9763                    base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(block_env.basefee),
9764                    withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
9765                    blob_gas_used: is_cancun.then_some(block_blob_gas_used),
9766                    excess_blob_gas: if is_cancun { block_env.blob_excess_gas() } else { None },
9767                    parent_beacon_block_root: ethereum_transitions.and_then(|transitions| {
9768                        (transitions.hardfork >= EthereumHardfork::Cancun)
9769                            .then_some(transitions.parent_beacon_block_root.unwrap_or_default())
9770                    }),
9771                    requests_hash: ethereum_transitions.and_then(|transitions| {
9772                        (transitions.hardfork >= EthereumHardfork::Prague)
9773                            .then(|| requests.requests_hash())
9774                    }),
9775                    ..Default::default()
9776                };
9777                let block_hash = header.hash_slow();
9778                let withdrawals = (spec_id >= SpecId::SHANGHAI).then_some(Default::default());
9779                let size = U256::from(
9780                    BlockBody {
9781                        transactions: transaction_envelopes,
9782                        ommers: vec![],
9783                        withdrawals: withdrawals.clone(),
9784                    }
9785                    .into_block(header.clone())
9786                    .length(),
9787                );
9788                for (transaction_index, transaction) in transactions.iter_mut().enumerate() {
9789                    transaction.block_hash = Some(block_hash);
9790                    transaction.block_number = Some(header.number);
9791                    transaction.transaction_index = Some(transaction_index as u64);
9792                    transaction.block_timestamp = Some(header.timestamp);
9793                }
9794                let mut block = alloy_rpc_types::Block {
9795                    header: AnyRpcHeader {
9796                        hash: block_hash,
9797                        inner: header.into(),
9798                        total_difficulty: None,
9799                        size: Some(size),
9800                    },
9801                    uncles: vec![],
9802                    transactions: BlockTransactions::Full(transactions),
9803                    withdrawals,
9804                };
9805
9806                if !return_full_transactions {
9807                    block.transactions.convert_to_hashes();
9808                }
9809
9810                for res in &mut call_res {
9811                    res.logs.iter_mut().for_each(|log| {
9812                        log.block_hash = Some(block.header.hash);
9813                    });
9814                }
9815
9816                let mut block = AnyRpcBlock::new(WithOtherFields::new(block));
9817                if is_arbitrum(self.protocol_chain_id()) {
9818                    block
9819                        .other
9820                        .insert("l1BlockNumber".to_string(), serde_json::json!(block_env.number));
9821                }
9822                let simulated_block = SimulatedBlock { inner: block, calls: call_res };
9823
9824                parent_hash = block_hash;
9825                cache_db.cache.block_hashes.insert(U256::from(rpc_block_number), block_hash);
9826                inherited_block_env.beneficiary = block_env.beneficiary;
9827                inherited_block_env.difficulty = block_env.difficulty;
9828                inherited_block_env.gas_limit = block_env.gas_limit;
9829                // Route through the fee manager so Tempo chains use their own base fee rules.
9830                let header = &simulated_block.inner.header;
9831                next_base_fee = self.fees.calculate_next_block_base_fee_from_header(&header.inner);
9832                parent_base_fee_per_gas = header.base_fee_per_gas().unwrap_or_default();
9833                parent_excess_blob_gas = header.excess_blob_gas().unwrap_or_default();
9834                parent_blob_gas_used = header.blob_gas_used().unwrap_or_default();
9835
9836                block_res.push(simulated_block);
9837                #[cfg(feature = "monad")]
9838                self::monad::advance_block_context(&mut monad_context);
9839            }
9840
9841            Ok(block_res)
9842        };
9843
9844        match block_request {
9845            Some(BlockRequest::Pending(pool_transactions)) => {
9846                self.with_pending_block(pool_transactions, |state, block| {
9847                    let header = &block.block.header;
9848                    let parent_fees = self.fees.calculate_parent_header_fees(header);
9849                    let optimism_jovian =
9850                        self.is_optimism_jovian_at_header(header, parent_fees.optimism_jovian);
9851                    let monad_context = self.active_monad_context_before_mined_transaction(
9852                        &block.block,
9853                        block.block.body.transactions.len(),
9854                    )?;
9855                    #[cfg(feature = "monad")]
9856                    let monad_context = {
9857                        let mut monad_context = monad_context;
9858                        self::monad::advance_block_context(&mut monad_context);
9859                        monad_context
9860                    };
9861                    simulate_at(
9862                        state,
9863                        self.block_env_from_header(header),
9864                        header.number(),
9865                        header.timestamp(),
9866                        header.hash_slow(),
9867                        parent_fees.base_fee,
9868                        monad_context,
9869                        header.base_fee_per_gas().unwrap_or_default(),
9870                        header.excess_blob_gas().unwrap_or_default(),
9871                        header.blob_gas_used().unwrap_or_default(),
9872                        parent_fees.extra_data,
9873                        optimism_jovian,
9874                    )
9875                })
9876                .await?
9877            }
9878            block_request => {
9879                let base_block_number = match block_request.as_ref() {
9880                    Some(BlockRequest::Number(number)) => BlockNumber::Number(*number),
9881                    Some(BlockRequest::Pending(_)) => unreachable!(),
9882                    None => BlockNumber::Latest,
9883                };
9884                let base_block = self
9885                    .block_by_number(base_block_number)
9886                    .await?
9887                    .ok_or(BlockchainError::BlockNotFound)?;
9888                let base_number = base_block.header.number();
9889                let base_timestamp = base_block.header.timestamp();
9890                let base_hash = base_block.header.hash;
9891                let parent_fees = self.fees.calculate_parent_header_fees(&base_block.header.inner);
9892                let optimism_jovian = self.is_optimism_jovian_at_header(
9893                    &base_block.header.inner,
9894                    parent_fees.optimism_jovian,
9895                );
9896
9897                #[cfg(feature = "monad")]
9898                let monad_context = if self.is_monad() {
9899                    Some(self.monad_context_for_child_of_block_number(base_number).await?)
9900                } else {
9901                    None
9902                };
9903                #[cfg(not(feature = "monad"))]
9904                let monad_context = None;
9905
9906                self.with_database_at(block_request, |state, block_env| {
9907                    simulate_at(
9908                        state,
9909                        block_env,
9910                        base_number,
9911                        base_timestamp,
9912                        base_hash,
9913                        parent_fees.base_fee,
9914                        monad_context,
9915                        base_block.header.base_fee_per_gas().unwrap_or_default(),
9916                        base_block.header.excess_blob_gas().unwrap_or_default(),
9917                        base_block.header.blob_gas_used().unwrap_or_default(),
9918                        parent_fees.extra_data,
9919                        optimism_jovian,
9920                    )
9921                })
9922                .await?
9923            }
9924        }
9925    }
9926
9927    pub fn get_blob_by_tx_hash(&self, hash: B256) -> Result<Option<Vec<alloy_consensus::Blob>>> {
9928        let storage = self.blockchain.storage.read();
9929        Ok(storage.transactions.get(&hash).and_then(|mined| {
9930            storage
9931                .blocks
9932                .get(&mined.block_hash)?
9933                .body
9934                .transactions
9935                .get(mined.info.transaction_index as usize)?
9936                .as_ref()
9937                .sidecar()
9938                .map(|sidecar| sidecar.sidecar.blobs().to_vec())
9939        }))
9940    }
9941
9942    /// Sets the fee token for a user address (Tempo-only).
9943    pub async fn set_fee_token(&self, user: Address, token: Address) -> DatabaseResult<()> {
9944        self.with_tempo_storage(|| {
9945            let mut fee_manager = TipFeeManager::new();
9946            fee_manager
9947                .set_user_token(user, IFeeManager::setUserTokenCall { token })
9948                .map_err(tempo_db_err)
9949        })
9950        .await
9951    }
9952
9953    /// Sets the fee token for a validator address (Tempo-only).
9954    pub async fn set_validator_fee_token(
9955        &self,
9956        validator: Address,
9957        token: Address,
9958    ) -> DatabaseResult<()> {
9959        self.with_tempo_storage(|| {
9960            let mut fee_manager = TipFeeManager::new();
9961            // Use Address::ZERO as beneficiary so the check `sender != beneficiary` passes
9962            fee_manager
9963                .set_validator_token(
9964                    validator,
9965                    IFeeManager::setValidatorTokenCall { token },
9966                    Address::ZERO,
9967                )
9968                .map_err(tempo_db_err)
9969        })
9970        .await
9971    }
9972
9973    /// Mints FeeAMM liquidity for a token pair (Tempo-only).
9974    pub async fn set_fee_amm_liquidity(
9975        &self,
9976        user_token: Address,
9977        validator_token: Address,
9978        amount: U256,
9979    ) -> DatabaseResult<()> {
9980        // T3+ rejects minting to the zero address.
9981        let admin = Address::repeat_byte(0x11);
9982        self.with_tempo_storage(|| {
9983            // Mint the required tokens to admin so it can provide liquidity.
9984            // grant_role_internal bypasses the caller check, matching genesis seeding.
9985            for &token_address in &[user_token, validator_token] {
9986                let mut token = TIP20Token::from_address(token_address).map_err(tempo_db_err)?;
9987                token
9988                    .grant_role_internal(admin, TIP20Token::issuer_role())
9989                    .map_err(tempo_db_err)?;
9990                token.mint(admin, ITIP20::mintCall { to: admin, amount }).map_err(tempo_db_err)?;
9991            }
9992            let mut fee_manager = TipFeeManager::new();
9993            fee_manager
9994                .mint(admin, user_token, validator_token, amount, admin)
9995                .map_err(tempo_db_err)?;
9996            Ok(())
9997        })
9998        .await
9999    }
10000
10001    /// Sets an account's balance for a deployed TIP-20 token (Tempo-only).
10002    pub async fn set_tip20_balance(
10003        &self,
10004        address: Address,
10005        token_address: Address,
10006        balance: U256,
10007    ) -> DatabaseResult<()> {
10008        if self.try_set_tip20_balance(address, token_address, balance).await? {
10009            return Ok(());
10010        }
10011
10012        Err(tempo_db_err(format!("address {token_address} is not a deployed TIP-20 token")))
10013    }
10014
10015    /// Sets an account's balance if the address is a deployed TIP-20 token (Tempo-only).
10016    pub async fn try_set_tip20_balance(
10017        &self,
10018        address: Address,
10019        token_address: Address,
10020        balance: U256,
10021    ) -> DatabaseResult<bool> {
10022        self.with_tempo_storage(|| {
10023            if !TIP20Factory::new().is_tip20(token_address).map_err(tempo_db_err)? {
10024                return Ok(false);
10025            }
10026
10027            let mut token = TIP20Token::from_address(token_address).map_err(tempo_db_err)?;
10028            token.balances[address].write(balance).map_err(tempo_db_err)?;
10029            Ok(true)
10030        })
10031        .await
10032    }
10033
10034    /// Runs `f` inside a Tempo storage context initialized from the current state
10035    /// (Tempo-only).
10036    async fn with_tempo_storage<R>(&self, f: impl FnOnce() -> R) -> R {
10037        let hardfork = self.hardfork();
10038        // One consistent snapshot of the current env to build the storage context.
10039        let (chain_id, timestamp, block_number) = {
10040            let env = self.evm_env.read();
10041            (env.cfg_env.chain_id, env.block_env.timestamp, env.block_env.number.to::<u64>())
10042        };
10043        let mut db = self.db.write().await;
10044        let mut storage = AnvilStorageProvider::new(
10045            &mut **db,
10046            chain_id,
10047            timestamp,
10048            block_number,
10049            hardfork.into(),
10050        );
10051        StorageCtx::enter(&mut storage, f)
10052    }
10053}
10054
10055/// Converts a Tempo error into an anvil [`DatabaseError`].
10056fn tempo_db_err<E: std::fmt::Display>(e: E) -> DatabaseError {
10057    DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))
10058}
10059
10060/// Get max nonce from transaction pool by address.
10061fn get_pool_transactions_nonce(
10062    pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
10063    address: Address,
10064) -> Option<u64> {
10065    if let Some(highest_nonce) = pool_transactions
10066        .iter()
10067        .filter(|tx| {
10068            *tx.pending_transaction.sender() == address
10069                && !tx.pending_transaction.transaction.as_ref().has_nonzero_tempo_nonce_key()
10070        })
10071        .map(|tx| tx.pending_transaction.nonce())
10072        .max()
10073    {
10074        let tx_count = highest_nonce.saturating_add(1);
10075        return Some(tx_count);
10076    }
10077    None
10078}
10079
10080impl<N: Network> Backend<N>
10081where
10082    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
10083{
10084    /// Validates a transaction candidate selected for mining.
10085    fn validate_mining_pool_transaction_for(
10086        &self,
10087        pool_tx: &PoolTransaction<FoundryTxEnvelope>,
10088        account: &AccountInfo,
10089        evm_env: &EvmEnv,
10090    ) -> Result<(), InvalidTransactionError> {
10091        #[cfg(feature = "monad")]
10092        if self.validate_monad_mining_pool_transaction_for(pool_tx, account, evm_env)? {
10093            return Ok(());
10094        }
10095
10096        self.validate_pool_transaction_for(&pool_tx.pending_transaction, account, evm_env)
10097    }
10098
10099    /// Runs Tempo's transaction-pool validation for `pending` against the latest state.
10100    ///
10101    /// This is the pipeline the node runs before admitting a transaction: it resolves the fee
10102    /// token the way execution will, including a token inferred from the called TIP20 or DEX
10103    /// swap and a preference the transaction itself sets, charges intrinsic gas, and checks the
10104    /// fee payer's balance against the maximum fee with execution's rounding, all without
10105    /// committing state.
10106    async fn validate_tempo_pool_transaction(
10107        &self,
10108        pending: &PendingTransaction<FoundryTxEnvelope>,
10109        evm_env: &EvmEnv,
10110    ) -> Result<(), BlockchainError> {
10111        let tx = pending.transaction.as_ref();
10112        let tx_env: TempoTxEnv =
10113            FromTxWithEncoded::from_encoded_tx(tx, *pending.sender(), tx.encoded_2718().into());
10114        let evm_env = evm_env.clone();
10115        self.with_database_at(None, move |state, _| {
10116            let cache_db = CacheDB::new(&*state);
10117            let mut inspector = self.build_inspector();
10118            let mut evm = TempoEvmFactory::default().create_evm_with_inspector(
10119                WrapDatabaseRef(&cache_db as &dyn DatabaseRef),
10120                Self::build_tempo_evm_env(&evm_env, self.tempo_hardfork()),
10121                &mut inspector,
10122            );
10123            self.inject_tempo_precompiles(&mut evm, &evm_env);
10124            evm.configure_for_pool();
10125            let (result, _) = evm.validate_pool_transaction(tx_env);
10126            result.map(drop).map_err(|err| match err {
10127                // Tempo reports a fee token shortfall through the native funds error; name the
10128                // token balance instead, since the sender holds no native balance to speak of.
10129                EVMError::Transaction(TempoInvalidTransaction::EthInvalidTransaction(
10130                    InvalidTransaction::LackOfFundForMaxFee { fee, balance },
10131                )) => InvalidTransactionError::TempoInsufficientFeeTokenBalance {
10132                    balance: *balance,
10133                    required: *fee,
10134                }
10135                .into(),
10136                err => err.into(),
10137            })
10138        })
10139        .await?
10140    }
10141}
10142
10143#[async_trait::async_trait]
10144impl<N: Network> TransactionValidator<FoundryTxEnvelope> for Backend<N>
10145where
10146    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
10147{
10148    async fn validate_pool_transaction(
10149        &self,
10150        tx: &PendingTransaction<FoundryTxEnvelope>,
10151    ) -> Result<(), BlockchainError> {
10152        let address = *tx.sender();
10153        let account = self.get_account(address).await?;
10154        #[cfg_attr(not(feature = "base"), allow(unused_mut))]
10155        let mut evm_env = self.next_evm_env(&**self.db.read().await)?;
10156        #[cfg(feature = "base")]
10157        if tx.transaction.as_ref().is_eip8130() {
10158            evm_env.block_env.timestamp = U256::from(self.eip8130_pool_timestamp());
10159        }
10160
10161        #[cfg(feature = "base")]
10162        if let FoundryTxEnvelope::Eip8130(signed) = tx.transaction.as_ref() {
10163            let body = signed.tx();
10164            let now = self.eip8130_pool_timestamp_ms();
10165            signed
10166                .validate_admission_static(self.chain_id().to())
10167                .map_err(|error| BlockchainError::Eip8130TransactionRejected(error.to_string()))?;
10168            signed
10169                .validate_timestamp(now)
10170                .map_err(|error| BlockchainError::Eip8130TransactionRejected(error.to_string()))?;
10171
10172            let (nonce_key_first_use, ready_for_execution) =
10173                if body.nonce_key == Eip8130Constants::NONCE_KEY_MAX {
10174                    (false, true)
10175                } else if body.nonce_key.is_zero() {
10176                    (account.nonce == 0, body.nonce_sequence == account.nonce)
10177                } else {
10178                    let slot = NonceManagerStorage::nonce_slot(address, body.nonce_key).map_err(
10179                        |error| BlockchainError::Eip8130TransactionRejected(error.to_string()),
10180                    )?;
10181                    let word = self.storage_at(NonceManagerStorage::ADDRESS, slot, None).await?;
10182                    let channel_nonce = Eip8130Nonce::decode_channel_nonce(word.into()).to::<u64>();
10183                    (channel_nonce == 0, body.nonce_sequence == channel_nonce)
10184                };
10185            let intrinsic = IntrinsicGas::compute(
10186                signed,
10187                &signed.encoded_2718(),
10188                &IntrinsicGasInput::worst_case(
10189                    nonce_key_first_use,
10190                    IntrinsicGasInput::sender_auto_delegated(&body.account_changes),
10191                    body.payer.is_some(),
10192                ),
10193            )
10194            .map_err(|error| BlockchainError::Eip8130TransactionRejected(error.to_string()))?;
10195            if intrinsic.execution_gas_available(body.gas_limit).is_none() {
10196                return Err(BlockchainError::Eip8130TransactionRejected(
10197                    "sender intrinsic gas exceeds transaction gas limit".to_string(),
10198                ));
10199            }
10200
10201            let payer = body.payer.unwrap_or(address);
10202            let payer_account =
10203                if payer == address { account.clone() } else { self.get_account(payer).await? };
10204            FeeCheck::validate_balance(
10205                payer_account.balance,
10206                body.gas_limit,
10207                intrinsic.payer_auth,
10208                body.max_fee_per_gas,
10209            )
10210            .map_err(|error| BlockchainError::Eip8130TransactionRejected(error.to_string()))?;
10211
10212            // Run once against read-only state so authorization, account changes, replay checks,
10213            // and payer rules fail at admission instead of after entering the pool. A buffered
10214            // nonce is the sole expected inclusion error: Base's pool accepts it until the lane
10215            // gap is filled, while every earlier authorize/apply step has still been validated.
10216            let db = self.db.read().await;
10217            let mut inspector = NoOpInspector;
10218            let validation = self.transact_envelope_with_inspector_ref_and_context(
10219                &**db,
10220                &evm_env,
10221                &mut inspector,
10222                tx,
10223                None,
10224            );
10225            Eip8130PhaseStatuses::clear();
10226            match validation {
10227                Ok(_) => {}
10228                Err(BlockchainError::Eip8130TransactionRejected(reason))
10229                    if !ready_for_execution && reason.contains("is ahead of the channel nonce") => {
10230                }
10231                Err(error) => return Err(error),
10232            }
10233        }
10234
10235        // Tempo AA: validate time bounds (async checks)
10236        if let FoundryTxEnvelope::Tempo(aa_tx) = tx.transaction.as_ref() {
10237            let tempo_tx = aa_tx.tx();
10238            let current_time = evm_env.block_env.timestamp.saturating_to::<u64>();
10239
10240            // Reject if valid_before is expired or too close to current time (< 3 seconds)
10241            const AA_VALID_BEFORE_MIN_SECS: u64 = 3;
10242            if let Some(valid_before) = tempo_tx.valid_before.map(|v| v.get()) {
10243                let min_allowed = current_time.saturating_add(AA_VALID_BEFORE_MIN_SECS);
10244                if valid_before <= min_allowed {
10245                    return Err(InvalidTransactionError::TempoValidBeforeExpired {
10246                        valid_before,
10247                        min_allowed,
10248                    }
10249                    .into());
10250                }
10251
10252                let hardfork = self.tempo_hardfork();
10253                if hardfork.is_t1() && tempo_tx.is_expiring_nonce_tx() {
10254                    let max_expiry_secs = hardfork.expiring_nonce_max_expiry_secs();
10255                    let max_allowed = current_time.saturating_add(max_expiry_secs);
10256                    if valid_before > max_allowed {
10257                        return Err(InvalidTransactionError::TempoValidBeforeTooFar {
10258                            valid_before,
10259                            max_expiry_secs,
10260                            max_allowed,
10261                        }
10262                        .into());
10263                    }
10264                }
10265            }
10266
10267            // Reject if valid_after is too far in the future. Mirrors Tempo's default pool limit
10268            // (`DEFAULT_AA_VALID_AFTER_MAX_SECS`), which is aligned with its queued transaction
10269            // lifetime.
10270            const AA_VALID_AFTER_MAX_SECS: u64 = 120;
10271            if let Some(valid_after) = tempo_tx.valid_after.map(|v| v.get()) {
10272                let max_allowed = current_time.saturating_add(AA_VALID_AFTER_MAX_SECS);
10273                if valid_after > max_allowed {
10274                    return Err(InvalidTransactionError::TempoValidAfterTooFar {
10275                        valid_after,
10276                        max_valid_after_secs: AA_VALID_AFTER_MAX_SECS,
10277                        max_allowed,
10278                    }
10279                    .into());
10280                }
10281            }
10282        }
10283
10284        self.validate_pool_transaction_for(tx, &account, &evm_env)?;
10285
10286        // Tempo charges gas in a fee token for every transaction type, never in the native token,
10287        // so the node's own pool validation replaces the native balance check skipped above.
10288        if self.is_tempo() && !self.disable_pool_balance_checks {
10289            self.validate_tempo_pool_transaction(tx, &evm_env).await?;
10290        }
10291
10292        Ok(())
10293    }
10294
10295    fn validate_pool_transaction_for(
10296        &self,
10297        pending: &PendingTransaction<FoundryTxEnvelope>,
10298        account: &AccountInfo,
10299        evm_env: &EvmEnv,
10300    ) -> Result<(), InvalidTransactionError> {
10301        let tx = &pending.transaction;
10302
10303        if let Some(tx_chain_id) = tx.chain_id() {
10304            let chain_id = self.chain_id();
10305            if chain_id.to::<u64>() != tx_chain_id {
10306                if let FoundryTxEnvelope::Legacy(tx) = tx.as_ref() {
10307                    // <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md>
10308                    if evm_env.cfg_env.spec >= SpecId::SPURIOUS_DRAGON && tx.chain_id().is_none() {
10309                        debug!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V");
10310                        return Err(InvalidTransactionError::IncompatibleEIP155);
10311                    }
10312                } else {
10313                    debug!(target: "backend", ?chain_id, ?tx_chain_id, "invalid chain id");
10314                    return Err(InvalidTransactionError::InvalidChainId);
10315                }
10316            }
10317        }
10318
10319        // Reject native value transfers on Tempo networks
10320        if self.is_tempo() && !tx.value().is_zero() {
10321            warn!(target: "backend", "[{:?}] native value transfer not allowed in Tempo mode", tx.hash());
10322            return Err(InvalidTransactionError::TempoNativeValueTransfer);
10323        }
10324
10325        // Tempo AA T5: cap authorization list size
10326        if self.is_tempo_hardfork_active(TempoHardfork::T5)
10327            && let FoundryTxEnvelope::Tempo(aa_tx) = tx.as_ref()
10328        {
10329            const MAX_TEMPO_AUTHORIZATIONS: usize = 16;
10330            let auth_count = aa_tx.tx().tempo_authorization_list.len();
10331            if auth_count > MAX_TEMPO_AUTHORIZATIONS {
10332                warn!(target: "backend", "[{:?}] Tempo tx has too many authorizations: {}", tx.hash(), auth_count);
10333                return Err(InvalidTransactionError::TempoTooManyAuthorizations {
10334                    count: auth_count,
10335                    max: MAX_TEMPO_AUTHORIZATIONS,
10336                });
10337            }
10338        }
10339
10340        #[cfg(feature = "base")]
10341        if let FoundryTxEnvelope::Eip8130(signed) = pending.transaction.as_ref() {
10342            signed
10343                .validate_admission_static(evm_env.cfg_env.chain_id)
10344                .map_err(|error| InvalidTransactionError::Eip8130(error.to_string()))?;
10345            signed
10346                .validate_timestamp(
10347                    evm_env.block_env.timestamp.saturating_to::<u64>().saturating_mul(1_000),
10348                )
10349                .map_err(|error| InvalidTransactionError::Eip8130(error.to_string()))?;
10350        }
10351
10352        // Nonce validation — skip for deposits (L1→L2), EIP-8130, and Tempo.
10353        #[cfg(any(feature = "base", feature = "optimism"))]
10354        let is_deposit_tx = pending.transaction.as_ref().is_deposit();
10355        #[cfg(not(any(feature = "base", feature = "optimism")))]
10356        let is_deposit_tx = false;
10357        #[cfg(feature = "base")]
10358        let is_eip8130_tx = pending.transaction.as_ref().is_eip8130();
10359        #[cfg(not(feature = "base"))]
10360        let is_eip8130_tx = false;
10361        let is_tempo_tx = pending.transaction.as_ref().is_tempo();
10362        let nonce = tx.nonce();
10363        if nonce < account.nonce && !is_deposit_tx && !is_eip8130_tx && !is_tempo_tx {
10364            debug!(target: "backend", "[{:?}] nonce too low", tx.hash());
10365            return Err(InvalidTransactionError::NonceTooLow);
10366        }
10367
10368        #[cfg(feature = "monad")]
10369        self.validate_monad_transaction_type(tx)?;
10370
10371        // EIP-4844 structural validation
10372        if evm_env.cfg_env.spec >= SpecId::CANCUN && tx.is_eip4844() {
10373            // Heavy (blob validation) checks
10374            let blob_tx = match tx.as_ref() {
10375                FoundryTxEnvelope::Eip4844(tx) => tx.tx(),
10376                _ => unreachable!(),
10377            };
10378
10379            let blob_count = blob_tx.tx().blob_versioned_hashes.len();
10380
10381            // Ensure there are blob hashes.
10382            if blob_count == 0 {
10383                return Err(InvalidTransactionError::NoBlobHashes);
10384            }
10385
10386            // Ensure the tx does not exceed the max blobs per transaction.
10387            let max_blobs_per_tx = self.blob_params().max_blobs_per_tx as usize;
10388            if blob_count > max_blobs_per_tx {
10389                return Err(InvalidTransactionError::TooManyBlobs(blob_count, max_blobs_per_tx));
10390            }
10391
10392            // Check for any blob validation errors if not impersonating.
10393            if !self.skip_blob_validation(Some(*pending.sender()))
10394                && let Err(err) = blob_tx.validate(EnvKzgSettings::default().get())
10395            {
10396                return Err(InvalidTransactionError::BlobTransactionValidationError(err));
10397            }
10398        }
10399
10400        // EIP-3860 initcode size validation, respects --code-size-limit / --disable-code-size-limit
10401        if evm_env.cfg_env.spec >= SpecId::SHANGHAI && tx.kind().is_create() {
10402            let max_initcode_size = evm_env
10403                .cfg_env
10404                .limit_contract_code_size
10405                .map(|limit| limit.saturating_mul(2))
10406                .unwrap_or_else(|| self.max_initcode_size(evm_env));
10407            if tx.input().len() > max_initcode_size {
10408                return Err(InvalidTransactionError::MaxInitCodeSizeExceeded);
10409            }
10410        }
10411
10412        // Balance and fee related checks
10413        if !self.disable_pool_balance_checks {
10414            // Gas limit validation. Under EIP-2780 a transaction's intrinsic gas starts at a lower
10415            // base, which execution then checks in full.
10416            let min_gas = if evm_env.cfg_env.enable_amsterdam_eip2780 {
10417                eip2780::TX_BASE_COST
10418            } else {
10419                MIN_TRANSACTION_GAS as u64
10420            };
10421            if tx.gas_limit() < min_gas {
10422                debug!(target: "backend", "[{:?}] gas too low", tx.hash());
10423                return Err(InvalidTransactionError::GasTooLow);
10424            }
10425
10426            // Check tx gas limit against block gas limit, if block gas limit is set.
10427            if !evm_env.cfg_env.disable_block_gas_limit
10428                && tx.gas_limit() > evm_env.block_env.gas_limit
10429            {
10430                debug!(target: "backend", "[{:?}] gas too high", tx.hash());
10431                return Err(InvalidTransactionError::GasTooHigh(ErrDetail {
10432                    detail: String::from("tx.gas_limit > env.block.gas_limit"),
10433                }));
10434            }
10435
10436            // Check tx gas limit against tx gas limit cap (Osaka hard fork and later).
10437            if self.enforced_tx_gas_limit_cap(evm_env).is_some_and(|cap| tx.gas_limit() > cap) {
10438                debug!(target: "backend", "[{:?}] gas too high", tx.hash());
10439                return Err(InvalidTransactionError::GasTooHigh(ErrDetail {
10440                    detail: String::from("tx.gas_limit > resolved tx gas limit cap"),
10441                }));
10442            }
10443
10444            // EIP-1559 fee validation (London hard fork and later).
10445            if evm_env.cfg_env.spec >= SpecId::LONDON {
10446                if tx.max_fee_per_gas() < evm_env.block_env.basefee.into() && !is_deposit_tx {
10447                    debug!(target: "backend", "max fee per gas={}, too low, block basefee={}", tx.max_fee_per_gas(), evm_env.block_env.basefee);
10448                    return Err(InvalidTransactionError::FeeCapTooLow);
10449                }
10450
10451                if !evm_env.cfg_env.disable_priority_fee_check
10452                    && let (Some(max_priority_fee_per_gas), max_fee_per_gas) =
10453                        (tx.as_ref().max_priority_fee_per_gas(), tx.as_ref().max_fee_per_gas())
10454                    && max_priority_fee_per_gas > max_fee_per_gas
10455                {
10456                    debug!(target: "backend", "max priority fee per gas={}, too high, max fee per gas={}", max_priority_fee_per_gas, max_fee_per_gas);
10457                    return Err(InvalidTransactionError::TipAboveFeeCap);
10458                }
10459            }
10460
10461            // EIP-4844 blob fee validation
10462            if evm_env.cfg_env.spec >= SpecId::CANCUN
10463                && tx.is_eip4844()
10464                && let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas()
10465                && let Some(blob_gas_and_price) = &evm_env.block_env.blob_excess_gas_and_price
10466                && max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice
10467            {
10468                debug!(target: "backend", "max fee per blob gas={}, too low, block blob gas price={}", max_fee_per_blob_gas, blob_gas_and_price.blob_gasprice);
10469                return Err(InvalidTransactionError::BlobFeeCapTooLow(
10470                    max_fee_per_blob_gas,
10471                    blob_gas_and_price.blob_gasprice,
10472                ));
10473            }
10474
10475            let value = tx.value();
10476            match tx.as_ref() {
10477                #[cfg(any(feature = "base", feature = "optimism"))]
10478                FoundryTxEnvelope::Deposit(deposit_tx) => {
10479                    // Deposit transactions
10480                    // https://specs.optimism.io/protocol/deposits.html#execution
10481                    // 1. no gas cost check required since already have prepaid gas from L1
10482                    // 2. increment account balance by deposited amount before checking for
10483                    //    sufficient funds `tx.value <= existing account value + deposited value`
10484                    if value > account.balance + U256::from(deposit_tx.mint) {
10485                        debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + U256::from(deposit_tx.mint), value, *pending.sender());
10486                        return Err(InvalidTransactionError::InsufficientFunds);
10487                    }
10488                }
10489                #[cfg(feature = "base")]
10490                FoundryTxEnvelope::Eip8130(_) => {
10491                    // EIP-8130 affordability is checked asynchronously against
10492                    // the resolved payer, including payer-authentication gas.
10493                }
10494                FoundryTxEnvelope::Tempo(_) => {
10495                    // Tempo AA transactions pay gas with fee tokens, not ETH.
10496                    // Fee token balance is validated in validate_pool_transaction (async).
10497                }
10498                _ if self.is_tempo() => {
10499                    // Every transaction on Tempo pays gas in a fee token; forked mainnet senders
10500                    // hold no native balance at all. The fee token balance is validated in
10501                    // validate_pool_transaction (async).
10502                }
10503                #[cfg(feature = "monad")]
10504                _ if self.validate_monad_transaction_funds(pending, account, evm_env)? => {}
10505                _ => {
10506                    let max_cost = (tx.gas_limit() as u128)
10507                        .saturating_mul(tx.max_fee_per_gas())
10508                        .saturating_add(
10509                            tx.blob_gas_used()
10510                                .map(|g| g as u128)
10511                                .unwrap_or(0)
10512                                .mul(tx.max_fee_per_blob_gas().unwrap_or(0)),
10513                        );
10514                    // check sufficient funds: `gas * price + value`
10515                    let req_funds =
10516                        max_cost.checked_add(value.saturating_to()).ok_or_else(|| {
10517                            debug!(target: "backend", "[{:?}] cost too high", tx.hash());
10518                            InvalidTransactionError::InsufficientFunds
10519                        })?;
10520                    if account.balance < U256::from(req_funds) {
10521                        debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender());
10522                        return Err(InvalidTransactionError::InsufficientFunds);
10523                    }
10524                }
10525            }
10526        }
10527        Ok(())
10528    }
10529
10530    fn validate_for(
10531        &self,
10532        tx: &PendingTransaction<FoundryTxEnvelope>,
10533        account: &AccountInfo,
10534        evm_env: &EvmEnv,
10535    ) -> Result<(), InvalidTransactionError> {
10536        self.validate_pool_transaction_for(tx, account, evm_env)?;
10537        // EIP-8130 counts nonces per channel in the nonce manager, so `tx.nonce()` is not
10538        // comparable to the account's protocol nonce. Admission already validated the channel.
10539        #[cfg(feature = "base")]
10540        if tx.transaction.as_ref().is_eip8130() {
10541            return Ok(());
10542        }
10543        if tx.nonce() > account.nonce {
10544            return Err(InvalidTransactionError::NonceTooHigh);
10545        }
10546        Ok(())
10547    }
10548}
10549
10550/// Replaces the cached hash of a [`Signed`] transaction, preserving the inner tx and signature.
10551fn rehash<T>(signed: Signed<T>, hash: B256) -> Signed<T>
10552where
10553    T: alloy_consensus::transaction::RlpEcdsaEncodableTx,
10554{
10555    let (t, sig, _) = signed.into_parts();
10556    Signed::new_unchecked(t, sig, hash)
10557}
10558
10559fn build_rpc_transaction(
10560    envelope: AnyTxEnvelope,
10561    from: Address,
10562    block: Option<&Block>,
10563    info: Option<&TransactionInfo>,
10564    effective_gas_price: Option<u128>,
10565) -> AnyRpcTransaction {
10566    let tx = Transaction {
10567        inner: Recovered::new_unchecked(envelope, from),
10568        block_hash: block.map(|block| block.header.hash_slow()),
10569        block_number: block.map(|block| block.header.number()),
10570        transaction_index: info.map(|info| info.transaction_index),
10571        effective_gas_price,
10572        block_timestamp: block.map(|block| block.header.timestamp()),
10573    };
10574    AnyRpcTransaction::from(WithOtherFields::new(tx))
10575}
10576
10577/// Creates a `AnyRpcTransaction` as it's expected for the `eth` RPC api from storage data
10578pub fn transaction_build(
10579    tx_hash: Option<B256>,
10580    eth_transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>,
10581    block: Option<&Block>,
10582    info: Option<TransactionInfo>,
10583    base_fee: Option<u64>,
10584) -> AnyRpcTransaction {
10585    let mined_from = info.as_ref().map(|info| info.from);
10586
10587    #[cfg(any(feature = "base", feature = "optimism"))]
10588    if let FoundryTxEnvelope::Deposit(deposit_tx) = eth_transaction.as_ref() {
10589        let dep_tx = deposit_tx;
10590
10591        let ser = serde_json::to_value(dep_tx).expect("could not serialize TxDeposit");
10592        let maybe_deposit_fields = OtherFields::try_from(ser);
10593
10594        match maybe_deposit_fields {
10595            Ok(mut fields) => {
10596                fields.remove("hash");
10597                fields.remove("from");
10598                // Add zeroed signature fields for backwards compatibility
10599                // https://specs.optimism.io/protocol/deposits.html#the-deposited-transaction-type
10600                fields.insert("v".to_string(), serde_json::to_value("0x0").unwrap());
10601                fields.insert("r".to_string(), serde_json::to_value(B256::ZERO).unwrap());
10602                fields.insert(String::from("s"), serde_json::to_value(B256::ZERO).unwrap());
10603                fields.insert(String::from("nonce"), serde_json::to_value("0x0").unwrap());
10604
10605                let inner = UnknownTypedTransaction {
10606                    ty: AnyTxType(DEPOSIT_TX_TYPE_ID),
10607                    fields,
10608                    memo: Default::default(),
10609                };
10610
10611                let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
10612                    hash: tx_hash.unwrap_or_else(|| eth_transaction.hash()),
10613                    inner,
10614                });
10615
10616                return build_rpc_transaction(
10617                    envelope,
10618                    mined_from.unwrap_or(deposit_tx.from),
10619                    block,
10620                    info.as_ref(),
10621                    None,
10622                );
10623            }
10624            Err(_) => {
10625                error!(target: "backend", "failed to serialize deposit transaction");
10626            }
10627        }
10628    }
10629
10630    #[cfg(feature = "base")]
10631    if let FoundryTxEnvelope::Eip8130(eip8130_tx) = eth_transaction.as_ref() {
10632        let from = mined_from.unwrap_or_else(|| eth_transaction.recover().unwrap_or_default());
10633        let rpc_tx = BaseRpcTransaction::from_transaction(
10634            Recovered::new_unchecked(BaseTxEnvelope::Eip8130(eip8130_tx.clone()), from),
10635            BaseTransactionInfo::default(),
10636        );
10637        let mut fields = OtherFields::try_from(
10638            serde_json::to_value(rpc_tx)
10639                .expect("could not serialize canonical EIP-8130 transaction"),
10640        )
10641        .expect("EIP-8130 transaction must serialize to an object");
10642        for key in [
10643            "hash",
10644            "type",
10645            "from",
10646            "blockHash",
10647            "blockNumber",
10648            "transactionIndex",
10649            "effectiveGasPrice",
10650        ] {
10651            fields.remove(key);
10652        }
10653        let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
10654            hash: eth_transaction.hash(),
10655            inner: UnknownTypedTransaction {
10656                ty: AnyTxType(EIP8130_TRANSACTION_TYPE),
10657                fields,
10658                memo: Default::default(),
10659            },
10660        });
10661        return build_rpc_transaction(envelope, from, block, info.as_ref(), None);
10662    }
10663
10664    if let FoundryTxEnvelope::Celo(tx) = eth_transaction.as_ref() {
10665        let mut fields =
10666            OtherFields::try_from(serde_json::to_value(tx).expect("valid CIP-64 transaction"))
10667                .expect("CIP-64 transaction serializes to an object");
10668        fields.remove("hash");
10669        let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
10670            hash: tx_hash.unwrap_or_else(|| eth_transaction.hash()),
10671            inner: UnknownTypedTransaction {
10672                ty: AnyTxType(foundry_primitives::CIP64_TX_TYPE),
10673                fields,
10674                memo: Default::default(),
10675            },
10676        });
10677        let from = mined_from.unwrap_or_else(|| eth_transaction.recover().unwrap_or_default());
10678        let effective_gas_price = block.map(|_| eth_transaction.effective_gas_price(base_fee));
10679        return build_rpc_transaction(envelope, from, block, info.as_ref(), effective_gas_price);
10680    }
10681    if let FoundryTxEnvelope::Tempo(tempo_tx) = eth_transaction.as_ref() {
10682        let from = mined_from.unwrap_or_else(|| eth_transaction.recover().unwrap_or_default());
10683        let ser = serde_json::to_value(tempo_tx).expect("could not serialize Tempo transaction");
10684        let maybe_tempo_fields = OtherFields::try_from(ser);
10685
10686        match maybe_tempo_fields {
10687            Ok(mut fields) => {
10688                fields.remove("hash");
10689                let inner = UnknownTypedTransaction {
10690                    ty: AnyTxType(TEMPO_TX_TYPE_ID),
10691                    fields,
10692                    memo: Default::default(),
10693                };
10694
10695                let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
10696                    hash: tx_hash.unwrap_or_else(|| eth_transaction.hash()),
10697                    inner,
10698                });
10699
10700                return build_rpc_transaction(envelope, from, block, info.as_ref(), None);
10701            }
10702            Err(_) => {
10703                error!(target: "backend", "failed to serialize tempo transaction");
10704            }
10705        }
10706    }
10707
10708    let from = mined_from.unwrap_or_else(|| eth_transaction.recover().unwrap_or_default());
10709    let effective_gas_price = eth_transaction.effective_gas_price(base_fee);
10710
10711    // if a specific hash was provided we update the transaction's hash
10712    // This is important for impersonated transactions since they all use the
10713    // `BYPASS_SIGNATURE` which would result in different hashes
10714    // Note: for impersonated transactions this only concerns pending transactions because
10715    // there's no `info` yet.
10716    let hash = tx_hash.unwrap_or_else(|| eth_transaction.hash());
10717
10718    let eth_envelope = FoundryTxEnvelope::from(eth_transaction)
10719        .try_into_eth()
10720        .expect("non-standard transactions are handled above");
10721
10722    let envelope = match eth_envelope {
10723        TxEnvelope::Legacy(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Legacy(rehash(s, hash))),
10724        TxEnvelope::Eip1559(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip1559(rehash(s, hash))),
10725        TxEnvelope::Eip2930(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip2930(rehash(s, hash))),
10726        TxEnvelope::Eip4844(s) => {
10727            let s = if block.is_some() { s.map(TxEip4844Variant::drop_sidecar) } else { s };
10728            AnyTxEnvelope::Ethereum(TxEnvelope::Eip4844(rehash(s, hash)))
10729        }
10730        TxEnvelope::Eip7702(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(rehash(s, hash))),
10731    };
10732
10733    build_rpc_transaction(envelope, from, block, info.as_ref(), Some(effective_gas_price))
10734}
10735
10736/// Prove a storage key's existence or nonexistence in the account's storage trie.
10737///
10738/// `storage_key` is the hash of the desired storage key, meaning
10739/// this will only work correctly under a secure trie.
10740/// `storage_key` == keccak(key)
10741pub fn prove_storage(
10742    storage: &alloy_primitives::map::U256Map<U256>,
10743    keys: &[B256],
10744) -> (B256, Vec<Vec<Bytes>>) {
10745    let keys: Vec<_> = keys.iter().map(|key| Nibbles::unpack(keccak256(key))).collect();
10746
10747    let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(keys.clone()));
10748
10749    for (key, value) in trie_storage(storage) {
10750        builder.add_leaf(key, &value);
10751    }
10752
10753    let root = builder.root();
10754
10755    let mut proofs = Vec::new();
10756    let all_proof_nodes = builder.take_proof_nodes();
10757
10758    for proof_key in keys {
10759        // Iterate over all proof nodes and find the matching ones.
10760        // The filtered results are guaranteed to be in order.
10761        let matching_proof_nodes =
10762            all_proof_nodes.matching_nodes_sorted(&proof_key).into_iter().map(|(_, node)| node);
10763        proofs.push(matching_proof_nodes.collect());
10764    }
10765
10766    (root, proofs)
10767}
10768
10769/// Builds an account and storage proof from one fixed local account set.
10770fn prove_account(db: &AddressMap<DbAccount>, address: Address, keys: &[B256]) -> AccountProof {
10771    let default = DbAccount::default();
10772    let account = db.get(&address).unwrap_or(&default);
10773
10774    let mut builder = HashBuilder::default()
10775        .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))]));
10776
10777    for (key, account) in trie_accounts(db) {
10778        builder.add_leaf(key, &account);
10779    }
10780
10781    let _ = builder.root();
10782
10783    let proof =
10784        builder.take_proof_nodes().into_nodes_sorted().into_iter().map(|(_, v)| v).collect();
10785    let (storage_hash, storage_proofs) = prove_storage(&account.storage, keys);
10786
10787    AccountProof {
10788        address,
10789        balance: account.info.balance,
10790        nonce: account.info.nonce,
10791        code_hash: account.info.code_hash(),
10792        storage_hash,
10793        account_proof: proof,
10794        storage_proof: keys
10795            .iter()
10796            .copied()
10797            .zip(storage_proofs)
10798            .map(|(key, proof)| {
10799                let storage_key: U256 = key.into();
10800                let value = account.storage.get(&storage_key).copied().unwrap_or_default();
10801                StorageProof { key: JsonStorageKey::Hash(key), value, proof }
10802            })
10803            .collect(),
10804    }
10805}
10806
10807pub fn is_arbitrum(chain_id: u64) -> bool {
10808    if let Ok(chain) = NamedChain::try_from(chain_id) {
10809        return chain.is_arbitrum();
10810    }
10811    false
10812}
10813
10814/// Commits a fully executed candidate cache to the live database.
10815fn commit_cache(db: &mut dyn Db, cache: revm::database::Cache) -> Result<(), BlockchainError> {
10816    let revm::database::Cache { accounts, contracts, .. } = cache;
10817    let mut changes = EvmState::default();
10818    for (address, db_account) in accounts {
10819        if db_account.account_state == AccountState::None {
10820            continue;
10821        }
10822
10823        let DbAccount { mut info, account_state, storage } = db_account;
10824        // `CacheDB` also records absent-account reads as `NotExisting`. They are not state changes
10825        // and must not become synthetic selfdestructs in the live database.
10826        if account_state == AccountState::NotExisting && db.basic(address)?.is_none() {
10827            continue;
10828        }
10829        if info.code.is_none() {
10830            info.code = contracts.get(&info.code_hash()).cloned();
10831        }
10832        let mut account = Account::from(info);
10833        account.mark_touch();
10834        match account_state {
10835            AccountState::NotExisting => account.mark_selfdestruct(),
10836            AccountState::StorageCleared => account.mark_created(),
10837            AccountState::Touched => {}
10838            AccountState::None => unreachable!(),
10839        }
10840        for (slot, value) in storage {
10841            let original = if account_state.is_storage_cleared() {
10842                U256::ZERO
10843            } else {
10844                db.storage(address, slot)?
10845            };
10846            account
10847                .storage
10848                .insert(slot, EvmStorageSlot::new_changed(original, value, TransactionId::ZERO));
10849        }
10850        changes.insert(address, account);
10851    }
10852    db.commit(changes);
10853    Ok(())
10854}
10855
10856fn simulate_rpc_error(code: i64, message: impl Into<String>) -> BlockchainError {
10857    BlockchainError::RpcError(RpcError {
10858        code: ErrorCode::from(code),
10859        message: message.into().into(),
10860        data: None,
10861    })
10862}
10863
10864fn previously_deleted_accounts(
10865    accounts: &AddressMap<DbAccount>,
10866    addresses: impl IntoIterator<Item = Address>,
10867) -> Vec<Address> {
10868    addresses
10869        .into_iter()
10870        .filter(|address| {
10871            accounts
10872                .get(address)
10873                .is_some_and(|account| account.account_state == AccountState::NotExisting)
10874        })
10875        .collect()
10876}
10877
10878fn preserve_deleted_storage(
10879    accounts: &mut AddressMap<DbAccount>,
10880    previously_deleted: Vec<Address>,
10881) {
10882    for address in previously_deleted {
10883        if let Some(account) = accounts.get_mut(&address)
10884            && account.account_state != AccountState::NotExisting
10885        {
10886            account.account_state = AccountState::StorageCleared;
10887        }
10888    }
10889}
10890
10891fn simulate_transaction_error(error: InvalidTransactionError) -> BlockchainError {
10892    let code = match &error {
10893        InvalidTransactionError::NonceTooLow => -38010,
10894        InvalidTransactionError::NonceTooHigh => -38011,
10895        InvalidTransactionError::NonceMaxValue => -32603,
10896        InvalidTransactionError::FeeCapTooLow => -38012,
10897        InvalidTransactionError::GasTooLow | InvalidTransactionError::GasTooHigh(_) => -38013,
10898        InvalidTransactionError::InsufficientFunds
10899        | InvalidTransactionError::InsufficientFundsForTransfer => -38014,
10900        _ => return BlockchainError::InvalidTransaction(error),
10901    };
10902
10903    simulate_rpc_error(code, format!("err: {error}"))
10904}
10905
10906pub(in crate::eth) fn sanitize_simulation_blocks<T>(
10907    blocks: Vec<SimBlock<T>>,
10908    base_number: u64,
10909    base_timestamp: u64,
10910    block_interval: u64,
10911) -> Result<Vec<SimBlock<T>>, BlockchainError> {
10912    let block_interval = block_interval.max(1);
10913    let mut sanitized = Vec::with_capacity(blocks.len());
10914    let mut previous_number = base_number;
10915    let mut previous_timestamp = base_timestamp;
10916
10917    for mut block in blocks {
10918        let mut overrides = block.block_overrides.take().unwrap_or_default();
10919        let default_number = previous_number.checked_add(1).ok_or_else(|| {
10920            simulate_rpc_error(-38020, "block number overflow while constructing sequence")
10921        })?;
10922        let number =
10923            overrides.number.map(|number| number.saturating_to()).unwrap_or(default_number);
10924
10925        if number <= previous_number {
10926            return Err(simulate_rpc_error(
10927                -38020,
10928                format!("block numbers must be in order: {number} <= {previous_number}"),
10929            ));
10930        }
10931
10932        let gap = number - previous_number - 1;
10933        let remaining = MAX_SIMULATE_BLOCKS as usize - sanitized.len();
10934        if gap as usize >= remaining {
10935            return Err(simulate_rpc_error(-38026, "too many blocks"));
10936        }
10937
10938        for offset in 0..gap {
10939            let timestamp = previous_timestamp.checked_add(block_interval).ok_or_else(|| {
10940                simulate_rpc_error(-38021, "block timestamp overflow while filling number gap")
10941            })?;
10942            sanitized.push(SimBlock {
10943                block_overrides: Some(BlockOverrides {
10944                    number: Some(U256::from(default_number + offset)),
10945                    time: Some(timestamp),
10946                    ..Default::default()
10947                }),
10948                state_overrides: None,
10949                calls: Vec::new(),
10950            });
10951            previous_timestamp = timestamp;
10952        }
10953
10954        let timestamp = match overrides.time {
10955            Some(timestamp) => timestamp,
10956            None => previous_timestamp.checked_add(block_interval).ok_or_else(|| {
10957                simulate_rpc_error(-38021, "block timestamp overflow while constructing sequence")
10958            })?,
10959        };
10960        if timestamp <= previous_timestamp {
10961            return Err(simulate_rpc_error(
10962                -38021,
10963                format!("block timestamps must be in order: {timestamp} <= {previous_timestamp}"),
10964            ));
10965        }
10966
10967        overrides.number = Some(U256::from(number));
10968        overrides.time = Some(timestamp);
10969        block.block_overrides = Some(overrides);
10970        sanitized.push(block);
10971        previous_number = number;
10972        previous_timestamp = timestamp;
10973    }
10974
10975    Ok(sanitized)
10976}
10977
10978/// Unpacks an [`ExecutionResult`] into its exit reason, gas used, output, and logs.
10979fn unpack_execution_result<H: IntoInstructionResult>(
10980    result: ExecutionResult<H>,
10981) -> (InstructionResult, u64, Option<Output>, Vec<revm::primitives::Log>) {
10982    match result {
10983        ExecutionResult::Success { reason, gas, output, logs, .. } => {
10984            (reason.into(), gas.tx_gas_used(), Some(output), logs)
10985        }
10986        ExecutionResult::Revert { gas, output, logs, .. } => {
10987            (InstructionResult::Revert, gas.tx_gas_used(), Some(Output::Call(output)), logs)
10988        }
10989        ExecutionResult::Halt { reason, gas, logs, .. } => {
10990            (reason.into_instruction_result(), gas.tx_gas_used(), None, logs)
10991        }
10992    }
10993}
10994
10995fn arbitrum_replay_block_number(block: &AnyRpcBlock) -> U256 {
10996    block
10997        .other
10998        .get("l1BlockNumber")
10999        .cloned()
11000        .and_then(|number| serde_json::from_value(number).ok())
11001        .unwrap_or_else(|| U256::from(block.header().number()))
11002}
11003
11004/// Converts a halt reason into an [`InstructionResult`].
11005///
11006/// Abstracts over network-specific halt reason types (`HaltReason`, `OpHaltReason`)
11007/// so that anvil code doesn't need to match on each variant directly.
11008pub use foundry_evm::core::evm::IntoInstructionResult;
11009
11010/// Creates an Ethereum-shaped genesis header from the EVM environment.
11011fn genesis_header(
11012    evm_env: &EvmEnv,
11013    base_fee: Option<u64>,
11014    timestamp: u64,
11015    genesis_number: u64,
11016) -> Header {
11017    let spec_id = *evm_env.spec_id();
11018    Header {
11019        timestamp,
11020        base_fee_per_gas: base_fee,
11021        gas_limit: evm_env.block_env.gas_limit,
11022        beneficiary: evm_env.block_env.beneficiary,
11023        difficulty: evm_env.block_env.difficulty,
11024        blob_gas_used: evm_env.block_env.blob_excess_gas_and_price.as_ref().map(|_| 0),
11025        excess_blob_gas: evm_env.block_env.blob_excess_gas(),
11026        number: genesis_number,
11027        parent_beacon_block_root: (spec_id >= SpecId::CANCUN).then_some(Default::default()),
11028        withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
11029        requests_hash: (spec_id >= SpecId::PRAGUE).then_some(EMPTY_REQUESTS_HASH),
11030        ..Default::default()
11031    }
11032}
11033
11034/// Creates an Ethereum header from a `genesis.json` configuration.
11035fn genesis_json_header(genesis: &Genesis) -> Header {
11036    let number = genesis.number.unwrap_or_default();
11037    let timestamp = genesis.timestamp;
11038    let is_london = genesis.config.is_london_active_at_block(number);
11039    let is_shanghai = genesis.config.is_shanghai_active_at_block_and_timestamp(number, timestamp);
11040    let is_cancun = genesis.config.is_cancun_active_at_block_and_timestamp(number, timestamp);
11041    let is_prague = genesis.config.prague_time.is_some_and(|fork| fork <= timestamp);
11042    let is_amsterdam = genesis.config.amsterdam_time.is_some_and(|fork| fork <= timestamp);
11043
11044    Header {
11045        number,
11046        parent_hash: genesis.parent_hash.unwrap_or_default(),
11047        gas_limit: genesis.gas_limit,
11048        difficulty: genesis.difficulty,
11049        nonce: genesis.nonce.into(),
11050        extra_data: genesis.extra_data.clone(),
11051        state_root: state_root_ref_unhashed(&genesis.alloc),
11052        timestamp,
11053        mix_hash: genesis.mix_hash,
11054        beneficiary: genesis.coinbase,
11055        base_fee_per_gas: is_london.then(|| {
11056            genesis
11057                .base_fee_per_gas
11058                .map(|fee| fee as u64)
11059                .unwrap_or(crate::eth::fees::INITIAL_BASE_FEE)
11060        }),
11061        withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
11062        parent_beacon_block_root: is_cancun.then_some(B256::ZERO),
11063        blob_gas_used: is_cancun.then_some(genesis.blob_gas_used.unwrap_or_default()),
11064        excess_blob_gas: is_cancun.then_some(genesis.excess_blob_gas.unwrap_or_default()),
11065        requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH),
11066        block_access_list_hash: is_amsterdam.then_some(EMPTY_BLOCK_ACCESS_LIST_HASH),
11067        slot_number: is_amsterdam.then_some(genesis.slot_number.unwrap_or_default()),
11068        ..Default::default()
11069    }
11070}
11071
11072/// Wraps an Ethereum-shaped header in the selected network's consensus header.
11073fn foundry_header(networks: &NetworkConfigs, header: Header) -> FoundryHeader {
11074    if networks.is_tempo() { FoundryHeader::tempo(header) } else { header.into() }
11075}
11076
11077#[cfg(test)]
11078mod tests {
11079    use super::{
11080        BlockRequest, FeeDetails, ForkCacheNamespace, ForkCacheSource, InstructionResult,
11081        MiningCommitHook, Output, StagedForkCacheLease, StagedForkDbUser,
11082        arbitrum_replay_block_number,
11083    };
11084    use crate::{NodeConfig, config::ForkTransactionReplay, spawn};
11085    use alloy_network::{AnyHeader, AnyRpcBlock, AnyRpcHeader, TransactionBuilder};
11086    use alloy_primitives::{Address, B256, Bytes, U256};
11087    use alloy_provider::Provider;
11088    use alloy_rpc_types::{Block, BlockTransactions, TransactionRequest, state::EvmOverrides};
11089    use alloy_serde::WithOtherFields;
11090    use foundry_config::NamedChain;
11091    use foundry_evm::{
11092        backend::{BlockchainDb, BlockchainDbMeta},
11093        hardfork::{EthereumHardfork, FoundryHardfork},
11094    };
11095    use foundry_evm_networks::arbitrum;
11096    use std::sync::Arc;
11097    use tempfile::tempdir;
11098    use tokio::sync::Notify;
11099
11100    #[cfg(feature = "base")]
11101    use base_common_precompiles::{ActivationRegistryStorage, B20FactoryStorage};
11102    #[cfg(feature = "base")]
11103    use foundry_evm::{hardforks::BaseUpgrade, traces::CallTraceDecoderBuilder};
11104
11105    #[cfg(all(feature = "base", feature = "optimism"))]
11106    use alloy_consensus::Header;
11107    #[cfg(all(feature = "base", feature = "optimism"))]
11108    use foundry_evm::hardfork::OpHardfork;
11109    #[cfg(all(feature = "base", feature = "optimism"))]
11110    use foundry_evm_networks::NetworkConfigs;
11111
11112    fn test_cache_db(cache_path: std::path::PathBuf) -> BlockchainDb {
11113        let db = BlockchainDb::new(BlockchainDbMeta::default(), Some(cache_path));
11114        db.block_hashes().write().insert(U256::ZERO, B256::repeat_byte(0x11));
11115        db.cache().flush();
11116        db
11117    }
11118
11119    #[test]
11120    fn arbitrum_transaction_replay_uses_l1_block_number() {
11121        let header = AnyHeader { number: 75_219_831, ..Default::default() };
11122        let mut block = AnyRpcBlock::new(
11123            Block::new(
11124                AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
11125                BlockTransactions::Full(Vec::new()),
11126            )
11127            .into(),
11128        );
11129        block.other.insert("l1BlockNumber".to_string(), serde_json::json!("0x10276d3"));
11130
11131        assert_eq!(arbitrum_replay_block_number(&block), U256::from(16_938_707));
11132    }
11133
11134    #[cfg(all(feature = "base", feature = "optimism"))]
11135    #[tokio::test(flavor = "multi_thread")]
11136    async fn optimism_fork_uses_optimism_schedule_for_base_headers() {
11137        let timestamp = 1_710_374_401;
11138        let (_, origin) = spawn(
11139            NodeConfig::test_base()
11140                .with_hardfork(Some(BaseUpgrade::Ecotone.into()))
11141                .with_genesis_timestamp(Some(timestamp)),
11142        )
11143        .await;
11144        let (api, _) = spawn(
11145            NodeConfig::test()
11146                .with_networks(NetworkConfigs::with_optimism())
11147                .with_eth_rpc_url(Some(origin.http_endpoint())),
11148        )
11149        .await;
11150
11151        assert!(api.backend.is_optimism());
11152        assert_eq!(api.backend.hardfork(), OpHardfork::Ecotone.into());
11153        assert!(
11154            !api.backend
11155                .is_optimism_jovian_at_header(&Header { timestamp, ..Default::default() }, None)
11156        );
11157    }
11158
11159    #[tokio::test]
11160    async fn fork_arbitrum_transaction_replay_preserves_rpc_block_number() {
11161        const GENESIS_BLOCK: u64 = 101;
11162        const L1_BLOCK: u64 = 10;
11163        const REPLAY_BLOCK: u64 = GENESIS_BLOCK + 1;
11164
11165        let config = || {
11166            NodeConfig::test()
11167                .with_chain_id(Some(NamedChain::Arbitrum as u64))
11168                .with_genesis_block_number(Some(GENESIS_BLOCK))
11169        };
11170        let (_source_api, source_handle) = spawn(config()).await;
11171        let source_provider = source_handle.http_provider();
11172        let sender = source_provider.get_accounts().await.unwrap()[0];
11173        let receipt = source_provider
11174            .send_transaction(WithOtherFields::new(
11175                TransactionRequest::default()
11176                    .with_from(sender)
11177                    .with_to(arbitrum::ARB_SYS_ADDRESS)
11178                    .with_input(Bytes::from(arbitrum::ARB_BLOCK_NUMBER_SELECTOR)),
11179            ))
11180            .await
11181            .unwrap()
11182            .get_receipt()
11183            .await
11184            .unwrap();
11185        let mut source_block = source_provider
11186            .get_block_by_hash(receipt.block_hash.unwrap())
11187            .full()
11188            .await
11189            .unwrap()
11190            .unwrap();
11191        source_block
11192            .other
11193            .insert("l1BlockNumber".to_string(), serde_json::json!(format!("0x{L1_BLOCK:x}")));
11194
11195        let (replay_api, _replay_handle) = spawn(config()).await;
11196        replay_api
11197            .backend
11198            .apply_fork_transaction_replay(ForkTransactionReplay { source_block, target_index: 0 })
11199            .await
11200            .unwrap();
11201
11202        let expected = arbitrum::arb_block_number_output(REPLAY_BLOCK);
11203        let replayed =
11204            replay_api.backend.mined_transaction_receipt(receipt.transaction_hash).unwrap();
11205        assert_eq!(replayed.out.unwrap(), expected);
11206        assert_eq!(replay_api.block_number().unwrap(), U256::from(REPLAY_BLOCK));
11207
11208        let output = replay_api
11209            .call(
11210                WithOtherFields::new(
11211                    TransactionRequest::default()
11212                        .with_to(arbitrum::ARB_SYS_ADDRESS)
11213                        .with_input(Bytes::from(arbitrum::ARB_BLOCK_NUMBER_SELECTOR)),
11214                ),
11215                None,
11216                EvmOverrides::default(),
11217            )
11218            .await
11219            .unwrap();
11220        assert_eq!(output, expected);
11221    }
11222
11223    fn test_endpoint_identity(
11224        hardfork: Option<FoundryHardfork>,
11225        instance_id: Option<B256>,
11226    ) -> super::ForkEndpointIdentity {
11227        super::ForkEndpointIdentity {
11228            execution_chain_id: 1,
11229            source_chain_id: 1,
11230            network: None,
11231            network_profile: None,
11232            hardfork,
11233            blob_params: None,
11234            instance_id,
11235            source_fork_block_number: None,
11236            source_fork_block_hash: None,
11237        }
11238    }
11239
11240    #[tokio::test]
11241    async fn missing_snapshot_block_does_not_change_head() {
11242        let (api, _handle) = spawn(NodeConfig::test()).await;
11243        let snapshot_hash = api.backend.best_hash();
11244        let snapshot = api.backend.create_state_snapshot().await;
11245        api.mine_one().await.unwrap();
11246        let best_number = api.backend.best_number();
11247        let best_hash = api.backend.best_hash();
11248        let head_wall_time = api.backend.time().last_block_wall_time();
11249
11250        api.backend.blockchain.storage.write().blocks.remove(&snapshot_hash);
11251        let err = api.backend.revert_state_snapshot(snapshot).await.unwrap_err();
11252
11253        assert!(matches!(err, super::BlockchainError::BlockNotFound));
11254        assert_eq!(api.backend.best_number(), best_number);
11255        assert_eq!(api.backend.best_hash(), best_hash);
11256        assert_eq!(api.backend.time().last_block_wall_time(), head_wall_time);
11257    }
11258
11259    #[tokio::test(flavor = "multi_thread")]
11260    async fn live_execution_snapshot_is_coherent_during_mining() {
11261        let (api, handle) = spawn(NodeConfig::test().with_no_mining(true)).await;
11262        let sender = handle.dev_wallets().next().unwrap().address();
11263        let recipient = Address::repeat_byte(0x11);
11264        let contract = Address::repeat_byte(0x22);
11265
11266        // Return the recipient balance followed by NUMBER. At block 0 both are zero; at block 1,
11267        // after the queued transfer is mined, both are one.
11268        let mut code = vec![0x73];
11269        code.extend_from_slice(recipient.as_slice());
11270        code.extend_from_slice(&[
11271            0x31, 0x60, 0x00, 0x52, 0x43, 0x60, 0x20, 0x52, 0x60, 0x40, 0x60, 0x00, 0xf3,
11272        ]);
11273        api.anvil_set_code(contract, code.into()).await.unwrap();
11274        api.send_transaction(WithOtherFields::new(
11275            TransactionRequest::default().from(sender).to(recipient).value(U256::ONE),
11276        ))
11277        .await
11278        .unwrap();
11279
11280        let resolved_head = api.backend.best_number();
11281        let db_guard = api.backend.db.write().await;
11282
11283        // Polling while holding the database makes the lock queue deterministic: mining owns the
11284        // first waiter and the call that already resolved `latest` owns the second.
11285        let mining_api = api.clone();
11286        let mut mining = Box::pin(async move { mining_api.mine_one().await });
11287        assert!(futures::poll!(mining.as_mut()).is_pending());
11288
11289        let request = WithOtherFields::new(TransactionRequest::default().to(contract));
11290        let mut call = Box::pin(api.backend.call(
11291            request,
11292            FeeDetails::zero(),
11293            Some(BlockRequest::Number(resolved_head)),
11294            EvmOverrides::default(),
11295        ));
11296        assert!(futures::poll!(call.as_mut()).is_pending());
11297
11298        let mut pending_block = Box::pin(api.backend.pending_block(Vec::new()));
11299        assert!(futures::poll!(pending_block.as_mut()).is_pending());
11300
11301        let mut state = Box::pin(api.serialized_state(false));
11302        assert!(futures::poll!(state.as_mut()).is_pending());
11303
11304        // Pause mining after the database commit but before canonical publication without locking
11305        // storage, so an incorrectly unblocked pending reader can observe the old parent.
11306        let hook =
11307            MiningCommitHook { reached: Arc::new(Notify::new()), resume: Arc::new(Notify::new()) };
11308        *api.backend.mining_commit_hook.lock() = Some(hook.clone());
11309        drop(db_guard);
11310        let mining = tokio::spawn(mining);
11311        hook.reached.notified().await;
11312
11313        // Mining retains the database write lock while waiting to publish the canonical head, so
11314        // neither a live call nor a pending block can observe the partially published snapshot.
11315        assert!(futures::poll!(call.as_mut()).is_pending());
11316        assert!(futures::poll!(pending_block.as_mut()).is_pending());
11317        assert!(futures::poll!(state.as_mut()).is_pending());
11318
11319        hook.resume.notify_one();
11320        mining.await.unwrap().unwrap();
11321
11322        let (exit, output, _, _) = call.await.unwrap();
11323        let pending_block = pending_block.await.unwrap();
11324        let state = state.await.unwrap();
11325
11326        assert_eq!(state.accounts[&recipient].balance, U256::ONE);
11327        assert_eq!(state.block.unwrap().number, U256::ONE);
11328        assert_eq!(state.best_block_number, Some(1));
11329
11330        assert_eq!(exit, InstructionResult::Return);
11331        let Some(Output::Call(output)) = output else { panic!("call did not return data") };
11332        assert_eq!(output.len(), 64);
11333        let balance = U256::from_be_slice(&output[..32]);
11334        let block_number = U256::from_be_slice(&output[32..]);
11335        // The call requested block 0, so it sees block 0's balance and number.
11336        assert_eq!((balance, block_number), (U256::ZERO, U256::ZERO));
11337
11338        assert_eq!(pending_block.block.header.number, api.backend.best_number() + 1);
11339        assert_eq!(pending_block.block.header.parent_hash, api.backend.best_hash());
11340        assert_eq!(pending_block.block.header.base_fee_per_gas, Some(875_175_000));
11341    }
11342
11343    /// Reads the recipient balance at head block 0 while block 1, which funds it, is being mined.
11344    async fn read_head_balance_while_mining(config: NodeConfig) -> U256 {
11345        let (api, handle) = spawn(config.with_no_mining(true)).await;
11346        let sender = handle.dev_wallets().next().unwrap().address();
11347        let recipient = Address::repeat_byte(0x11);
11348        api.send_transaction(WithOtherFields::new(
11349            TransactionRequest::default().from(sender).to(recipient).value(U256::from(1)),
11350        ))
11351        .await
11352        .unwrap();
11353
11354        // Pause mining after the next block's state is committed while it still holds the
11355        // database write lock and before it publishes the new head.
11356        let hook =
11357            MiningCommitHook { reached: Arc::new(Notify::new()), resume: Arc::new(Notify::new()) };
11358        *api.backend.mining_commit_hook.lock() = Some(hook.clone());
11359        let mining_api = api.clone();
11360        let mining = tokio::spawn(async move { mining_api.mine_one().await });
11361        hook.reached.notified().await;
11362
11363        // The read names the current head, block 0, and queues behind the miner's write lock.
11364        let head = api.backend.best_number();
11365        assert_eq!(head, 0);
11366        let mut balance =
11367            Box::pin(api.backend.get_balance(recipient, Some(BlockRequest::Number(head))));
11368        assert!(futures::poll!(balance.as_mut()).is_pending());
11369
11370        hook.resume.notify_one();
11371        mining.await.unwrap().unwrap();
11372        assert_eq!(api.backend.best_number(), 1);
11373
11374        balance.await.unwrap()
11375    }
11376
11377    #[tokio::test(flavor = "multi_thread")]
11378    async fn head_number_read_waiting_on_mining_keeps_requested_state() {
11379        // The read must be answered from block 0, not from block 1 mined while it waited.
11380        assert_eq!(read_head_balance_while_mining(NodeConfig::test()).await, U256::ZERO);
11381    }
11382
11383    #[tokio::test(flavor = "multi_thread")]
11384    async fn head_number_read_waiting_on_mining_without_history_reads_live_state() {
11385        // Without state history block 0's state is gone, so the read is served from the live state
11386        // instead of failing.
11387        let config = NodeConfig::test().set_pruned_history(Some(None));
11388        assert_eq!(read_head_balance_while_mining(config).await, U256::from(1));
11389    }
11390
11391    struct CacheFlushingDb(BlockchainDb);
11392
11393    impl Drop for CacheFlushingDb {
11394        fn drop(&mut self) {
11395            self.0.block_hashes().write().insert(U256::ONE, B256::repeat_byte(0x22));
11396            self.0.cache().flush();
11397        }
11398    }
11399
11400    #[test]
11401    fn staged_fork_cache_lease_waits_for_last_owner() {
11402        let root = tempdir().unwrap();
11403        let block_cache_dir = root.path().join("1");
11404        let cache_path = block_cache_dir.join("storage.json");
11405        let sibling_cache_path = block_cache_dir.join("storage-sibling.json");
11406        let db = test_cache_db(cache_path.clone());
11407        std::fs::write(&sibling_cache_path, b"sibling").unwrap();
11408        let lease = StagedForkCacheLease::new(db.clone(), Some(cache_path.clone()));
11409        let last_user = lease.clone();
11410
11411        lease.rollback().unwrap();
11412        assert!(!db.block_hashes().read().is_empty());
11413        drop(lease);
11414        assert!(!db.block_hashes().read().is_empty());
11415
11416        drop(last_user);
11417        assert!(db.block_hashes().read().is_empty());
11418        assert!(!cache_path.exists());
11419        assert!(sibling_cache_path.exists());
11420    }
11421
11422    #[tokio::test]
11423    async fn staged_fork_cache_lease_survives_task_cancellation() {
11424        let root = tempdir().unwrap();
11425        let block_cache_dir = root.path().join("1");
11426        let cache_path = block_cache_dir.join("storage.json");
11427        let cache_db = test_cache_db(cache_path.clone());
11428        let lease = StagedForkCacheLease::new(cache_db.clone(), Some(cache_path.clone()));
11429        let db = Arc::new(tokio::sync::RwLock::new(CacheFlushingDb(cache_db.clone())));
11430        let task_user = StagedForkDbUser { db: Some(Arc::clone(&db)), cache_lease: lease.clone() };
11431        drop(db);
11432        let task = tokio::spawn(async move {
11433            let _db = task_user.db().read().await;
11434            std::future::pending::<()>().await;
11435        });
11436        tokio::task::yield_now().await;
11437
11438        // Precise closure capture must not detach the lease from the task's database handle.
11439        assert_eq!(Arc::strong_count(lease.0.as_ref().unwrap()), 2);
11440        lease.rollback().unwrap();
11441        drop(lease);
11442
11443        task.abort();
11444        assert!(task.await.unwrap_err().is_cancelled());
11445
11446        assert!(cache_db.block_hashes().read().is_empty());
11447        assert!(!cache_path.exists());
11448    }
11449
11450    #[test]
11451    fn staged_fork_cache_lease_disarm_preserves_committed_cache() {
11452        let root = tempdir().unwrap();
11453        let block_cache_dir = root.path().join("1");
11454        let cache_path = block_cache_dir.join("storage.json");
11455        let db = test_cache_db(cache_path.clone());
11456        let lease = StagedForkCacheLease::new(db.clone(), Some(cache_path.clone()));
11457
11458        lease.disarm();
11459        drop(lease);
11460
11461        assert!(!db.block_hashes().read().is_empty());
11462        assert!(cache_path.exists());
11463    }
11464
11465    #[test]
11466    fn fork_cache_namespace_preserves_sibling_endpoints() {
11467        let root = tempdir().unwrap();
11468        let target_file = "storage-target.json";
11469        let sibling_file = "storage-sibling.json";
11470        let regular_cache_entry = root.path().join("latest.json");
11471        std::fs::write(&regular_cache_entry, b"ordinary RPC cache entry").unwrap();
11472        for block in ["1", "2"] {
11473            let block_dir = root.path().join(block);
11474            std::fs::create_dir_all(&block_dir).unwrap();
11475            std::fs::write(block_dir.join(target_file), b"target").unwrap();
11476            std::fs::write(block_dir.join(sibling_file), b"sibling").unwrap();
11477        }
11478        let namespace = ForkCacheNamespace {
11479            chain_cache_dir: root.path().to_path_buf(),
11480            file_name: target_file.to_string(),
11481        };
11482
11483        namespace.invalidate().unwrap();
11484
11485        for block in ["1", "2"] {
11486            let block_dir = root.path().join(block);
11487            assert!(!block_dir.join(target_file).exists());
11488            assert!(block_dir.join(sibling_file).exists());
11489        }
11490        assert!(regular_cache_entry.exists());
11491    }
11492
11493    #[test]
11494    fn startup_fork_cache_user_rolls_back_after_database_flush() {
11495        let root = tempdir().unwrap();
11496        let block_cache_dir = root.path().join("1");
11497        let cache_path = block_cache_dir.join("storage.json");
11498        let cache_db = test_cache_db(cache_path.clone());
11499        let user = StagedForkDbUser {
11500            db: Some(Arc::new(tokio::sync::RwLock::new(CacheFlushingDb(cache_db.clone())))),
11501            cache_lease: StagedForkCacheLease::new(cache_db.clone(), Some(cache_path.clone())),
11502        };
11503
11504        drop(user);
11505
11506        assert!(cache_db.block_hashes().read().is_empty());
11507        assert!(!cache_path.exists());
11508    }
11509
11510    #[test]
11511    fn startup_fork_cache_user_preserves_cache_after_commit() {
11512        let root = tempdir().unwrap();
11513        let block_cache_dir = root.path().join("1");
11514        let cache_path = block_cache_dir.join("storage.json");
11515        let cache_db = test_cache_db(cache_path.clone());
11516        let user = StagedForkDbUser {
11517            db: Some(Arc::new(tokio::sync::RwLock::new(CacheFlushingDb(cache_db.clone())))),
11518            cache_lease: StagedForkCacheLease::new(cache_db.clone(), Some(cache_path.clone())),
11519        };
11520        user.cache_lease.disarm();
11521
11522        drop(user);
11523
11524        assert_eq!(cache_db.block_hashes().read().get(&U256::ONE), Some(&B256::repeat_byte(0x22)));
11525        assert!(cache_path.exists());
11526    }
11527
11528    #[test]
11529    fn fork_cache_source_invalidates_only_same_url_authoritative_replacements() {
11530        let anonymous = test_endpoint_identity(None, None);
11531        let anonymous_source = ForkCacheSource {
11532            rpc_url: "http://localhost".to_string(),
11533            endpoint_identity: anonymous,
11534        };
11535        let mut changed_anonymous = anonymous;
11536        changed_anonymous.source_chain_id = 2;
11537
11538        assert!(
11539            !anonymous_source
11540                .authoritative_identity_changed_at_same_url("http://localhost", changed_anonymous)
11541        );
11542        assert!(
11543            !anonymous_source
11544                .authoritative_identity_changed_at_same_url("http://mirror", anonymous)
11545        );
11546
11547        let hardfork = Some(FoundryHardfork::Ethereum(EthereumHardfork::Prague));
11548        let authoritative = test_endpoint_identity(hardfork, Some(B256::repeat_byte(0x22)));
11549        let authoritative_source = ForkCacheSource {
11550            rpc_url: "http://localhost".to_string(),
11551            endpoint_identity: authoritative,
11552        };
11553        let replacement = test_endpoint_identity(hardfork, Some(B256::repeat_byte(0x33)));
11554
11555        assert!(
11556            !authoritative_source
11557                .authoritative_identity_changed_at_same_url("http://localhost", authoritative)
11558        );
11559        assert!(
11560            authoritative_source
11561                .authoritative_identity_changed_at_same_url("http://localhost", replacement)
11562        );
11563        assert!(
11564            anonymous_source
11565                .authoritative_identity_changed_at_same_url("http://localhost", authoritative)
11566        );
11567        assert!(
11568            authoritative_source
11569                .authoritative_identity_changed_at_same_url("http://localhost", anonymous)
11570        );
11571    }
11572
11573    #[tokio::test]
11574    async fn test_deterministic_block_mining() {
11575        // Test that mine_block produces deterministic block hashes with same initial conditions
11576        let genesis_timestamp = 1743944919u64;
11577
11578        // Create two identical backends
11579        let config_a = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into());
11580        let config_b = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into());
11581
11582        let (api_a, _handle_a) = spawn(config_a).await;
11583        let (api_b, _handle_b) = spawn(config_b).await;
11584
11585        // Mine empty blocks (no transactions) on both backends
11586        let outcome_a_1 = api_a.backend.mine_block(vec![]).await.unwrap();
11587        let outcome_b_1 = api_b.backend.mine_block(vec![]).await.unwrap();
11588
11589        // Both should mine the same block number
11590        assert_eq!(outcome_a_1.block_number, outcome_b_1.block_number);
11591
11592        // Get the actual blocks to compare hashes
11593        let block_a_1 =
11594            api_a.block_by_number(outcome_a_1.block_number.into()).await.unwrap().unwrap();
11595        let block_b_1 =
11596            api_b.block_by_number(outcome_b_1.block_number.into()).await.unwrap().unwrap();
11597
11598        // The block hashes should be identical
11599        assert_eq!(
11600            block_a_1.header.hash, block_b_1.header.hash,
11601            "Block hashes should be deterministic. Got {} vs {}",
11602            block_a_1.header.hash, block_b_1.header.hash
11603        );
11604
11605        // Mine another block to ensure it remains deterministic
11606        let outcome_a_2 = api_a.backend.mine_block(vec![]).await.unwrap();
11607        let outcome_b_2 = api_b.backend.mine_block(vec![]).await.unwrap();
11608
11609        let block_a_2 =
11610            api_a.block_by_number(outcome_a_2.block_number.into()).await.unwrap().unwrap();
11611        let block_b_2 =
11612            api_b.block_by_number(outcome_b_2.block_number.into()).await.unwrap().unwrap();
11613
11614        assert_eq!(
11615            block_a_2.header.hash, block_b_2.header.hash,
11616            "Second block hashes should also be deterministic. Got {} vs {}",
11617            block_a_2.header.hash, block_b_2.header.hash
11618        );
11619
11620        // Ensure the blocks are different (sanity check)
11621        assert_ne!(
11622            block_a_1.header.hash, block_a_2.header.hash,
11623            "Different blocks should have different hashes"
11624        );
11625    }
11626
11627    #[cfg(feature = "monad")]
11628    #[tokio::test]
11629    async fn monad_load_state_rebuilds_participant_cache() {
11630        use alloy_network::TransactionBuilder as _;
11631        use alloy_provider::Provider as _;
11632
11633        let config = || {
11634            NodeConfig::test_monad()
11635                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
11636        };
11637        let (api, handle) = spawn(config()).await;
11638        let provider = handle.http_provider();
11639        let accounts = provider.get_accounts().await.unwrap();
11640        let sender = accounts[0];
11641
11642        let receipt = provider
11643            .send_transaction(
11644                alloy_rpc_types::TransactionRequest::default()
11645                    .with_from(sender)
11646                    .with_to(accounts[1])
11647                    .with_value(U256::ONE)
11648                    .into(),
11649            )
11650            .await
11651            .unwrap()
11652            .get_receipt()
11653            .await
11654            .unwrap();
11655        let block_hash = receipt.block_hash.unwrap();
11656        let state = api.serialized_state(false).await.unwrap();
11657
11658        let (loaded_api, _handle) = spawn(config()).await;
11659        loaded_api.backend.load_state(state).await.unwrap();
11660
11661        let storage = loaded_api.backend.blockchain.storage.read();
11662        let participants = storage.monad_block_participants.get(&block_hash).unwrap();
11663        assert!(participants.contains(&sender));
11664    }
11665
11666    #[cfg(feature = "monad")]
11667    #[tokio::test]
11668    async fn monad_load_state_restores_pruned_participant_cache() {
11669        use alloy_consensus::BlockHeader as _;
11670        use alloy_network::TransactionBuilder as _;
11671        use alloy_provider::Provider as _;
11672
11673        let config = || {
11674            NodeConfig::test_monad()
11675                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
11676                .with_transaction_block_keeper(Some(1usize))
11677        };
11678        let (api, handle) = spawn(config()).await;
11679        let provider = handle.http_provider();
11680        let accounts = provider.get_accounts().await.unwrap();
11681        let sender = accounts[0];
11682
11683        let first_receipt = provider
11684            .send_transaction(
11685                alloy_rpc_types::TransactionRequest::default()
11686                    .with_from(sender)
11687                    .with_to(accounts[1])
11688                    .with_value(U256::ONE)
11689                    .into(),
11690            )
11691            .await
11692            .unwrap()
11693            .get_receipt()
11694            .await
11695            .unwrap();
11696        let second_receipt = provider
11697            .send_transaction(
11698                alloy_rpc_types::TransactionRequest::default()
11699                    .with_from(sender)
11700                    .with_to(accounts[1])
11701                    .with_value(U256::ONE)
11702                    .into(),
11703            )
11704            .await
11705            .unwrap()
11706            .get_receipt()
11707            .await
11708            .unwrap();
11709        let first_block_hash = first_receipt.block_hash.unwrap();
11710        let second_block_hash = second_receipt.block_hash.unwrap();
11711
11712        {
11713            let storage = api.backend.blockchain.storage.read();
11714            let first_block = storage.blocks.get(&first_block_hash).unwrap();
11715            assert!(first_block.body.transactions.is_empty());
11716            assert_ne!(
11717                first_block.header.transactions_root(),
11718                alloy_consensus::constants::EMPTY_ROOT_HASH
11719            );
11720            assert!(storage.monad_block_participants[&first_block_hash].contains(&sender));
11721            assert!(!storage.blocks[&second_block_hash].body.transactions.is_empty());
11722        }
11723
11724        let state = api.serialized_state(false).await.unwrap();
11725        assert!(state.monad_block_participants[&first_block_hash].contains(&sender));
11726        assert_eq!(state.monad_block_participants.len(), 2);
11727        assert!(state.monad_block_participants[&second_block_hash].contains(&sender));
11728
11729        let (loaded_api, _handle) = spawn(config()).await;
11730        loaded_api.backend.load_state(state).await.unwrap();
11731
11732        {
11733            let storage = loaded_api.backend.blockchain.storage.read();
11734            assert!(storage.monad_block_participants[&first_block_hash].contains(&sender));
11735            assert!(storage.monad_block_participants[&second_block_hash].contains(&sender));
11736        }
11737
11738        let outcome = loaded_api.backend.mine_block(vec![]).await.unwrap();
11739        assert_eq!(outcome.block_number, 3);
11740    }
11741
11742    #[cfg(feature = "monad")]
11743    #[tokio::test]
11744    async fn monad_load_state_rejection_is_atomic() {
11745        use alloy_consensus::BlockHeader as _;
11746        use alloy_network::TransactionBuilder as _;
11747        use alloy_provider::Provider as _;
11748
11749        let config = || {
11750            NodeConfig::test_monad()
11751                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
11752                .with_transaction_block_keeper(Some(1usize))
11753        };
11754        let (source_api, source_handle) = spawn(config()).await;
11755        let source_provider = source_handle.http_provider();
11756        let accounts = source_provider.get_accounts().await.unwrap();
11757        let sentinel = alloy_primitives::Address::repeat_byte(0x77);
11758
11759        source_api.anvil_set_balance(sentinel, U256::from(123)).await.unwrap();
11760        for nonce in 0..2 {
11761            source_provider
11762                .send_transaction(
11763                    alloy_rpc_types::TransactionRequest::default()
11764                        .with_from(accounts[0])
11765                        .with_to(accounts[1])
11766                        .with_nonce(nonce)
11767                        .with_value(U256::ONE)
11768                        .into(),
11769                )
11770                .await
11771                .unwrap()
11772                .get_receipt()
11773                .await
11774                .unwrap();
11775        }
11776
11777        let mut invalid_state = source_api.serialized_state(false).await.unwrap();
11778        let pruned_block_hash = invalid_state
11779            .blocks
11780            .iter()
11781            .find(|block| {
11782                block.transactions.is_empty()
11783                    && block.header.transactions_root()
11784                        != alloy_consensus::constants::EMPTY_ROOT_HASH
11785            })
11786            .unwrap()
11787            .header
11788            .hash_slow();
11789        assert!(invalid_state.monad_block_participants.remove(&pruned_block_hash).is_some());
11790
11791        let (target_api, _handle) = spawn(config()).await;
11792        target_api.anvil_set_balance(sentinel, U256::from(7)).await.unwrap();
11793        target_api.mine_one().await.unwrap();
11794        let original_best_hash = target_api.backend.best_hash();
11795        let original_best_number = target_api.backend.best_number();
11796        let original_block_env = target_api.backend.evm_env.read().block_env.clone();
11797        let original_balance = target_api.backend.current_balance(sentinel).await.unwrap();
11798
11799        let err = target_api.backend.load_state(invalid_state).await.unwrap_err();
11800        assert!(matches!(err, super::BlockchainError::DataUnavailable));
11801        assert_eq!(target_api.backend.best_hash(), original_best_hash);
11802        assert_eq!(target_api.backend.best_number(), original_best_number);
11803        assert_eq!(target_api.backend.evm_env.read().block_env, original_block_env);
11804        assert_eq!(target_api.backend.current_balance(sentinel).await.unwrap(), original_balance);
11805
11806        let outcome = target_api.backend.mine_block(vec![]).await.unwrap();
11807        assert_eq!(outcome.block_number, original_best_number + 1);
11808    }
11809
11810    #[tokio::test]
11811    async fn trace_decoder_follows_executed_hardfork_for_cross_namespace_override() {
11812        let (api, _) = spawn(
11813            NodeConfig::test_tempo()
11814                .with_hardfork(Some(FoundryHardfork::Ethereum(EthereumHardfork::Prague))),
11815        )
11816        .await;
11817
11818        let decoder = api.backend.call_trace_decoder();
11819        assert_eq!(decoder.hardfork(), Some(FoundryHardfork::Tempo(api.backend.tempo_hardfork())));
11820        // The refresh compares the same coerced value, so repeated calls stay stable.
11821        assert!(Arc::ptr_eq(&decoder, &api.backend.call_trace_decoder()));
11822    }
11823
11824    #[cfg(feature = "monad")]
11825    #[tokio::test]
11826    async fn monad_trace_decoder_follows_resolved_hardfork() {
11827        let (monad_eight, _) = spawn(
11828            NodeConfig::test_monad()
11829                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadEight.into())),
11830        )
11831        .await;
11832        let stale_monad_nine = foundry_evm::traces::CallTraceDecoderBuilder::new()
11833            .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
11834            .build();
11835        *monad_eight.backend.call_trace_decoder.write() = Arc::new(stale_monad_nine);
11836
11837        let decoder = monad_eight.backend.call_trace_decoder();
11838        assert_eq!(
11839            decoder.hardfork(),
11840            Some(foundry_evm::hardfork::MonadHardfork::MonadEight.into())
11841        );
11842        assert!(
11843            !decoder
11844                .labels
11845                .contains_key(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
11846        );
11847
11848        let (monad_nine, _) = spawn(
11849            NodeConfig::test_monad()
11850                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into())),
11851        )
11852        .await;
11853        let stale_monad_eight = foundry_evm::traces::CallTraceDecoderBuilder::new()
11854            .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadEight.into()))
11855            .build();
11856        *monad_nine.backend.call_trace_decoder.write() = Arc::new(stale_monad_eight);
11857
11858        let decoder = monad_nine.backend.call_trace_decoder();
11859        assert_eq!(
11860            decoder.hardfork(),
11861            Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into())
11862        );
11863        assert_eq!(
11864            decoder
11865                .labels
11866                .get(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
11867                .map(String::as_str),
11868            Some("ReserveBalance")
11869        );
11870    }
11871
11872    #[cfg(feature = "base")]
11873    #[tokio::test]
11874    async fn base_trace_decoder_follows_resolved_upgrade() {
11875        let (azul, _) =
11876            spawn(NodeConfig::test_base().with_hardfork(Some(BaseUpgrade::Azul.into()))).await;
11877        let stale_beryl =
11878            CallTraceDecoderBuilder::new().with_hardfork(Some(BaseUpgrade::Beryl.into())).build();
11879        *azul.backend.call_trace_decoder.write() = Arc::new(stale_beryl);
11880
11881        let decoder = azul.backend.call_trace_decoder();
11882        assert_eq!(decoder.hardfork(), Some(BaseUpgrade::Azul.into()));
11883        assert!(!decoder.labels.contains_key(&B20FactoryStorage::ADDRESS));
11884        assert!(!decoder.labels.contains_key(&ActivationRegistryStorage::ADDRESS));
11885
11886        let (beryl, _) =
11887            spawn(NodeConfig::test_base().with_hardfork(Some(BaseUpgrade::Beryl.into()))).await;
11888        let stale_azul =
11889            CallTraceDecoderBuilder::new().with_hardfork(Some(BaseUpgrade::Azul.into())).build();
11890        *beryl.backend.call_trace_decoder.write() = Arc::new(stale_azul);
11891
11892        let decoder = beryl.backend.call_trace_decoder();
11893        assert_eq!(decoder.hardfork(), Some(BaseUpgrade::Beryl.into()));
11894        assert_eq!(
11895            decoder.labels.get(&B20FactoryStorage::ADDRESS).map(String::as_str),
11896            Some("B20Factory")
11897        );
11898        assert_eq!(
11899            decoder.labels.get(&ActivationRegistryStorage::ADDRESS).map(String::as_str),
11900            Some("ActivationRegistry")
11901        );
11902    }
11903}