Skip to main content

anvil/eth/backend/mem/
mod.rs

1//! In-memory blockchain backend.
2use self::{in_memory_db::StateRootDb, state::trie_storage};
3
4use crate::{
5    ForkChoice, NodeConfig, PrecompileFactory,
6    config::{ForkTransactionReplay, PruneStateHistoryConfig},
7    eth::{
8        backend::{
9            cheats::{CheatEcrecover, CheatsManager},
10            db::{
11                AnvilCacheDB, BLOCKHASH_HISTORY, Db, MaybeFullDatabase, SerializableState, StateDb,
12            },
13            executor::{
14                AnvilBlockExecutor, BlockExecutionKind, EthereumBlockTransitions,
15                ExecutedPoolTransactions, FoundryReceiptBuilder, PoolTransactionHooks,
16                PoolTxGasConfig, apply_ethereum_post_execution_changes,
17                apply_ethereum_pre_execution_changes, block_blob_gas_limit,
18                build_tx_env_for_pending, execute_pool_transaction, execute_pool_transactions,
19            },
20            fork::{ClientFork, ForkEndpointIdentity},
21            genesis::GenesisConfig,
22            mem::{
23                state::{state_root, state_trie_witness, storage_root, trie_accounts},
24                storage::MinedTransactionReceipt,
25            },
26            notifications::{ChainNotification, ChainNotifications, NewBlockNotification},
27            replay::{
28                ExecutedHistoricalReplay, HistoricalReplayTransaction,
29                PreparedForkTransactionReplay, execute_historical_replay,
30                prepare_fork_transaction_replay,
31            },
32            tempo::AnvilStorageProvider,
33            time::{TimeManager, utc_from_secs},
34            validate::TransactionValidator,
35        },
36        error::{BlockchainError, ErrDetail, InvalidTransactionError},
37        fees::{FeeDetails, FeeManager, FeeSnapshot, MIN_SUGGESTED_PRIORITY_FEE},
38        macros::node_info,
39        pool::transactions::PoolTransaction,
40        preserve_simulation_request_fields,
41    },
42    mem::{
43        inspector::{AnvilInspector, InspectorTxConfig},
44        storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome},
45    },
46};
47use alloy_chains::NamedChain;
48use alloy_consensus::{
49    Blob, BlockHeader, EnvKzgSettings, Header, Signed, Transaction as TransactionTrait,
50    TransactionEnvelope, TrieAccount, TxEip4844Variant, TxEnvelope, TxReceipt, Typed2718,
51    constants::EMPTY_WITHDRAWALS,
52    proofs::{calculate_receipt_root, calculate_transaction_root},
53    transaction::Recovered,
54};
55use alloy_eips::{
56    BlockNumHash, Encodable2718, eip2935, eip4788,
57    eip4844::{DATA_GAS_PER_BLOB, kzg_to_versioned_hash},
58    eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS,
59    eip7002, eip7251,
60    eip7685::EMPTY_REQUESTS_HASH,
61    eip7840::BlobParams,
62    eip7910::SystemContract,
63};
64use alloy_evm::{
65    Database, EthEvmFactory, Evm, EvmEnv, EvmFactory, FromTxWithEncoded,
66    block::{BlockExecutionResult, BlockExecutor, StateDB},
67    eth::EthEvmContext,
68    overrides::{OverrideBlockHashes, apply_state_overrides},
69    precompiles::{DynPrecompile, MovePrecompileError, Precompile, PrecompilesMap},
70};
71use alloy_network::{
72    AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType,
73    BlockResponse, Network, NetworkTransactionBuilder, ReceiptResponse, UnknownTxEnvelope,
74    UnknownTypedTransaction,
75};
76#[cfg(feature = "optimism")]
77use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx};
78use alloy_primitives::{
79    Address, B256, Bloom, Bytes, Signature, TxKind, U64, U256, address, hex, keccak256,
80    map::{AddressMap, B256Set, HashMap, HashSet},
81};
82use alloy_rlp::{Decodable, Encodable};
83use alloy_rpc_types::{
84    AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides,
85    BlockTransactions, EIP1186AccountProofResponse as AccountProof,
86    EIP1186StorageProof as StorageProof, Filter, Header as AlloyHeader, Index, Log, Transaction,
87    TransactionReceipt,
88    anvil::Forking,
89    debug::ExecutionWitness,
90    request::TransactionRequest,
91    serde_helpers::JsonStorageKey,
92    simulate::{
93        MAX_SIMULATE_BLOCKS, SimBlock, SimCallResult, SimulateError, SimulatePayload,
94        SimulatedBlock,
95    },
96    state::{EvmOverrides, StateOverride},
97    trace::{
98        filter::TraceFilter,
99        geth::{
100            CallConfig, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig,
101            GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
102            NoopFrame, TraceResult,
103        },
104        opcode::{BlockOpcodeGas, TransactionOpcodeGas},
105        parity::{
106            LocalizedTransactionTrace, TraceResults, TraceResultsWithTransactionHash, TraceType,
107        },
108    },
109};
110use alloy_rpc_types_eth::{AccountInfo as RpcAccountInfo, Bundle, EthCallResponse};
111use alloy_rpc_types_mev::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult};
112use alloy_serde::{OtherFields, WithOtherFields};
113use alloy_sol_types::SolCall;
114use alloy_trie::{HashBuilder, Nibbles, proof::ProofRetainer};
115use anvil_core::eth::{
116    block::{Block, BlockInfo, canonical_block, create_block},
117    transaction::{MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo},
118};
119use anvil_rpc::error::{ErrorCode, RpcError};
120use chrono::Datelike;
121use eyre::{Context, Result};
122use flate2::{Compression, read::GzDecoder, write::GzEncoder};
123#[cfg(feature = "optimism")]
124use foundry_evm::hardfork::OpHardfork;
125use foundry_evm::{
126    backend::{BlockchainDb, DatabaseError, DatabaseResult, RevertStateSnapshotAction},
127    constants::{DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE},
128    core::{
129        evm::{EvmEnvFor, TempoEvmNetwork},
130        precompiles::EC_RECOVER,
131    },
132    decode::RevertDecoder,
133    hardfork::{EthereumHardfork, FoundryHardfork},
134    inspectors::AccessListInspector,
135    traces::{
136        CallTraceDecoder, FourByteInspector, GethTraceBuilder, TracingInspector,
137        TracingInspectorConfig,
138    },
139    utils::{
140        apply_chain_specific_tx_replay_env_changes_for_chain, block_env_from_header,
141        get_blob_base_fee_update_fraction, get_blob_base_fee_update_fraction_by_spec_id,
142        get_blob_params_by_hardfork,
143    },
144};
145use foundry_evm_networks::{NetworkConfigs, arbitrum};
146#[cfg(feature = "optimism")]
147use foundry_primitives::get_deposit_tx_parts;
148use foundry_primitives::{
149    FoundryHeader, FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest,
150    FoundryTxEnvelope, FoundryTxReceipt, TempoTransactionRequest,
151};
152use futures::channel::mpsc::{UnboundedSender, unbounded};
153#[cfg(feature = "optimism")]
154use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, POST_EXEC_TX_TYPE_ID};
155#[cfg(feature = "optimism")]
156use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts};
157use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
158use revm::{
159    Database as RevmDatabase, DatabaseCommit, Inspector,
160    context::{Block as RevmBlock, BlockEnv, Cfg, CfgEnv, ContextSetters, ContextTr, TxEnv},
161    context_interface::{
162        JournalTr,
163        block::BlobExcessGasAndPrice,
164        result::{ExecutionResult, HaltReason, Output, ResultAndState},
165        transaction::TransactionType,
166    },
167    database::{AccountState, CacheDB, DbAccount, WrapDatabaseRef},
168    handler::{
169        EthFrame, EvmTr, EvmTrError, FrameResult, FrameTr, Handler as EvmHandler, validation,
170    },
171    inspector::{InspectorEvmTr, InspectorHandler},
172    interpreter::{InstructionResult, interpreter::EthInterpreter, interpreter_action::FrameInit},
173    precompile::{PrecompileSpecId, Precompiles},
174    primitives::{KECCAK_EMPTY, hardfork::SpecId},
175    state::{Account, AccountInfo, EvmState, EvmStorageSlot, TransactionId},
176};
177use revm_inspectors::opcode::OpcodeGasInspector;
178use std::{
179    collections::BTreeMap,
180    fmt::{self, Debug},
181    io::{Read, Write},
182    marker::PhantomData,
183    ops::Mul,
184    path::{Path, PathBuf},
185    sync::{
186        Arc,
187        atomic::{AtomicBool, Ordering},
188    },
189    time::Duration,
190};
191use storage::{Blockchain, DEFAULT_HISTORY_LIMIT, MinedTransaction};
192use tempo_evm::evm::TempoEvmFactory;
193use tempo_hardfork::TempoHardfork;
194use tempo_precompiles::{
195    NONCE_PRECOMPILE_ADDRESS, TIP_FEE_MANAGER_ADDRESS, extend_tempo_precompiles,
196    nonce::NonceManager,
197    storage::{Handler, StorageActions, StorageCtx},
198    tip_fee_manager::{IFeeManager, TipFeeManager},
199    tip20::{ISSUER_ROLE, ITIP20, TIP20Token},
200    tip20_factory::TIP20Factory,
201};
202use tempo_primitives::{
203    AASigned, SignatureType, TEMPO_TX_TYPE_ID, TempoSignature,
204    transaction::{
205        Call, KeychainSignature, PrimitiveSignature, RecoveredTempoAuthorization,
206        tt_signature::{P256SignatureWithPreHash, WebAuthnSignature},
207    },
208};
209use tempo_revm::{
210    ExecutionContext, TempoBatchCallEnv, TempoBlockEnv, TempoHaltReason, TempoTxEnv,
211    evm::TempoContext, gas_params::tempo_gas_params,
212};
213use tokio::{sync::RwLock as AsyncRwLock, task::JoinSet};
214
215/// Side-channel container for OP-specific deposit info produced by
216/// [`Backend::build_call_env_with_base`] and consumed by the OP transact path.
217///
218/// When the `optimism` feature is enabled, this is an alias for
219/// `op_revm::DepositTransactionParts`. When disabled, it is a zero-sized
220/// stand-in so the eth/tempo dispatch chain still type-checks.
221#[cfg(feature = "optimism")]
222type OpCallDepositInfo = DepositTransactionParts;
223#[cfg(not(feature = "optimism"))]
224#[derive(Default, Clone, Debug)]
225struct OpCallDepositInfo;
226
227/// Fully prepared fork replacement awaiting an atomic backend commit.
228pub(crate) struct StagedForkReset {
229    node_config: NodeConfig,
230    db: Box<dyn Db>,
231    fees: FeeManager,
232    evm_env: EvmEnv,
233    fork: ClientFork,
234    timestamp: u64,
235    discard_old_cached_state: bool,
236    invalidated_cache_namespaces: Vec<ForkCacheNamespace>,
237    flush_old_cache: bool,
238    cache_lease: StagedForkCacheLease,
239}
240
241/// Fully prepared in-memory replacement awaiting an atomic backend commit.
242pub(crate) struct StagedMemoryReset<N: Network> {
243    node_config: NodeConfig,
244    db: Box<dyn Db>,
245    fees: FeeManager,
246    evm_env: EvmEnv,
247    hardfork: FoundryHardfork,
248    storage: BlockchainStorage<N>,
249    timestamp: u64,
250    flush_old_cache: bool,
251}
252
253/// Identifies the endpoint that supplied the most recently committed fork.
254#[derive(Clone, Debug, PartialEq, Eq)]
255struct ForkCacheSource {
256    rpc_url: String,
257    endpoint_identity: ForkEndpointIdentity,
258}
259
260impl ForkCacheSource {
261    fn from_fork(fork: &ClientFork) -> Option<Self> {
262        let config = fork.config.read();
263        Some(Self {
264            rpc_url: config.eth_rpc_url()?.to_string(),
265            endpoint_identity: config.endpoint_identity,
266        })
267    }
268
269    fn authoritative_identity_changed_at_same_url(
270        &self,
271        rpc_url: &str,
272        endpoint_identity: ForkEndpointIdentity,
273    ) -> bool {
274        // `hardfork` is populated only when `anvil_nodeInfo` succeeds, which makes the complete
275        // endpoint identity authoritative. Anonymous RPC endpoints intentionally retain Foundry's
276        // existing cache behavior when reused through the same URL.
277        let authoritative =
278            self.endpoint_identity.is_authoritative() || endpoint_identity.is_authoritative();
279        self.rpc_url == rpc_url && authoritative && self.endpoint_identity != endpoint_identity
280    }
281}
282
283/// Identifies one endpoint's persisted cache files across all blocks of a source chain.
284#[derive(Clone, Debug, PartialEq, Eq)]
285struct ForkCacheNamespace {
286    chain_cache_dir: PathBuf,
287    file_name: String,
288}
289
290impl ForkCacheNamespace {
291    fn new(source_chain_id: u64, rpc_url: &str) -> Option<Self> {
292        Some(Self {
293            chain_cache_dir: foundry_config::Config::foundry_chain_cache_dir(source_chain_id)?,
294            file_name: format!("storage-{}.json", hex::encode(keccak256(rpc_url))),
295        })
296    }
297
298    fn invalidate(&self) -> Result<(), BlockchainError> {
299        let entries = match std::fs::read_dir(&self.chain_cache_dir) {
300            Ok(entries) => entries,
301            Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()),
302            Err(err) => {
303                return Err(BlockchainError::Internal(format!(
304                    "failed to inspect fork cache at {}: {err}",
305                    self.chain_cache_dir.display()
306                )));
307            }
308        };
309
310        for entry in entries {
311            let entry = entry.map_err(|err| {
312                BlockchainError::Internal(format!(
313                    "failed to inspect fork cache at {}: {err}",
314                    self.chain_cache_dir.display()
315                ))
316            })?;
317            let file_type = entry.file_type().map_err(|err| {
318                BlockchainError::Internal(format!(
319                    "failed to inspect fork cache entry at {}: {err}",
320                    entry.path().display()
321                ))
322            })?;
323            if !file_type.is_dir() {
324                continue;
325            }
326            let cache_path = entry.path().join(&self.file_name);
327            if let Err(err) = std::fs::remove_file(&cache_path)
328                && err.kind() != std::io::ErrorKind::NotFound
329            {
330                return Err(BlockchainError::Internal(format!(
331                    "failed to invalidate fork cache at {}: {err}",
332                    cache_path.display()
333                )));
334            }
335        }
336        Ok(())
337    }
338}
339
340/// Keeps a staged fork from persisting remote state unless it is committed.
341#[derive(Clone, Debug, Default)]
342struct StagedForkCacheLease(Option<Arc<StagedForkCacheLeaseInner>>);
343
344#[derive(Debug)]
345struct StagedForkCacheLeaseInner {
346    db: BlockchainDb,
347    cache_path: PathBuf,
348    armed: AtomicBool,
349}
350
351impl StagedForkCacheLease {
352    fn new(db: BlockchainDb, cache_path: Option<PathBuf>) -> Self {
353        Self(cache_path.map(|cache_path| {
354            Arc::new(StagedForkCacheLeaseInner { db, cache_path, armed: AtomicBool::new(true) })
355        }))
356    }
357
358    fn for_db(db: &BlockchainDb) -> Self {
359        Self::new(db.clone(), db.cache().cache_path().map(Path::to_path_buf))
360    }
361
362    fn disarm(&self) {
363        if let Some(inner) = &self.0 {
364            inner.armed.store(false, Ordering::Release);
365        }
366    }
367
368    fn rollback(&self) -> Result<(), BlockchainError> {
369        let Some(inner) = &self.0 else { return Ok(()) };
370        // A clone owned by an in-flight database user must outlive that user's SharedBackend.
371        // Leave cleanup armed for the final owner instead of racing its eventual cache flush.
372        if Arc::strong_count(inner) == 1 && inner.armed.load(Ordering::Acquire) {
373            inner.invalidate()?;
374            inner.armed.store(false, Ordering::Release);
375        }
376        Ok(())
377    }
378}
379
380impl StagedForkCacheLeaseInner {
381    fn invalidate(&self) -> Result<(), BlockchainError> {
382        self.db.db().clear();
383        self.db.cache().flush();
384        if let Err(err) = std::fs::remove_file(&self.cache_path)
385            && err.kind() != std::io::ErrorKind::NotFound
386        {
387            return Err(BlockchainError::Internal(format!(
388                "failed to invalidate fork cache at {}: {err}",
389                self.cache_path.display()
390            )));
391        }
392        Ok(())
393    }
394}
395
396impl Drop for StagedForkCacheLeaseInner {
397    fn drop(&mut self) {
398        if self.armed.swap(false, Ordering::AcqRel)
399            && let Err(err) = self.invalidate()
400        {
401            warn!(target: "backend", %err, "failed to roll back staged fork cache");
402        }
403    }
404}
405
406/// Couples an asynchronous staged-database user to its rollback lease.
407///
408/// Fields drop in declaration order, so the database handle (and its SharedBackend) is released
409/// before the lease can perform final cache cleanup.
410struct StagedForkDbUser<D> {
411    db: Option<Arc<AsyncRwLock<D>>>,
412    cache_lease: StagedForkCacheLease,
413}
414
415impl<D> Clone for StagedForkDbUser<D> {
416    fn clone(&self) -> Self {
417        Self { db: self.db.clone(), cache_lease: self.cache_lease.clone() }
418    }
419}
420
421impl<D> StagedForkDbUser<D> {
422    const fn db(&self) -> &Arc<AsyncRwLock<D>> {
423        self.db.as_ref().expect("staged fork database must be present until drop")
424    }
425}
426
427impl<D> Drop for StagedForkDbUser<D> {
428    fn drop(&mut self) {
429        // Explicitly release the SharedBackend before `cache_lease` can invalidate its cache.
430        drop(self.db.take());
431    }
432}
433
434#[cfg(feature = "monad")]
435pub(crate) type MonadReplayContext = monad_revm::MonadChainContext;
436// Opaque stand-in that keeps feature-independent replay context plumbing type-stable.
437#[cfg(not(feature = "monad"))]
438#[derive(Clone)]
439pub(crate) struct MonadReplayContext;
440
441#[cfg(feature = "monad")]
442enum MonadExecutionContext<'a> {
443    Exact(Box<MonadReplayContext>),
444    Next(&'a mut MonadReplayContext),
445}
446
447#[cfg(not(feature = "monad"))]
448struct MonadExecutionContext<'a> {
449    _marker: std::marker::PhantomData<&'a mut MonadReplayContext>,
450}
451
452#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
453enum EnvelopeExecutionKind {
454    #[default]
455    Transaction,
456    Replay,
457}
458
459#[cfg_attr(not(feature = "monad"), allow(dead_code))]
460struct EnvelopeExecution<'a> {
461    monad_context: Option<MonadExecutionContext<'a>>,
462    kind: EnvelopeExecutionKind,
463    hardfork: FoundryHardfork,
464}
465
466impl<'a> EnvelopeExecution<'a> {
467    const fn transaction(
468        monad_context: Option<MonadExecutionContext<'a>>,
469        hardfork: FoundryHardfork,
470    ) -> Self {
471        Self { monad_context, kind: EnvelopeExecutionKind::Transaction, hardfork }
472    }
473
474    const fn replay(
475        monad_context: Option<MonadExecutionContext<'a>>,
476        hardfork: FoundryHardfork,
477    ) -> Self {
478        Self { monad_context, kind: EnvelopeExecutionKind::Replay, hardfork }
479    }
480}
481
482#[cfg(feature = "monad")]
483fn monad_execution_context_at(
484    context: Option<&MonadReplayContext>,
485    current_tx_index: usize,
486) -> Option<MonadExecutionContext<'static>> {
487    context.map(|context| {
488        let mut context = context.clone();
489        context.current_tx_index = current_tx_index;
490        MonadExecutionContext::Exact(Box::new(context))
491    })
492}
493
494#[cfg(not(feature = "monad"))]
495const fn monad_execution_context_at(
496    _context: Option<&MonadReplayContext>,
497    _current_tx_index: usize,
498) -> Option<MonadExecutionContext<'static>> {
499    None
500}
501
502#[cfg(feature = "monad")]
503const fn next_monad_context(context: &mut MonadReplayContext) -> MonadExecutionContext<'_> {
504    MonadExecutionContext::Next(context)
505}
506
507#[cfg(not(feature = "monad"))]
508const fn next_monad_context(_context: &mut MonadReplayContext) -> MonadExecutionContext<'_> {
509    MonadExecutionContext { _marker: std::marker::PhantomData }
510}
511
512const fn noop_before_transaction<E, T>(_evm: &mut E, _tx: &T) {}
513
514const fn noop_on_execution_error<E>(_evm: &mut E) {}
515
516/// Maximum cumulative gas available to one `eth_simulateV1` request.
517const SIMULATE_GAS_CAP: u64 = 50_000_000;
518const SEPOLIA_DEPOSIT_CONTRACT_ADDRESS: Address =
519    address!("7f02c3e3c98b133055b8b348b2ac625669ed295d");
520const HOLESKY_DEPOSIT_CONTRACT_ADDRESS: Address =
521    address!("4242424242424242424242424242424242424242");
522
523/// Fixed transaction context for direct Tempo RPC simulations.
524const TEMPO_RPC_SIMULATION_CONTEXT: B256 = B256::new(*b"TEMPO_RPC_SIMULATION_MPP_CONTEXT");
525
526/// Ethereum handler that skips blob fee cap validation for non-validating calls with a zero cap.
527struct SimulationHandler<EVM, ERROR, FRAME> {
528    _phantom: PhantomData<(EVM, ERROR, FRAME)>,
529}
530
531impl<EVM, ERROR, FRAME> Default for SimulationHandler<EVM, ERROR, FRAME> {
532    fn default() -> Self {
533        Self { _phantom: PhantomData }
534    }
535}
536
537impl<EVM, ERROR, FRAME> EvmHandler for SimulationHandler<EVM, ERROR, FRAME>
538where
539    EVM: EvmTr<
540            Context: ContextTr<
541                Block = BlockEnv,
542                Tx = TxEnv,
543                Journal: JournalTr<State = EvmState>,
544            > + ContextSetters,
545            Frame = FRAME,
546        >,
547    ERROR: EvmTrError<EVM>,
548    FRAME: FrameTr<FrameResult = FrameResult, FrameInit = FrameInit>,
549{
550    type Evm = EVM;
551    type Error = ERROR;
552    type HaltReason = HaltReason;
553
554    fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
555        let skip_blob_fee_check = evm.ctx_ref().cfg().is_base_fee_check_disabled()
556            && evm.ctx_ref().tx().tx_type == 3
557            && evm.ctx_ref().tx().max_fee_per_blob_gas == 0;
558        if !skip_blob_fee_check {
559            return validation::validate_env(evm.ctx());
560        }
561
562        let block = evm.ctx_ref().block().clone();
563        let mut validation_block = block.clone();
564        if let Some(blob_gas_and_price) = &mut validation_block.blob_excess_gas_and_price {
565            blob_gas_and_price.blob_gasprice = 0;
566        }
567        evm.ctx().set_block(validation_block);
568        let result = validation::validate_env(evm.ctx());
569        evm.ctx().set_block(block);
570        result
571    }
572}
573
574impl<EVM, ERROR> InspectorHandler for SimulationHandler<EVM, ERROR, EthFrame<EthInterpreter>>
575where
576    EVM: InspectorEvmTr<
577            Context: ContextTr<
578                Block = BlockEnv,
579                Tx = TxEnv,
580                Journal: JournalTr<State = EvmState>,
581            > + ContextSetters,
582            Frame = EthFrame<EthInterpreter>,
583            Inspector: Inspector<<EVM as EvmTr>::Context, EthInterpreter>,
584        >,
585    ERROR: EvmTrError<EVM>,
586{
587    type IT = EthInterpreter;
588}
589
590#[derive(Clone)]
591enum CallTxEnv {
592    Eth(TxEnv),
593    #[cfg(feature = "monad")]
594    Monad(TxEnv),
595    #[cfg(feature = "optimism")]
596    Op(OpTransaction<TxEnv>),
597    Tempo(TempoTxEnv),
598}
599
600impl CallTxEnv {
601    #[cfg_attr(not(feature = "js-tracer"), allow(dead_code))]
602    const fn base(&self) -> &TxEnv {
603        match self {
604            Self::Eth(tx) => tx,
605            #[cfg(feature = "monad")]
606            Self::Monad(tx) => tx,
607            #[cfg(feature = "optimism")]
608            Self::Op(tx) => &tx.base,
609            Self::Tempo(tx) => &tx.inner,
610        }
611    }
612
613    const fn base_mut(&mut self) -> &mut TxEnv {
614        match self {
615            Self::Eth(tx) => tx,
616            #[cfg(feature = "monad")]
617            Self::Monad(tx) => tx,
618            #[cfg(feature = "optimism")]
619            Self::Op(tx) => &mut tx.base,
620            Self::Tempo(tx) => &mut tx.inner,
621        }
622    }
623
624    fn into_base(self) -> TxEnv {
625        match self {
626            Self::Eth(tx) => tx,
627            #[cfg(feature = "monad")]
628            Self::Monad(tx) => tx,
629            #[cfg(feature = "optimism")]
630            Self::Op(tx) => tx.base,
631            Self::Tempo(tx) => tx.inner,
632        }
633    }
634
635    fn uses_protocol_call_nonce(&self) -> bool {
636        match self {
637            Self::Eth(tx) => matches!(tx.kind, TxKind::Call(_)),
638            #[cfg(feature = "monad")]
639            Self::Monad(tx) => matches!(tx.kind, TxKind::Call(_)),
640            #[cfg(feature = "optimism")]
641            Self::Op(tx) => matches!(tx.base.kind, TxKind::Call(_)),
642            Self::Tempo(tx) => tx.tempo_tx_env.as_ref().map_or_else(
643                || matches!(tx.inner.kind, TxKind::Call(_)),
644                |aa| {
645                    aa.nonce_key.is_zero()
646                        && aa
647                            .aa_calls
648                            .first()
649                            .is_some_and(|call| matches!(call.to, TxKind::Call(_)))
650                },
651            ),
652        }
653    }
654}
655
656fn apply_tempo_envelope_identity(tx_env: &mut CallTxEnv, simulated_tx: Option<&AASigned>) {
657    if let (CallTxEnv::Tempo(tx_env), Some(simulated_tx)) = (tx_env, simulated_tx) {
658        tx_env.unique_tx_identifier = Some(simulated_tx.expiring_nonce_hash(tx_env.inner.caller));
659        if let Some(batch) = &mut tx_env.tempo_tx_env {
660            batch.tx_hash = *simulated_tx.hash();
661        }
662    }
663}
664
665struct PreparedCall {
666    evm_env: EvmEnv,
667    tx_env: CallTxEnv,
668    simulated_tempo_tx: Option<AASigned>,
669}
670
671#[derive(Default)]
672struct TypedCallOverrides {
673    gas_limit: Option<u64>,
674    access_list: Option<AccessList>,
675    disable_fee_charge: bool,
676}
677
678pub(crate) struct GasEstimateCallOptions {
679    gas_limit: u64,
680    disable_fee_charge: bool,
681    monad_context: Option<MonadReplayContext>,
682}
683
684impl GasEstimateCallOptions {
685    pub(crate) const fn new(
686        gas_limit: u64,
687        disable_fee_charge: bool,
688        monad_context: Option<MonadReplayContext>,
689    ) -> Self {
690        Self { gas_limit, disable_fee_charge, monad_context }
691    }
692}
693
694/// Marker trait that abstracts over the per-network inspector trait bounds
695/// required by the in-memory backend. The OP bound is only included when the
696/// `optimism` feature is enabled.
697#[cfg(all(feature = "optimism", feature = "monad"))]
698pub trait BackendInspector<DB: Database>:
699    Inspector<EthEvmContext<DB>>
700    + Inspector<OpEvmContext<DB>>
701    + Inspector<TempoContext<DB>>
702    + Inspector<alloy_monad_evm::MonadContext<DB>>
703{
704}
705#[cfg(all(feature = "optimism", feature = "monad"))]
706impl<DB: Database, T> BackendInspector<DB> for T where
707    T: Inspector<EthEvmContext<DB>>
708        + Inspector<OpEvmContext<DB>>
709        + Inspector<TempoContext<DB>>
710        + Inspector<alloy_monad_evm::MonadContext<DB>>
711{
712}
713#[cfg(all(feature = "optimism", not(feature = "monad")))]
714pub trait BackendInspector<DB: Database>:
715    Inspector<EthEvmContext<DB>> + Inspector<OpEvmContext<DB>> + Inspector<TempoContext<DB>>
716{
717}
718#[cfg(all(feature = "optimism", not(feature = "monad")))]
719impl<DB: Database, T> BackendInspector<DB> for T where
720    T: Inspector<EthEvmContext<DB>> + Inspector<OpEvmContext<DB>> + Inspector<TempoContext<DB>>
721{
722}
723#[cfg(all(not(feature = "optimism"), feature = "monad"))]
724pub trait BackendInspector<DB: Database>:
725    Inspector<EthEvmContext<DB>>
726    + Inspector<TempoContext<DB>>
727    + Inspector<alloy_monad_evm::MonadContext<DB>>
728{
729}
730#[cfg(all(not(feature = "optimism"), feature = "monad"))]
731impl<DB: Database, T> BackendInspector<DB> for T where
732    T: Inspector<EthEvmContext<DB>>
733        + Inspector<TempoContext<DB>>
734        + Inspector<alloy_monad_evm::MonadContext<DB>>
735{
736}
737#[cfg(all(not(feature = "optimism"), not(feature = "monad")))]
738pub trait BackendInspector<DB: Database>:
739    Inspector<EthEvmContext<DB>> + Inspector<TempoContext<DB>>
740{
741}
742#[cfg(all(not(feature = "optimism"), not(feature = "monad")))]
743impl<DB: Database, T> BackendInspector<DB> for T where
744    T: Inspector<EthEvmContext<DB>> + Inspector<TempoContext<DB>>
745{
746}
747pub mod cache;
748pub mod fork_db;
749pub mod in_memory_db;
750pub mod inspector;
751#[cfg(feature = "monad")]
752mod monad;
753#[cfg(feature = "optimism")]
754pub mod optimism;
755pub mod state;
756pub mod storage;
757
758/// Helper trait that combines revm::DatabaseRef with Debug.
759/// This is needed because alloy-evm requires Debug on Database implementations.
760/// With trait upcasting now stable, we can now upcast from this trait to revm::DatabaseRef.
761pub trait DatabaseRef: revm::DatabaseRef<Error = DatabaseError> + Debug {}
762impl<T> DatabaseRef for T where T: revm::DatabaseRef<Error = DatabaseError> + Debug {}
763impl DatabaseRef for dyn crate::eth::backend::db::Db {}
764
765// Gas per transaction not creating a contract.
766pub const MIN_TRANSACTION_GAS: u128 = 21000;
767// Gas per transaction creating a contract.
768pub const MIN_CREATE_GAS: u128 = 53000;
769
770fn tempo_nonce(
771    state: &dyn DatabaseRef,
772    caller: Address,
773    nonce_key: U256,
774) -> Result<u64, BlockchainError> {
775    if nonce_key.is_zero() {
776        return Ok(state.basic_ref(caller)?.map(|account| account.nonce).unwrap_or_default());
777    }
778    if nonce_key == U256::MAX {
779        return Ok(0);
780    }
781    let slot = NonceManager::new().nonces[caller][nonce_key].slot();
782    Ok(state.storage_ref(NONCE_PRECOMPILE_ADDRESS, slot)?.saturating_to())
783}
784
785fn mock_tempo_signature(
786    key_type: SignatureType,
787    key_data: Option<Bytes>,
788    key_id: Option<Address>,
789    caller: Address,
790    is_t1c: bool,
791) -> TempoSignature {
792    let signature = match key_type {
793        SignatureType::Secp256k1 => {
794            PrimitiveSignature::Secp256k1(Signature::new(U256::ZERO, U256::ZERO, false))
795        }
796        SignatureType::P256 => PrimitiveSignature::P256(P256SignatureWithPreHash {
797            r: B256::ZERO,
798            s: B256::ZERO,
799            pub_key_x: B256::ZERO,
800            pub_key_y: B256::ZERO,
801            pre_hash: false,
802        }),
803        SignatureType::WebAuthn => {
804            const CLIENT_JSON: &str = r#"{"type":"webauthn.get","challenge":"","origin":""}"#;
805            const AUTH_DATA_SIZE: usize = 37;
806            const MIN_SIZE: usize = AUTH_DATA_SIZE + CLIENT_JSON.len();
807            const DEFAULT_SIZE: usize = 800;
808            const MAX_SIZE: usize = 8192;
809
810            let size = key_data
811                .as_deref()
812                .and_then(|data| match data.len() {
813                    1 => Some(data[0] as usize),
814                    2 => Some(u16::from_be_bytes([data[0], data[1]]) as usize),
815                    4 => Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize),
816                    _ => None,
817                })
818                .unwrap_or(DEFAULT_SIZE)
819                .clamp(MIN_SIZE, MAX_SIZE);
820            let mut webauthn_data = vec![0u8; AUTH_DATA_SIZE];
821            webauthn_data[32] = 0x01;
822            let padding = "x".repeat(size - MIN_SIZE);
823            webauthn_data.extend_from_slice(
824                format!(r#"{{"type":"webauthn.get","challenge":"","origin":"{padding}"}}"#)
825                    .as_bytes(),
826            );
827            PrimitiveSignature::WebAuthn(WebAuthnSignature {
828                webauthn_data: webauthn_data.into(),
829                r: B256::ZERO,
830                s: B256::ZERO,
831                pub_key_x: B256::ZERO,
832                pub_key_y: B256::ZERO,
833            })
834        }
835    };
836
837    if key_id.is_some() {
838        let signature = if is_t1c {
839            KeychainSignature::new(caller, signature)
840        } else {
841            KeychainSignature::new_v1(caller, signature)
842        };
843        TempoSignature::Keychain(signature)
844    } else {
845        TempoSignature::Primitive(signature)
846    }
847}
848
849fn call_config_from_tracer_config(
850    tracer_config: GethDebugTracerConfig,
851) -> Result<CallConfig, serde_json::Error> {
852    let mut tracer_config = tracer_config.into_json();
853    if let Some(config) = tracer_config.as_object_mut()
854        && !config.contains_key("onlyTopCall")
855        && let Some(only_top_level_call) = config.remove("onlyTopLevelCall")
856    {
857        config.insert("onlyTopCall".to_string(), only_top_level_call);
858    }
859
860    GethDebugTracerConfig(tracer_config).into_call_config()
861}
862
863pub type State = foundry_evm::utils::StateChangeset;
864
865#[derive(Clone, Debug, Default)]
866struct SimulationPrecompileOverrides {
867    moves: Vec<(Address, Address)>,
868}
869
870/// A block request, which includes the Pool Transactions if it's Pending
871pub enum BlockRequest<T> {
872    Pending(Vec<Arc<PoolTransaction<T>>>),
873    Number(u64),
874}
875
876impl<T> fmt::Debug for BlockRequest<T> {
877    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
878        match self {
879            Self::Pending(txs) => f.debug_tuple("Pending").field(&txs.len()).finish(),
880            Self::Number(n) => f.debug_tuple("Number").field(n).finish(),
881        }
882    }
883}
884
885impl<T> BlockRequest<T> {
886    pub const fn block_number(&self) -> BlockNumber {
887        match *self {
888            Self::Pending(_) => BlockNumber::Pending,
889            Self::Number(n) => BlockNumber::Number(n),
890        }
891    }
892}
893
894struct StateSnapshot {
895    block_number: u64,
896    block_hash: B256,
897    fees: FeeSnapshot,
898    time_offset: i128,
899}
900
901/// Gives access to the [revm::Database]
902pub struct Backend<N: Network> {
903    /// Access to [`revm::Database`] abstraction.
904    ///
905    /// This will be used in combination with [`alloy_evm::Evm`] and is responsible for feeding
906    /// data to the evm during its execution.
907    ///
908    /// At time of writing, there are two different types of `Db`:
909    ///   - [`MemDb`](crate::mem::in_memory_db::MemDb): everything is stored in memory
910    ///   - [`ForkDb`](crate::mem::fork_db::ForkedDatabase): forks off a remote client, missing
911    ///     data is retrieved via RPC-calls
912    ///
913    /// In order to commit changes to the [`revm::Database`], the [`alloy_evm::Evm`] requires
914    /// mutable access, which requires a write-lock from this `db`. In forking mode, the time
915    /// during which the write-lock is active depends on whether the `ForkDb` can provide all
916    /// requested data from memory or whether it has to retrieve it via RPC calls first. This
917    /// means that it potentially blocks for some time, even taking into account the rate
918    /// limits of RPC endpoints. Therefore the `Db` is guarded by a `tokio::sync::RwLock` here
919    /// so calls that need to read from it, while it's currently written to, don't block. E.g.
920    /// a new block is currently mined and a new [`Self::set_storage_at()`] request is being
921    /// executed.
922    db: Arc<AsyncRwLock<Box<dyn Db>>>,
923    /// stores all block related data in memory.
924    blockchain: Blockchain<N>,
925    /// Historic states of previous blocks.
926    states: Arc<RwLock<InMemoryBlockStates>>,
927    /// EVM environment data of the chain (block env, cfg env).
928    evm_env: Arc<RwLock<EvmEnv>>,
929    /// Network configuration (optimism, custom precompiles, etc.)
930    networks: NetworkConfigs,
931    /// The active hardfork.
932    hardfork: Arc<RwLock<FoundryHardfork>>,
933    /// This is set if this is currently forked off another client.
934    fork: Arc<RwLock<Option<ClientFork>>>,
935    /// The last source that supplied the live fork backend, retained across memory resets.
936    last_fork_cache_source: Arc<RwLock<Option<ForkCacheSource>>>,
937    /// Provides time related info, like timestamp.
938    time: TimeManager,
939    /// Contains state of custom overrides.
940    cheats: CheatsManager,
941    /// Contains fee data.
942    fees: FeeManager,
943    /// Initialised genesis.
944    genesis: GenesisConfig,
945    /// Listeners for new blocks that get notified when a new block was imported or when logs were
946    /// removed from the canonical chain due to a reorg.
947    new_block_listeners: Arc<Mutex<Vec<UnboundedSender<ChainNotification>>>>,
948    /// Keeps track of active state snapshots at a specific block.
949    active_state_snapshots: Arc<Mutex<HashMap<U256, StateSnapshot>>>,
950    enable_steps_tracing: bool,
951    print_logs: bool,
952    print_traces: bool,
953    /// Recorder used for decoding traces, used together with print_traces.
954    call_trace_decoder: Arc<RwLock<Arc<CallTraceDecoder>>>,
955    /// How to keep history state
956    prune_state_history_config: PruneStateHistoryConfig,
957    /// max number of blocks with transactions in memory
958    transaction_block_keeper: Option<usize>,
959    pub(crate) node_config: Arc<AsyncRwLock<NodeConfig>>,
960    /// Slots in an epoch
961    slots_in_an_epoch: u64,
962    /// Precompiles to inject to the EVM.
963    precompile_factory: Option<Arc<dyn PrecompileFactory>>,
964    /// Prevent race conditions during mining
965    mining: Arc<tokio::sync::Mutex<()>>,
966    /// Disable pool balance checks
967    disable_pool_balance_checks: bool,
968    /// Keeps startup fork-cache rollback armed until startup initialization completes.
969    ///
970    /// This must remain the final field so all other backend-held database references are released
971    /// before a rejected startup invalidates its cache.
972    startup_fork_cache_user: StagedForkDbUser<Box<dyn Db>>,
973}
974
975impl<N: Network> Clone for Backend<N> {
976    fn clone(&self) -> Self {
977        Self {
978            db: self.db.clone(),
979            blockchain: self.blockchain.clone(),
980            states: self.states.clone(),
981            evm_env: self.evm_env.clone(),
982            networks: self.networks,
983            hardfork: self.hardfork.clone(),
984            fork: self.fork.clone(),
985            last_fork_cache_source: self.last_fork_cache_source.clone(),
986            time: self.time.clone(),
987            cheats: self.cheats.clone(),
988            fees: self.fees.clone(),
989            genesis: self.genesis.clone(),
990            new_block_listeners: self.new_block_listeners.clone(),
991            active_state_snapshots: self.active_state_snapshots.clone(),
992            enable_steps_tracing: self.enable_steps_tracing,
993            print_logs: self.print_logs,
994            print_traces: self.print_traces,
995            call_trace_decoder: self.call_trace_decoder.clone(),
996            prune_state_history_config: self.prune_state_history_config,
997            transaction_block_keeper: self.transaction_block_keeper,
998            node_config: self.node_config.clone(),
999            slots_in_an_epoch: self.slots_in_an_epoch,
1000            precompile_factory: self.precompile_factory.clone(),
1001            mining: self.mining.clone(),
1002            disable_pool_balance_checks: self.disable_pool_balance_checks,
1003            startup_fork_cache_user: self.startup_fork_cache_user.clone(),
1004        }
1005    }
1006}
1007
1008impl<N: Network> fmt::Debug for Backend<N> {
1009    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1010        f.debug_struct("Backend").finish_non_exhaustive()
1011    }
1012}
1013
1014// Methods that are generic over any Network.
1015impl<N: Network> Backend<N> {
1016    /// Sets the account to impersonate
1017    ///
1018    /// Returns `true` if the account is already impersonated
1019    pub fn impersonate(&self, addr: Address) -> bool {
1020        if self.cheats.impersonated_accounts().contains(&addr) {
1021            return true;
1022        }
1023        // Ensure EIP-3607 is disabled
1024        self.evm_env.write().cfg_env.disable_eip3607 = true;
1025        self.cheats.impersonate(addr)
1026    }
1027
1028    /// Removes the account that from the impersonated set
1029    ///
1030    /// If the impersonated `addr` is a contract then we also reset the code here
1031    pub fn stop_impersonating(&self, addr: Address) {
1032        self.cheats.stop_impersonating(&addr);
1033    }
1034
1035    /// If set to true will make every account impersonated
1036    pub fn auto_impersonate_account(&self, enabled: bool) {
1037        self.cheats.set_auto_impersonate_account(enabled);
1038    }
1039
1040    /// Returns the configured fork, if any
1041    pub fn get_fork(&self) -> Option<ClientFork> {
1042        self.fork.read().clone()
1043    }
1044
1045    /// Marks startup fork-cache writes as belonging to the validated live backend.
1046    pub(crate) fn commit_startup_fork_cache(&self) {
1047        self.startup_fork_cache_user.cache_lease.disarm();
1048    }
1049
1050    /// Returns the database
1051    pub fn get_db(&self) -> &Arc<AsyncRwLock<Box<dyn Db>>> {
1052        &self.db
1053    }
1054
1055    /// Locks block production while a backend-wide lifecycle transition is committed.
1056    pub(crate) async fn lock_mining(&self) -> tokio::sync::MutexGuard<'_, ()> {
1057        self.mining.lock().await
1058    }
1059
1060    /// Returns the `AccountInfo` from the database
1061    pub async fn get_account(&self, address: Address) -> DatabaseResult<AccountInfo> {
1062        Ok(self.db.read().await.basic_ref(address)?.unwrap_or_default())
1063    }
1064
1065    /// Whether we're forked off some remote client
1066    pub fn is_fork(&self) -> bool {
1067        self.fork.read().is_some()
1068    }
1069
1070    /// Writes the CREATE2 deployer code directly to the database at the address provided.
1071    pub async fn set_create2_deployer(&self, address: Address) -> DatabaseResult<()> {
1072        self.set_code(address, Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE)).await?;
1073        Ok(())
1074    }
1075
1076    /// Updates memory limits that should be more strict when auto-mine is enabled
1077    pub(crate) fn update_interval_mine_block_time(&self, block_time: Duration) {
1078        self.states.write().update_interval_mine_block_time(block_time)
1079    }
1080
1081    /// Returns the `TimeManager` responsible for timestamps
1082    pub const fn time(&self) -> &TimeManager {
1083        &self.time
1084    }
1085
1086    /// Returns the `CheatsManager` responsible for executing cheatcodes
1087    pub const fn cheats(&self) -> &CheatsManager {
1088        &self.cheats
1089    }
1090
1091    /// Whether to skip blob validation
1092    pub fn skip_blob_validation(&self, impersonator: Option<Address>) -> bool {
1093        self.cheats().auto_impersonate_accounts()
1094            || impersonator
1095                .is_some_and(|addr| self.cheats().impersonated_accounts().contains(&addr))
1096    }
1097
1098    /// Returns the `FeeManager` that manages fee/pricings
1099    pub const fn fees(&self) -> &FeeManager {
1100        &self.fees
1101    }
1102
1103    /// The EVM environment data of the blockchain
1104    pub const fn evm_env(&self) -> &Arc<RwLock<EvmEnv>> {
1105        &self.evm_env
1106    }
1107
1108    /// Returns the current best hash of the chain
1109    pub fn best_hash(&self) -> B256 {
1110        self.blockchain.storage.read().best_hash
1111    }
1112
1113    /// Returns the current best number of the chain
1114    pub fn best_number(&self) -> u64 {
1115        self.blockchain.storage.read().best_number
1116    }
1117
1118    /// Sets the block number
1119    pub fn set_block_number(&self, number: u64) {
1120        self.evm_env.write().block_env.number = U256::from(number);
1121    }
1122
1123    /// Returns the client coinbase address.
1124    pub fn coinbase(&self) -> Address {
1125        self.evm_env.read().block_env.beneficiary
1126    }
1127
1128    /// Returns the client coinbase address.
1129    pub fn chain_id(&self) -> U256 {
1130        U256::from(self.evm_env.read().cfg_env.chain_id)
1131    }
1132
1133    /// Returns the chain ID that defines protocol behavior.
1134    fn protocol_chain_id(&self) -> u64 {
1135        self.get_fork().map_or_else(|| self.evm_env.read().cfg_env.chain_id, |fork| fork.chain_id())
1136    }
1137
1138    pub fn set_chain_id(&self, chain_id: u64) {
1139        self.evm_env.write().cfg_env.chain_id = chain_id;
1140    }
1141
1142    /// Returns the genesis data for the Beacon API.
1143    pub const fn genesis_time(&self) -> u64 {
1144        self.genesis.timestamp
1145    }
1146
1147    /// Returns the configured genesis block number.
1148    pub const fn genesis_number(&self) -> u64 {
1149        self.genesis.number
1150    }
1151
1152    /// Returns balance of the given account.
1153    pub async fn current_balance(&self, address: Address) -> DatabaseResult<U256> {
1154        Ok(self.get_account(address).await?.balance)
1155    }
1156
1157    /// Returns balance of the given account.
1158    pub async fn current_nonce(&self, address: Address) -> DatabaseResult<u64> {
1159        Ok(self.get_account(address).await?.nonce)
1160    }
1161
1162    /// Sets the coinbase address
1163    pub fn set_coinbase(&self, address: Address) {
1164        self.evm_env.write().block_env.beneficiary = address;
1165    }
1166
1167    /// Sets the `prevrandao` value to use for the next mined block.
1168    ///
1169    /// This is a one-shot override that is consumed by the next block; afterwards anvil resumes its
1170    /// default per-block `prevrandao` derivation.
1171    pub fn set_next_block_prevrandao(&self, prevrandao: B256) {
1172        self.cheats.set_next_block_prevrandao(prevrandao);
1173    }
1174
1175    /// Sets the nonce of the given address
1176    pub async fn set_nonce(&self, address: Address, nonce: U256) -> DatabaseResult<()> {
1177        self.db.write().await.set_nonce(address, nonce.try_into().unwrap_or(u64::MAX))
1178    }
1179
1180    /// Sets the balance of the given address
1181    pub async fn set_balance(&self, address: Address, balance: U256) -> DatabaseResult<()> {
1182        self.db.write().await.set_balance(address, balance)
1183    }
1184
1185    /// Sets the code of the given address
1186    pub async fn set_code(&self, address: Address, code: Bytes) -> DatabaseResult<()> {
1187        self.db.write().await.set_code(address, code)
1188    }
1189
1190    /// Sets the value for the given slot of the given address
1191    pub async fn set_storage_at(
1192        &self,
1193        address: Address,
1194        slot: U256,
1195        val: B256,
1196    ) -> DatabaseResult<()> {
1197        self.db.write().await.set_storage_at(address, slot.into(), val)
1198    }
1199
1200    /// Returns the configured specid
1201    pub fn spec_id(&self) -> SpecId {
1202        *self.evm_env.read().spec_id()
1203    }
1204
1205    /// Returns true for post London
1206    pub fn is_eip1559(&self) -> bool {
1207        (self.spec_id() as u8) >= (SpecId::LONDON as u8)
1208    }
1209
1210    /// Returns true for post Merge
1211    pub fn is_eip3675(&self) -> bool {
1212        (self.spec_id() as u8) >= (SpecId::MERGE as u8)
1213    }
1214
1215    /// Returns true for post Berlin
1216    pub fn is_eip2930(&self) -> bool {
1217        (self.spec_id() as u8) >= (SpecId::BERLIN as u8)
1218    }
1219
1220    /// Returns true for post Cancun
1221    pub fn is_eip4844(&self) -> bool {
1222        (self.spec_id() as u8) >= (SpecId::CANCUN as u8)
1223    }
1224
1225    /// Returns true for post Prague
1226    pub fn is_eip7702(&self) -> bool {
1227        (self.spec_id() as u8) >= (SpecId::PRAGUE as u8)
1228    }
1229
1230    /// Returns true if op-stack deposits are active.
1231    ///
1232    /// Always `false` when built without the `optimism` feature.
1233    pub const fn is_optimism(&self) -> bool {
1234        self.networks.is_optimism()
1235    }
1236
1237    /// Returns true if Tempo network mode is active
1238    pub const fn is_tempo(&self) -> bool {
1239        self.networks.is_tempo()
1240    }
1241
1242    /// Returns true if Monad network mode is active
1243    pub const fn is_monad(&self) -> bool {
1244        self.networks.is_monad()
1245    }
1246
1247    /// Returns the active execution profile name.
1248    pub const fn execution_profile_name(&self) -> &'static str {
1249        self.networks.execution_profile_name()
1250    }
1251
1252    /// Reconstructs a locally mined transaction using its authoritative stored sender.
1253    fn pending_mined_transaction(
1254        &self,
1255        transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>,
1256    ) -> Result<PendingTransaction<FoundryTxEnvelope>, BlockchainError> {
1257        #[cfg(feature = "monad")]
1258        if self.is_monad() {
1259            return Self::monad_pending_mined_transaction_from_storage(
1260                &self.blockchain.storage.read(),
1261                transaction,
1262            );
1263        }
1264        Ok(PendingTransaction::from_maybe_impersonated(transaction)?)
1265    }
1266
1267    #[cfg(not(feature = "monad"))]
1268    fn active_monad_context_for_mined_block(
1269        &self,
1270        _block: &Block,
1271    ) -> Result<Option<MonadReplayContext>, BlockchainError> {
1272        Ok(None)
1273    }
1274
1275    #[cfg(not(feature = "monad"))]
1276    fn active_monad_context_before_mined_transaction(
1277        &self,
1278        _block: &Block,
1279        _current_tx_index: usize,
1280    ) -> Result<Option<MonadReplayContext>, BlockchainError> {
1281        Ok(None)
1282    }
1283
1284    /// Returns the active hardfork.
1285    pub fn hardfork(&self) -> FoundryHardfork {
1286        if let Some(hardfork) =
1287            self.fork.read().as_ref().and_then(|fork| fork.config.read().hardfork)
1288        {
1289            return hardfork;
1290        }
1291        *self.hardfork.read()
1292    }
1293
1294    /// Returns canonical Ethereum transition configuration only for an Ethereum network.
1295    fn ethereum_block_transitions(
1296        &self,
1297        hardfork: FoundryHardfork,
1298        parent_beacon_block_root: Option<B256>,
1299        execution_kind: BlockExecutionKind,
1300    ) -> Option<EthereumBlockTransitions> {
1301        if self.is_optimism() || self.is_tempo() {
1302            return None;
1303        }
1304        let FoundryHardfork::Ethereum(hardfork) = hardfork else { return None };
1305        Some(EthereumBlockTransitions {
1306            hardfork,
1307            deposit_contract_address: self.ethereum_deposit_contract_address(),
1308            parent_beacon_block_root,
1309            execution_kind,
1310        })
1311    }
1312
1313    /// Returns the configured deposit contract, then the canonical address for known chains.
1314    fn ethereum_deposit_contract_address(&self) -> Address {
1315        if let Some(address) = self
1316            .genesis
1317            .genesis_init
1318            .as_ref()
1319            .and_then(|genesis| genesis.config.deposit_contract_address)
1320        {
1321            return address;
1322        }
1323
1324        match NamedChain::try_from(self.evm_env.read().cfg_env.chain_id) {
1325            Ok(NamedChain::Sepolia) => SEPOLIA_DEPOSIT_CONTRACT_ADDRESS,
1326            Ok(NamedChain::Holesky) => HOLESKY_DEPOSIT_CONTRACT_ADDRESS,
1327            // Hoodi shares the mainnet address; other chains use Alloy's mainnet fallback.
1328            _ => MAINNET_DEPOSIT_CONTRACT_ADDRESS,
1329        }
1330    }
1331
1332    /// Returns the active Tempo hardfork.
1333    pub fn tempo_hardfork(&self) -> TempoHardfork {
1334        TempoHardfork::from(self.hardfork())
1335    }
1336
1337    /// Returns the active Monad hardfork.
1338    #[cfg(feature = "monad")]
1339    pub fn monad_hardfork(&self) -> monad_revm::MonadHardfork {
1340        monad_revm::MonadHardfork::from(self.hardfork())
1341    }
1342
1343    /// Returns whether a Tempo hardfork is active on this backend.
1344    pub fn is_tempo_hardfork_active(&self, hardfork: TempoHardfork) -> bool {
1345        self.is_tempo() && self.tempo_hardfork() >= hardfork
1346    }
1347
1348    /// Returns the precompiles for the current spec.
1349    pub fn precompiles(&self) -> BTreeMap<String, Address> {
1350        let spec_id = self.spec_id();
1351        let mut precompiles =
1352            PrecompilesMap::from_static(Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)));
1353        let chain_id = self.protocol_chain_id();
1354        let timestamp = self.evm_env.read().block_env.timestamp.saturating_to();
1355        self.networks.inject_chain_precompiles(&mut precompiles, chain_id, timestamp);
1356
1357        let mut precompiles_map = BTreeMap::<String, Address>::default();
1358        for address in precompiles.addresses() {
1359            let precompile = precompiles.get(address).expect("precompile address must resolve");
1360            precompiles_map.insert(precompile.precompile_id().name().to_string(), *address);
1361        }
1362
1363        // Extend with configured network precompiles.
1364        precompiles_map.extend(self.networks.precompiles(Some(self.hardfork())));
1365
1366        if let Some(factory) = &self.precompile_factory {
1367            for (address, precompile) in factory.precompiles() {
1368                precompiles_map.insert(precompile.precompile_id().to_string(), address);
1369            }
1370        }
1371
1372        precompiles_map
1373    }
1374
1375    /// Returns the system contracts for the current spec.
1376    pub fn system_contracts(&self) -> BTreeMap<SystemContract, Address> {
1377        let mut system_contracts = BTreeMap::<SystemContract, Address>::default();
1378
1379        let spec_id = self.spec_id();
1380
1381        if spec_id >= SpecId::CANCUN {
1382            system_contracts.extend(SystemContract::cancun());
1383        }
1384
1385        if spec_id >= SpecId::PRAGUE {
1386            system_contracts.extend(SystemContract::prague(None));
1387        }
1388
1389        system_contracts
1390    }
1391
1392    /// Returns the active [`BlobParams`].
1393    pub fn blob_params(&self) -> BlobParams {
1394        self.fees.blob_params()
1395    }
1396
1397    fn simulation_blob_params_at_timestamp(&self, timestamp: u64) -> BlobParams {
1398        let configured_hardfork = self.hardfork();
1399        if let FoundryHardfork::Ethereum(
1400            configured
1401            @ (EthereumHardfork::Osaka | EthereumHardfork::Bpo1 | EthereumHardfork::Bpo2),
1402        ) = configured_hardfork
1403            && let Some(hardfork) = FoundryHardfork::from_chain_and_timestamp(
1404                self.evm_env.read().cfg_env.chain_id,
1405                timestamp,
1406            )
1407            && let FoundryHardfork::Ethereum(
1408                scheduled @ (EthereumHardfork::Osaka
1409                | EthereumHardfork::Bpo1
1410                | EthereumHardfork::Bpo2),
1411            ) = hardfork
1412        {
1413            let hardfork = match (configured, scheduled) {
1414                (EthereumHardfork::Bpo2, _) | (_, EthereumHardfork::Bpo2) => EthereumHardfork::Bpo2,
1415                (EthereumHardfork::Bpo1, _) | (_, EthereumHardfork::Bpo1) => EthereumHardfork::Bpo1,
1416                _ => EthereumHardfork::Osaka,
1417            };
1418            return get_blob_params_by_hardfork(hardfork.into());
1419        }
1420        get_blob_params_by_hardfork(configured_hardfork)
1421    }
1422
1423    #[cfg(feature = "optimism")]
1424    fn is_optimism_jovian_at_header<H: BlockHeader>(
1425        &self,
1426        header: &H,
1427        decoded: Option<bool>,
1428    ) -> bool {
1429        if !self.is_optimism() {
1430            return false;
1431        }
1432        if let Some(jovian) = decoded {
1433            return jovian;
1434        }
1435        if !header.extra_data().is_empty() {
1436            return false;
1437        }
1438        let hardfork = if self.get_fork().is_some() {
1439            FoundryHardfork::from_chain_and_timestamp(self.protocol_chain_id(), header.timestamp())
1440                .unwrap_or_else(|| self.hardfork())
1441        } else {
1442            self.hardfork()
1443        };
1444        OpHardfork::from(hardfork) >= OpHardfork::Jovian
1445    }
1446
1447    #[cfg(not(feature = "optimism"))]
1448    fn is_optimism_jovian_at_header<H: BlockHeader>(
1449        &self,
1450        _header: &H,
1451        _decoded: Option<bool>,
1452    ) -> bool {
1453        false
1454    }
1455
1456    /// Returns an error if EIP1559 is not active (pre Berlin)
1457    pub fn ensure_eip1559_active(&self) -> Result<(), BlockchainError> {
1458        if self.is_eip1559() {
1459            return Ok(());
1460        }
1461        Err(BlockchainError::EIP1559TransactionUnsupportedAtHardfork)
1462    }
1463
1464    /// Returns an error if EIP1559 is not active (pre muirGlacier)
1465    pub fn ensure_eip2930_active(&self) -> Result<(), BlockchainError> {
1466        if self.is_eip2930() {
1467            return Ok(());
1468        }
1469        Err(BlockchainError::EIP2930TransactionUnsupportedAtHardfork)
1470    }
1471
1472    pub fn ensure_eip4844_active(&self) -> Result<(), BlockchainError> {
1473        if self.is_eip4844() {
1474            return Ok(());
1475        }
1476        Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork)
1477    }
1478
1479    pub fn ensure_eip7702_active(&self) -> Result<(), BlockchainError> {
1480        if self.is_eip7702() {
1481            return Ok(());
1482        }
1483        Err(BlockchainError::EIP7702TransactionUnsupportedAtHardfork)
1484    }
1485
1486    /// Returns an error if op-stack deposits are not active
1487    #[cfg(feature = "optimism")]
1488    pub const fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> {
1489        if self.is_optimism() {
1490            return Ok(());
1491        }
1492        Err(BlockchainError::DepositTransactionUnsupported)
1493    }
1494
1495    /// Returns an error if Tempo transactions are not active
1496    pub const fn ensure_tempo_active(&self) -> Result<(), BlockchainError> {
1497        if self.is_tempo() {
1498            return Ok(());
1499        }
1500        Err(BlockchainError::TempoTransactionUnsupported)
1501    }
1502
1503    /// Builds the [`InspectorTxConfig`] from the backend's current settings.
1504    fn inspector_tx_config(&self) -> InspectorTxConfig {
1505        InspectorTxConfig {
1506            print_traces: self.print_traces,
1507            print_logs: self.print_logs,
1508            enable_steps_tracing: self.enable_steps_tracing,
1509            call_trace_decoder: self.call_trace_decoder(),
1510        }
1511    }
1512
1513    /// Returns a trace decoder configured for the currently resolved hardfork.
1514    fn call_trace_decoder(&self) -> Arc<CallTraceDecoder> {
1515        let hardfork = Some(self.networks.executed_hardfork(self.hardfork()));
1516        let decoder = self.call_trace_decoder.read();
1517        if decoder.hardfork() == hardfork {
1518            return Arc::clone(&decoder);
1519        }
1520        drop(decoder);
1521
1522        let mut decoder = self.call_trace_decoder.write();
1523        let mut updated = decoder.as_ref().clone();
1524        updated.set_hardfork(hardfork);
1525        *decoder = Arc::new(updated);
1526        Arc::clone(&decoder)
1527    }
1528
1529    /// Builds the [`PoolTxGasConfig`] from the given EVM environment.
1530    fn pool_tx_gas_config(&self, evm_env: &EvmEnv) -> PoolTxGasConfig {
1531        let spec_id = *evm_env.spec_id();
1532        let is_cancun = spec_id >= SpecId::CANCUN;
1533        let blob_params = self.blob_params();
1534        PoolTxGasConfig {
1535            disable_block_gas_limit: evm_env.cfg_env.disable_block_gas_limit,
1536            tx_gas_limit_cap: evm_env.cfg_env.tx_gas_limit_cap,
1537            tx_gas_limit_cap_resolved: self.tx_gas_limit_cap(evm_env),
1538            max_blob_gas_per_block: blob_params.max_blob_gas_per_block(),
1539            is_cancun,
1540        }
1541    }
1542
1543    #[cfg(feature = "monad")]
1544    fn monad_cfg_env(&self, evm_env: &EvmEnv) -> Option<monad_revm::MonadCfgEnv> {
1545        if !self.is_monad() {
1546            return None;
1547        }
1548
1549        let hardfork = monad_revm::MonadHardfork::from(self.hardfork());
1550        Some(monad_revm::MonadCfgEnv::from(evm_env.cfg_env.clone().with_spec_and_gas_params(
1551            hardfork,
1552            monad_revm::instructions::monad_gas_params(hardfork),
1553        )))
1554    }
1555
1556    fn tx_gas_limit_cap(&self, evm_env: &EvmEnv) -> u64 {
1557        #[cfg(feature = "monad")]
1558        if let Some(cfg) = self.monad_cfg_env(evm_env) {
1559            return cfg.tx_gas_limit_cap();
1560        }
1561        evm_env.cfg_env.tx_gas_limit_cap()
1562    }
1563
1564    pub(crate) fn fallback_tx_gas_limit(&self, evm_env: &EvmEnv) -> u64 {
1565        let block_gas_limit = evm_env.block_env.gas_limit;
1566        if evm_env.cfg_env.tx_gas_limit_cap.is_none() {
1567            block_gas_limit.min(self.tx_gas_limit_cap(evm_env))
1568        } else {
1569            block_gas_limit
1570        }
1571    }
1572
1573    fn max_initcode_size(&self, evm_env: &EvmEnv) -> usize {
1574        #[cfg(feature = "monad")]
1575        if let Some(cfg) = self.monad_cfg_env(evm_env) {
1576            return cfg.max_initcode_size();
1577        }
1578        evm_env.cfg_env.max_initcode_size()
1579    }
1580
1581    /// Returns the block gas limit
1582    pub fn gas_limit(&self) -> u64 {
1583        self.evm_env.read().block_env.gas_limit
1584    }
1585
1586    /// Sets the block gas limit
1587    pub fn set_gas_limit(&self, gas_limit: u64) {
1588        self.evm_env.write().block_env.gas_limit = gas_limit;
1589    }
1590
1591    /// Returns the current base fee
1592    pub fn base_fee(&self) -> u64 {
1593        self.fees.base_fee()
1594    }
1595
1596    /// Returns whether the minimum suggested priority fee is enforced
1597    pub const fn is_min_priority_fee_enforced(&self) -> bool {
1598        self.fees.is_min_priority_fee_enforced()
1599    }
1600
1601    pub fn excess_blob_gas_and_price(&self) -> Option<BlobExcessGasAndPrice> {
1602        self.fees.excess_blob_gas_and_price()
1603    }
1604
1605    /// Sets the current basefee
1606    pub fn set_base_fee(&self, basefee: u64) {
1607        self.fees.set_base_fee(basefee)
1608    }
1609
1610    /// Sets the gas price
1611    pub fn set_gas_price(&self, price: u128) {
1612        self.fees.set_gas_price(price)
1613    }
1614
1615    pub fn elasticity(&self) -> f64 {
1616        self.fees.elasticity()
1617    }
1618
1619    /// Returns the total difficulty of the chain until this block
1620    ///
1621    /// Note: this will always be `0` in memory mode
1622    /// In forking mode this will always be the total difficulty of the forked block
1623    pub fn total_difficulty(&self) -> U256 {
1624        self.blockchain.storage.read().total_difficulty
1625    }
1626
1627    /// Creates a new `evm_snapshot` at the current height.
1628    ///
1629    /// Returns the id of the snapshot created.
1630    pub async fn create_state_snapshot(&self) -> U256 {
1631        let num = self.best_number();
1632        let hash = self.best_hash();
1633        let id = self.db.write().await.snapshot_state();
1634        trace!(target: "backend", "creating snapshot {} at {}", id, num);
1635        self.active_state_snapshots.lock().insert(
1636            id,
1637            StateSnapshot {
1638                block_number: num,
1639                block_hash: hash,
1640                fees: self.fees.snapshot(),
1641                time_offset: self.time.offset(),
1642            },
1643        );
1644        id
1645    }
1646
1647    pub fn list_state_snapshots(&self) -> BTreeMap<U256, (u64, B256)> {
1648        self.active_state_snapshots
1649            .lock()
1650            .iter()
1651            .map(|(&id, snapshot)| (id, (snapshot.block_number, snapshot.block_hash)))
1652            .collect()
1653    }
1654
1655    /// Returns the environment for the next block
1656    fn next_evm_env(&self) -> EvmEnv {
1657        let mut evm_env = self.evm_env.read().clone();
1658        // increase block number for this block
1659        evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1));
1660        evm_env.block_env.basefee = self.base_fee();
1661        evm_env.block_env.blob_excess_gas_and_price = self.excess_blob_gas_and_price();
1662        evm_env.block_env.timestamp = U256::from(self.time.current_call_timestamp());
1663        evm_env
1664    }
1665
1666    /// Returns the environment for replaying transactions from a historical block.
1667    fn tx_replay_evm_env(&self, block: &Block) -> (EvmEnv, FoundryHardfork) {
1668        let mut evm_env = self.evm_env.read().clone();
1669        evm_env.block_env = block_env_from_header(&block.header);
1670        let hardfork = self.hardfork();
1671        #[cfg(feature = "monad")]
1672        let hardfork = if self.is_monad() {
1673            let block_hash = block.header.hash_slow();
1674            let fallback = crate::eth::backend::db::MonadBlockReplayProfile {
1675                execution_chain_id: evm_env.cfg_env.chain_id,
1676                hardfork: self.monad_hardfork(),
1677            };
1678            let profile = self
1679                .blockchain
1680                .storage
1681                .read()
1682                .monad_block_replay_profiles
1683                .get(&block_hash)
1684                .copied()
1685                .unwrap_or(fallback);
1686            evm_env.cfg_env.chain_id = profile.execution_chain_id;
1687            evm_env.cfg_env.spec = profile.hardfork.into();
1688            profile.hardfork.into()
1689        } else {
1690            hardfork
1691        };
1692        apply_chain_specific_tx_replay_env_changes_for_chain(
1693            &mut evm_env,
1694            self.protocol_chain_id(),
1695        );
1696        (evm_env, hardfork)
1697    }
1698
1699    /// Creates the database and environment for replaying a locally mined block.
1700    ///
1701    /// An empty block execution applies protocol-level pre-execution changes, such as the
1702    /// EIP-2935 parent hash system call, through the same network-specific executor used while
1703    /// mining.
1704    fn prepare_block_replay<'a>(
1705        &self,
1706        block: &Block,
1707        parent_state: &'a StateDb,
1708    ) -> Result<(CacheDB<&'a StateDb>, EvmEnv, FoundryHardfork), BlockchainError> {
1709        self.prepare_block_replay_with_db(block, parent_state)
1710    }
1711
1712    /// Creates an overlay and applies block-start transitions for a locally mined block replay.
1713    fn prepare_block_replay_with_db<DB>(
1714        &self,
1715        block: &Block,
1716        db: DB,
1717    ) -> Result<(CacheDB<DB>, EvmEnv, FoundryHardfork), BlockchainError>
1718    where
1719        DB: DatabaseRef<Error = DatabaseError> + Debug,
1720    {
1721        let mut cache_db = AnvilCacheDB::new(db);
1722        let (evm_env, hardfork) = self.tx_replay_evm_env(block);
1723        let spec_id = *evm_env.spec_id();
1724        let inspector_tx_config = self.inspector_tx_config();
1725        let gas_config = self.pool_tx_gas_config(&evm_env);
1726
1727        self.execute_with_block_executor(
1728            &mut cache_db,
1729            &evm_env,
1730            block.header.parent_hash,
1731            spec_id,
1732            hardfork,
1733            block.header.parent_beacon_block_root,
1734            BlockExecutionKind::TransactionPrefix,
1735            &[],
1736            &gas_config,
1737            &inspector_tx_config,
1738            &|_, _| Ok(()),
1739        )?;
1740
1741        Ok((cache_db.0, evm_env, hardfork))
1742    }
1743
1744    /// Replays the stored transaction prefix `[0, end)` into an existing block overlay.
1745    fn replay_mined_transaction_prefix<DB>(
1746        &self,
1747        cache_db: &mut CacheDB<DB>,
1748        evm_env: &EvmEnv,
1749        hardfork: FoundryHardfork,
1750        block: &Block,
1751        end: usize,
1752    ) -> Result<(), BlockchainError>
1753    where
1754        DB: DatabaseRef<Error = DatabaseError> + Debug,
1755    {
1756        let monad_context = self.active_monad_context_for_mined_block(block)?;
1757        for (index, transaction) in block.body.transactions[..end].iter().enumerate() {
1758            let pending = self.pending_mined_transaction(transaction.clone())?;
1759            let mut inspector = AnvilInspector::default();
1760            let transaction_context = monad_execution_context_at(monad_context.as_ref(), index);
1761            let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
1762                cache_db,
1763                evm_env,
1764                &mut inspector,
1765                &pending,
1766                EnvelopeExecution::replay(transaction_context, hardfork),
1767            )?;
1768            cache_db.commit(result.state);
1769        }
1770        Ok(())
1771    }
1772
1773    /// Builds [`Inspector`] with the configured options.
1774    fn build_inspector(&self) -> AnvilInspector {
1775        let mut inspector = AnvilInspector::default();
1776
1777        if self.print_logs {
1778            inspector = inspector.with_log_collector();
1779        }
1780        if self.print_traces {
1781            inspector = inspector.with_trace_printer();
1782        }
1783
1784        inspector
1785    }
1786
1787    /// Builds an inspector configured for block mining (tracing always enabled).
1788    fn build_mining_inspector(&self) -> AnvilInspector {
1789        let mut inspector = AnvilInspector::default().with_tracing();
1790        if self.enable_steps_tracing {
1791            inspector = inspector.with_steps_tracing();
1792        }
1793        if self.print_logs {
1794            inspector = inspector.with_log_collector();
1795        }
1796        if self.print_traces {
1797            inspector = inspector.with_trace_printer();
1798        }
1799        inspector
1800    }
1801
1802    /// Returns a new block event stream that yields Notifications when a new block was added or
1803    /// when logs were removed from the canonical chain due to a reorg
1804    pub fn new_block_notifications(&self) -> ChainNotifications {
1805        let (tx, rx) = unbounded();
1806        self.new_block_listeners.lock().push(tx);
1807        trace!(target: "backed", "added new block listener");
1808        rx
1809    }
1810
1811    /// Returns the number of new-block listeners. Closed listeners are pruned lazily on the next
1812    /// new block notification.
1813    pub fn new_block_listeners_count(&self) -> usize {
1814        self.new_block_listeners.lock().len()
1815    }
1816
1817    /// Notifies all `new_block_listeners` about the new block
1818    fn notify_on_new_block(&self, header: Header, hash: B256) {
1819        // cleanup closed notification streams first, if the channel is closed we can remove the
1820        // sender half for the set
1821        self.new_block_listeners.lock().retain(|tx| !tx.is_closed());
1822
1823        let notification =
1824            ChainNotification::Block(NewBlockNotification { hash, header: Arc::new(header) });
1825
1826        self.new_block_listeners
1827            .lock()
1828            .retain(|tx| tx.unbounded_send(notification.clone()).is_ok());
1829    }
1830
1831    /// Notifies all `new_block_listeners` about the logs that were removed from the canonical
1832    /// chain due to a reorg.
1833    fn notify_on_removed_logs(&self, logs: Vec<Log>) {
1834        // cleanup closed notification streams first, if the channel is closed we can remove the
1835        // sender half for the set
1836        self.new_block_listeners.lock().retain(|tx| !tx.is_closed());
1837
1838        let notification = ChainNotification::RemovedLogs(Arc::new(logs));
1839
1840        self.new_block_listeners
1841            .lock()
1842            .retain(|tx| tx.unbounded_send(notification.clone()).is_ok());
1843    }
1844
1845    /// Returns the block number for the given block id
1846    pub fn convert_block_number(&self, block: Option<BlockNumber>) -> u64 {
1847        let current = self.best_number();
1848        match block.unwrap_or(BlockNumber::Latest) {
1849            BlockNumber::Latest | BlockNumber::Pending => current,
1850            BlockNumber::Earliest => 0,
1851            BlockNumber::Number(num) => num,
1852            BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch),
1853            BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2),
1854        }
1855    }
1856
1857    /// Returns the canonical hash for the given block number.
1858    pub(crate) fn block_hash_by_number(&self, number: u64) -> Option<B256> {
1859        self.blockchain.hash(BlockNumber::Number(number).into(), self.slots_in_an_epoch)
1860    }
1861
1862    /// Returns the block and its hash for the given id
1863    pub(crate) fn get_block_with_hash(&self, id: impl Into<BlockId>) -> Option<(Block, B256)> {
1864        let hash = self.blockchain.hash(id.into(), self.slots_in_an_epoch)?;
1865        let block = self.get_block_by_hash(hash)?;
1866        Some((block, hash))
1867    }
1868
1869    pub fn get_block(&self, id: impl Into<BlockId>) -> Option<Block> {
1870        self.get_block_with_hash(id).map(|(block, _)| block)
1871    }
1872
1873    pub fn get_block_by_hash(&self, hash: B256) -> Option<Block> {
1874        self.blockchain.get_block_by_hash(&hash)
1875    }
1876
1877    /// Returns the base fees for the block after a fee history range.
1878    ///
1879    /// Mining publishes a new canonical block before advancing the fee manager. Holding the mining
1880    /// lock makes choosing between an existing child and the current head's pending fees atomic
1881    /// with that publication sequence.
1882    pub(crate) async fn fee_history_next_fees(&self, highest: u64) -> Option<(u128, u128)> {
1883        let _mining_guard = self.mining.lock().await;
1884        let next_number = highest.checked_add(1)?;
1885        if let Some(block) = self.get_block(next_number) {
1886            Some((
1887                block.header.base_fee_per_gas.unwrap_or_default() as u128,
1888                block.header.blob_fee(self.blob_params()).unwrap_or_default(),
1889            ))
1890        } else if highest == self.best_number() {
1891            Some((self.fees().base_fee() as u128, self.fees().base_fee_per_blob_gas()))
1892        } else {
1893            None
1894        }
1895    }
1896
1897    /// Returns the traces for the given transaction
1898    pub(crate) fn mined_parity_trace_transaction(
1899        &self,
1900        hash: B256,
1901    ) -> Option<Vec<LocalizedTransactionTrace>> {
1902        self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.parity_traces())
1903    }
1904
1905    /// Returns the traces for the given block
1906    pub(crate) fn mined_parity_trace_block(
1907        &self,
1908        block: u64,
1909    ) -> Option<Vec<LocalizedTransactionTrace>> {
1910        let block = self.get_block(block)?;
1911        let mut traces = vec![];
1912        let storage = self.blockchain.storage.read();
1913        for tx in block.body.transactions {
1914            if let Some(mined_tx) = storage.transactions.get(&tx.hash()) {
1915                traces.extend(mined_tx.parity_traces());
1916            }
1917        }
1918        Some(traces)
1919    }
1920
1921    /// Returns the mined transaction for the given hash
1922    pub(crate) fn mined_transaction(&self, hash: B256) -> Option<MinedTransaction<N>> {
1923        self.blockchain.storage.read().transactions.get(&hash).cloned()
1924    }
1925
1926    /// Overrides the given signature to impersonate the specified address during ecrecover.
1927    pub async fn impersonate_signature(
1928        &self,
1929        signature: Bytes,
1930        address: Address,
1931    ) -> Result<(), BlockchainError> {
1932        self.cheats.add_recover_override(signature, address);
1933        Ok(())
1934    }
1935
1936    /// Returns code by its hash
1937    pub async fn debug_code_by_hash(
1938        &self,
1939        code_hash: B256,
1940        block_id: Option<BlockId>,
1941    ) -> Result<Option<Bytes>, BlockchainError> {
1942        if let Ok(code) = self.db.read().await.code_by_hash_ref(code_hash) {
1943            return Ok(Some(code.original_bytes()));
1944        }
1945        if let Some(fork) = self.get_fork() {
1946            return Ok(fork.debug_code_by_hash(code_hash, block_id).await?);
1947        }
1948
1949        Ok(None)
1950    }
1951
1952    /// Returns the value associated with a key from the database
1953    /// Currently only supports bytecode lookups.
1954    ///
1955    /// Based on Reth implementation: <https://github.com/paradigmxyz/reth/blob/66cfa9ed1a8c4bc2424aacf6fb2c1e67a78ee9a2/crates/rpc/rpc/src/debug.rs#L1146-L1178>
1956    ///
1957    /// Key should be: 0x63 (1-byte prefix) + 32 bytes (code_hash)
1958    /// Total key length must be 33 bytes.
1959    pub async fn debug_db_get(&self, key: String) -> Result<Option<Bytes>, BlockchainError> {
1960        let key_bytes = if key.starts_with("0x") {
1961            hex::decode(&key)
1962                .map_err(|_| BlockchainError::Message("Invalid hex key".to_string()))?
1963        } else {
1964            key.into_bytes()
1965        };
1966
1967        // Validate key length: must be 33 bytes (1 byte prefix + 32 bytes code hash)
1968        if key_bytes.len() != 33 {
1969            return Err(BlockchainError::Message(format!(
1970                "Invalid key length: expected 33 bytes, got {}",
1971                key_bytes.len()
1972            )));
1973        }
1974
1975        // Check for bytecode prefix (0x63 = 'c' in ASCII)
1976        if key_bytes[0] != 0x63 {
1977            return Err(BlockchainError::Message(
1978                "Key prefix must be 0x63 for code hash lookups".to_string(),
1979            ));
1980        }
1981
1982        let code_hash = B256::from_slice(&key_bytes[1..33]);
1983
1984        // Use the existing debug_code_by_hash method to retrieve the bytecode
1985        self.debug_code_by_hash(code_hash, None).await
1986    }
1987
1988    fn mined_block_by_hash(&self, hash: B256) -> Option<AnyRpcBlock> {
1989        let block = self.blockchain.get_block_by_hash(&hash)?;
1990        Some(self.convert_block_with_hash(block, Some(hash)))
1991    }
1992
1993    pub(crate) async fn mined_transactions_by_block_number(
1994        &self,
1995        number: BlockNumber,
1996    ) -> Option<Vec<AnyRpcTransaction>> {
1997        if let Some(block) = self.get_block(number) {
1998            return self.mined_transactions_in_block(&block);
1999        }
2000        None
2001    }
2002
2003    /// Returns all transactions given a block
2004    pub(crate) fn mined_transactions_in_block(
2005        &self,
2006        block: &Block,
2007    ) -> Option<Vec<AnyRpcTransaction>> {
2008        let mut transactions = Vec::with_capacity(block.body.transactions.len());
2009        let base_fee = block.header.base_fee_per_gas();
2010        let storage = self.blockchain.storage.read();
2011        for hash in block.body.transactions.iter().map(|tx| tx.hash()) {
2012            let info = storage.transactions.get(&hash)?.info.clone();
2013            let tx = block.body.transactions.get(info.transaction_index as usize)?.clone();
2014
2015            let tx = transaction_build(Some(hash), tx, Some(block), Some(info), base_fee);
2016            transactions.push(tx);
2017        }
2018        Some(transactions)
2019    }
2020
2021    pub fn mined_block_by_number(&self, number: BlockNumber) -> Option<AnyRpcBlock> {
2022        let (block, hash) = self.get_block_with_hash(number)?;
2023        let mut block = self.convert_block_with_hash(block, Some(hash));
2024        block.transactions.convert_to_hashes();
2025        Some(block)
2026    }
2027
2028    pub fn get_full_block(&self, id: impl Into<BlockId>) -> Option<AnyRpcBlock> {
2029        let (block, hash) = self.get_block_with_hash(id)?;
2030        let transactions = self.mined_transactions_in_block(&block)?;
2031        let mut block = self.convert_block_with_hash(block, Some(hash));
2032        block.inner.transactions = BlockTransactions::Full(transactions);
2033        Some(block)
2034    }
2035
2036    /// Takes a block as it's stored internally and returns the eth api conform block format.
2037    pub fn convert_block(&self, block: Block) -> AnyRpcBlock {
2038        self.convert_block_with_hash(block, None)
2039    }
2040
2041    /// Takes a block as it's stored internally and returns the eth api conform block format.
2042    /// If `known_hash` is provided, it will be used instead of computing `hash_slow()`.
2043    pub fn convert_block_with_hash(&self, block: Block, known_hash: Option<B256>) -> AnyRpcBlock {
2044        let transactions = block.body.transactions.iter().map(|tx| tx.hash()).collect();
2045        let block = canonical_block(block);
2046        let size = U256::from(block.length() as u32);
2047        let header = block.header;
2048
2049        let hash = known_hash.unwrap_or_else(|| header.hash_slow());
2050        let number = header.number();
2051        let withdrawals_root = header.withdrawals_root();
2052        let tempo_fields = header
2053            .as_tempo()
2054            .map(|header| {
2055                (
2056                    header.timestamp_millis(),
2057                    header.general_gas_limit,
2058                    header.shared_gas_limit,
2059                    header.timestamp_millis_part,
2060                )
2061            })
2062            .or_else(|| {
2063                self.is_tempo()
2064                    .then(|| (header.timestamp().saturating_mul(1000), header.gas_limit(), 0, 0))
2065            });
2066
2067        let block = AlloyBlock {
2068            header: AlloyHeader {
2069                inner: AnyHeader::from(header.into_inner()),
2070                hash,
2071                total_difficulty: Some(self.total_difficulty()),
2072                size: Some(size),
2073            },
2074            transactions: alloy_rpc_types::BlockTransactions::Hashes(transactions),
2075            uncles: vec![],
2076            withdrawals: withdrawals_root.map(|_| Default::default()),
2077        };
2078
2079        let mut block = WithOtherFields::new(block);
2080
2081        // If Arbitrum, apply chain specifics to converted block.
2082        if is_arbitrum(self.protocol_chain_id()) {
2083            // Set `l1BlockNumber` field.
2084            block.other.insert("l1BlockNumber".to_string(), number.into());
2085        }
2086
2087        if let Some((
2088            timestamp_millis,
2089            general_gas_limit,
2090            shared_gas_limit,
2091            timestamp_millis_part,
2092        )) = tempo_fields
2093        {
2094            block.other.insert(
2095                "timestampMillis".to_string(),
2096                serde_json::Value::String(format!("0x{timestamp_millis:x}")),
2097            );
2098            block.other.insert(
2099                "mainBlockGeneralGasLimit".to_string(),
2100                serde_json::Value::String(format!("0x{general_gas_limit:x}")),
2101            );
2102            block.other.insert(
2103                "sharedGasLimit".to_string(),
2104                serde_json::Value::String(format!("0x{shared_gas_limit:x}")),
2105            );
2106            block.other.insert(
2107                "timestampMillisPart".to_string(),
2108                serde_json::Value::String(format!("0x{timestamp_millis_part:x}")),
2109            );
2110        }
2111
2112        AnyRpcBlock::from(block)
2113    }
2114
2115    pub async fn block_by_hash(&self, hash: B256) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2116        trace!(target: "backend", "get block by hash {:?}", hash);
2117        if let tx @ Some(_) = self.mined_block_by_hash(hash) {
2118            return Ok(tx);
2119        }
2120
2121        if let Some(fork) = self.get_fork() {
2122            return Ok(fork.block_by_hash(hash).await?);
2123        }
2124
2125        Ok(None)
2126    }
2127
2128    pub async fn block_by_hash_full(
2129        &self,
2130        hash: B256,
2131    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2132        trace!(target: "backend", "get block by hash {:?}", hash);
2133        if let tx @ Some(_) = self.get_full_block(hash) {
2134            return Ok(tx);
2135        }
2136
2137        if let Some(fork) = self.get_fork() {
2138            return Ok(fork.block_by_hash_full(hash).await?);
2139        }
2140
2141        Ok(None)
2142    }
2143
2144    pub async fn block_by_number(
2145        &self,
2146        number: BlockNumber,
2147    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2148        trace!(target: "backend", "get block by number {:?}", number);
2149        if let tx @ Some(_) = self.mined_block_by_number(number) {
2150            return Ok(tx);
2151        }
2152
2153        if let Some(fork) = self.get_fork() {
2154            let number = self.convert_block_number(Some(number));
2155            if fork.predates_fork_inclusive(number) {
2156                return Ok(fork.block_by_number(number).await?);
2157            }
2158        }
2159
2160        Ok(None)
2161    }
2162
2163    pub async fn block_by_number_full(
2164        &self,
2165        number: BlockNumber,
2166    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2167        trace!(target: "backend", "get block by number {:?}", number);
2168        if let tx @ Some(_) = self.get_full_block(number) {
2169            return Ok(tx);
2170        }
2171
2172        if let Some(fork) = self.get_fork() {
2173            let number = self.convert_block_number(Some(number));
2174            if fork.predates_fork_inclusive(number) {
2175                return Ok(fork.block_by_number_full(number).await?);
2176            }
2177        }
2178
2179        Ok(None)
2180    }
2181
2182    /// Converts the `BlockNumber` into a numeric value
2183    ///
2184    /// # Errors
2185    ///
2186    /// returns an error if the requested number is larger than the current height
2187    pub async fn ensure_block_number<T: Into<BlockId>>(
2188        &self,
2189        block_id: Option<T>,
2190    ) -> Result<u64, BlockchainError> {
2191        let current = self.best_number();
2192        let requested =
2193            match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) {
2194                BlockId::Hash(hash) => {
2195                    self.block_by_hash(hash.block_hash)
2196                        .await?
2197                        .ok_or(BlockchainError::BlockNotFound)?
2198                        .header
2199                        .number
2200                }
2201                BlockId::Number(num) => match num {
2202                    BlockNumber::Latest | BlockNumber::Pending => current,
2203                    BlockNumber::Earliest => U64::ZERO.to::<u64>(),
2204                    BlockNumber::Number(num) => num,
2205                    BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch),
2206                    BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2),
2207                },
2208            };
2209
2210        if requested > current {
2211            Err(BlockchainError::BlockOutOfRange(current, requested))
2212        } else {
2213            Ok(requested)
2214        }
2215    }
2216
2217    /// Injects all configured precompiles into the given precompile map.
2218    ///
2219    /// This applies five layers:
2220    /// 1. Network-specific precompiles (e.g. Tempo, OP)
2221    /// 2. Chain- and timestamp-specific precompiles
2222    /// 3. User-provided precompiles via [`PrecompileFactory`]
2223    /// 4. Cheatcode ecrecover overrides (if active)
2224    /// 5. Block-specific precompiles (e.g. ArbSys)
2225    fn inject_precompiles(&self, precompiles: &mut PrecompilesMap, evm_env: &EvmEnv) {
2226        self.inject_configured_precompiles(precompiles, evm_env);
2227
2228        if let Some(block_number) = self.arbitrum_block_number(evm_env) {
2229            self.inject_arbitrum_precompile_at_block(precompiles, block_number);
2230        }
2231    }
2232
2233    fn inject_configured_precompiles(&self, precompiles: &mut PrecompilesMap, evm_env: &EvmEnv) {
2234        self.networks.inject_precompiles(precompiles);
2235        self.networks.inject_chain_precompiles(
2236            precompiles,
2237            self.protocol_chain_id(),
2238            evm_env.block_env.timestamp.saturating_to(),
2239        );
2240
2241        if let Some(factory) = &self.precompile_factory {
2242            factory.install(precompiles);
2243        }
2244
2245        let cheats = Arc::new(self.cheats.clone());
2246        if cheats.has_recover_overrides() {
2247            let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats));
2248            precompiles.apply_precompile(&EC_RECOVER, move |_| {
2249                Some(DynPrecompile::new_stateful(
2250                    cheat_ecrecover.precompile_id().clone(),
2251                    move |input| cheat_ecrecover.call(input),
2252                ))
2253            });
2254        }
2255    }
2256
2257    fn inject_arbitrum_precompile_at_block(
2258        &self,
2259        precompiles: &mut PrecompilesMap,
2260        block_number: u64,
2261    ) {
2262        precompiles.apply_precompile(&arbitrum::ARB_SYS_ADDRESS, move |_| {
2263            Some(arbitrum::arb_sys_precompile(block_number))
2264        });
2265    }
2266
2267    fn simulation_precompile_overrides(
2268        &self,
2269        state_overrides: Option<&StateOverride>,
2270        evm_env: &EvmEnv,
2271    ) -> Result<SimulationPrecompileOverrides, BlockchainError> {
2272        let mut moves = state_overrides
2273            .into_iter()
2274            .flatten()
2275            .filter_map(|(source, account)| {
2276                account.move_precompile_to.map(|destination| (*source, destination))
2277            })
2278            .collect::<Vec<_>>();
2279        moves.sort_unstable();
2280        if moves.is_empty() {
2281            return Ok(SimulationPrecompileOverrides::default());
2282        }
2283        if self.is_optimism() || self.is_tempo() || self.is_monad() {
2284            return Err(simulate_rpc_error(
2285                -32000,
2286                "precompile moves are not supported on this network",
2287            ));
2288        }
2289
2290        let mut precompiles = PrecompilesMap::from_static(Precompiles::new(
2291            PrecompileSpecId::from_spec_id(*evm_env.spec_id()),
2292        ));
2293        self.inject_precompiles(&mut precompiles, evm_env);
2294        let precompile_addresses = precompiles.addresses().copied().collect::<HashSet<_>>();
2295
2296        // Validate every source first so invalid-source errors take precedence over the more
2297        // specific move errors below.
2298        for (source, _) in &moves {
2299            if !precompile_addresses.contains(source) {
2300                return Err(simulate_rpc_error(
2301                    -32000,
2302                    format!("account {source} is not a precompile"),
2303                ));
2304            }
2305        }
2306        for (source, destination) in &moves {
2307            if source == destination {
2308                return Err(simulate_rpc_error(
2309                    -38022,
2310                    format!("cannot move precompile {source} to itself"),
2311                ));
2312            }
2313        }
2314        let mut destinations = Vec::with_capacity(moves.len());
2315        for (_, destination) in &moves {
2316            if destinations.contains(destination) {
2317                return Err(simulate_rpc_error(
2318                    -38023,
2319                    format!("multiple precompiles moved to {destination}"),
2320                ));
2321            }
2322            destinations.push(*destination);
2323        }
2324
2325        Ok(SimulationPrecompileOverrides { moves })
2326    }
2327
2328    fn apply_simulation_precompile_overrides(
2329        &self,
2330        precompiles: &mut PrecompilesMap,
2331        overrides: &SimulationPrecompileOverrides,
2332    ) -> Result<alloy_primitives::map::AddressSet, BlockchainError> {
2333        let warm_addresses = precompiles.addresses().copied().collect();
2334        precompiles.move_precompiles(overrides.moves.iter().copied()).map_err(
2335            |MovePrecompileError::NotAPrecompile(address)| {
2336                simulate_rpc_error(-32000, format!("account {address} is not a precompile"))
2337            },
2338        )?;
2339
2340        // A dynamic lookup must not restore a precompile removed from its protocol address.
2341        let moved_sources =
2342            Arc::new(overrides.moves.iter().map(|(source, _)| *source).collect::<HashSet<_>>());
2343        precompiles.map_precompile_lookup(move |address, previous| {
2344            if moved_sources.contains(address) {
2345                None
2346            } else {
2347                previous.and_then(|lookup| lookup.lookup(address))
2348            }
2349        });
2350        Ok(warm_addresses)
2351    }
2352
2353    fn inject_tempo_precompiles<DB, I>(
2354        &self,
2355        evm: &mut tempo_evm::evm::TempoEvm<DB, I>,
2356        evm_env: &EvmEnv,
2357    ) where
2358        DB: Database,
2359        I: Inspector<TempoContext<DB>>,
2360    {
2361        self.inject_configured_precompiles(evm.precompiles_mut(), evm_env);
2362        // Re-extend Tempo precompiles, preserving shared non-creditable slots.
2363        let cfg = evm.ctx().cfg.clone();
2364        let non_creditable_slots = evm.non_creditable_slots();
2365        extend_tempo_precompiles(
2366            evm.precompiles_mut(),
2367            &cfg,
2368            StorageActions::disabled(),
2369            non_creditable_slots,
2370        );
2371    }
2372
2373    /// Executes a call with the Ethereum EVM.
2374    ///
2375    /// Creates an Ethereum EVM, injects precompiles, and transacts with a
2376    /// plain [`TxEnv`].
2377    fn transact_eth_with_inspector_ref<'db, I, DB>(
2378        &self,
2379        db: &'db DB,
2380        evm_env: &EvmEnv,
2381        inspector: &mut I,
2382        tx_env: TxEnv,
2383    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2384    where
2385        DB: DatabaseRef + ?Sized,
2386        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
2387        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2388    {
2389        self.transact_eth_with_inspector_ref_and_precompile_overrides(
2390            db,
2391            evm_env,
2392            inspector,
2393            tx_env,
2394            &SimulationPrecompileOverrides::default(),
2395        )
2396    }
2397
2398    fn transact_eth_with_inspector_ref_and_precompile_overrides<'db, I, DB>(
2399        &self,
2400        db: &'db DB,
2401        evm_env: &EvmEnv,
2402        inspector: &mut I,
2403        tx_env: TxEnv,
2404        overrides: &SimulationPrecompileOverrides,
2405    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2406    where
2407        DB: DatabaseRef + ?Sized,
2408        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
2409        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2410    {
2411        let mut evm = EthEvmFactory::default().create_evm_with_inspector(
2412            WrapDatabaseRef(db),
2413            evm_env.clone(),
2414            inspector,
2415        );
2416        self.inject_precompiles(evm.precompiles_mut(), evm_env);
2417        if !overrides.moves.is_empty() {
2418            let warm_addresses =
2419                self.apply_simulation_precompile_overrides(evm.precompiles_mut(), overrides)?;
2420            // EIP-2929 warms protocol precompile addresses, not simulation-only destinations.
2421            evm.ctx_mut().journal_mut().warm_precompiles(&warm_addresses);
2422        }
2423        Ok(evm.transact(tx_env)?)
2424    }
2425
2426    fn transact_eth_simulation_with_inspector_ref<'db, I, DB>(
2427        &self,
2428        db: &'db DB,
2429        evm_env: &EvmEnv,
2430        inspector: &mut I,
2431        tx_env: TxEnv,
2432        overrides: &SimulationPrecompileOverrides,
2433    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2434    where
2435        DB: DatabaseRef + ?Sized,
2436        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
2437        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2438    {
2439        let mut evm = EthEvmFactory::default().create_evm_with_inspector(
2440            WrapDatabaseRef(db),
2441            evm_env.clone(),
2442            inspector,
2443        );
2444        self.inject_precompiles(evm.precompiles_mut(), evm_env);
2445        if !overrides.moves.is_empty() {
2446            let warm_addresses =
2447                self.apply_simulation_precompile_overrides(evm.precompiles_mut(), overrides)?;
2448            // EIP-2929 warms protocol precompile addresses, not simulation-only destinations.
2449            evm.ctx_mut().journal_mut().warm_precompiles(&warm_addresses);
2450        }
2451
2452        let mut evm = evm.into_inner();
2453        ContextSetters::set_tx(evm.ctx_mut(), tx_env);
2454        let mut handler = SimulationHandler::<
2455            _,
2456            revm::context::result::EVMError<DatabaseError>,
2457            EthFrame<EthInterpreter>,
2458        >::default();
2459        let result = handler.inspect_run(&mut evm)?;
2460        let state = evm.ctx_mut().journal_mut().finalize();
2461        Ok(ResultAndState { result, state })
2462    }
2463
2464    /// Executes an envelope through the active network EVM with optional Monad block context.
2465    ///
2466    /// Returns both the execution result and the base [`TxEnv`].
2467    fn transact_envelope_with_inspector_ref_and_context<'db, I, DB>(
2468        &self,
2469        db: &'db DB,
2470        evm_env: &EvmEnv,
2471        inspector: &mut I,
2472        pending: &PendingTransaction<FoundryTxEnvelope>,
2473        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
2474            MonadExecutionContext<'_>,
2475        >,
2476    ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
2477    where
2478        DB: DatabaseRef + ?Sized,
2479        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
2480        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2481    {
2482        self.transact_envelope_with_inspector_ref_and_context_kind(
2483            db,
2484            evm_env,
2485            inspector,
2486            pending,
2487            EnvelopeExecution::transaction(monad_context, self.hardfork()),
2488        )
2489    }
2490
2491    /// Replays a mined envelope through the active network's canonical replay entry point.
2492    fn replay_envelope_with_inspector_ref_and_context<'db, I, DB>(
2493        &self,
2494        db: &'db DB,
2495        evm_env: &EvmEnv,
2496        inspector: &mut I,
2497        pending: &PendingTransaction<FoundryTxEnvelope>,
2498        execution: EnvelopeExecution<'_>,
2499    ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
2500    where
2501        DB: DatabaseRef + ?Sized,
2502        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
2503        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2504    {
2505        self.transact_envelope_with_inspector_ref_and_context_kind(
2506            db, evm_env, inspector, pending, execution,
2507        )
2508    }
2509
2510    fn transact_envelope_with_inspector_ref_and_context_kind<'db, I, DB>(
2511        &self,
2512        db: &'db DB,
2513        evm_env: &EvmEnv,
2514        inspector: &mut I,
2515        pending: &PendingTransaction<FoundryTxEnvelope>,
2516        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] execution: EnvelopeExecution<
2517            '_,
2518        >,
2519    ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
2520    where
2521        DB: DatabaseRef + ?Sized,
2522        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
2523        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2524    {
2525        let tx = pending.transaction.as_ref();
2526        let sender = *pending.sender();
2527        if tx.is_tempo() {
2528            let tx_env: TempoTxEnv =
2529                FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
2530            let base = tx_env.inner.clone();
2531            let result = self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)?;
2532            return Ok((result, base));
2533        }
2534        #[cfg(feature = "optimism")]
2535        if self.is_optimism() {
2536            let op_tx: OpTransaction<TxEnv> =
2537                FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
2538            let base = op_tx.base.clone();
2539            let result = self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx)?;
2540            return Ok((result, base));
2541        }
2542        let tx_env: TxEnv = build_tx_env_for_pending(pending, self.cheats());
2543        let base = tx_env.clone();
2544        #[cfg(feature = "monad")]
2545        let result = if self.is_monad() {
2546            let context = monad::resolve_execution_context(execution.monad_context, &tx_env);
2547            self.transact_monad_with_inspector_ref(
2548                db,
2549                evm_env,
2550                inspector,
2551                tx_env,
2552                monad::PreparedExecution {
2553                    context,
2554                    kind: execution.kind,
2555                    hardfork: monad_revm::MonadHardfork::from(execution.hardfork),
2556                },
2557            )?
2558        } else {
2559            self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?
2560        };
2561        #[cfg(not(feature = "monad"))]
2562        let result = self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?;
2563        Ok((result, base))
2564    }
2565
2566    /// Builds the Tempo [`EvmEnv`] (spec, gas params, [`TempoBlockEnv`]) from a base
2567    /// env.
2568    fn build_tempo_evm_env(&self, evm_env: &EvmEnv) -> EvmEnvFor<TempoEvmNetwork> {
2569        let hardfork = self.tempo_hardfork();
2570        EvmEnv::new(
2571            evm_env.cfg_env.clone().with_spec_and_gas_params(hardfork, tempo_gas_params(hardfork)),
2572            TempoBlockEnv {
2573                inner: evm_env.block_env.clone(),
2574                timestamp_millis_part: 0,
2575                ..Default::default()
2576            },
2577        )
2578    }
2579
2580    /// Creates a Tempo EVM, injects precompiles, and transacts with a native [`TempoTxEnv`].
2581    fn transact_tempo_with_inspector_ref<'db, I, DB>(
2582        &self,
2583        db: &'db DB,
2584        evm_env: &EvmEnv,
2585        inspector: &mut I,
2586        tx_env: TempoTxEnv,
2587    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2588    where
2589        DB: DatabaseRef + ?Sized,
2590        I: Inspector<TempoContext<WrapDatabaseRef<&'db DB>>>,
2591        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2592    {
2593        let tempo_env = self.build_tempo_evm_env(evm_env);
2594        let mut evm = TempoEvmFactory::default().create_evm_with_inspector(
2595            WrapDatabaseRef(db),
2596            tempo_env,
2597            inspector,
2598        );
2599        self.inject_tempo_precompiles(&mut evm, evm_env);
2600        let result = evm.transact(tx_env)?;
2601        Ok(ResultAndState {
2602            result: result.result.map_haltreason(|h| match h {
2603                TempoHaltReason::Ethereum(eth) => eth,
2604                _ => HaltReason::PrecompileError,
2605            }),
2606            state: result.state,
2607        })
2608    }
2609
2610    /// Creates a concrete EVM + [`AnvilBlockExecutor`], runs pre-execution changes, and
2611    /// executes pool transactions. Returns the execution results and drops the EVM.
2612    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
2613    fn execute_with_block_executor<DB>(
2614        &self,
2615        db: DB,
2616        evm_env: &EvmEnv,
2617        parent_hash: B256,
2618        spec_id: SpecId,
2619        hardfork: FoundryHardfork,
2620        parent_beacon_block_root: Option<B256>,
2621        execution_kind: BlockExecutionKind,
2622        pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
2623        gas_config: &PoolTxGasConfig,
2624        inspector_tx_config: &InspectorTxConfig,
2625        validator: &dyn Fn(
2626            &PoolTransaction<FoundryTxEnvelope>,
2627            &AccountInfo,
2628        ) -> Result<(), InvalidTransactionError>,
2629    ) -> Result<
2630        (ExecutedPoolTransactions<FoundryTxEnvelope>, BlockExecutionResult<FoundryReceiptEnvelope>),
2631        BlockchainError,
2632    >
2633    where
2634        DB: StateDB<Error = DatabaseError>,
2635    {
2636        #[cfg(feature = "monad")]
2637        if self.is_monad() {
2638            return self.execute_with_monad_block_executor(
2639                db,
2640                evm_env,
2641                parent_hash,
2642                spec_id,
2643                hardfork,
2644                pool_transactions,
2645                gas_config,
2646                inspector_tx_config,
2647                validator,
2648            );
2649        }
2650
2651        let inspector = self.build_mining_inspector();
2652        let ethereum_transitions =
2653            self.ethereum_block_transitions(hardfork, parent_beacon_block_root, execution_kind);
2654
2655        macro_rules! run {
2656            (
2657                $evm:expr,
2658                $before_transaction:expr,
2659                $execute_transaction:expr,
2660                $on_execution_error:expr
2661            ) => {{
2662                self.inject_precompiles($evm.precompiles_mut(), evm_env);
2663                let mut executor =
2664                    AnvilBlockExecutor::new($evm, parent_hash, spec_id, ethereum_transitions)
2665                        .with_max_blob_gas_per_block(gas_config.max_blob_gas_per_block);
2666                #[cfg(feature = "optimism")]
2667                if self.is_optimism() {
2668                    executor.set_optimism_hardfork(hardfork);
2669                }
2670                executor
2671                    .apply_pre_execution_changes()
2672                    .map_err(|err| BlockchainError::Internal(err.to_string()))?;
2673                let mut hooks = PoolTransactionHooks {
2674                    before_transaction: $before_transaction,
2675                    execute_transaction: $execute_transaction,
2676                    on_execution_error: $on_execution_error,
2677                };
2678                let pool_result = execute_pool_transactions(
2679                    &mut executor,
2680                    pool_transactions,
2681                    gas_config,
2682                    inspector_tx_config,
2683                    self.cheats(),
2684                    validator,
2685                    &mut hooks,
2686                );
2687                let (evm, block_result) =
2688                    executor.finish().map_err(|err| BlockchainError::Internal(err.to_string()))?;
2689                drop(evm);
2690                Ok((pool_result, block_result))
2691            }};
2692        }
2693
2694        #[cfg(feature = "optimism")]
2695        if self.is_optimism() {
2696            let op_env = EvmEnv::new(
2697                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(hardfork.into()),
2698                evm_env.block_env.clone(),
2699            );
2700            let mut evm =
2701                OpEvmFactory::<OpTx>::default().create_evm_with_inspector(db, op_env, inspector);
2702            return run!(
2703                evm,
2704                noop_before_transaction,
2705                execute_pool_transaction,
2706                noop_on_execution_error
2707            );
2708        }
2709
2710        if self.is_tempo() {
2711            let tempo_env = self.build_tempo_evm_env(evm_env);
2712            let mut evm =
2713                TempoEvmFactory::default().create_evm_with_inspector(db, tempo_env, inspector);
2714            return run!(
2715                evm,
2716                noop_before_transaction,
2717                execute_pool_transaction,
2718                noop_on_execution_error
2719            );
2720        }
2721        let mut evm =
2722            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
2723        run!(evm, noop_before_transaction, execute_pool_transaction, noop_on_execution_error)
2724    }
2725
2726    /// Applies Ethereum block-start transitions to a disposable simulation candidate.
2727    fn apply_simulation_pre_execution_changes<DB>(
2728        &self,
2729        db: DB,
2730        evm_env: &EvmEnv,
2731        parent_hash: B256,
2732        transitions: EthereumBlockTransitions,
2733    ) -> Result<(), BlockchainError>
2734    where
2735        DB: StateDB<Error = DatabaseError>,
2736    {
2737        let inspector = self.build_mining_inspector();
2738        let mut evm =
2739            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
2740        self.inject_precompiles(evm.precompiles_mut(), evm_env);
2741        apply_ethereum_pre_execution_changes(&mut evm, parent_hash, transitions)
2742            .map_err(|err| BlockchainError::Internal(err.to_string()))
2743    }
2744
2745    /// Applies Ethereum post-block transitions to a disposable simulation candidate.
2746    fn apply_simulation_post_execution_changes<DB>(
2747        &self,
2748        db: DB,
2749        evm_env: &EvmEnv,
2750        transitions: EthereumBlockTransitions,
2751        receipts: &[FoundryReceiptEnvelope],
2752    ) -> Result<alloy_eips::eip7685::Requests, BlockchainError>
2753    where
2754        DB: StateDB<Error = DatabaseError>,
2755    {
2756        let inspector = self.build_mining_inspector();
2757        let mut evm =
2758            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
2759        self.inject_precompiles(evm.precompiles_mut(), evm_env);
2760        apply_ethereum_post_execution_changes(&mut evm, transitions, receipts)
2761            .map_err(|err| BlockchainError::Internal(err.to_string()))
2762    }
2763
2764    /// ## EVM settings
2765    ///
2766    /// 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>:
2767    ///
2768    ///  - `disable_eip3607` is set to `true`
2769    ///  - `disable_base_fee` is set to `true`
2770    ///  - `tx_gas_limit_cap` is set to `Some(u64::MAX)` indicating no gas limit cap
2771    ///  - `nonce` check is skipped
2772    fn build_call_env_with_base(
2773        &self,
2774        request: WithOtherFields<TransactionRequest>,
2775        fee_details: FeeDetails,
2776        block_env: BlockEnv,
2777        base_evm_env: Option<&EvmEnv>,
2778    ) -> (EvmEnv, TxEnv, OpCallDepositInfo) {
2779        let tx_type = request.minimal_tx_type() as u8;
2780
2781        let WithOtherFields::<TransactionRequest> {
2782            inner:
2783                TransactionRequest {
2784                    from,
2785                    to,
2786                    gas,
2787                    value,
2788                    input,
2789                    access_list,
2790                    blob_versioned_hashes,
2791                    authorization_list,
2792                    nonce,
2793                    sidecar: _,
2794                    chain_id,
2795                    .. // Rest of the gas fees related fields are taken from `fee_details`
2796                },
2797            other,
2798        } = request;
2799
2800        let FeeDetails {
2801            gas_price,
2802            max_fee_per_gas,
2803            max_priority_fee_per_gas,
2804            max_fee_per_blob_gas,
2805        } = fee_details;
2806
2807        let gas_limit = gas.unwrap_or(block_env.gas_limit);
2808        let mut evm_env = base_evm_env.cloned().unwrap_or_else(|| self.evm_env.read().clone());
2809        evm_env.block_env = block_env;
2810        // we want to disable this in eth_call, since this is common practice used by other node
2811        // impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
2812        evm_env.cfg_env.disable_block_gas_limit = true;
2813        evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX);
2814
2815        // The basefee should be ignored for calls against state for
2816        // - eth_call
2817        // - eth_estimateGas
2818        // - eth_createAccessList
2819        // - tracing
2820        evm_env.cfg_env.disable_base_fee = true;
2821
2822        // Disable nonce check in revm
2823        evm_env.cfg_env.disable_nonce_check = true;
2824
2825        let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| {
2826            self.fees().raw_gas_price().saturating_add(MIN_SUGGESTED_PRIORITY_FEE)
2827        });
2828        let caller = from.unwrap_or_default();
2829        let to = to.as_ref().and_then(TxKind::to);
2830        let blob_hashes = blob_versioned_hashes.unwrap_or_default();
2831        let mut tx_env = TxEnv {
2832            caller,
2833            gas_limit,
2834            gas_price,
2835            gas_priority_fee: max_priority_fee_per_gas,
2836            max_fee_per_blob_gas: max_fee_per_blob_gas
2837                .or_else(|| {
2838                    if blob_hashes.is_empty() { Some(0) } else { evm_env.block_env.blob_gasprice() }
2839                })
2840                .unwrap_or_default(),
2841            kind: match to {
2842                Some(addr) => TxKind::Call(*addr),
2843                None => TxKind::Create,
2844            },
2845            tx_type,
2846            value: value.unwrap_or_default(),
2847            data: input.into_input().unwrap_or_default(),
2848            chain_id: Some(chain_id.unwrap_or(evm_env.cfg_env.chain_id)),
2849            access_list: access_list.unwrap_or_default(),
2850            blob_hashes,
2851            ..Default::default()
2852        };
2853        tx_env.set_signed_authorization(authorization_list.unwrap_or_default());
2854
2855        if let Some(nonce) = nonce {
2856            tx_env.nonce = nonce;
2857        }
2858
2859        if evm_env.block_env.basefee == 0 {
2860            // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee`
2861            // 0 is only possible if it's manually set
2862            evm_env.cfg_env.disable_base_fee = true;
2863        }
2864
2865        // Deposit transaction? (only valid when op-stack deposits are active)
2866        #[cfg(feature = "optimism")]
2867        let op_deposit = if self.ensure_op_deposits_active().is_ok()
2868            && let Ok(deposit) = get_deposit_tx_parts(&other)
2869        {
2870            deposit
2871        } else {
2872            OpCallDepositInfo::default()
2873        };
2874        #[cfg(not(feature = "optimism"))]
2875        let op_deposit = {
2876            // `other` carries OP-only deposit fields; consumed only when feature is enabled.
2877            let _ = &other;
2878            OpCallDepositInfo
2879        };
2880
2881        (evm_env, tx_env, op_deposit)
2882    }
2883
2884    fn prepare_call_env(
2885        &self,
2886        state: &dyn DatabaseRef,
2887        request: WithOtherFields<TransactionRequest>,
2888        fee_details: FeeDetails,
2889        block_env: BlockEnv,
2890    ) -> Result<PreparedCall, BlockchainError> {
2891        self.prepare_call_env_from_base(state, request, fee_details, block_env, None)
2892    }
2893
2894    fn prepare_call_env_from_base(
2895        &self,
2896        state: &dyn DatabaseRef,
2897        request: WithOtherFields<TransactionRequest>,
2898        fee_details: FeeDetails,
2899        block_env: BlockEnv,
2900        base_evm_env: Option<&EvmEnv>,
2901    ) -> Result<PreparedCall, BlockchainError> {
2902        let request = self.parse_transaction_request(request)?;
2903        self.prepare_typed_call_env_with_base(state, request, fee_details, block_env, base_evm_env)
2904    }
2905
2906    const fn base_call_tx_env(&self, tx_env: TxEnv) -> CallTxEnv {
2907        #[cfg(feature = "monad")]
2908        if self.is_monad() {
2909            return CallTxEnv::Monad(tx_env);
2910        }
2911        CallTxEnv::Eth(tx_env)
2912    }
2913
2914    fn prepare_base_call_env_with_base(
2915        &self,
2916        request: WithOtherFields<TransactionRequest>,
2917        fee_details: FeeDetails,
2918        block_env: BlockEnv,
2919        base_evm_env: Option<&EvmEnv>,
2920    ) -> PreparedCall {
2921        let (evm_env, tx_env, op_deposit) =
2922            self.build_call_env_with_base(request, fee_details, block_env, base_evm_env);
2923        #[cfg(feature = "optimism")]
2924        let tx_env = if self.is_optimism() {
2925            CallTxEnv::Op(OpTransaction {
2926                base: tx_env,
2927                deposit: op_deposit,
2928                enveloped_tx: Some(Bytes::new()),
2929            })
2930        } else if self.is_tempo() {
2931            CallTxEnv::Tempo(TempoTxEnv::from(tx_env))
2932        } else {
2933            self.base_call_tx_env(tx_env)
2934        };
2935        #[cfg(not(feature = "optimism"))]
2936        let tx_env = {
2937            let _ = op_deposit;
2938            if self.is_tempo() {
2939                CallTxEnv::Tempo(TempoTxEnv::from(tx_env))
2940            } else {
2941                self.base_call_tx_env(tx_env)
2942            }
2943        };
2944        PreparedCall { evm_env, tx_env, simulated_tempo_tx: None }
2945    }
2946
2947    /// Classifies an RPC request according to the active network.
2948    pub(crate) fn parse_transaction_request(
2949        &self,
2950        request: WithOtherFields<TransactionRequest>,
2951    ) -> Result<FoundryTransactionRequest, BlockchainError> {
2952        let transaction_type = request.transaction_type;
2953        if !self.is_tempo() && transaction_type != Some(TEMPO_TX_TYPE_ID) {
2954            #[cfg(feature = "optimism")]
2955            if transaction_type == Some(DEPOSIT_TX_TYPE_ID)
2956                || transaction_type == Some(POST_EXEC_TX_TYPE_ID)
2957                || get_deposit_tx_parts(&request.other).is_ok()
2958            {
2959                return Ok(FoundryTransactionRequest::Op(request));
2960            }
2961            return Ok(FoundryTransactionRequest::Ethereum(request.into_inner()));
2962        }
2963
2964        let parsed: FoundryTransactionRequest =
2965            request.try_into().map_err(|err: serde_json::Error| {
2966                BlockchainError::InvalidTransactionRequest(err.to_string())
2967            })?;
2968        if parsed.is_tempo() {
2969            self.ensure_tempo_active()?;
2970        }
2971        if parsed.is_tempo()
2972            && self.is_tempo()
2973            && transaction_type.is_some_and(|ty| ty != TEMPO_TX_TYPE_ID)
2974        {
2975            return Err(BlockchainError::FailedToDecodeTransaction);
2976        }
2977        Ok(parsed)
2978    }
2979
2980    fn build_tempo_request_env(
2981        &self,
2982        request: TempoTransactionRequest,
2983        mut base: TxEnv,
2984    ) -> Result<(TempoTxEnv, AASigned), BlockchainError> {
2985        let fee_payer = request.fee_payer_signature.map(|_| {
2986            request.clone().build_aa().ok().and_then(|tx| tx.recover_fee_payer(base.caller).ok())
2987        });
2988
2989        // Build the response representation separately so the mocked execution signature does not
2990        // leak into RPC output.
2991        let mut response_request = request.clone();
2992        response_request.inner.from = Some(base.caller);
2993        response_request.inner.gas = Some(base.gas_limit);
2994        response_request.inner.nonce = Some(base.nonce);
2995        response_request.inner.chain_id = base.chain_id;
2996        response_request.inner.max_fee_per_gas = Some(base.gas_price);
2997        response_request.inner.max_priority_fee_per_gas =
2998            Some(base.gas_priority_fee.unwrap_or_default());
2999        response_request.inner.access_list = Some(base.access_list.clone());
3000        if response_request.calls.is_empty()
3001            && response_request.inner.to.is_none()
3002            && !base.data.is_empty()
3003        {
3004            response_request.inner.to = Some(base.kind);
3005        }
3006        let response_tx = response_request
3007            .build_aa()
3008            .map_err(|err| BlockchainError::InvalidTransactionRequest(err.to_string()))?;
3009        let response_tx = response_tx.into_signed(TempoSignature::default());
3010        let key_type = request.key_type.unwrap_or(SignatureType::Secp256k1);
3011        let key_data = request.key_data.clone();
3012        let key_id = request.key_id;
3013        let signature = mock_tempo_signature(
3014            key_type,
3015            key_data,
3016            key_id,
3017            base.caller,
3018            self.tempo_hardfork().is_t1c(),
3019        );
3020        let mut calls = request.calls;
3021        if let Some(to) = request.inner.to {
3022            calls.push(Call {
3023                to,
3024                value: request.inner.value.unwrap_or_default(),
3025                input: request.inner.input.into_input().unwrap_or_default(),
3026            });
3027        } else if calls.is_empty() && !base.data.is_empty() {
3028            // Alloy represents an omitted top-level `to` as `None`; preserve Ethereum CREATE
3029            // semantics by materializing it as the final Tempo call.
3030            calls.push(Call { to: base.kind, value: base.value, input: base.data.clone() });
3031        }
3032        if let Some(first_call) = calls.first() {
3033            base.kind = first_call.to;
3034            base.value = first_call.value;
3035            base.data = first_call.input.clone();
3036        }
3037        let tx_env = TempoTxEnv {
3038            fee_token: request.fee_token,
3039            is_system_tx: false,
3040            execution_context: ExecutionContext::Simulation,
3041            unique_tx_identifier: Some(TEMPO_RPC_SIMULATION_CONTEXT),
3042            fee_payer,
3043            tempo_tx_env: Some(Box::new(TempoBatchCallEnv {
3044                aa_calls: calls,
3045                signature,
3046                tempo_authorization_list: request
3047                    .tempo_authorization_list
3048                    .into_iter()
3049                    .map(RecoveredTempoAuthorization::new)
3050                    .collect(),
3051                nonce_key: request.nonce_key.unwrap_or_default(),
3052                key_authorization: request.key_authorization,
3053                signature_hash: B256::ZERO,
3054                tx_hash: B256::ZERO,
3055                valid_before: request.valid_before.map(|value| value.get()),
3056                valid_after: request.valid_after.map(|value| value.get()),
3057                subblock_transaction: false,
3058                override_key_id: key_id,
3059                expiring_nonce_idx: None,
3060            })),
3061            inner: base,
3062        };
3063        Ok((tx_env, response_tx))
3064    }
3065
3066    fn prepare_typed_call_env(
3067        &self,
3068        state: &dyn DatabaseRef,
3069        request: FoundryTransactionRequest,
3070        fee_details: FeeDetails,
3071        block_env: BlockEnv,
3072    ) -> Result<PreparedCall, BlockchainError> {
3073        self.prepare_typed_call_env_with_base(state, request, fee_details, block_env, None)
3074    }
3075
3076    fn prepare_typed_call_env_with_base(
3077        &self,
3078        state: &dyn DatabaseRef,
3079        request: FoundryTransactionRequest,
3080        fee_details: FeeDetails,
3081        block_env: BlockEnv,
3082        base_evm_env: Option<&EvmEnv>,
3083    ) -> Result<PreparedCall, BlockchainError> {
3084        match request {
3085            FoundryTransactionRequest::Tempo(tempo_request) => {
3086                self.ensure_tempo_active()?;
3087                let mut tempo_request = *tempo_request;
3088                if tempo_request.inner.nonce.is_none() {
3089                    let caller = tempo_request.inner.from.unwrap_or_default();
3090                    tempo_request.inner.nonce = Some(tempo_nonce(
3091                        state,
3092                        caller,
3093                        tempo_request.nonce_key.unwrap_or_default(),
3094                    )?);
3095                }
3096                let inner = WithOtherFields::new(tempo_request.inner.clone());
3097                let (evm_env, base, _) =
3098                    self.build_call_env_with_base(inner, fee_details, block_env, base_evm_env);
3099                let (tx_env, simulated_tempo_tx) =
3100                    self.build_tempo_request_env(tempo_request, base)?;
3101                Ok(PreparedCall {
3102                    evm_env,
3103                    tx_env: CallTxEnv::Tempo(tx_env),
3104                    simulated_tempo_tx: Some(simulated_tempo_tx),
3105                })
3106            }
3107            FoundryTransactionRequest::Ethereum(request) => Ok(self
3108                .prepare_base_call_env_with_base(
3109                    WithOtherFields::new(request),
3110                    fee_details,
3111                    block_env,
3112                    base_evm_env,
3113                )),
3114            #[cfg(feature = "optimism")]
3115            FoundryTransactionRequest::Op(request) => Ok(self.prepare_base_call_env_with_base(
3116                request,
3117                fee_details,
3118                block_env,
3119                base_evm_env,
3120            )),
3121        }
3122    }
3123
3124    fn transact_call_with_inspector_ref<'db, I, DB>(
3125        &self,
3126        db: &'db DB,
3127        evm_env: &EvmEnv,
3128        inspector: &mut I,
3129        tx_env: CallTxEnv,
3130        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
3131            MonadExecutionContext<'_>,
3132        >,
3133    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
3134    where
3135        DB: DatabaseRef + ?Sized,
3136        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
3137        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
3138    {
3139        self.transact_call_with_inspector_ref_at_hardfork(
3140            db,
3141            evm_env,
3142            inspector,
3143            tx_env,
3144            monad_context,
3145            self.hardfork(),
3146        )
3147    }
3148
3149    fn transact_call_with_inspector_ref_at_hardfork<'db, I, DB>(
3150        &self,
3151        db: &'db DB,
3152        evm_env: &EvmEnv,
3153        inspector: &mut I,
3154        tx_env: CallTxEnv,
3155        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
3156            MonadExecutionContext<'_>,
3157        >,
3158        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] hardfork: FoundryHardfork,
3159    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
3160    where
3161        DB: DatabaseRef + ?Sized,
3162        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
3163        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
3164    {
3165        match tx_env {
3166            CallTxEnv::Eth(tx_env) => {
3167                self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)
3168            }
3169            #[cfg(feature = "monad")]
3170            CallTxEnv::Monad(tx_env) => {
3171                let context = monad::resolve_execution_context(monad_context, &tx_env);
3172                self.transact_monad_with_inspector_ref(
3173                    db,
3174                    evm_env,
3175                    inspector,
3176                    tx_env,
3177                    monad::PreparedExecution {
3178                        context,
3179                        kind: EnvelopeExecutionKind::Transaction,
3180                        hardfork: monad_revm::MonadHardfork::from(hardfork),
3181                    },
3182                )
3183            }
3184            #[cfg(feature = "optimism")]
3185            CallTxEnv::Op(tx_env) => {
3186                self.transact_op_with_inspector_ref(db, evm_env, inspector, tx_env)
3187            }
3188            CallTxEnv::Tempo(tx_env) => {
3189                self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)
3190            }
3191        }
3192    }
3193
3194    pub fn call_with_state(
3195        &self,
3196        state: &dyn DatabaseRef,
3197        request: WithOtherFields<TransactionRequest>,
3198        fee_details: FeeDetails,
3199        block_env: BlockEnv,
3200    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3201        self.call_with_state_and_context(state, request, fee_details, block_env, None)
3202    }
3203
3204    pub(crate) fn call_with_state_and_context(
3205        &self,
3206        state: &dyn DatabaseRef,
3207        request: WithOtherFields<TransactionRequest>,
3208        fee_details: FeeDetails,
3209        block_env: BlockEnv,
3210        mut monad_context: Option<MonadReplayContext>,
3211    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3212        let mut inspector = self.build_inspector();
3213        let PreparedCall { evm_env, tx_env, .. } =
3214            self.prepare_call_env(state, request, fee_details, block_env)?;
3215        let ResultAndState { result, state } = self.transact_call_with_inspector_ref(
3216            state,
3217            &evm_env,
3218            &mut inspector,
3219            tx_env,
3220            monad_context.as_mut().map(next_monad_context),
3221        )?;
3222
3223        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3224        inspector.print_logs();
3225
3226        if self.print_traces {
3227            inspector.into_print_traces(self.call_trace_decoder());
3228        }
3229
3230        Ok((exit_reason, out, gas_used as u128, state))
3231    }
3232
3233    pub(crate) fn call_with_state_typed_gas_limit(
3234        &self,
3235        state: &dyn DatabaseRef,
3236        request: FoundryTransactionRequest,
3237        fee_details: FeeDetails,
3238        block_env: BlockEnv,
3239        options: GasEstimateCallOptions,
3240    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3241        let GasEstimateCallOptions { gas_limit, disable_fee_charge, monad_context } = options;
3242        self.call_with_state_typed_inner(
3243            state,
3244            request,
3245            fee_details,
3246            block_env,
3247            TypedCallOverrides {
3248                gas_limit: Some(gas_limit),
3249                disable_fee_charge,
3250                ..Default::default()
3251            },
3252            monad_context,
3253        )
3254    }
3255
3256    pub(crate) fn call_with_state_typed_access_list(
3257        &self,
3258        state: &dyn DatabaseRef,
3259        request: FoundryTransactionRequest,
3260        fee_details: FeeDetails,
3261        block_env: BlockEnv,
3262        access_list: AccessList,
3263        monad_context: Option<MonadReplayContext>,
3264    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3265        self.call_with_state_typed_inner(
3266            state,
3267            request,
3268            fee_details,
3269            block_env,
3270            TypedCallOverrides { access_list: Some(access_list), ..Default::default() },
3271            monad_context,
3272        )
3273    }
3274
3275    fn call_with_state_typed_inner(
3276        &self,
3277        state: &dyn DatabaseRef,
3278        request: FoundryTransactionRequest,
3279        fee_details: FeeDetails,
3280        block_env: BlockEnv,
3281        overrides: TypedCallOverrides,
3282        mut monad_context: Option<MonadReplayContext>,
3283    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3284        let mut inspector = self.build_inspector();
3285        let PreparedCall { mut evm_env, mut tx_env, .. } =
3286            self.prepare_typed_call_env(state, request, fee_details, block_env)?;
3287        evm_env.cfg_env.disable_fee_charge = overrides.disable_fee_charge;
3288        if let Some(gas_limit) = overrides.gas_limit {
3289            tx_env.base_mut().gas_limit = gas_limit;
3290        }
3291        if let Some(access_list) = overrides.access_list {
3292            let tx_env = tx_env.base_mut();
3293            tx_env.access_list = access_list;
3294            if tx_env.tx_type == TransactionType::Legacy as u8 {
3295                tx_env.tx_type = TransactionType::Eip2930 as u8;
3296            }
3297        }
3298        let ResultAndState { result, state } = self.transact_call_with_inspector_ref(
3299            state,
3300            &evm_env,
3301            &mut inspector,
3302            tx_env,
3303            monad_context.as_mut().map(next_monad_context),
3304        )?;
3305        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3306        inspector.print_logs();
3307        Ok((exit_reason, out, gas_used as u128, state))
3308    }
3309
3310    pub fn build_access_list_with_state(
3311        &self,
3312        state: &dyn DatabaseRef,
3313        request: WithOtherFields<TransactionRequest>,
3314        fee_details: FeeDetails,
3315        block_env: BlockEnv,
3316    ) -> Result<(InstructionResult, Option<Output>, u64, AccessList), BlockchainError> {
3317        self.build_access_list_with_state_and_context(state, request, fee_details, block_env, None)
3318    }
3319
3320    pub(crate) fn build_access_list_with_state_and_context(
3321        &self,
3322        state: &dyn DatabaseRef,
3323        request: WithOtherFields<TransactionRequest>,
3324        fee_details: FeeDetails,
3325        block_env: BlockEnv,
3326        mut monad_context: Option<MonadReplayContext>,
3327    ) -> Result<(InstructionResult, Option<Output>, u64, AccessList), BlockchainError> {
3328        let mut inspector =
3329            AccessListInspector::new(request.access_list.clone().unwrap_or_default());
3330
3331        let PreparedCall { evm_env, tx_env, .. } =
3332            self.prepare_call_env(state, request, fee_details, block_env)?;
3333        let ResultAndState { result, state: _ } = self.transact_call_with_inspector_ref(
3334            state,
3335            &evm_env,
3336            &mut inspector,
3337            tx_env,
3338            monad_context.as_mut().map(next_monad_context),
3339        )?;
3340        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3341        let access_list = inspector.access_list();
3342        #[cfg(feature = "monad")]
3343        let access_list = if self.is_monad() {
3344            monad::normalize_access_list(access_list, self.monad_hardfork())
3345        } else {
3346            access_list
3347        };
3348        Ok((exit_reason, out, gas_used, access_list))
3349    }
3350
3351    fn arbitrum_block_number(&self, evm_env: &EvmEnv) -> Option<u64> {
3352        if !arbitrum::is_arbitrum_chain(self.protocol_chain_id()) {
3353            return None;
3354        }
3355
3356        let env_block = evm_env.block_env.number.saturating_to();
3357        Some(self.get_fork().map_or(env_block, |fork| fork.block_number().max(env_block)))
3358    }
3359
3360    pub fn get_code_with_state(
3361        &self,
3362        state: &dyn DatabaseRef,
3363        address: Address,
3364    ) -> Result<Bytes, BlockchainError> {
3365        trace!(target: "backend", "get code for {:?}", address);
3366        let account = state.basic_ref(address)?.unwrap_or_default();
3367        if account.code_hash == KECCAK_EMPTY {
3368            // if the code hash is `KECCAK_EMPTY`, we check no further
3369            return Ok(Default::default());
3370        }
3371        let code = if let Some(code) = account.code {
3372            code
3373        } else {
3374            state.code_by_hash_ref(account.code_hash)?
3375        };
3376        Ok(code.bytes()[..code.len()].to_vec().into())
3377    }
3378
3379    pub fn get_balance_with_state<D>(
3380        &self,
3381        state: D,
3382        address: Address,
3383    ) -> Result<U256, BlockchainError>
3384    where
3385        D: DatabaseRef,
3386    {
3387        trace!(target: "backend", "get balance for {:?}", address);
3388        Ok(state.basic_ref(address)?.unwrap_or_default().balance)
3389    }
3390
3391    pub async fn transaction_by_block_number_and_index(
3392        &self,
3393        number: BlockNumber,
3394        index: Index,
3395    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
3396        if let Some(block) = self.mined_block_by_number(number) {
3397            return Ok(self.mined_transaction_by_block_hash_and_index(block.header.hash, index));
3398        }
3399
3400        if let Some(fork) = self.get_fork() {
3401            let number = self.convert_block_number(Some(number));
3402            if fork.predates_fork(number) {
3403                return Ok(fork
3404                    .transaction_by_block_number_and_index(number, index.into())
3405                    .await?);
3406            }
3407        }
3408
3409        Ok(None)
3410    }
3411
3412    pub async fn transaction_by_block_hash_and_index(
3413        &self,
3414        hash: B256,
3415        index: Index,
3416    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
3417        if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) {
3418            return Ok(tx);
3419        }
3420
3421        if let Some(fork) = self.get_fork() {
3422            return Ok(fork.transaction_by_block_hash_and_index(hash, index.into()).await?);
3423        }
3424
3425        Ok(None)
3426    }
3427
3428    pub fn mined_transaction_by_block_hash_and_index(
3429        &self,
3430        block_hash: B256,
3431        index: Index,
3432    ) -> Option<AnyRpcTransaction> {
3433        let (info, block, tx) = {
3434            let storage = self.blockchain.storage.read();
3435            let block = storage.blocks.get(&block_hash).cloned()?;
3436            let index: usize = index.into();
3437            let tx = block.body.transactions.get(index)?.clone();
3438            let info = storage.transactions.get(&tx.hash())?.info.clone();
3439            (info, block, tx)
3440        };
3441
3442        Some(transaction_build(
3443            Some(info.transaction_hash),
3444            tx,
3445            Some(&block),
3446            Some(info),
3447            block.header.base_fee_per_gas(),
3448        ))
3449    }
3450
3451    pub async fn transaction_by_hash(
3452        &self,
3453        hash: B256,
3454    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
3455        trace!(target: "backend", "transaction_by_hash={:?}", hash);
3456        if let tx @ Some(_) = self.mined_transaction_by_hash(hash) {
3457            return Ok(tx);
3458        }
3459
3460        if let Some(fork) = self.get_fork() {
3461            return fork
3462                .transaction_by_hash(hash)
3463                .await
3464                .map_err(BlockchainError::AlloyForkProvider);
3465        }
3466
3467        Ok(None)
3468    }
3469
3470    pub fn mined_transaction_by_hash(&self, hash: B256) -> Option<AnyRpcTransaction> {
3471        let (info, block) = {
3472            let storage = self.blockchain.storage.read();
3473            let MinedTransaction { info, block_hash, .. } =
3474                storage.transactions.get(&hash)?.clone();
3475            let block = storage.blocks.get(&block_hash).cloned()?;
3476            (info, block)
3477        };
3478        let tx = block.body.transactions.get(info.transaction_index as usize)?.clone();
3479
3480        Some(transaction_build(
3481            Some(info.transaction_hash),
3482            tx,
3483            Some(&block),
3484            Some(info),
3485            block.header.base_fee_per_gas(),
3486        ))
3487    }
3488
3489    /// Returns the traces for the given transaction
3490    pub async fn trace_transaction(
3491        &self,
3492        hash: B256,
3493    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
3494        if let Some(traces) = self.mined_parity_trace_transaction(hash) {
3495            return Ok(traces);
3496        }
3497
3498        if let Some(fork) = self.get_fork() {
3499            return Ok(fork.trace_transaction(hash).await?);
3500        }
3501
3502        Ok(vec![])
3503    }
3504
3505    /// Returns a transaction trace at a given index.
3506    pub async fn trace_get(
3507        &self,
3508        hash: B256,
3509        indices: Vec<Index>,
3510    ) -> Result<Option<LocalizedTransactionTrace>, BlockchainError> {
3511        if indices.len() != 1 {
3512            return Ok(None);
3513        }
3514
3515        let index: usize = indices[0].into();
3516        if let Some(traces) = self.mined_parity_trace_transaction(hash) {
3517            return Ok(traces.into_iter().nth(index));
3518        }
3519
3520        if let Some(fork) = self.get_fork() {
3521            return Ok(fork.trace_get(hash, indices).await?);
3522        }
3523
3524        Ok(None)
3525    }
3526
3527    /// Returns the traces for the given block
3528    pub async fn trace_block(
3529        &self,
3530        block: BlockNumber,
3531    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
3532        let number = self.convert_block_number(Some(block));
3533        if let Some(traces) = self.mined_parity_trace_block(number) {
3534            return Ok(traces);
3535        }
3536
3537        if let Some(fork) = self.get_fork()
3538            && fork.predates_fork(number)
3539        {
3540            return Ok(fork.trace_block(number).await?);
3541        }
3542
3543        Ok(vec![])
3544    }
3545
3546    /// Executes a transaction call and returns requested parity trace results.
3547    pub async fn trace_call(
3548        &self,
3549        request: WithOtherFields<TransactionRequest>,
3550        fee_details: FeeDetails,
3551        trace_types: HashSet<TraceType>,
3552        block_request: BlockRequest<FoundryTxEnvelope>,
3553        block_id: BlockId,
3554    ) -> Result<TraceResults, BlockchainError>
3555    where
3556        Self: TransactionValidator<FoundryTxEnvelope>,
3557        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
3558    {
3559        if let BlockRequest::Number(number) = &block_request
3560            && let Some(fork) = self.get_fork()
3561            && fork.predates_fork(*number)
3562        {
3563            return Ok(fork.trace_call(request, trace_types, block_id).await?);
3564        }
3565
3566        self.with_database_at_and_context(Some(block_request), |state, block, mut monad_context| {
3567            let cache_db = CacheDB::new(state);
3568            let mut inspector =
3569                TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
3570            let PreparedCall { evm_env, tx_env, .. } =
3571                self.prepare_call_env(&cache_db, request, fee_details, block)?;
3572            let result = self.transact_call_with_inspector_ref(
3573                &cache_db,
3574                &evm_env,
3575                &mut inspector,
3576                tx_env,
3577                monad_context.as_mut().map(next_monad_context),
3578            )?;
3579
3580            inspector
3581                .into_parity_builder()
3582                .into_trace_results_with_state(&result, &trace_types, &cache_db)
3583                .map_err(Into::into)
3584        })
3585        .await
3586    }
3587
3588    /// Replays all transactions in a block and returns the requested traces for each transaction
3589    pub async fn trace_replay_block_transactions(
3590        &self,
3591        block: BlockNumber,
3592        trace_types: HashSet<TraceType>,
3593    ) -> Result<Vec<TraceResultsWithTransactionHash>, BlockchainError> {
3594        let block_number = self.convert_block_number(Some(block));
3595
3596        // Try mined blocks first
3597        if let Some(results) =
3598            self.mined_parity_trace_replay_block_transactions(block_number, &trace_types)?
3599        {
3600            return Ok(results);
3601        }
3602
3603        // Fallback to fork if block predates fork
3604        if let Some(fork) = self.get_fork()
3605            && fork.predates_fork(block_number)
3606        {
3607            return Ok(fork.trace_replay_block_transactions(block_number, trace_types).await?);
3608        }
3609
3610        Ok(vec![])
3611    }
3612
3613    /// Replays a mined transaction and returns the requested traces.
3614    pub async fn trace_replay_transaction(
3615        &self,
3616        hash: B256,
3617        trace_types: HashSet<TraceType>,
3618    ) -> Result<TraceResults, BlockchainError> {
3619        let block_number =
3620            self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.block_number);
3621
3622        // If the transaction was mined locally, replay it locally. Do not fall
3623        // through to the fork when the local replay fails; that would misreport
3624        // a local data problem as an upstream transaction lookup.
3625        if let Some(block_number) = block_number {
3626            let results = self
3627                .mined_parity_trace_replay_block_transactions(block_number, &trace_types)?
3628                .ok_or(BlockchainError::BlockNotFound)?;
3629
3630            return results
3631                .into_iter()
3632                .find(|result| result.transaction_hash == hash)
3633                .map(|result| result.full_trace)
3634                .ok_or_else(|| {
3635                    BlockchainError::Internal(format!(
3636                        "replayed block {block_number} for local transaction {hash:?}, \
3637                         but its trace was missing"
3638                    ))
3639                });
3640        }
3641
3642        // Not known locally: forward to the fork if present.
3643        if let Some(fork) = self.get_fork() {
3644            return Ok(fork.trace_replay_transaction(hash, trace_types).await?);
3645        }
3646
3647        Err(BlockchainError::TransactionNotFound)
3648    }
3649
3650    /// Traces a raw transaction without committing it to the chain state or mempool.
3651    pub async fn trace_raw_transaction(
3652        &self,
3653        pending_transaction: PendingTransaction<FoundryTxEnvelope>,
3654        trace_types: HashSet<TraceType>,
3655        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3656    ) -> Result<TraceResults, BlockchainError>
3657    where
3658        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
3659    {
3660        let trace_config = TracingInspectorConfig::from_parity_config(&trace_types);
3661
3662        self.with_database_at_and_context(block_request, |state, block_env, mut monad_context| {
3663            let cache_db = CacheDB::new(state);
3664            let mut evm_env = self.evm_env.read().clone();
3665            evm_env.block_env = block_env;
3666
3667            let mut inspector = TracingInspector::new(trace_config);
3668            let (result, _) = self.transact_envelope_with_inspector_ref_and_context(
3669                &cache_db,
3670                &evm_env,
3671                &mut inspector,
3672                &pending_transaction,
3673                monad_context.as_mut().map(next_monad_context),
3674            )?;
3675
3676            inspector
3677                .into_parity_builder()
3678                .into_trace_results_with_state(&result, &trace_types, &cache_db)
3679                .map_err(BlockchainError::from)
3680        })
3681        .await
3682    }
3683
3684    /// Traces calls sequentially against a shared in-memory state.
3685    pub async fn trace_call_many(
3686        &self,
3687        calls: Vec<(WithOtherFields<TransactionRequest>, HashSet<TraceType>)>,
3688        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3689    ) -> Result<Vec<TraceResults>, BlockchainError>
3690    where
3691        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
3692    {
3693        self.with_database_at_and_context(block_request, |state, block_env, mut monad_context| {
3694            let mut cache_db = CacheDB::new(state);
3695            let mut results = Vec::with_capacity(calls.len());
3696            let mut calls = calls.into_iter().peekable();
3697
3698            while let Some((request, trace_types)) = calls.next() {
3699                let fee_details = FeeDetails::new(
3700                    request.gas_price,
3701                    request.max_fee_per_gas,
3702                    request.max_priority_fee_per_gas,
3703                    request.max_fee_per_blob_gas,
3704                )?
3705                .or_zero_fees();
3706                let PreparedCall { evm_env, mut tx_env, simulated_tempo_tx } =
3707                    self.prepare_call_env(&cache_db, request, fee_details, block_env.clone())?;
3708                apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
3709
3710                let trace_config = TracingInspectorConfig::from_parity_config(&trace_types);
3711                let mut inspector = TracingInspector::new(trace_config);
3712                let result = self.transact_call_with_inspector_ref(
3713                    &cache_db,
3714                    &evm_env,
3715                    &mut inspector,
3716                    tx_env,
3717                    monad_context.as_mut().map(next_monad_context),
3718                )?;
3719
3720                let trace_result = inspector
3721                    .into_parity_builder()
3722                    .into_trace_results_with_state(&result, &trace_types, &cache_db)
3723                    .map_err(BlockchainError::from)?;
3724                results.push(trace_result);
3725
3726                if calls.peek().is_some() {
3727                    cache_db.commit(result.state);
3728                }
3729            }
3730
3731            Ok(results)
3732        })
3733        .await
3734    }
3735
3736    /// Returns the trace results for all transactions in a mined block by replaying them
3737    fn mined_parity_trace_replay_block_transactions(
3738        &self,
3739        block_number: u64,
3740        trace_types: &HashSet<TraceType>,
3741    ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, BlockchainError> {
3742        let Some(block) = self.get_block(block_number) else { return Ok(None) };
3743
3744        // Execute this in the context of the parent state
3745        let parent_hash = block.header.parent_hash;
3746        let trace_config = TracingInspectorConfig::from_parity_config(trace_types);
3747
3748        let read_guard = self.states.upgradable_read();
3749        if let Some(state) = read_guard.get_state(&parent_hash) {
3750            self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types)
3751                .map(Some)
3752        } else {
3753            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
3754            let Some(state) = write_guard.get_on_disk_state(&parent_hash) else {
3755                return Ok(None);
3756            };
3757            self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types)
3758                .map(Some)
3759        }
3760    }
3761
3762    /// Replays all transactions in a block with the tracing inspector to generate TraceResults
3763    fn replay_block_transactions_with_inspector(
3764        &self,
3765        block: &Block,
3766        parent_state: &StateDb,
3767        trace_config: TracingInspectorConfig,
3768        trace_types: &HashSet<TraceType>,
3769    ) -> Result<Vec<TraceResultsWithTransactionHash>, BlockchainError> {
3770        let (mut cache_db, evm_env, hardfork) = self.prepare_block_replay(block, parent_state)?;
3771        let mut results = Vec::new();
3772        let monad_context = self.active_monad_context_for_mined_block(block)?;
3773
3774        // Execute each transaction in the block with tracing
3775        for tx_envelope in &block.body.transactions {
3776            let tx_hash = tx_envelope.hash();
3777
3778            // Create a fresh inspector for this transaction
3779            let mut inspector = TracingInspector::new(trace_config);
3780
3781            // Prepare transaction environment and execute
3782            let pending_tx = self.pending_mined_transaction(tx_envelope.clone())?;
3783            let transaction_context =
3784                monad_execution_context_at(monad_context.as_ref(), results.len());
3785            let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
3786                &cache_db,
3787                &evm_env,
3788                &mut inspector,
3789                &pending_tx,
3790                EnvelopeExecution::replay(transaction_context, hardfork),
3791            )?;
3792
3793            // Build TraceResults from the inspector and execution result
3794            let full_trace = inspector
3795                .into_parity_builder()
3796                .into_trace_results_with_state(&result, trace_types, &cache_db)
3797                .map_err(BlockchainError::from)?;
3798
3799            results.push(TraceResultsWithTransactionHash { transaction_hash: tx_hash, full_trace });
3800
3801            // Commit the state changes for the next transaction
3802            cache_db.commit(result.state);
3803        }
3804
3805        Ok(results)
3806    }
3807
3808    // Returns the traces matching a given filter
3809    pub async fn trace_filter(
3810        &self,
3811        filter: TraceFilter,
3812    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
3813        let matcher = filter.matcher();
3814        let start = filter.from_block.unwrap_or(0);
3815        let end = filter.to_block.unwrap_or_else(|| self.best_number());
3816
3817        if start > end {
3818            return Err(BlockchainError::RpcError(RpcError::invalid_params(
3819                "invalid block range, ensure that to block is greater than from block".to_string(),
3820            )));
3821        }
3822
3823        let dist = end - start;
3824        if dist > 300 {
3825            return Err(BlockchainError::RpcError(RpcError::invalid_params(
3826                "block range too large, currently limited to 300".to_string(),
3827            )));
3828        }
3829
3830        // Accumulate tasks for block range
3831        let mut trace_tasks = vec![];
3832        for num in start..=end {
3833            trace_tasks.push(self.trace_block(num.into()));
3834        }
3835
3836        // Execute tasks and filter traces
3837        let traces = futures::future::try_join_all(trace_tasks).await?;
3838        let filtered_traces =
3839            traces.into_iter().flatten().filter(|trace| matcher.matches(&trace.trace));
3840
3841        // Apply after and count
3842        let filtered_traces: Vec<_> = if let Some(after) = filter.after {
3843            filtered_traces.skip(after as usize).collect()
3844        } else {
3845            filtered_traces.collect()
3846        };
3847
3848        let filtered_traces: Vec<_> = if let Some(count) = filter.count {
3849            filtered_traces.into_iter().take(count as usize).collect()
3850        } else {
3851            filtered_traces
3852        };
3853
3854        Ok(filtered_traces)
3855    }
3856
3857    pub fn get_blobs_by_block_id(
3858        &self,
3859        id: impl Into<BlockId>,
3860        versioned_hashes: Vec<B256>,
3861    ) -> Result<Option<Vec<alloy_consensus::Blob>>> {
3862        Ok(self.get_block(id).map(|block| {
3863            block
3864                .body
3865                .transactions
3866                .iter()
3867                .filter_map(|tx| tx.as_ref().sidecar())
3868                .flat_map(|sidecar| {
3869                    sidecar.sidecar.blobs().iter().zip(sidecar.sidecar.commitments().iter())
3870                })
3871                .filter(|(_, commitment)| {
3872                    // Filter blobs by versioned_hashes if provided
3873                    versioned_hashes.is_empty()
3874                        || versioned_hashes.contains(&kzg_to_versioned_hash(commitment.as_slice()))
3875                })
3876                .map(|(blob, _)| *blob)
3877                .collect()
3878        }))
3879    }
3880
3881    #[allow(clippy::large_stack_frames)]
3882    pub fn get_blob_by_versioned_hash(&self, hash: B256) -> Result<Option<Blob>> {
3883        let storage = self.blockchain.storage.read();
3884        for block in storage.blocks.values() {
3885            for tx in &block.body.transactions {
3886                let typed_tx = tx.as_ref();
3887                if let Some(sidecar) = typed_tx.sidecar() {
3888                    for versioned_hash in sidecar.sidecar.versioned_hashes() {
3889                        if versioned_hash == hash
3890                            && let Some(index) =
3891                                sidecar.sidecar.commitments().iter().position(|commitment| {
3892                                    kzg_to_versioned_hash(commitment.as_slice()) == *hash
3893                                })
3894                            && let Some(blob) = sidecar.sidecar.blobs().get(index)
3895                        {
3896                            return Ok(Some(*blob));
3897                        }
3898                    }
3899                }
3900            }
3901        }
3902        Ok(None)
3903    }
3904
3905    /// Initialises the balance of the given accounts
3906    #[expect(clippy::too_many_arguments)]
3907    pub async fn with_genesis(
3908        db: Arc<AsyncRwLock<Box<dyn Db>>>,
3909        env: Arc<RwLock<EvmEnv>>,
3910        networks: NetworkConfigs,
3911        genesis: GenesisConfig,
3912        fees: FeeManager,
3913        fork: Arc<RwLock<Option<ClientFork>>>,
3914        enable_steps_tracing: bool,
3915        print_logs: bool,
3916        print_traces: bool,
3917        call_trace_decoder: Arc<CallTraceDecoder>,
3918        prune_state_history_config: PruneStateHistoryConfig,
3919        max_persisted_states: Option<usize>,
3920        transaction_block_keeper: Option<usize>,
3921        automine_block_time: Option<Duration>,
3922        cache_path: Option<PathBuf>,
3923        node_config: Arc<AsyncRwLock<NodeConfig>>,
3924    ) -> Result<Self> {
3925        let last_fork_cache_source = fork.read().as_ref().and_then(ForkCacheSource::from_fork);
3926        // if this is a fork then adjust the blockchain storage
3927        let blockchain = if let Some(fork) = fork.read().as_ref() {
3928            trace!(target: "backend", "using forked blockchain at {}", fork.block_number());
3929            Blockchain::forked(fork.block_number(), fork.block_hash(), fork.total_difficulty())
3930        } else {
3931            let header = genesis_header(
3932                &env.read(),
3933                fees.is_eip1559().then(|| fees.base_fee()),
3934                genesis.timestamp,
3935                genesis.number,
3936            );
3937            Blockchain::new(foundry_header(&networks, header))
3938        };
3939
3940        // Sync EVM block.number with genesis for non-fork mode.
3941        // Fork mode syncs in setup_fork_db_config() instead.
3942        if fork.read().is_none() {
3943            env.write().block_env.number = U256::from(genesis.number);
3944
3945            // The genesis block keeps its base fee, but the next block must already follow Tempo's
3946            // rules (e.g. T7 clamps the seed down to the cap). Fork mode seeds this from the fork
3947            // block instead.
3948            if fees.tempo_hardfork().is_some() {
3949                let env = env.read();
3950                let next_base_fee = fees.get_next_block_base_fee_per_gas(
3951                    0,
3952                    env.block_env.gas_limit,
3953                    env.block_env.basefee,
3954                );
3955                drop(env);
3956                fees.set_base_fee(next_base_fee);
3957            }
3958        }
3959
3960        let start_timestamp = if let Some(fork) = fork.read().as_ref() {
3961            fork.timestamp()
3962        } else {
3963            genesis.timestamp
3964        };
3965
3966        let mut states = if prune_state_history_config.is_config_enabled() {
3967            // if prune state history is enabled, configure the state cache only for memory
3968            prune_state_history_config
3969                .max_memory_history
3970                .map(|limit| InMemoryBlockStates::new(limit, 0))
3971                .unwrap_or_default()
3972                .memory_only()
3973        } else if max_persisted_states.is_some() {
3974            max_persisted_states
3975                .map(|limit| InMemoryBlockStates::new(DEFAULT_HISTORY_LIMIT, limit))
3976                .unwrap_or_default()
3977        } else {
3978            Default::default()
3979        };
3980
3981        if let Some(cache_path) = cache_path {
3982            states = states.disk_path(cache_path);
3983        }
3984
3985        let (slots_in_an_epoch, precompile_factory, disable_pool_balance_checks, hardfork) = {
3986            let cfg = node_config.read().await;
3987            (
3988                cfg.slots_in_an_epoch,
3989                cfg.precompile_factory.clone(),
3990                cfg.disable_pool_balance_checks,
3991                cfg.get_hardfork(),
3992            )
3993        };
3994        let startup_cache_lease = if fork.read().is_some() {
3995            let db = db.read().await;
3996            let inner = db
3997                .maybe_inner()
3998                .map_err(|err| eyre::eyre!("fork database is missing its cache backend: {err}"))?;
3999            StagedForkCacheLease::for_db(inner)
4000        } else {
4001            StagedForkCacheLease::default()
4002        };
4003        let startup_fork_cache_user =
4004            StagedForkDbUser { db: Some(Arc::clone(&db)), cache_lease: startup_cache_lease };
4005
4006        let backend = Self {
4007            db,
4008            blockchain,
4009            states: Arc::new(RwLock::new(states)),
4010            evm_env: env,
4011            networks,
4012            hardfork: Arc::new(RwLock::new(hardfork)),
4013            fork,
4014            last_fork_cache_source: Arc::new(RwLock::new(last_fork_cache_source)),
4015            time: TimeManager::new(start_timestamp),
4016            cheats: Default::default(),
4017            new_block_listeners: Default::default(),
4018            fees,
4019            genesis,
4020            active_state_snapshots: Arc::new(Mutex::new(Default::default())),
4021            enable_steps_tracing,
4022            print_logs,
4023            print_traces,
4024            call_trace_decoder: Arc::new(RwLock::new(call_trace_decoder)),
4025            prune_state_history_config,
4026            transaction_block_keeper,
4027            node_config,
4028            slots_in_an_epoch,
4029            precompile_factory,
4030            mining: Arc::new(tokio::sync::Mutex::new(())),
4031            disable_pool_balance_checks,
4032            startup_fork_cache_user,
4033        };
4034
4035        #[cfg(feature = "monad")]
4036        let monad_fork =
4037            if backend.networks.is_monad() { backend.fork.read().clone() } else { None };
4038        #[cfg(feature = "monad")]
4039        if let Some(fork) = monad_fork {
4040            monad::cache_fork_context(&fork).await?;
4041        }
4042
4043        if let Some(interval_block_time) = automine_block_time {
4044            backend.update_interval_mine_block_time(interval_block_time);
4045        }
4046
4047        // Note: this can only fail in forking mode, in which case we can't recover
4048        backend.apply_genesis().await.wrap_err("failed to create genesis")?;
4049        Ok(backend)
4050    }
4051
4052    /// Applies the configured genesis settings
4053    ///
4054    /// This will fund, create the genesis accounts
4055    async fn apply_genesis(&self) -> Result<(), DatabaseError> {
4056        trace!(target: "backend", "setting genesis balances");
4057
4058        if self.fork.read().is_some() {
4059            return self
4060                .apply_fork_genesis(
4061                    Arc::clone(&self.db),
4062                    self.startup_fork_cache_user.cache_lease.clone(),
4063                )
4064                .await;
4065        }
4066
4067        let mut db = self.db.write().await;
4068        for (account, info) in self.genesis.account_infos() {
4069            db.insert_account(account, info);
4070        }
4071
4072        // insert the new genesis hash to the database so it's available for the next block in
4073        // the evm
4074        db.insert_block_hash(U256::from(self.best_number()), self.best_hash());
4075
4076        if let Some(transitions) =
4077            self.ethereum_block_transitions(self.hardfork(), None, BlockExecutionKind::Complete)
4078        {
4079            if transitions.hardfork >= EthereumHardfork::Cancun {
4080                db.set_code(eip4788::BEACON_ROOTS_ADDRESS, eip4788::BEACON_ROOTS_CODE.clone())?;
4081            }
4082            if transitions.hardfork >= EthereumHardfork::Prague {
4083                db.set_code(
4084                    eip2935::HISTORY_STORAGE_ADDRESS,
4085                    eip2935::HISTORY_STORAGE_CODE.clone(),
4086                )?;
4087                db.set_code(
4088                    eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
4089                    eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone(),
4090                )?;
4091                db.set_code(
4092                    eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
4093                    eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_CODE.clone(),
4094                )?;
4095            }
4096        }
4097        // apply the genesis.json alloc
4098        self.genesis.apply_genesis_json_alloc(&mut **db)?;
4099        drop(db);
4100        self.apply_funded_accounts(&self.db).await?;
4101
4102        // Initialize Tempo precompiles and fee tokens when in Tempo mode (not in fork mode).
4103        // In fork mode, precompiles are inherited from the forked origin.
4104        if self.networks.is_tempo() && !self.is_fork() {
4105            let chain_id = self.evm_env.read().cfg_env.chain_id;
4106            let timestamp = self.genesis.timestamp;
4107            let test_accounts: Vec<Address> = self.genesis.accounts.clone();
4108            let hardfork = self.tempo_hardfork();
4109            let mut db = self.db.write().await;
4110            crate::eth::backend::tempo::initialize_tempo_precompiles(
4111                &mut **db,
4112                chain_id,
4113                timestamp,
4114                &test_accounts,
4115                hardfork,
4116            )
4117            .map_err(|e| {
4118                tracing::error!(target: "backend", "failed to initialize Tempo precompiles: {e}");
4119                DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))
4120            })?;
4121            trace!(target: "backend", "initialized Tempo precompiles and fee tokens for {} accounts", test_accounts.len());
4122        }
4123
4124        trace!(target: "backend", "set genesis balances");
4125
4126        Ok(())
4127    }
4128
4129    /// Applies genesis allocations to a fork database before it becomes live.
4130    async fn apply_fork_genesis(
4131        &self,
4132        db: Arc<AsyncRwLock<Box<dyn Db>>>,
4133        cache_lease: StagedForkCacheLease,
4134    ) -> Result<(), DatabaseError> {
4135        let user = StagedForkDbUser { db: Some(db), cache_lease };
4136        let mut genesis_accounts = JoinSet::new();
4137        for address in self.genesis.accounts.iter().copied() {
4138            let task_user = StagedForkDbUser {
4139                db: Some(Arc::clone(user.db())),
4140                cache_lease: user.cache_lease.clone(),
4141            };
4142
4143            // The fork database can fetch independent accounts concurrently.
4144            genesis_accounts.spawn(async move {
4145                let db = task_user.db().read().await;
4146                let info = db.basic_ref(address)?.unwrap_or_default();
4147                Ok::<_, DatabaseError>((address, info))
4148            });
4149        }
4150
4151        let mut account_infos = Vec::with_capacity(self.genesis.accounts.len());
4152        while let Some(result) = genesis_accounts.join_next().await {
4153            match result {
4154                Ok(Ok(account)) => account_infos.push(account),
4155                Ok(Err(err)) => {
4156                    genesis_accounts.shutdown().await;
4157                    return Err(err);
4158                }
4159                Err(err) => {
4160                    genesis_accounts.shutdown().await;
4161                    return Err(DatabaseError::AnyRequest(Arc::new(eyre::eyre!(
4162                        "fork genesis account task failed: {err}"
4163                    ))));
4164                }
4165            }
4166        }
4167        let mut db_guard = user.db().write().await;
4168        for (address, mut info) in account_infos {
4169            info.balance = self.genesis.balance;
4170            db_guard.insert_account(address, info);
4171        }
4172        self.genesis.apply_genesis_json_alloc(&mut **db_guard)?;
4173        drop(db_guard);
4174        self.apply_funded_accounts(user.db()).await
4175    }
4176
4177    /// Applies explicit `--fund` balances while preserving account metadata inherited from a fork.
4178    async fn apply_funded_accounts(
4179        &self,
4180        db: &Arc<AsyncRwLock<Box<dyn Db>>>,
4181    ) -> Result<(), DatabaseError> {
4182        let funded_accounts = self.node_config.read().await.funded_accounts.clone();
4183        let mut accounts = Vec::with_capacity(funded_accounts.len());
4184        for (address, balance) in funded_accounts {
4185            let mut info = db.read().await.basic_ref(address)?.unwrap_or_default();
4186            info.balance = balance;
4187            accounts.push((address, info));
4188        }
4189        let mut db = db.write().await;
4190        for (address, info) in accounts {
4191            db.insert_account(address, info);
4192        }
4193        Ok(())
4194    }
4195
4196    /// Populates a detached in-memory database from explicit reset inputs.
4197    #[allow(clippy::too_many_arguments)]
4198    fn populate_memory_db(
4199        db: &mut dyn Db,
4200        genesis: &GenesisConfig,
4201        funded_accounts: &HashMap<Address, U256>,
4202        hardfork: FoundryHardfork,
4203        chain_id: u64,
4204        is_tempo: bool,
4205        tempo_hardfork: Option<TempoHardfork>,
4206        genesis_hash: B256,
4207        install_create2_deployer: bool,
4208    ) -> Result<(), DatabaseError> {
4209        for (account, info) in genesis.account_infos() {
4210            db.insert_account(account, info);
4211        }
4212        db.insert_block_hash(U256::from(genesis.number), genesis_hash);
4213
4214        if let FoundryHardfork::Ethereum(hardfork) = hardfork {
4215            if hardfork >= EthereumHardfork::Cancun {
4216                db.set_code(eip4788::BEACON_ROOTS_ADDRESS, eip4788::BEACON_ROOTS_CODE.clone())?;
4217            }
4218            if hardfork >= EthereumHardfork::Prague {
4219                db.set_code(
4220                    eip2935::HISTORY_STORAGE_ADDRESS,
4221                    eip2935::HISTORY_STORAGE_CODE.clone(),
4222                )?;
4223                db.set_code(
4224                    eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
4225                    eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone(),
4226                )?;
4227                db.set_code(
4228                    eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
4229                    eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_CODE.clone(),
4230                )?;
4231            }
4232        }
4233
4234        genesis.apply_genesis_json_alloc(db)?;
4235        for (&address, &balance) in funded_accounts {
4236            let mut info = db.basic_ref(address)?.unwrap_or_default();
4237            info.balance = balance;
4238            db.insert_account(address, info);
4239        }
4240
4241        if is_tempo {
4242            let hardfork = tempo_hardfork.ok_or_else(|| {
4243                DatabaseError::AnyRequest(Arc::new(eyre::eyre!(
4244                    "missing Tempo hardfork during memory reset"
4245                )))
4246            })?;
4247            crate::eth::backend::tempo::initialize_tempo_precompiles(
4248                db,
4249                chain_id,
4250                genesis.timestamp,
4251                &genesis.accounts,
4252                hardfork,
4253            )
4254            .map_err(|err| DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{err}"))))?;
4255        }
4256
4257        if install_create2_deployer {
4258            db.set_code(
4259                DEFAULT_CREATE2_DEPLOYER,
4260                Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE),
4261            )?;
4262        }
4263
4264        Ok(())
4265    }
4266
4267    /// Prepares a fresh fork without mutating the live backend.
4268    pub(crate) async fn prepare_fork_reset(
4269        &self,
4270        forking: Forking,
4271        serving_instance_id: B256,
4272    ) -> Result<StagedForkReset, BlockchainError> {
4273        let previous_fork = self.get_fork();
4274        let previous_source = self
4275            .last_fork_cache_source
4276            .read()
4277            .clone()
4278            .or_else(|| previous_fork.as_ref().and_then(ForkCacheSource::from_fork));
4279        let configured_rpc_urls = self.node_config.read().await.fork_urls.clone();
4280        let rpc_url_was_provided = forking.json_rpc_url.is_some();
4281        let target_rpc_urls = if let Some(url) = forking.json_rpc_url {
4282            vec![url]
4283        } else if !configured_rpc_urls.is_empty() {
4284            configured_rpc_urls
4285        } else {
4286            previous_fork
4287                .as_ref()
4288                .map(|fork| fork.config.read().fork_urls.clone())
4289                .filter(|urls| !urls.is_empty())
4290                .ok_or_else(|| {
4291                    RpcError::invalid_params(
4292                        "Forking not enabled and RPC URL not provided to start forking",
4293                    )
4294                })?
4295        };
4296        let flush_old_cache = previous_fork.is_some();
4297        if flush_old_cache {
4298            // Staging opens a separate BlockchainDb from disk. Persist the live remote cache first
4299            // so an unchanged source and block can reuse it without copying locally modified state.
4300            self.db.write().await.maybe_flush_cache().map_err(BlockchainError::Internal)?;
4301        }
4302
4303        for _ in 0..3 {
4304            if let Some(staged) = self
4305                .stage_fork_reset(
4306                    &target_rpc_urls,
4307                    forking.block_number,
4308                    serving_instance_id,
4309                    previous_source.clone(),
4310                    flush_old_cache,
4311                    rpc_url_was_provided,
4312                )
4313                .await?
4314            {
4315                return Ok(staged);
4316            }
4317        }
4318        Err(BlockchainError::Internal(
4319            "fork endpoint changed while the replacement was being staged".to_string(),
4320        ))
4321    }
4322
4323    /// Builds and validates one complete fork replacement without mutating the live backend.
4324    async fn stage_fork_reset(
4325        &self,
4326        target_rpc_urls: &[String],
4327        block_number: Option<u64>,
4328        serving_instance_id: B256,
4329        previous_source: Option<ForkCacheSource>,
4330        flush_old_cache: bool,
4331        rpc_url_was_provided: bool,
4332    ) -> Result<Option<StagedForkReset>, BlockchainError> {
4333        let target_rpc_url = target_rpc_urls.first().ok_or_else(|| {
4334            BlockchainError::Internal("at least one fork URL is required".to_string())
4335        })?;
4336        let mut staged_config = self.node_config.read().await.clone();
4337        if rpc_url_was_provided {
4338            staged_config.fork_chain_id = None;
4339        }
4340        let configured_endpoint_is_anvil = staged_config.fork_endpoint_is_anvil
4341            && staged_config.fork_urls.contains(target_rpc_url);
4342        let cached_endpoint_is_anvil = previous_source.as_ref().is_some_and(|source| {
4343            source.rpc_url == *target_rpc_url && source.endpoint_identity.is_authoritative()
4344        });
4345        staged_config.fork_endpoint_is_anvil =
4346            configured_endpoint_is_anvil || cached_endpoint_is_anvil;
4347        staged_config.fork_urls = target_rpc_urls.to_vec();
4348        staged_config.fork_choice = block_number.map(|number| ForkChoice::Block(number as i128));
4349        let mut staged_env = self.evm_env.read().clone();
4350        staged_config.apply_tempo_fork_beneficiary_default(&mut staged_env);
4351        let staged_fees = self.fees.detached();
4352        let (mut staged_db, staged_client_config) = staged_config
4353            .setup_fork_db_config(target_rpc_url.clone(), &mut staged_env, &staged_fees)
4354            .await?;
4355        let cache_lease = StagedForkCacheLease::for_db(staged_db.inner());
4356        let cache_identity_changed = previous_source.as_ref().is_some_and(|source| {
4357            source.authoritative_identity_changed_at_same_url(
4358                target_rpc_url,
4359                staged_client_config.endpoint_identity,
4360            )
4361        });
4362        if cache_identity_changed {
4363            staged_db.clear_into_state_snapshot();
4364            staged_db.insert_block_hash(
4365                U256::from(staged_client_config.block_number),
4366                staged_client_config.block_hash,
4367            );
4368        }
4369        let mut invalidated_cache_namespaces = Vec::new();
4370        if cache_identity_changed && !staged_config.no_storage_caching {
4371            if let Some(source) = &previous_source
4372                && let Some(namespace) = ForkCacheNamespace::new(
4373                    source.endpoint_identity.source_chain_id,
4374                    target_rpc_url,
4375                )
4376            {
4377                invalidated_cache_namespaces.push(namespace);
4378            }
4379            if let Some(namespace) =
4380                ForkCacheNamespace::new(staged_client_config.chain_id, target_rpc_url)
4381                && !invalidated_cache_namespaces.contains(&namespace)
4382            {
4383                invalidated_cache_namespaces.push(namespace);
4384            }
4385        }
4386        let discard_old_cached_state =
4387            cache_identity_changed && flush_old_cache && !invalidated_cache_namespaces.is_empty();
4388        let staged_db: Arc<AsyncRwLock<Box<dyn Db>>> =
4389            Arc::new(AsyncRwLock::new(Box::new(staged_db)));
4390        let staged_fork = ClientFork::new(staged_client_config.clone(), Arc::clone(&staged_db));
4391        let attempt = async {
4392            let target_networks =
4393                staged_client_config.endpoint_identity.network_profile.unwrap_or_default();
4394            if !staged_config.has_explicit_network_selection()
4395                && !self.networks.supports_fork_source(&target_networks)
4396            {
4397                return Err(RpcError::invalid_params(format!(
4398                    "cannot reset Anvil across network families ({} -> {}); start a new \
4399                     instance with matching network configuration",
4400                    self.execution_profile_name(),
4401                    target_networks.execution_profile_name()
4402                ))
4403                .into());
4404            }
4405            if staged_client_config.endpoint_identity.instance_id == Some(serving_instance_id) {
4406                return Err(
4407                    RpcError::invalid_params("cannot reset Anvil to its own RPC endpoint").into()
4408                );
4409            }
4410            let fork_block = staged_fork
4411                .block_by_number(staged_fork.block_number())
4412                .await?
4413                .ok_or(BlockchainError::BlockNotFound)?;
4414            if fork_block.header.hash != staged_client_config.block_hash {
4415                return Ok(None);
4416            }
4417            self.apply_fork_genesis(Arc::clone(&staged_db), cache_lease.clone()).await?;
4418
4419            #[cfg(feature = "monad")]
4420            if self.is_monad() {
4421                monad::cache_fork_context(&staged_fork).await?;
4422            }
4423
4424            if !staged_config
4425                .fork_urls_match_context(
4426                    target_rpc_urls,
4427                    staged_client_config.endpoint_identity,
4428                    staged_client_config.block_number,
4429                    staged_client_config.block_hash,
4430                )
4431                .await?
4432            {
4433                return Ok(None);
4434            }
4435
4436            Ok(Some((fork_block, staged_fork.storage.read().clone())))
4437        }
4438        .await;
4439        drop(staged_fork);
4440        let (fork_block, staged_storage) = match attempt {
4441            Ok(Some(staged)) => staged,
4442            Ok(None) => {
4443                drop(staged_db);
4444                self.rollback_staged_fork_cache(cache_lease, flush_old_cache).await?;
4445                return Ok(None);
4446            }
4447            Err(err) => {
4448                drop(staged_db);
4449                self.rollback_staged_fork_cache(cache_lease, flush_old_cache).await?;
4450                return Err(err);
4451            }
4452        };
4453        let staged_db = match Arc::try_unwrap(staged_db) {
4454            Ok(staged_db) => staged_db.into_inner(),
4455            Err(staged_db) => {
4456                drop(staged_db);
4457                self.rollback_staged_fork_cache(cache_lease, flush_old_cache).await?;
4458                return Err(BlockchainError::Internal(
4459                    "staged fork database still has active references".to_string(),
4460                ));
4461            }
4462        };
4463
4464        let fork = ClientFork::new(staged_client_config, Arc::clone(&self.db));
4465        *fork.storage.write() = staged_storage;
4466        Ok(Some(StagedForkReset {
4467            node_config: staged_config,
4468            db: staged_db,
4469            fees: staged_fees,
4470            evm_env: staged_env,
4471            fork,
4472            timestamp: fork_block.header.timestamp(),
4473            discard_old_cached_state,
4474            invalidated_cache_namespaces,
4475            flush_old_cache,
4476            cache_lease,
4477        }))
4478    }
4479
4480    async fn rollback_staged_fork_cache(
4481        &self,
4482        cache_lease: StagedForkCacheLease,
4483        restore_live_cache: bool,
4484    ) -> Result<(), BlockchainError> {
4485        let rollback_err = cache_lease.rollback().err();
4486        // If immediate invalidation failed, dropping the final lease retries cleanup before the
4487        // live cache is restored at a potentially shared path.
4488        drop(cache_lease);
4489        let restore_err =
4490            if restore_live_cache { self.db.read().await.maybe_flush_cache().err() } else { None };
4491        match (rollback_err, restore_err) {
4492            (None, None) => Ok(()),
4493            (Some(err), None) => Err(err),
4494            (None, Some(err)) => Err(BlockchainError::Internal(format!(
4495                "failed to restore the live fork cache after staged reset rollback: {err}"
4496            ))),
4497            (Some(rollback), Some(restore)) => Err(BlockchainError::Internal(format!(
4498                "{rollback}; restoring the live fork cache also failed: {restore}"
4499            ))),
4500        }
4501    }
4502
4503    /// Atomically publishes a fully prepared fork replacement.
4504    pub(crate) async fn commit_fork_reset(
4505        &self,
4506        staged: StagedForkReset,
4507    ) -> Result<(), BlockchainError> {
4508        let fork_block_number = staged.fork.block_number();
4509        let fork_block_hash = staged.fork.block_hash();
4510        let fork_total_difficulty = staged.fork.total_difficulty();
4511        let fork_cache_source = ForkCacheSource::from_fork(&staged.fork);
4512
4513        // Acquire asynchronous write guards before flushing and replacing the live database so no
4514        // old-context request can populate its cache between those operations.
4515        let mut node_config = self.node_config.write().await;
4516        let mut db = self.db.write().await;
4517        if staged.flush_old_cache {
4518            db.maybe_flush_cache().map_err(BlockchainError::Internal)?;
4519        }
4520        if let Err(err) =
4521            staged.invalidated_cache_namespaces.iter().try_for_each(ForkCacheNamespace::invalidate)
4522        {
4523            // Prevent the rejected staged backend from flushing partial target state, then restore
4524            // the still-live cache after the endpoint namespace cleanup failed.
4525            let StagedForkReset { db: staged_db, cache_lease, flush_old_cache, .. } = staged;
4526            drop(staged_db);
4527            let rollback_err = cache_lease.rollback().err();
4528            drop(cache_lease);
4529            let restore_err = if flush_old_cache { db.maybe_flush_cache().err() } else { None };
4530            let mut message = err.to_string();
4531            if let Some(err) = rollback_err {
4532                message.push_str(&format!("; staged cache rollback also failed: {err}"));
4533            }
4534            if let Some(err) = restore_err {
4535                message.push_str(&format!("; restoring the live fork cache also failed: {err}"));
4536            }
4537            return Err(BlockchainError::Internal(message));
4538        }
4539        if staged.discard_old_cached_state {
4540            db.clear_into_state_snapshot();
4541        }
4542        staged.cache_lease.disarm();
4543        let StagedForkReset {
4544            node_config: staged_node_config,
4545            db: staged_db,
4546            fees,
4547            evm_env,
4548            fork,
4549            timestamp,
4550            ..
4551        } = staged;
4552        *node_config = staged_node_config;
4553        *db = staged_db;
4554        self.fees.replace_from(&fees);
4555        *self.evm_env.write() = evm_env;
4556        *self.fork.write() = Some(fork);
4557        *self.last_fork_cache_source.write() = fork_cache_source;
4558        *self.blockchain.storage.write() =
4559            BlockchainStorage::forked(fork_block_number, fork_block_hash, fork_total_difficulty);
4560        self.states.write().clear();
4561        self.active_state_snapshots.lock().clear();
4562        self.time.reset(timestamp);
4563        self.cheats.clear_next_block_prevrandao();
4564
4565        trace!(target: "backend", "reset fork");
4566        Ok(())
4567    }
4568
4569    /// Builds a complete in-memory replacement without mutating the live backend.
4570    pub(crate) async fn prepare_memory_reset(
4571        &self,
4572    ) -> Result<StagedMemoryReset<N>, BlockchainError> {
4573        let reset_from_fork = self.is_fork();
4574        let genesis_timestamp = self.genesis.timestamp;
4575        let genesis_number = self.genesis.number;
4576        let mut staged_config = self.node_config.read().await.clone();
4577        staged_config.fork_source_chain_id = None;
4578        staged_config.fork_execution_chain_id = None;
4579        staged_config.fork_endpoint_is_anvil = false;
4580        staged_config.restore_fork_overrides();
4581        let local_chain_id = staged_config.get_chain_id();
4582        staged_config.update_wallet_chain_id(local_chain_id);
4583        let (
4584            local_gas_limit,
4585            local_base_fee,
4586            local_base_fee_is_explicit,
4587            local_gas_price,
4588            local_blob_params,
4589            local_blob_excess_gas_and_price,
4590            local_beneficiary,
4591            install_create2_deployer,
4592        ) = {
4593            (
4594                staged_config.gas_limit(),
4595                staged_config.get_base_fee(),
4596                staged_config.base_fee.is_some()
4597                    || staged_config
4598                        .genesis
4599                        .as_ref()
4600                        .is_some_and(|genesis| genesis.base_fee_per_gas.is_some()),
4601                staged_config.get_gas_price(),
4602                staged_config.get_blob_params(),
4603                staged_config.get_blob_excess_gas_and_price(),
4604                staged_config.genesis.as_ref().map(|genesis| genesis.coinbase).unwrap_or_default(),
4605                !staged_config.disable_default_create2_deployer,
4606            )
4607        };
4608
4609        let local_hardfork = staged_config.get_hardfork();
4610        let local_spec = SpecId::from(local_hardfork);
4611        let local_tempo_hardfork =
4612            self.networks.is_tempo().then(|| TempoHardfork::from(local_hardfork));
4613        let staged_fees = self.fees.detached();
4614        staged_fees.set_execution_rules(
4615            local_spec,
4616            self.networks.base_fee_params(genesis_timestamp),
4617            local_tempo_hardfork,
4618        );
4619        #[cfg(feature = "optimism")]
4620        if self.networks.is_optimism() {
4621            staged_fees.set_optimism_hardfork(local_hardfork);
4622        }
4623        staged_fees.set_blob_params(local_blob_params);
4624        staged_fees.set_blob_excess_gas_and_price(local_blob_excess_gas_and_price);
4625
4626        // Explicit local configuration always wins, while Tempo configuration uses the
4627        // hardfork's own seed. An implicit in-memory Ethereum chain otherwise keeps its live base
4628        // fee in the reset genesis; returning from a fork restores the local default instead of
4629        // leaking remote fee state. Compute this up front so the env, storage, and fee manager all
4630        // agree.
4631        let preserve_live_base_fee =
4632            !reset_from_fork && !local_base_fee_is_explicit && local_tempo_hardfork.is_none();
4633        let genesis_base_fee =
4634            if preserve_live_base_fee { staged_fees.base_fee() } else { local_base_fee };
4635
4636        let mut staged_cfg = CfgEnv::default();
4637        staged_cfg.set_spec_and_mainnet_gas_params(local_spec);
4638        staged_cfg.chain_id = local_chain_id;
4639        staged_cfg.limit_contract_code_size = staged_config.code_size_limit;
4640        staged_cfg.disable_eip3607 = true;
4641        staged_cfg.disable_block_gas_limit = staged_config.disable_block_gas_limit;
4642        if !staged_config.enable_tx_gas_limit {
4643            staged_cfg.tx_gas_limit_cap = Some(u64::MAX);
4644        }
4645        if let Some(memory_limit) = staged_config.memory_limit {
4646            staged_cfg.memory_limit = memory_limit;
4647        }
4648        let staged_env = EvmEnv::new(
4649            staged_cfg,
4650            BlockEnv {
4651                number: U256::from(genesis_number),
4652                beneficiary: local_beneficiary,
4653                timestamp: U256::from(genesis_timestamp),
4654                gas_limit: local_gas_limit,
4655                basefee: genesis_base_fee,
4656                prevrandao: Some(B256::ZERO),
4657                ..Default::default()
4658            },
4659        );
4660
4661        let base_fee = staged_fees.is_eip1559().then_some(genesis_base_fee);
4662        let header = genesis_header(&staged_env, base_fee, genesis_timestamp, genesis_number);
4663        let staged_storage = BlockchainStorage::new(foundry_header(&self.networks, header));
4664
4665        // Seed the next block's fee state. Tempo always advances through its hardfork rule, an
4666        // implicit in-memory Ethereum reset restores Anvil's default, and explicit or
4667        // fork-to-memory Ethereum resets retain the local configured value.
4668        staged_fees.set_base_fee(genesis_base_fee);
4669        if staged_fees.is_eip1559() {
4670            let next_base_fee = if staged_fees.tempo_hardfork().is_some() {
4671                staged_fees.get_next_block_base_fee_per_gas(
4672                    0,
4673                    staged_env.block_env.gas_limit,
4674                    genesis_base_fee,
4675                )
4676            } else if preserve_live_base_fee {
4677                crate::eth::fees::INITIAL_BASE_FEE
4678            } else {
4679                genesis_base_fee
4680            };
4681            staged_fees.set_base_fee(next_base_fee);
4682        }
4683        staged_fees.set_gas_price(local_gas_price);
4684
4685        let mut staged_db: Box<dyn Db> = Box::new(StateRootDb::new(
4686            self.prune_state_history_config.is_state_history_supported(),
4687        ));
4688        Self::populate_memory_db(
4689            &mut *staged_db,
4690            &self.genesis,
4691            &staged_config.funded_accounts,
4692            local_hardfork,
4693            local_chain_id,
4694            self.networks.is_tempo(),
4695            local_tempo_hardfork,
4696            staged_storage.genesis_hash,
4697            install_create2_deployer,
4698        )?;
4699
4700        Ok(StagedMemoryReset {
4701            node_config: staged_config,
4702            db: staged_db,
4703            fees: staged_fees,
4704            evm_env: staged_env,
4705            hardfork: local_hardfork,
4706            storage: staged_storage,
4707            timestamp: genesis_timestamp,
4708            flush_old_cache: reset_from_fork,
4709        })
4710    }
4711
4712    /// Atomically publishes a fully prepared in-memory replacement.
4713    pub(crate) async fn commit_memory_reset(
4714        &self,
4715        staged: StagedMemoryReset<N>,
4716    ) -> Result<(), BlockchainError> {
4717        let StagedMemoryReset {
4718            node_config,
4719            db,
4720            fees,
4721            evm_env,
4722            hardfork,
4723            storage,
4724            timestamp,
4725            flush_old_cache,
4726        } = staged;
4727        let mut live_config = self.node_config.write().await;
4728        let mut live_db = self.db.write().await;
4729        if flush_old_cache {
4730            live_db.maybe_flush_cache().map_err(BlockchainError::Internal)?;
4731        }
4732
4733        *live_config = node_config;
4734        *live_db = db;
4735        self.fees.replace_from(&fees);
4736        *self.evm_env.write() = evm_env;
4737        *self.hardfork.write() = hardfork;
4738        *self.fork.write() = None;
4739        *self.blockchain.storage.write() = storage;
4740        self.states.write().clear();
4741        self.active_state_snapshots.lock().clear();
4742        self.time.reset(timestamp);
4743        self.cheats.clear_next_block_prevrandao();
4744        trace!(target: "backend", "reset to fresh in-memory state");
4745        Ok(())
4746    }
4747
4748    /// Reverts the state to the state snapshot identified by the given `id`.
4749    pub async fn revert_state_snapshot(&self, id: U256) -> Result<bool, BlockchainError> {
4750        let Some((num, hash, fees, time_offset)) =
4751            self.active_state_snapshots.lock().get(&id).map(|snapshot| {
4752                (snapshot.block_number, snapshot.block_hash, snapshot.fees, snapshot.time_offset)
4753            })
4754        else {
4755            return Ok(false);
4756        };
4757        let block = self.block_by_hash(hash).await?.ok_or(BlockchainError::BlockNotFound)?;
4758        if !self.db.write().await.revert_state(id, RevertStateSnapshotAction::RevertRemove) {
4759            return Ok(false);
4760        }
4761        {
4762            let mut snapshots = self.active_state_snapshots.lock();
4763            snapshots.remove(&id);
4764            snapshots.retain(|snapshot_id, _| *snapshot_id < id);
4765        }
4766        // Revert the storage that's newer than the snapshot.
4767        self.blockchain.storage.write().unwind_to(num, hash);
4768
4769        let reset_time = block.header.timestamp();
4770        self.time.reset_with_offset(reset_time, time_offset);
4771        // drop any pending next-block prevrandao override so it does not leak into a block
4772        self.cheats.clear_next_block_prevrandao();
4773
4774        {
4775            let mut env = self.evm_env.write();
4776            env.block_env = BlockEnv {
4777                number: U256::from(num),
4778                timestamp: U256::from(block.header.timestamp()),
4779                difficulty: block.header.difficulty(),
4780                // ensures prevrandao is set
4781                prevrandao: Some(block.header.mix_hash().unwrap_or_default()),
4782                gas_limit: block.header.gas_limit(),
4783                // Keep previous `beneficiary` and `basefee` value
4784                beneficiary: env.block_env.beneficiary,
4785                basefee: env.block_env.basefee,
4786                ..Default::default()
4787            };
4788        }
4789        self.fees.restore(fees);
4790        Ok(true)
4791    }
4792
4793    /// executes the transactions without writing to the underlying database
4794    pub async fn inspect_tx(
4795        &self,
4796        tx: Arc<PoolTransaction<FoundryTxEnvelope>>,
4797    ) -> Result<
4798        (InstructionResult, Option<Output>, u64, State, Vec<revm::primitives::Log>),
4799        BlockchainError,
4800    > {
4801        let evm_env = self.next_evm_env();
4802        let db = self.db.read().await;
4803        let mut inspector = self.build_inspector();
4804        #[cfg(feature = "monad")]
4805        let mut monad_context = self
4806            .is_monad()
4807            .then(|| self.monad_context_for_child_of(self.best_hash()))
4808            .transpose()?;
4809        #[cfg(not(feature = "monad"))]
4810        let mut monad_context = None;
4811        let (ResultAndState { result, state }, _) = self
4812            .transact_envelope_with_inspector_ref_and_context(
4813                &**db,
4814                &evm_env,
4815                &mut inspector,
4816                &tx.pending_transaction,
4817                monad_context.as_mut().map(next_monad_context),
4818            )?;
4819        let (exit_reason, gas_used, out, logs) = unpack_execution_result(result);
4820
4821        inspector.print_logs();
4822
4823        if self.print_traces {
4824            inspector.print_traces(self.call_trace_decoder());
4825        }
4826
4827        Ok((exit_reason, out, gas_used, state, logs))
4828    }
4829}
4830
4831impl<N: Network> Backend<N>
4832where
4833    N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
4834{
4835    /// Returns all `Log`s mined by the node that were emitted in the `block` and match the `Filter`
4836    fn mined_logs_for_block(&self, filter: Filter, block: Block, block_hash: B256) -> Vec<Log> {
4837        let mut all_logs = Vec::new();
4838        let mut block_log_index = 0u32;
4839
4840        let storage = self.blockchain.storage.read();
4841
4842        for tx in block.body.transactions {
4843            let Some(tx) = storage.transactions.get(&tx.hash()) else {
4844                continue;
4845            };
4846
4847            let logs = tx.receipt.logs();
4848            let transaction_hash = tx.info.transaction_hash;
4849
4850            for log in logs {
4851                if filter.matches(log) {
4852                    all_logs.push(Log {
4853                        inner: log.clone(),
4854                        block_hash: Some(block_hash),
4855                        block_number: Some(block.header.number()),
4856                        block_timestamp: Some(block.header.timestamp()),
4857                        transaction_hash: Some(transaction_hash),
4858                        transaction_index: Some(tx.info.transaction_index),
4859                        log_index: Some(block_log_index as u64),
4860                        removed: false,
4861                    });
4862                }
4863                block_log_index += 1;
4864            }
4865        }
4866        all_logs
4867    }
4868
4869    /// Returns all logs of the blocks with a number greater than `block_number`, marked as
4870    /// removed.
4871    ///
4872    /// This is used during a reorg to capture the logs of the blocks that are about to be
4873    /// unwound before their transactions and receipts are cleared from storage, so they can be
4874    /// re-delivered to log subscriptions and filters with `removed: true`.
4875    fn removed_logs_since(&self, block_number: u64) -> Vec<Log> {
4876        let storage = self.blockchain.storage.read();
4877        let mut all_logs = Vec::new();
4878
4879        for num in (block_number + 1)..=storage.best_number {
4880            if let Some(hash) = storage.hashes.get(&num)
4881                && let Some(block) = storage.blocks.get(hash)
4882            {
4883                let mut block_log_index = 0u64;
4884                for tx in &block.body.transactions {
4885                    if let Some(tx) = storage.transactions.get(&tx.hash()) {
4886                        for log in tx.receipt.logs() {
4887                            all_logs.push(Log {
4888                                inner: log.clone(),
4889                                block_hash: Some(*hash),
4890                                block_number: Some(num),
4891                                block_timestamp: Some(block.header.timestamp()),
4892                                transaction_hash: Some(tx.info.transaction_hash),
4893                                transaction_index: Some(tx.info.transaction_index),
4894                                log_index: Some(block_log_index),
4895                                removed: true,
4896                            });
4897                            block_log_index += 1;
4898                        }
4899                    }
4900                }
4901            }
4902        }
4903
4904        all_logs
4905    }
4906
4907    /// Returns the logs of the block that match the filter
4908    async fn logs_for_block(
4909        &self,
4910        filter: Filter,
4911        hash: B256,
4912    ) -> Result<Vec<Log>, BlockchainError> {
4913        if let Some(block) = self.blockchain.get_block_by_hash(&hash) {
4914            return Ok(self.mined_logs_for_block(filter, block, hash));
4915        }
4916
4917        if let Some(fork) = self.get_fork() {
4918            return Ok(fork.logs(&filter).await?);
4919        }
4920
4921        Err(BlockchainError::UnknownBlock)
4922    }
4923
4924    /// Returns the logs that match the filter in the given range of blocks
4925    async fn logs_for_range(
4926        &self,
4927        filter: &Filter,
4928        mut from: u64,
4929        to: u64,
4930    ) -> Result<Vec<Log>, BlockchainError> {
4931        let mut all_logs = Vec::new();
4932
4933        // get the range that predates the fork if any
4934        if let Some(fork) = self.get_fork() {
4935            let to_on_fork = if fork.predates_fork(to) {
4936                to
4937            } else {
4938                // adjust the ranges
4939                fork.block_number()
4940            };
4941
4942            if fork.predates_fork_inclusive(from) {
4943                // this data is only available on the forked client
4944                let filter = filter.clone().from_block(from).to_block(to_on_fork);
4945                all_logs = fork.logs(&filter).await?;
4946
4947                // update the range
4948                from = fork.block_number() + 1;
4949            }
4950        }
4951
4952        for number in from..=to {
4953            if let Some((block, hash)) = self.get_block_with_hash(number) {
4954                all_logs.extend(self.mined_logs_for_block(filter.clone(), block, hash));
4955            }
4956        }
4957
4958        Ok(all_logs)
4959    }
4960
4961    /// Returns the logs according to the filter
4962    pub async fn logs(&self, filter: Filter) -> Result<Vec<Log>, BlockchainError> {
4963        trace!(target: "backend", "get logs [{:?}]", filter);
4964        if let Some(hash) = filter.get_block_hash() {
4965            self.logs_for_block(filter, hash).await
4966        } else {
4967            let best = self.best_number();
4968            let to_block =
4969                self.convert_block_number(filter.block_option.get_to_block().copied()).min(best);
4970            let from_block =
4971                self.convert_block_number(filter.block_option.get_from_block().copied());
4972            if from_block > best {
4973                return Err(BlockchainError::BlockOutOfRange(best, from_block));
4974            }
4975
4976            self.logs_for_range(&filter, from_block, to_block).await
4977        }
4978    }
4979
4980    /// Returns all receipts of the block
4981    pub fn mined_receipts(&self, hash: B256) -> Option<Vec<N::ReceiptEnvelope>> {
4982        let storage = self.blockchain.storage.read();
4983        let block = storage.blocks.get(&hash)?;
4984        block
4985            .body
4986            .transactions
4987            .iter()
4988            .map(|transaction| {
4989                storage.transactions.get(&transaction.hash()).map(|tx| tx.receipt.clone())
4990            })
4991            .collect()
4992    }
4993}
4994
4995// Mining methods — generic over N: Network, with Foundry-associated-type bounds for now.
4996impl<N: Network> Backend<N>
4997where
4998    Self: TransactionValidator<FoundryTxEnvelope>,
4999    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
5000{
5001    /// Mines a new block and stores it.
5002    ///
5003    /// this will execute all transaction in the order they come in and return all the markers they
5004    /// provide.
5005    pub async fn mine_block(
5006        &self,
5007        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
5008    ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
5009        self.do_mine_block(pool_transactions).await
5010    }
5011
5012    /// Replays a transaction-hash fork prefix before the live pool and miner are created.
5013    pub(crate) async fn apply_fork_transaction_replay(
5014        &self,
5015        replay: ForkTransactionReplay,
5016    ) -> Result<()> {
5017        let source_chain_id = self.protocol_chain_id();
5018        let arbitrum_block_numbers = is_arbitrum(source_chain_id).then(|| {
5019            (
5020                replay.source_block.header().number(),
5021                arbitrum_replay_block_number(&replay.source_block),
5022            )
5023        });
5024        let prepared = prepare_fork_transaction_replay(replay, self.is_monad())?;
5025        let fallback_execution_chain_id = self
5026            .get_fork()
5027            .map(|fork| fork.execution_chain_id())
5028            .unwrap_or_else(|| self.chain_id().to());
5029        let execution_chain_id = prepared.execution_chain_id(fallback_execution_chain_id)?;
5030        let PreparedForkTransactionReplay { transactions, timestamp, parent_beacon_block_root } =
5031            prepared;
5032        eyre::ensure!(!transactions.is_empty(), "fork transaction replay prefix is empty");
5033        let next_timestamp = timestamp.checked_add(1).ok_or_else(|| {
5034            eyre::eyre!("fork transaction replay timestamp cannot be incremented")
5035        })?;
5036
5037        let _mining_guard = self.mining.lock().await;
5038        let current_base_fee = self.base_fee();
5039        let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price();
5040        let mut evm_env = self.evm_env.read().clone();
5041        if evm_env.block_env.basefee == 0 {
5042            evm_env.cfg_env.disable_base_fee = true;
5043        }
5044
5045        let best_number = self.blockchain.storage.read().best_number;
5046        let block_number = best_number.saturating_add(1);
5047        if arbitrum_block_numbers.is_some() {
5048            evm_env.block_env.number = U256::from(block_number);
5049        } else {
5050            evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1));
5051        }
5052        evm_env.block_env.basefee = current_base_fee;
5053        evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price;
5054        evm_env.block_env.timestamp = U256::from(timestamp);
5055
5056        let best_hash = self.blockchain.storage.read().best_hash;
5057        let mut prevrandao_input = [0u8; 40];
5058        prevrandao_input[..32].copy_from_slice(best_hash.as_slice());
5059        prevrandao_input[32..].copy_from_slice(&block_number.to_le_bytes());
5060        evm_env.block_env.prevrandao = Some(
5061            self.cheats.take_next_block_prevrandao().unwrap_or_else(|| keccak256(prevrandao_input)),
5062        );
5063
5064        let mut replay_env = evm_env.clone();
5065        let arbitrum_rpc_block_number = arbitrum_block_numbers.map(|(rpc, evm)| {
5066            replay_env.block_env.number = evm;
5067            rpc
5068        });
5069        replay_env.cfg_env.chain_id = execution_chain_id;
5070        apply_chain_specific_tx_replay_env_changes_for_chain(&mut replay_env, source_chain_id);
5071        let inspector_tx_config = self.inspector_tx_config();
5072
5073        let scheduled_hardfork =
5074            FoundryHardfork::from_chain_and_timestamp(source_chain_id, timestamp);
5075        #[cfg(feature = "monad")]
5076        let mut monad_replay = self
5077            .prepare_monad_fork_replay(
5078                source_chain_id,
5079                execution_chain_id,
5080                timestamp,
5081                best_hash,
5082                &transactions,
5083            )
5084            .await?;
5085        #[cfg(feature = "monad")]
5086        let hardfork = monad_replay
5087            .as_ref()
5088            .map(monad::ForkReplay::hardfork)
5089            .unwrap_or_else(|| scheduled_hardfork.unwrap_or_else(|| self.hardfork()));
5090        #[cfg(not(feature = "monad"))]
5091        let hardfork = scheduled_hardfork.unwrap_or_else(|| self.hardfork());
5092        if !self.is_optimism() && !self.is_tempo() {
5093            replay_env.cfg_env.spec = SpecId::from(hardfork);
5094            // Cancun requires blob excess gas even for non-blob txs.
5095            if replay_env.cfg_env.spec >= SpecId::CANCUN
5096                && replay_env.block_env.blob_excess_gas_and_price.is_none()
5097            {
5098                replay_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(
5099                    0,
5100                    get_blob_base_fee_update_fraction_by_spec_id(replay_env.cfg_env.spec),
5101                ));
5102            }
5103        }
5104
5105        #[cfg(feature = "monad")]
5106        let monad_context = monad_replay.as_mut().and_then(monad::ForkReplay::take_context);
5107        #[cfg(not(feature = "monad"))]
5108        let monad_context = None;
5109
5110        let (block_info, state_changes, block_hash) = {
5111            let db = self.db.read().await;
5112            let mut overlay = AnvilCacheDB::new(&**db);
5113            let ExecutedHistoricalReplay {
5114                block_result,
5115                transactions,
5116                transaction_infos,
5117                state_changes,
5118            } = self.execute_with_replay_block_executor(
5119                &mut overlay,
5120                &replay_env,
5121                best_hash,
5122                arbitrum_rpc_block_number,
5123                hardfork,
5124                parent_beacon_block_root,
5125                &transactions,
5126                &inspector_tx_config,
5127                monad_context,
5128            )?;
5129            let state_root = overlay.maybe_state_root().unwrap_or_default();
5130            let block_info = self.build_block_info(
5131                &replay_env,
5132                best_hash,
5133                block_number,
5134                state_root,
5135                block_result,
5136                transactions,
5137                transaction_infos,
5138                parent_beacon_block_root,
5139            );
5140            let block_hash = block_info.block.header.hash_slow();
5141            (block_info, state_changes, block_hash)
5142        };
5143
5144        if self.prune_state_history_config.is_state_history_supported() {
5145            let state = self.db.read().await.current_state();
5146            self.states.write().insert(best_hash, state);
5147        }
5148
5149        {
5150            let mut db = self.db.write().await;
5151            for state in state_changes {
5152                db.commit(state);
5153            }
5154            db.insert_block_hash(U256::from(block_number), block_hash);
5155        }
5156
5157        let BlockInfo { block, transactions, receipts } = block_info;
5158        let header = block.header.clone();
5159        {
5160            let mut storage = self.blockchain.storage.write();
5161            storage.best_number = block_number;
5162            storage.best_hash = block_hash;
5163            if !self.is_eip3675() {
5164                storage.total_difficulty =
5165                    storage.total_difficulty.saturating_add(header.difficulty);
5166            }
5167            storage.blocks.insert(block_hash, block);
5168            storage.hashes.insert(block_number, block_hash);
5169            #[cfg(feature = "monad")]
5170            if let Some(replay) = &mut monad_replay {
5171                replay.store_metadata(&mut storage, block_hash);
5172            }
5173            for (info, receipt) in transactions.into_iter().zip(receipts) {
5174                let mined_tx = MinedTransaction { info, receipt, block_hash, block_number };
5175                storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx);
5176            }
5177
5178            if let Some(transaction_block_keeper) = self.transaction_block_keeper
5179                && storage.blocks.len() > transaction_block_keeper
5180            {
5181                let to_clear = block_number
5182                    .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX));
5183                storage.remove_block_transactions_by_number(to_clear)
5184            }
5185        }
5186
5187        #[cfg(feature = "monad")]
5188        if let Some(replay) = &monad_replay {
5189            self.finalize_monad_fork_replay(replay, &mut evm_env);
5190        }
5191
5192        evm_env.block_env.difficulty = U256::ZERO;
5193        *self.evm_env.write() = evm_env;
5194        self.time.reset(timestamp);
5195        self.time.set_next_block_timestamp(next_timestamp)?;
5196
5197        #[cfg(feature = "optimism")]
5198        if self.is_optimism() {
5199            self.fees.set_optimism_base_fee_rules(header.extra_data());
5200        }
5201        let next_block_base_fee = self.fees.get_next_block_base_fee_from_header(&header);
5202        let next_block_excess_blob_gas = self.networks.next_block_blob_excess_gas(
5203            self.fees.blob_params(),
5204            header.excess_blob_gas.unwrap_or_default(),
5205            header.blob_gas_used.unwrap_or_default(),
5206            header.base_fee_per_gas.unwrap_or_default(),
5207        );
5208        self.fees.set_base_fee(next_block_base_fee);
5209        self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
5210            next_block_excess_blob_gas,
5211            get_blob_base_fee_update_fraction_by_spec_id(*self.evm_env.read().spec_id()),
5212        ));
5213        self.notify_on_new_block(header.into_inner(), block_hash);
5214
5215        Ok(())
5216    }
5217
5218    #[allow(clippy::too_many_arguments)]
5219    fn execute_with_replay_block_executor<DB>(
5220        &self,
5221        db: DB,
5222        evm_env: &EvmEnv,
5223        parent_hash: B256,
5224        arbitrum_rpc_block_number: Option<u64>,
5225        hardfork: FoundryHardfork,
5226        parent_beacon_block_root: Option<B256>,
5227        transactions: &[HistoricalReplayTransaction],
5228        inspector_tx_config: &InspectorTxConfig,
5229        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
5230            MonadReplayContext,
5231        >,
5232    ) -> Result<ExecutedHistoricalReplay>
5233    where
5234        DB: StateDB<Error = DatabaseError>,
5235    {
5236        #[cfg(feature = "monad")]
5237        if self.is_monad() {
5238            return self.execute_with_monad_replay_block_executor(
5239                db,
5240                evm_env,
5241                parent_hash,
5242                hardfork,
5243                transactions,
5244                inspector_tx_config,
5245                monad_context,
5246            );
5247        }
5248
5249        let inspector = self.build_mining_inspector();
5250        let ethereum_transitions = self.ethereum_block_transitions(
5251            hardfork,
5252            parent_beacon_block_root,
5253            BlockExecutionKind::TransactionPrefix,
5254        );
5255
5256        macro_rules! run {
5257            ($evm:expr) => {{
5258                run!($evm, |executor| execute_historical_replay(
5259                    executor,
5260                    transactions,
5261                    inspector_tx_config,
5262                ))
5263            }};
5264            ($evm:expr, $execute:expr) => {{
5265                self.inject_precompiles($evm.precompiles_mut(), evm_env);
5266                if let Some(block_number) = arbitrum_rpc_block_number {
5267                    self.inject_arbitrum_precompile_at_block($evm.precompiles_mut(), block_number);
5268                }
5269                // Replay re-executes an already-valid historical prefix, so it does not apply the
5270                // local EIP-4844 budget. Jovian still uses the source block's gas limit as its DA
5271                // budget through `set_optimism_hardfork` below.
5272                let mut executor = AnvilBlockExecutor::new(
5273                    $evm,
5274                    parent_hash,
5275                    *evm_env.spec_id(),
5276                    ethereum_transitions,
5277                )
5278                .with_state_changes();
5279                #[cfg(feature = "optimism")]
5280                if self.is_optimism() {
5281                    executor.set_optimism_hardfork(hardfork);
5282                }
5283                executor
5284                    .apply_pre_execution_changes()
5285                    .wrap_err("failed to apply replay block-start transitions")?;
5286                let (stored_transactions, transaction_infos) = $execute(&mut executor)?;
5287                let state_changes = executor.take_state_changes();
5288                let (evm, block_result) =
5289                    executor.finish().wrap_err("failed to finish replay block execution")?;
5290                drop(evm);
5291                Ok(ExecutedHistoricalReplay {
5292                    block_result,
5293                    transactions: stored_transactions,
5294                    transaction_infos,
5295                    state_changes,
5296                })
5297            }};
5298        }
5299
5300        #[cfg(feature = "optimism")]
5301        if self.is_optimism() {
5302            let op_env = EvmEnv::new(
5303                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(hardfork.into()),
5304                evm_env.block_env.clone(),
5305            );
5306            let mut evm =
5307                OpEvmFactory::<OpTx>::default().create_evm_with_inspector(db, op_env, inspector);
5308            return run!(evm);
5309        }
5310
5311        if self.is_tempo() {
5312            let tempo_env = self.build_tempo_evm_env(evm_env);
5313            let mut evm =
5314                TempoEvmFactory::default().create_evm_with_inspector(db, tempo_env, inspector);
5315            return run!(evm);
5316        }
5317
5318        let mut evm =
5319            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
5320        run!(evm)
5321    }
5322
5323    /// Builds a [`BlockInfo`] from the EVM environment, execution results, and transactions.
5324    #[allow(clippy::too_many_arguments)]
5325    fn build_block_info(
5326        &self,
5327        evm_env: &EvmEnv,
5328        parent_hash: B256,
5329        number: u64,
5330        state_root: B256,
5331        block_result: BlockExecutionResult<FoundryReceiptEnvelope>,
5332        transactions: Vec<MaybeImpersonatedTransaction<FoundryTxEnvelope>>,
5333        transaction_infos: Vec<TransactionInfo>,
5334        parent_beacon_block_root: Option<B256>,
5335    ) -> BlockInfo<N> {
5336        let spec_id = *evm_env.spec_id();
5337        let is_shanghai = spec_id >= SpecId::SHANGHAI;
5338        let is_cancun = spec_id >= SpecId::CANCUN;
5339        let is_prague = spec_id >= SpecId::PRAGUE;
5340
5341        let receipts_root = calculate_receipt_root(&block_result.receipts);
5342        let cumulative_blob_gas_used = is_cancun.then_some(block_result.blob_gas_used);
5343        let bloom = block_result.receipts.iter().fold(Bloom::default(), |mut b, r| {
5344            b.accrue_bloom(r.logs_bloom());
5345            b
5346        });
5347
5348        let header = Header {
5349            parent_hash,
5350            ommers_hash: Default::default(),
5351            beneficiary: evm_env.block_env.beneficiary,
5352            state_root,
5353            transactions_root: Default::default(),
5354            receipts_root,
5355            logs_bloom: bloom,
5356            difficulty: evm_env.block_env.difficulty,
5357            number,
5358            gas_limit: evm_env.block_env.gas_limit,
5359            gas_used: block_result.gas_used,
5360            timestamp: evm_env.block_env.timestamp.saturating_to(),
5361            extra_data: self.fees.base_fee_extra_data(),
5362            mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(),
5363            nonce: Default::default(),
5364            base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(evm_env.block_env.basefee),
5365            parent_beacon_block_root: is_cancun
5366                .then(|| parent_beacon_block_root.unwrap_or_default()),
5367            blob_gas_used: cumulative_blob_gas_used,
5368            excess_blob_gas: if is_cancun { evm_env.block_env.blob_excess_gas() } else { None },
5369            withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
5370            requests_hash: is_prague.then(|| block_result.requests.requests_hash()),
5371            block_access_list_hash: None,
5372            slot_number: None,
5373        };
5374
5375        let block = create_block(foundry_header(&self.networks, header), transactions);
5376        BlockInfo { block, transactions: transaction_infos, receipts: block_result.receipts }
5377    }
5378
5379    async fn do_mine_block(
5380        &self,
5381        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
5382    ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
5383        let _mining_guard = self.mining.lock().await;
5384        trace!(target: "backend", "creating new block with {} transactions", pool_transactions.len());
5385
5386        let (outcome, header, block_hash) = {
5387            let current_base_fee = self.base_fee();
5388            let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price();
5389
5390            let mut evm_env = self.evm_env.read().clone();
5391            let hardfork = self.hardfork();
5392
5393            if evm_env.block_env.basefee == 0 {
5394                // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee`
5395                // 0 is only possible if it's manually set
5396                evm_env.cfg_env.disable_base_fee = true;
5397            }
5398
5399            let block_number = self.blockchain.storage.read().best_number.saturating_add(1);
5400
5401            // increase block number for this block
5402            if is_arbitrum(self.protocol_chain_id()) {
5403                // Temporary set `env.block.number` to `block_number` for Arbitrum chains.
5404                evm_env.block_env.number = U256::from(block_number);
5405            } else {
5406                evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1));
5407            }
5408
5409            evm_env.block_env.basefee = current_base_fee;
5410            evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price;
5411
5412            let best_hash = self.blockchain.storage.read().best_hash;
5413
5414            let mut input = [0u8; 40];
5415            input[..32].copy_from_slice(best_hash.as_slice());
5416            input[32..].copy_from_slice(&block_number.to_le_bytes());
5417            // Use the `prevrandao` value set via `anvil_setNextBlockPrevRandao` for this block if
5418            // one was provided, otherwise derive it from the parent hash and block number. The
5419            // manual override is consumed here so it only applies to this single block.
5420            let next_prevrandao = self.cheats.prepare_next_block_prevrandao();
5421            evm_env.block_env.prevrandao =
5422                Some(next_prevrandao.map_or_else(|| keccak256(input), |pending| pending.value));
5423
5424            let (block_info, included, invalid, not_yet_valid, block_hash, parent_state) = {
5425                let mut db = self.db.write().await;
5426
5427                // finally set the next block timestamp, this is done just before execution, because
5428                // there can be concurrent requests that can delay acquiring the db lock and we want
5429                // to ensure the timestamp is as close as possible to the actual execution.
5430                let pending_timestamp = self.time.prepare_next_timestamp();
5431                evm_env.block_env.timestamp = U256::from(pending_timestamp.timestamp);
5432
5433                // Forced historical transactions bypass pool admission and are replayed while
5434                // mining. Keep this exception local to the disposable mining environment.
5435                let mut mining_evm_env = evm_env.clone();
5436                if pool_transactions.iter().any(|tx| tx.is_replay) {
5437                    apply_chain_specific_tx_replay_env_changes_for_chain(
5438                        &mut mining_evm_env,
5439                        self.protocol_chain_id(),
5440                    );
5441                }
5442
5443                let spec_id = *mining_evm_env.spec_id();
5444
5445                let inspector_tx_config = self.inspector_tx_config();
5446                let gas_config = self.pool_tx_gas_config(&mining_evm_env);
5447
5448                let mut candidate_db = AnvilCacheDB::new(&**db);
5449                let (pool_result, block_result) = self.execute_with_block_executor(
5450                    &mut candidate_db,
5451                    &mining_evm_env,
5452                    best_hash,
5453                    spec_id,
5454                    hardfork,
5455                    Some(B256::ZERO),
5456                    BlockExecutionKind::Complete,
5457                    &pool_transactions,
5458                    &gas_config,
5459                    &inspector_tx_config,
5460                    &|pool_tx, account| {
5461                        let validation_env =
5462                            if pool_tx.is_replay { &mining_evm_env } else { &evm_env };
5463                        self.validate_mining_pool_transaction_for(pool_tx, account, validation_env)
5464                    },
5465                )?;
5466
5467                let included = pool_result.included;
5468                let invalid = pool_result.invalid;
5469                let not_yet_valid = pool_result.not_yet_valid;
5470
5471                let CacheDB { cache, db: _ } = candidate_db.0;
5472                let parent_state = self
5473                    .prune_state_history_config
5474                    .is_state_history_supported()
5475                    .then(|| db.current_state());
5476                commit_cache(&mut **db, cache)?;
5477                let state_root = db.maybe_state_root().unwrap_or_default();
5478                let block_info = self.build_block_info(
5479                    &mining_evm_env,
5480                    best_hash,
5481                    block_number,
5482                    state_root,
5483                    block_result,
5484                    pool_result.txs,
5485                    pool_result.tx_info,
5486                    Some(B256::ZERO),
5487                );
5488
5489                // Update the new blockhash in the db itself.
5490                let block_hash = block_info.block.header.hash_slow();
5491                db.insert_block_hash(U256::from(block_info.block.header.number()), block_hash);
5492                self.time.commit_next_timestamp(pending_timestamp);
5493                if let Some(pending) = next_prevrandao {
5494                    self.cheats.consume_next_block_prevrandao(pending);
5495                }
5496
5497                (block_info, included, invalid, not_yet_valid, block_hash, parent_state)
5498            };
5499
5500            // create the new block with the current timestamp
5501            let BlockInfo { block, transactions, receipts } = block_info;
5502
5503            let header = block.header.clone();
5504            #[cfg(feature = "monad")]
5505            let monad_participants = self.is_monad().then(|| {
5506                let tx_envs = included
5507                    .iter()
5508                    .map(|pool_tx| {
5509                        build_tx_env_for_pending::<FoundryTxEnvelope, TxEnv>(
5510                            &pool_tx.pending_transaction,
5511                            self.cheats(),
5512                        )
5513                    })
5514                    .collect::<Vec<_>>();
5515                foundry_evm::core::evm::monad_block_participants(&tx_envs)
5516            });
5517
5518            if let Some(parent_state) = parent_state {
5519                self.states.write().insert(best_hash, parent_state);
5520            }
5521
5522            trace!(
5523                target: "backend",
5524                "Mined block {} with {} tx {:?}",
5525                block_number,
5526                transactions.len(),
5527                transactions.iter().map(|tx| tx.transaction_hash).collect::<Vec<_>>()
5528            );
5529            let mut storage = self.blockchain.storage.write();
5530            // update block metadata
5531            storage.best_number = block_number;
5532            storage.best_hash = block_hash;
5533            // Difficulty is removed and not used after Paris (aka TheMerge). Value is replaced with
5534            // prevrandao. https://github.com/bluealloy/revm/blob/1839b3fce8eaeebb85025576f2519b80615aca1e/crates/interpreter/src/instructions/host_env.rs#L27
5535            if !self.is_eip3675() {
5536                storage.total_difficulty =
5537                    storage.total_difficulty.saturating_add(header.difficulty);
5538            }
5539
5540            storage.blocks.insert(block_hash, block);
5541            storage.hashes.insert(block_number, block_hash);
5542            #[cfg(feature = "monad")]
5543            if let Some(participants) = monad_participants {
5544                monad::store_block_metadata(
5545                    &mut storage,
5546                    block_hash,
5547                    participants,
5548                    evm_env.cfg_env.chain_id,
5549                    hardfork,
5550                );
5551            }
5552
5553            node_info!("");
5554            // insert all transactions
5555            for (info, receipt) in transactions.into_iter().zip(receipts) {
5556                // log some tx info
5557                node_info!("    Transaction: {:?}", info.transaction_hash);
5558                if let Some(contract) = &info.contract_address {
5559                    node_info!("    Contract created: {contract}");
5560                }
5561                node_info!("    Gas used: {}", info.gas_used);
5562                if !info.exit.is_ok() {
5563                    let r = RevertDecoder::new().decode(
5564                        info.out.as_ref().map(|b| &b[..]).unwrap_or_default(),
5565                        Some(info.exit),
5566                    );
5567                    node_info!("    Error: reverted with: {r}");
5568                }
5569                node_info!("");
5570
5571                let mined_tx = MinedTransaction { info, receipt, block_hash, block_number };
5572                storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx);
5573            }
5574
5575            // remove old transactions that exceed the transaction block keeper
5576            if let Some(transaction_block_keeper) = self.transaction_block_keeper
5577                && storage.blocks.len() > transaction_block_keeper
5578            {
5579                let to_clear = block_number
5580                    .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX));
5581                storage.remove_block_transactions_by_number(to_clear)
5582            }
5583
5584            self.time.mark_block_created();
5585
5586            // we intentionally set the difficulty to `0` for newer blocks
5587            evm_env.block_env.difficulty = U256::from(0);
5588
5589            // update env with new values
5590            *self.evm_env.write() = evm_env;
5591
5592            let timestamp = utc_from_secs(header.timestamp);
5593
5594            node_info!("    Block Number: {}", block_number);
5595            node_info!("    Block Hash: {:?}", block_hash);
5596            if timestamp.year() > 9999 {
5597                // rf2822 panics with more than 4 digits
5598                node_info!("    Block Time: {:?}\n", timestamp.to_rfc3339());
5599            } else {
5600                node_info!("    Block Time: {:?}\n", timestamp.to_rfc2822());
5601            }
5602
5603            let outcome = MinedBlockOutcome { block_number, included, invalid, not_yet_valid };
5604
5605            (outcome, header, block_hash)
5606        };
5607        let next_block_base_fee = self.fees.get_next_block_base_fee_from_header(&header);
5608        let next_block_excess_blob_gas = self.networks.next_block_blob_excess_gas(
5609            self.fees.blob_params(),
5610            header.excess_blob_gas.unwrap_or_default(),
5611            header.blob_gas_used.unwrap_or_default(),
5612            header.base_fee_per_gas.unwrap_or_default(),
5613        );
5614
5615        // update next base fee
5616        self.fees.set_base_fee(next_block_base_fee);
5617
5618        self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
5619            next_block_excess_blob_gas,
5620            self.fees.blob_params().update_fraction as u64,
5621        ));
5622
5623        // notify all listeners
5624        self.notify_on_new_block(header.into_inner(), block_hash);
5625
5626        Ok(outcome)
5627    }
5628
5629    /// Reorg the chain to a common height and execute blocks to build new chain.
5630    ///
5631    /// The state of the chain is rewound using `rewind` to the common block, including the db,
5632    /// storage, and env.
5633    ///
5634    /// Finally, `do_mine_block` is called to create the new chain.
5635    pub async fn reorg(
5636        &self,
5637        depth: u64,
5638        tx_pairs: HashMap<u64, Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>>,
5639        common_block: Block,
5640    ) -> Result<(), BlockchainError> {
5641        self.rollback(common_block).await?;
5642        // Create the new reorged chain, filling the blocks with transactions if supplied
5643        for i in 0..depth {
5644            let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new);
5645            let outcome = self.do_mine_block(to_be_mined).await?;
5646            node_info!(
5647                "    Mined reorg block number {}. With {} valid txs and with invalid {} txs",
5648                outcome.block_number,
5649                outcome.included.len(),
5650                outcome.invalid.len()
5651            );
5652        }
5653
5654        Ok(())
5655    }
5656
5657    /// Creates the pending block
5658    ///
5659    /// This will execute all transaction in the order they come but will not mine the block
5660    pub async fn pending_block(
5661        &self,
5662        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
5663    ) -> BlockInfo<N> {
5664        self.with_pending_block(pool_transactions, |_, block| block).await
5665    }
5666
5667    /// Creates the pending block
5668    ///
5669    /// This will execute all transaction in the order they come but will not mine the block
5670    pub async fn with_pending_block<F, T>(
5671        &self,
5672        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
5673        f: F,
5674    ) -> T
5675    where
5676        F: FnOnce(Box<dyn MaybeFullDatabase + '_>, BlockInfo<N>) -> T,
5677    {
5678        let db = self.db.read().await;
5679        let evm_env = self.next_evm_env();
5680
5681        let mut cache_db = AnvilCacheDB::new(&*db);
5682
5683        let parent_hash = self.blockchain.storage.read().best_hash;
5684
5685        let spec_id = *evm_env.spec_id();
5686
5687        let inspector_tx_config = self.inspector_tx_config();
5688        let gas_config = self.pool_tx_gas_config(&evm_env);
5689
5690        let (pool_result, block_result) = self
5691            .execute_with_block_executor(
5692                &mut cache_db,
5693                &evm_env,
5694                parent_hash,
5695                spec_id,
5696                self.hardfork(),
5697                Some(B256::ZERO),
5698                BlockExecutionKind::Complete,
5699                &pool_transactions,
5700                &gas_config,
5701                &inspector_tx_config,
5702                &|pool_tx, account| {
5703                    self.validate_pool_transaction_for(
5704                        &pool_tx.pending_transaction,
5705                        account,
5706                        &evm_env,
5707                    )
5708                },
5709            )
5710            .expect("pending block execution failed");
5711
5712        // Extract inner CacheDB (which implements MaybeFullDatabase)
5713        let cache_db = cache_db.0;
5714
5715        let state_root = cache_db.maybe_state_root().unwrap_or_default();
5716        let block_number = evm_env.block_env.number.saturating_to();
5717        let block_info = self.build_block_info(
5718            &evm_env,
5719            parent_hash,
5720            block_number,
5721            state_root,
5722            block_result,
5723            pool_result.txs,
5724            pool_result.tx_info,
5725            Some(B256::ZERO),
5726        );
5727
5728        f(Box::new(cache_db), block_info)
5729    }
5730
5731    /// Returns the ERC20/TIP20 token balance for an account.
5732    ///
5733    /// Calls `balanceOf(address)` on the token contract. Returns `U256::ZERO` if
5734    /// the call fails (e.g. the token contract doesn't exist).
5735    pub async fn get_fee_token_balance(
5736        &self,
5737        token: Address,
5738        account: Address,
5739    ) -> Result<U256, BlockchainError> {
5740        // balanceOf(address) selector: 0x70a08231
5741        let mut calldata = vec![0x70, 0xa0, 0x82, 0x31];
5742        // ABI-encode the address (left-padded to 32 bytes)
5743        calldata.extend_from_slice(&[0u8; 12]);
5744        calldata.extend_from_slice(account.as_slice());
5745
5746        let request = WithOtherFields::new(TransactionRequest {
5747            from: Some(Address::ZERO),
5748            to: Some(TxKind::Call(token)),
5749            input: calldata.into(),
5750            ..Default::default()
5751        });
5752
5753        let fee_details = FeeDetails::zero();
5754        let (exit, out, _, _) = self.call(request, fee_details, None, Default::default()).await?;
5755
5756        // Check if call succeeded
5757        if exit != InstructionResult::Return && exit != InstructionResult::Stop {
5758            // Return zero balance if call failed (token might not exist)
5759            return Ok(U256::ZERO);
5760        }
5761
5762        // Decode U256 from output
5763        match out {
5764            Some(Output::Call(data)) if data.len() >= 32 => Ok(U256::from_be_slice(&data[..32])),
5765            _ => Ok(U256::ZERO),
5766        }
5767    }
5768
5769    /// Returns the account used to sponsor Tempo fee-payer requests handled by this node.
5770    ///
5771    /// Returns `None` on non-Tempo networks.
5772    pub async fn tempo_fee_payer(&self) -> Option<Address> {
5773        if !self.is_tempo() {
5774            return None;
5775        }
5776        self.node_config.read().await.tempo_fee_payer_address()
5777    }
5778
5779    /// Returns the fee token an account pays with, as stored in the Tempo fee manager.
5780    ///
5781    /// Falls back to PathUSD when the account has no stored preference or the lookup fails.
5782    pub async fn tempo_user_fee_token(&self, account: Address) -> Result<Address, BlockchainError> {
5783        let calldata = IFeeManager::userTokensCall { user: account }.abi_encode();
5784
5785        let request = WithOtherFields::new(TransactionRequest {
5786            from: Some(Address::ZERO),
5787            to: Some(TxKind::Call(TIP_FEE_MANAGER_ADDRESS)),
5788            input: calldata.into(),
5789            ..Default::default()
5790        });
5791
5792        let (exit, out, _, _) =
5793            self.call(request, FeeDetails::zero(), None, Default::default()).await?;
5794
5795        let token = if exit == InstructionResult::Return
5796            && let Some(Output::Call(data)) = out
5797        {
5798            IFeeManager::userTokensCall::abi_decode_returns(&data).unwrap_or(Address::ZERO)
5799        } else {
5800            Address::ZERO
5801        };
5802
5803        if token.is_zero() {
5804            return Ok(foundry_evm::core::tempo::PATH_USD_ADDRESS);
5805        }
5806        Ok(token)
5807    }
5808
5809    /// Executes the [TransactionRequest] without writing to the DB
5810    ///
5811    /// # Errors
5812    ///
5813    /// Returns an error if the `block_number` is greater than the current height
5814    pub async fn call(
5815        &self,
5816        request: WithOtherFields<TransactionRequest>,
5817        fee_details: FeeDetails,
5818        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
5819        overrides: EvmOverrides,
5820    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
5821        self.with_database_at_and_context(block_request, |state, mut block, monad_context| {
5822            let block_number = block.number;
5823            let (exit, out, gas, state) = {
5824                let mut cache_db = CacheDB::new(state);
5825                if let Some(state_overrides) = overrides.state {
5826                    apply_state_overrides(state_overrides.into_iter().collect(), &mut cache_db)?;
5827                }
5828                if let Some(block_overrides) = overrides.block {
5829                    cache_db.apply_block_overrides(*block_overrides, &mut block);
5830                }
5831                self.call_with_state_and_context(
5832                    &cache_db,
5833                    request,
5834                    fee_details,
5835                    block,
5836                    monad_context,
5837                )
5838            }?;
5839            trace!(target: "backend", "call return {:?} out: {:?} gas {} on block {}", exit, out, gas, block_number);
5840            Ok((exit, out, gas, state))
5841        })
5842        .await
5843    }
5844
5845    pub async fn call_with_tracing(
5846        &self,
5847        request: WithOtherFields<TransactionRequest>,
5848        fee_details: FeeDetails,
5849        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
5850        opts: GethDebugTracingCallOptions,
5851    ) -> Result<GethTrace, BlockchainError> {
5852        let GethDebugTracingCallOptions {
5853            tracing_options,
5854            block_overrides,
5855            state_overrides,
5856            tx_index,
5857        } = opts;
5858
5859        if let Some(tx_index) = tx_index {
5860            return self
5861                .call_with_tracing_at_tx_index(
5862                    request,
5863                    fee_details,
5864                    block_request,
5865                    tx_index,
5866                    tracing_options,
5867                    state_overrides,
5868                    block_overrides,
5869                )
5870                .await;
5871        }
5872
5873        self.with_database_at_and_context(block_request, |state, block, monad_context| {
5874            let cache_db = CacheDB::new(state);
5875            self.trace_call_with_state(
5876                request,
5877                fee_details,
5878                block,
5879                cache_db,
5880                tracing_options,
5881                state_overrides,
5882                block_overrides,
5883                monad_context,
5884                None,
5885            )
5886        })
5887        .await
5888    }
5889
5890    #[allow(clippy::too_many_arguments)]
5891    async fn call_with_tracing_at_tx_index(
5892        &self,
5893        request: WithOtherFields<TransactionRequest>,
5894        fee_details: FeeDetails,
5895        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
5896        tx_index: u64,
5897        tracing_options: GethDebugTracingOptions,
5898        state_overrides: Option<StateOverride>,
5899        block_overrides: Option<BlockOverrides>,
5900    ) -> Result<GethTrace, BlockchainError> {
5901        let tx_index = usize::try_from(tx_index).map_err(|_| {
5902            BlockchainError::RpcError(RpcError::invalid_params(format!(
5903                "tx_index {tx_index} does not fit in usize"
5904            )))
5905        })?;
5906        let block_number = match block_request {
5907            Some(BlockRequest::Pending(_)) => {
5908                return Err(BlockchainError::RpcError(RpcError::invalid_params(
5909                    "tx_index is not supported for pending blocks".to_string(),
5910                )));
5911            }
5912            Some(BlockRequest::Number(number)) => number,
5913            None => self.best_number(),
5914        };
5915        let block_id = BlockId::Number(BlockNumber::Number(block_number));
5916
5917        if let Some(block) = self.get_block(block_id) {
5918            return self.mined_trace_call_at_tx_index(
5919                request,
5920                fee_details,
5921                &block,
5922                tx_index,
5923                tracing_options,
5924                state_overrides,
5925                block_overrides,
5926            );
5927        }
5928
5929        if let Some(fork) = self.get_fork()
5930            && fork.predates_fork_inclusive(block_number)
5931        {
5932            let opts = GethDebugTracingCallOptions {
5933                tracing_options,
5934                state_overrides,
5935                block_overrides,
5936                tx_index: Some(tx_index as u64),
5937            };
5938            return Ok(fork.debug_trace_call(request, block_id, opts).await?);
5939        }
5940
5941        Err(BlockchainError::BlockNotFound)
5942    }
5943
5944    #[allow(clippy::too_many_arguments)]
5945    fn mined_trace_call_at_tx_index(
5946        &self,
5947        request: WithOtherFields<TransactionRequest>,
5948        fee_details: FeeDetails,
5949        block: &Block,
5950        tx_index: usize,
5951        tracing_options: GethDebugTracingOptions,
5952        state_overrides: Option<StateOverride>,
5953        block_overrides: Option<BlockOverrides>,
5954    ) -> Result<GethTrace, BlockchainError> {
5955        let transaction_count = block.body.transactions.len();
5956        if tx_index >= transaction_count {
5957            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
5958                "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
5959            ))));
5960        }
5961
5962        let trace = |parent_state: &StateDb| -> Result<GethTrace, BlockchainError> {
5963            let db = Box::new(parent_state) as Box<dyn MaybeFullDatabase + '_>;
5964            let (mut cache_db, evm_env, hardfork) = self.prepare_block_replay_with_db(block, db)?;
5965            self.replay_mined_transaction_prefix(
5966                &mut cache_db,
5967                &evm_env,
5968                hardfork,
5969                block,
5970                tx_index,
5971            )?;
5972            self.trace_call_with_state(
5973                request,
5974                fee_details,
5975                evm_env.block_env.clone(),
5976                cache_db,
5977                tracing_options,
5978                state_overrides,
5979                block_overrides,
5980                self.active_monad_context_before_mined_transaction(block, tx_index)?,
5981                Some((evm_env, hardfork)),
5982            )
5983        };
5984
5985        let read_guard = self.states.upgradable_read();
5986        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
5987            trace(state)
5988        } else {
5989            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
5990            let state = write_guard
5991                .get_on_disk_state(&block.header.parent_hash)
5992                .ok_or(BlockchainError::BlockNotFound)?;
5993            trace(state)
5994        }
5995    }
5996
5997    #[allow(clippy::too_many_arguments)]
5998    fn trace_call_with_state(
5999        &self,
6000        request: WithOtherFields<TransactionRequest>,
6001        fee_details: FeeDetails,
6002        mut block: BlockEnv,
6003        mut cache_db: CacheDB<Box<dyn MaybeFullDatabase + '_>>,
6004        tracing_options: GethDebugTracingOptions,
6005        state_overrides: Option<StateOverride>,
6006        block_overrides: Option<BlockOverrides>,
6007        mut monad_context: Option<MonadReplayContext>,
6008        historical_execution: Option<(EvmEnv, FoundryHardfork)>,
6009    ) -> Result<GethTrace, BlockchainError> {
6010        let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options;
6011        let block_number = block.number;
6012        let base_evm_env = historical_execution.as_ref().map(|(evm_env, _)| evm_env);
6013        let hardfork = historical_execution
6014            .as_ref()
6015            .map(|(_, hardfork)| *hardfork)
6016            .unwrap_or_else(|| self.hardfork());
6017
6018        if let Some(state_overrides) = state_overrides {
6019            apply_state_overrides(state_overrides, &mut cache_db)?;
6020        }
6021        if let Some(block_overrides) = block_overrides {
6022            cache_db.apply_block_overrides(block_overrides, &mut block);
6023        }
6024
6025        if let Some(tracer) = tracer {
6026            return match tracer {
6027                GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
6028                    GethDebugBuiltInTracerType::CallTracer => {
6029                        let call_config = call_config_from_tracer_config(tracer_config)
6030                            .map_err(|e| RpcError::invalid_params(e.to_string()))?;
6031
6032                        let mut inspector = self.build_inspector().with_tracing_config(
6033                            TracingInspectorConfig::from_geth_call_config(&call_config),
6034                        );
6035
6036                        let PreparedCall { evm_env, tx_env, .. } = self
6037                            .prepare_call_env_from_base(
6038                                &cache_db,
6039                                request,
6040                                fee_details,
6041                                block,
6042                                base_evm_env,
6043                            )?;
6044                        let ResultAndState { result, state: _ } = self
6045                            .transact_call_with_inspector_ref_at_hardfork(
6046                                &cache_db,
6047                                &evm_env,
6048                                &mut inspector,
6049                                tx_env,
6050                                monad_context.as_mut().map(next_monad_context),
6051                                hardfork,
6052                            )?;
6053
6054                        inspector.print_logs();
6055                        if self.print_traces {
6056                            inspector.print_traces(self.call_trace_decoder());
6057                        }
6058
6059                        let tracing_inspector = inspector.tracer.expect("tracer disappeared");
6060
6061                        Ok(tracing_inspector
6062                            .into_geth_builder()
6063                            .geth_call_traces(call_config, result.tx_gas_used())
6064                            .into())
6065                    }
6066                    GethDebugBuiltInTracerType::PreStateTracer => {
6067                        let pre_state_config = tracer_config
6068                            .into_pre_state_config()
6069                            .map_err(|e| RpcError::invalid_params(e.to_string()))?;
6070
6071                        let mut inspector = TracingInspector::new(
6072                            TracingInspectorConfig::from_geth_prestate_config(&pre_state_config),
6073                        );
6074
6075                        let PreparedCall { evm_env, tx_env, .. } = self
6076                            .prepare_call_env_from_base(
6077                                &cache_db,
6078                                request,
6079                                fee_details,
6080                                block,
6081                                base_evm_env,
6082                            )?;
6083                        let result = self.transact_call_with_inspector_ref_at_hardfork(
6084                            &cache_db,
6085                            &evm_env,
6086                            &mut inspector,
6087                            tx_env,
6088                            monad_context.as_mut().map(next_monad_context),
6089                            hardfork,
6090                        )?;
6091
6092                        Ok(inspector
6093                            .into_geth_builder()
6094                            .geth_prestate_traces(&result, &pre_state_config, cache_db)?
6095                            .into())
6096                    }
6097                    GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()),
6098                    GethDebugBuiltInTracerType::FourByteTracer
6099                    | GethDebugBuiltInTracerType::MuxTracer
6100                    | GethDebugBuiltInTracerType::FlatCallTracer
6101                    | GethDebugBuiltInTracerType::Erc7562Tracer
6102                    | GethDebugBuiltInTracerType::StateGasTracer => {
6103                        Err(RpcError::invalid_params("unsupported tracer type").into())
6104                    }
6105                },
6106                #[cfg(not(feature = "js-tracer"))]
6107                GethDebugTracerType::JsTracer(_) => {
6108                    Err(RpcError::invalid_params("unsupported tracer type").into())
6109                }
6110                #[cfg(feature = "js-tracer")]
6111                GethDebugTracerType::JsTracer(code) => {
6112                    let config = tracer_config.into_json();
6113                    let mut inspector =
6114                        revm_inspectors::tracing::js::JsInspector::new(code, config)
6115                            .map_err(|err| BlockchainError::Message(err.to_string()))?;
6116
6117                    let PreparedCall { evm_env, tx_env, .. } = self.prepare_call_env_from_base(
6118                        &cache_db,
6119                        request,
6120                        fee_details,
6121                        block.clone(),
6122                        base_evm_env,
6123                    )?;
6124                    let result = self.transact_call_with_inspector_ref_at_hardfork(
6125                        &cache_db,
6126                        &evm_env,
6127                        &mut inspector,
6128                        tx_env.clone(),
6129                        monad_context.as_mut().map(next_monad_context),
6130                        hardfork,
6131                    )?;
6132                    let res = inspector
6133                        .json_result(result, tx_env.base(), &block, &cache_db)
6134                        .map_err(|err| BlockchainError::Message(err.to_string()))?;
6135
6136                    Ok(GethTrace::JS(res))
6137                }
6138            };
6139        }
6140
6141        // defaults to StructLog tracer used since no tracer is specified
6142        let mut inspector = self
6143            .build_inspector()
6144            .with_tracing_config(TracingInspectorConfig::from_geth_config(&config));
6145
6146        let PreparedCall { evm_env, tx_env, .. } =
6147            self.prepare_call_env_from_base(&cache_db, request, fee_details, block, base_evm_env)?;
6148        let ResultAndState { result, state: _ } = self
6149            .transact_call_with_inspector_ref_at_hardfork(
6150                &cache_db,
6151                &evm_env,
6152                &mut inspector,
6153                tx_env,
6154                monad_context.as_mut().map(next_monad_context),
6155                hardfork,
6156            )?;
6157
6158        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
6159
6160        let tracing_inspector = inspector.tracer.expect("tracer disappeared");
6161        let return_value = out.as_ref().map(|o| o.data()).cloned().unwrap_or_default();
6162
6163        trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call");
6164
6165        let res = tracing_inspector
6166            .into_geth_builder()
6167            .geth_traces(gas_used, return_value, config)
6168            .into();
6169
6170        Ok(res)
6171    }
6172
6173    /// Helper function to execute a closure with the database at a specific block
6174    pub async fn with_database_at<F, T>(
6175        &self,
6176        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6177        f: F,
6178    ) -> Result<T, BlockchainError>
6179    where
6180        F: FnOnce(Box<dyn MaybeFullDatabase + '_>, BlockEnv) -> T,
6181    {
6182        let block_number = match block_request {
6183            Some(BlockRequest::Pending(pool_transactions)) => {
6184                let result = self
6185                    .with_pending_block(pool_transactions, |state, block| {
6186                        let block = block.block;
6187                        f(state, block_env_from_header(&block.header))
6188                    })
6189                    .await;
6190                return Ok(result);
6191            }
6192            Some(BlockRequest::Number(bn)) => Some(BlockNumber::Number(bn)),
6193            None => None,
6194        };
6195        let block_number = self.convert_block_number(block_number);
6196        let current_number = self.best_number();
6197
6198        // Reject requests for future blocks that don't exist yet
6199        if block_number > current_number {
6200            return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
6201        }
6202
6203        if block_number < current_number {
6204            if let Some((block_hash, block)) = self
6205                .block_by_number(BlockNumber::Number(block_number))
6206                .await?
6207                .map(|block| (block.header.hash, block))
6208            {
6209                let read_guard = self.states.upgradable_read();
6210                if let Some(state_db) = read_guard.get_state(&block_hash) {
6211                    return Ok(f(Box::new(state_db), block_env_from_header(&block.header)));
6212                }
6213
6214                let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6215                if let Some(state) = write_guard.get_on_disk_state(&block_hash) {
6216                    return Ok(f(Box::new(state), block_env_from_header(&block.header)));
6217                }
6218            }
6219
6220            warn!(target: "backend", "Not historic state found for block={}", block_number);
6221            return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
6222        }
6223
6224        let db = self.db.read().await;
6225        let block = self.evm_env.read().block_env.clone();
6226        Ok(f(Box::new(&**db), block))
6227    }
6228
6229    /// Executes a closure with both state and network context at a specific block.
6230    pub(crate) async fn with_database_at_and_context<F, T>(
6231        &self,
6232        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6233        f: F,
6234    ) -> Result<T, BlockchainError>
6235    where
6236        F: FnOnce(
6237            Box<dyn MaybeFullDatabase + '_>,
6238            BlockEnv,
6239            Option<MonadReplayContext>,
6240        ) -> Result<T, BlockchainError>,
6241    {
6242        let block_number = match block_request {
6243            Some(BlockRequest::Pending(pool_transactions)) => {
6244                return self
6245                    .with_pending_block(pool_transactions, |state, block_info| {
6246                        let context = self.active_monad_context_before_mined_transaction(
6247                            &block_info.block,
6248                            block_info.block.body.transactions.len(),
6249                        )?;
6250                        let block_env = block_env_from_header(&block_info.block.header);
6251                        f(state, block_env, context)
6252                    })
6253                    .await;
6254            }
6255            Some(BlockRequest::Number(number)) => Some(BlockNumber::Number(number)),
6256            None => None,
6257        };
6258        let block_number = self.convert_block_number(block_number);
6259        let current_number = self.best_number();
6260
6261        if block_number > current_number {
6262            return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
6263        }
6264
6265        #[cfg(feature = "monad")]
6266        let context = if self.is_monad() {
6267            Some(self.monad_context_for_child_of_block_number(block_number).await?)
6268        } else {
6269            None
6270        };
6271        #[cfg(not(feature = "monad"))]
6272        let context = None;
6273
6274        if block_number < current_number {
6275            if let Some((block_hash, block)) = self
6276                .block_by_number(BlockNumber::Number(block_number))
6277                .await?
6278                .map(|block| (block.header.hash, block))
6279            {
6280                let read_guard = self.states.upgradable_read();
6281                if let Some(state_db) = read_guard.get_state(&block_hash) {
6282                    return f(Box::new(state_db), block_env_from_header(&block.header), context);
6283                }
6284
6285                let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6286                if let Some(state) = write_guard.get_on_disk_state(&block_hash) {
6287                    return f(Box::new(state), block_env_from_header(&block.header), context);
6288                }
6289            }
6290
6291            warn!(target: "backend", "Not historic state found for block={}", block_number);
6292            return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
6293        }
6294
6295        let db = self.db.read().await;
6296        let block = self.evm_env.read().block_env.clone();
6297        f(Box::new(&**db), block, context)
6298    }
6299
6300    pub async fn storage_at(
6301        &self,
6302        address: Address,
6303        index: U256,
6304        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6305    ) -> Result<B256, BlockchainError> {
6306        self.with_database_at(block_request, |db, _| {
6307            trace!(target: "backend", "get storage for {:?} at {:?}", address, index);
6308            let val = db.storage_ref(address, index)?;
6309            Ok(val.into())
6310        })
6311        .await?
6312    }
6313
6314    pub async fn tempo_nonce(
6315        &self,
6316        caller: Address,
6317        nonce_key: U256,
6318        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6319    ) -> Result<u64, BlockchainError> {
6320        self.with_database_at(block_request, |state, _| tempo_nonce(&state, caller, nonce_key))
6321            .await?
6322    }
6323
6324    /// Returns storage values for multiple accounts and slots in a single call.
6325    pub async fn storage_values(
6326        &self,
6327        requests: HashMap<Address, Vec<B256>>,
6328        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6329    ) -> Result<HashMap<Address, Vec<B256>>, BlockchainError> {
6330        self.with_database_at(block_request, |db, _| {
6331            trace!(target: "backend", "get storage values for {} addresses", requests.len());
6332            let mut result: HashMap<Address, Vec<B256>> = HashMap::default();
6333            for (address, slots) in &requests {
6334                let mut values = Vec::with_capacity(slots.len());
6335                for slot in slots {
6336                    let val = db.storage_ref(*address, (*slot).into())?;
6337                    values.push(val.into());
6338                }
6339                result.insert(*address, values);
6340            }
6341            Ok(result)
6342        })
6343        .await?
6344    }
6345
6346    /// Returns the code of the address
6347    ///
6348    /// If the code is not present and fork mode is enabled then this will try to fetch it from the
6349    /// forked client
6350    pub async fn get_code(
6351        &self,
6352        address: Address,
6353        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6354    ) -> Result<Bytes, BlockchainError> {
6355        self.with_database_at(block_request, |db, _| self.get_code_with_state(&db, address)).await?
6356    }
6357
6358    /// Returns the balance of the address
6359    ///
6360    /// If the requested number predates the fork then this will fetch it from the endpoint
6361    pub async fn get_balance(
6362        &self,
6363        address: Address,
6364        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6365    ) -> Result<U256, BlockchainError> {
6366        self.with_database_at(block_request, |db, _| self.get_balance_with_state(db, address))
6367            .await?
6368    }
6369
6370    pub async fn get_account_at_block(
6371        &self,
6372        address: Address,
6373        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6374    ) -> Result<TrieAccount, BlockchainError> {
6375        self.with_database_at(block_request, |block_db, _| {
6376            let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
6377            let account = db.get(&address).cloned().unwrap_or_default();
6378            let storage_root = storage_root(&account.storage);
6379            let code_hash = account.info.code_hash;
6380            let balance = account.info.balance;
6381            let nonce = account.info.nonce;
6382            Ok(TrieAccount { balance, nonce, code_hash, storage_root })
6383        })
6384        .await?
6385    }
6386
6387    /// Returns the nonce of the address
6388    ///
6389    /// If the requested number predates the fork then this will fetch it from the endpoint
6390    pub async fn get_nonce(
6391        &self,
6392        address: Address,
6393        block_request: BlockRequest<FoundryTxEnvelope>,
6394    ) -> Result<u64, BlockchainError> {
6395        if let BlockRequest::Pending(pool_transactions) = &block_request
6396            && let Some(value) = get_pool_transactions_nonce(pool_transactions, address)
6397        {
6398            return Ok(value);
6399        }
6400        let final_block_request = match block_request {
6401            BlockRequest::Pending(_) => BlockRequest::Number(self.best_number()),
6402            BlockRequest::Number(bn) => BlockRequest::Number(bn),
6403        };
6404
6405        self.with_database_at(Some(final_block_request), |db, _| {
6406            trace!(target: "backend", "get nonce for {:?}", address);
6407            Ok(db.basic_ref(address)?.unwrap_or_default().nonce)
6408        })
6409        .await?
6410    }
6411
6412    fn replay_tx_with_inspector<I, F, T>(
6413        &self,
6414        hash: B256,
6415        mut inspector: I,
6416        f: F,
6417    ) -> Result<T, BlockchainError>
6418    where
6419        for<'a> I: BackendInspector<WrapDatabaseRef<&'a CacheDB<Box<&'a StateDb>>>> + 'a,
6420        for<'a> F:
6421            FnOnce(ResultAndState<HaltReason>, CacheDB<Box<&'a StateDb>>, I, TxEnv, EvmEnv) -> T,
6422    {
6423        let block = {
6424            let storage = self.blockchain.storage.read();
6425            let MinedTransaction { block_hash, .. } = storage
6426                .transactions
6427                .get(&hash)
6428                .cloned()
6429                .ok_or(BlockchainError::TransactionNotFound)?;
6430
6431            storage.blocks.get(&block_hash).cloned().ok_or(BlockchainError::BlockNotFound)?
6432        };
6433
6434        let index = block
6435            .body
6436            .transactions
6437            .iter()
6438            .position(|tx| tx.hash() == hash)
6439            .expect("transaction not found in block");
6440
6441        let trace = |parent_state: &StateDb| -> Result<T, BlockchainError> {
6442            let (mut cache_db, evm_env, hardfork) =
6443                self.prepare_block_replay_with_db(&block, Box::new(parent_state))?;
6444            self.replay_mined_transaction_prefix(&mut cache_db, &evm_env, hardfork, &block, index)?;
6445
6446            let target_tx = block.body.transactions[index].clone();
6447            let target_tx = self.pending_mined_transaction(target_tx)?;
6448            let monad_context = self.active_monad_context_for_mined_block(&block)?;
6449            let transaction_context = monad_execution_context_at(monad_context.as_ref(), index);
6450            let (result, base_tx_env) = self.replay_envelope_with_inspector_ref_and_context(
6451                &cache_db,
6452                &evm_env,
6453                &mut inspector,
6454                &target_tx,
6455                EnvelopeExecution::replay(transaction_context, hardfork),
6456            )?;
6457
6458            Ok(f(result, cache_db, inspector, base_tx_env, evm_env))
6459        };
6460
6461        let read_guard = self.states.upgradable_read();
6462        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
6463            trace(state)
6464        } else {
6465            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6466            let state = write_guard
6467                .get_on_disk_state(&block.header.parent_hash)
6468                .ok_or(BlockchainError::BlockNotFound)?;
6469            trace(state)
6470        }
6471    }
6472
6473    /// Traces the transaction with the js tracer
6474    #[cfg(feature = "js-tracer")]
6475    pub async fn trace_tx_with_js_tracer(
6476        &self,
6477        hash: B256,
6478        code: String,
6479        opts: GethDebugTracingOptions,
6480    ) -> Result<GethTrace, BlockchainError> {
6481        let GethDebugTracingOptions { tracer_config, .. } = opts;
6482        let config = tracer_config.into_json();
6483        let inspector = revm_inspectors::tracing::js::JsInspector::new(code, config)
6484            .map_err(|err| BlockchainError::Message(err.to_string()))?;
6485        let trace = self.replay_tx_with_inspector(
6486            hash,
6487            inspector,
6488            |result, cache_db, mut inspector, tx_env, evm_env| {
6489                inspector
6490                    .json_result(
6491                        result,
6492                        &alloy_evm::IntoTxEnv::into_tx_env(tx_env),
6493                        &evm_env.block_env,
6494                        &cache_db,
6495                    )
6496                    .map_err(|e| BlockchainError::Message(e.to_string()))
6497            },
6498        )??;
6499        Ok(GethTrace::JS(trace))
6500    }
6501
6502    /// Prove an account's existence or nonexistence in the state trie.
6503    ///
6504    /// Returns a merkle proof of the account's trie node, `account_key` == keccak(address)
6505    pub async fn prove_account_at(
6506        &self,
6507        address: Address,
6508        keys: Vec<B256>,
6509        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6510    ) -> Result<AccountProof, BlockchainError> {
6511        let block_number = block_request.as_ref().map(|r| r.block_number());
6512
6513        self.with_database_at(block_request, |block_db, _| {
6514            trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number);
6515            let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
6516            let account = db.get(&address).cloned().unwrap_or_default();
6517
6518            let mut builder = HashBuilder::default()
6519                .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))]));
6520
6521            for (key, account) in trie_accounts(db) {
6522                builder.add_leaf(key, &account);
6523            }
6524
6525            let _ = builder.root();
6526
6527            let proof = builder
6528                .take_proof_nodes()
6529                .into_nodes_sorted()
6530                .into_iter()
6531                .map(|(_, v)| v)
6532                .collect();
6533            let (storage_hash, storage_proofs) = prove_storage(&account.storage, &keys);
6534
6535            let account_proof = AccountProof {
6536                address,
6537                balance: account.info.balance,
6538                nonce: account.info.nonce,
6539                code_hash: account.info.code_hash,
6540                storage_hash,
6541                account_proof: proof,
6542                storage_proof: keys
6543                    .into_iter()
6544                    .zip(storage_proofs)
6545                    .map(|(key, proof)| {
6546                        let storage_key: U256 = key.into();
6547                        let value = account.storage.get(&storage_key).copied().unwrap_or_default();
6548                        StorageProof { key: JsonStorageKey::Hash(key), value, proof }
6549                    })
6550                    .collect(),
6551            };
6552
6553            Ok(account_proof)
6554        })
6555        .await?
6556    }
6557}
6558
6559impl<N: Network> Backend<N>
6560where
6561    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
6562{
6563    /// Returns opcode gas usage for the given transaction.
6564    pub async fn trace_transaction_opcode_gas(
6565        &self,
6566        hash: B256,
6567    ) -> Result<Option<TransactionOpcodeGas>, BlockchainError> {
6568        match self.replay_tx_with_inspector(
6569            hash,
6570            OpcodeGasInspector::default(),
6571            move |_, _, inspector, _, _| TransactionOpcodeGas {
6572                transaction_hash: hash,
6573                opcode_gas: inspector.opcode_gas_iter().collect(),
6574            },
6575        ) {
6576            Ok(trace) => Ok(Some(trace)),
6577            Err(BlockchainError::TransactionNotFound) => {
6578                if let Some(fork) = self.get_fork() {
6579                    return Ok(fork.trace_transaction_opcode_gas(hash).await?);
6580                }
6581
6582                Ok(None)
6583            }
6584            Err(err) => Err(err),
6585        }
6586    }
6587
6588    /// Returns opcode gas usage for all transactions in the given block.
6589    pub async fn trace_block_opcode_gas(
6590        &self,
6591        block_id: BlockId,
6592    ) -> Result<Option<BlockOpcodeGas>, BlockchainError> {
6593        if let Some((block, block_hash)) = self.get_block_with_hash(block_id) {
6594            return self.mined_block_opcode_gas(&block, block_hash).map(Some);
6595        }
6596
6597        if let Some(fork) = self.get_fork() {
6598            let number = self.ensure_block_number(Some(block_id)).await?;
6599            if fork.predates_fork_inclusive(number) {
6600                return Ok(fork.trace_block_opcode_gas(block_id).await?);
6601            }
6602        }
6603
6604        Err(BlockchainError::BlockNotFound)
6605    }
6606
6607    fn mined_block_opcode_gas(
6608        &self,
6609        block: &Block,
6610        block_hash: B256,
6611    ) -> Result<BlockOpcodeGas, BlockchainError> {
6612        // Genesis has no parent state or protocol pre-execution to replay.
6613        if block.header.number() == self.genesis_number() {
6614            return Ok(BlockOpcodeGas {
6615                block_hash,
6616                block_number: block.header.number(),
6617                transactions: Vec::new(),
6618            });
6619        }
6620
6621        let parent_hash = block.header.parent_hash;
6622
6623        let trace = |parent_state: &StateDb| -> Result<Vec<TransactionOpcodeGas>, BlockchainError> {
6624            let (mut cache_db, evm_env, hardfork) =
6625                self.prepare_block_replay(block, parent_state)?;
6626            let mut transactions = Vec::with_capacity(block.body.transactions.len());
6627            let monad_context = self.active_monad_context_for_mined_block(block)?;
6628
6629            for tx_envelope in &block.body.transactions {
6630                let mut inspector = OpcodeGasInspector::default();
6631                let pending_tx = self.pending_mined_transaction(tx_envelope.clone())?;
6632                let transaction_context =
6633                    monad_execution_context_at(monad_context.as_ref(), transactions.len());
6634                let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
6635                    &cache_db,
6636                    &evm_env,
6637                    &mut inspector,
6638                    &pending_tx,
6639                    EnvelopeExecution::replay(transaction_context, hardfork),
6640                )?;
6641
6642                transactions.push(TransactionOpcodeGas {
6643                    transaction_hash: tx_envelope.hash(),
6644                    opcode_gas: inspector.opcode_gas_iter().collect(),
6645                });
6646
6647                cache_db.commit(result.state);
6648            }
6649
6650            Ok(transactions)
6651        };
6652
6653        let read_guard = self.states.upgradable_read();
6654        let transactions = if let Some(state) = read_guard.get_state(&parent_hash) {
6655            trace(state)?
6656        } else {
6657            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6658            let state = write_guard
6659                .get_on_disk_state(&parent_hash)
6660                .ok_or(BlockchainError::BlockNotFound)?;
6661            trace(state)?
6662        };
6663
6664        Ok(BlockOpcodeGas { block_hash, block_number: block.header.number(), transactions })
6665    }
6666
6667    /// Returns a best-effort execution witness for the given block, in the same format as reth's
6668    /// `debug_executionWitness`.
6669    ///
6670    /// Anvil does not track which state a block's execution actually touched, so this returns a
6671    /// witness for the entire parent state instead: the RLP encoding of every node of the parent
6672    /// state trie (including all storage tries), all contract codes, and the preimages of all
6673    /// account addresses and storage slots. This is a strict superset of the minimal witness, so
6674    /// stateless re-execution of the block against it works, but the witness size grows with the
6675    /// total state instead of the state accessed by the block.
6676    ///
6677    /// Limitations:
6678    /// - Not supported while forking: only remotely accessed accounts are known locally, and the
6679    ///   locally computed state roots do not match the remote chain's roots.
6680    /// - The parent block's state must still be available in the state history, i.e. it must not
6681    ///   have been discarded via `--prune-history`.
6682    /// - The genesis block has no witness since it has no parent state.
6683    /// - `headers` contains the ancestor headers within the 256 block `BLOCKHASH` window that are
6684    ///   known locally, which may be fewer than 256.
6685    pub async fn debug_execution_witness(
6686        &self,
6687        block: BlockNumber,
6688    ) -> Result<ExecutionWitness, BlockchainError> {
6689        let number = self.convert_block_number(Some(block));
6690        let best = self.best_number();
6691        if number > best {
6692            return Err(BlockchainError::BlockOutOfRange(best, number));
6693        }
6694        let Some(parent) = number.checked_sub(1) else {
6695            return Err(BlockchainError::Message(
6696                "genesis block has no parent state to build a witness from".to_string(),
6697            ));
6698        };
6699
6700        let mut headers = Vec::new();
6701        for ancestor in (number.saturating_sub(BLOCKHASH_HISTORY)..number).rev() {
6702            let Some(block) = self.get_block(ancestor) else { break };
6703            headers.push(alloy_rlp::encode(&block.header).into());
6704        }
6705
6706        self.with_database_at(Some(BlockRequest::Number(parent)), |state, _| {
6707            let Some(accounts) = state.maybe_full_db() else {
6708                return Err(BlockchainError::Message(
6709                    "debug_executionWitness is not supported while forking".to_string(),
6710                ));
6711            };
6712
6713            let (_, nodes) = state_trie_witness(&accounts);
6714            let mut codes = Vec::new();
6715            let mut seen_codes = B256Set::default();
6716            let mut keys = Vec::new();
6717            for (address, account) in &accounts {
6718                keys.push(Bytes::copy_from_slice(address.as_slice()));
6719                for slot in account.storage.keys() {
6720                    keys.push(Bytes::copy_from_slice(&slot.to_be_bytes::<32>()));
6721                }
6722                if account.info.code_hash != KECCAK_EMPTY
6723                    && seen_codes.insert(account.info.code_hash)
6724                {
6725                    let code = match &account.info.code {
6726                        Some(code) => code.original_bytes(),
6727                        None => state.code_by_hash_ref(account.info.code_hash)?.original_bytes(),
6728                    };
6729                    codes.push(code);
6730                }
6731            }
6732            keys.sort_unstable();
6733            keys.dedup();
6734
6735            Ok(ExecutionWitness { state: nodes, codes, keys, headers })
6736        })
6737        .await?
6738    }
6739
6740    /// Returns account information after replaying a block through the transaction at `tx_index`.
6741    pub async fn debug_account_info_at(
6742        &self,
6743        block_id: BlockId,
6744        tx_index: Index,
6745        address: Address,
6746    ) -> Result<Option<RpcAccountInfo>, BlockchainError> {
6747        if let Some((block, _)) = self.get_block_with_hash(block_id) {
6748            return self.mined_debug_account_info_at(&block, tx_index, address).map(Some);
6749        }
6750
6751        if let Some(fork) = self.get_fork() {
6752            let number = self.ensure_block_number(Some(block_id)).await?;
6753            if fork.predates_fork_inclusive(number) {
6754                // Delegate the resolved block number so tags (`latest`/`pending`/`safe`/
6755                // `finalized`) are resolved against the fork's head instead of drifting with
6756                // the upstream chain. Hashes are forwarded unchanged.
6757                let resolved = match block_id {
6758                    BlockId::Hash(_) => block_id,
6759                    _ => BlockId::number(number),
6760                };
6761                return Ok(fork.debug_account_info_at(resolved, tx_index, address).await?);
6762            }
6763        }
6764
6765        Err(BlockchainError::BlockNotFound)
6766    }
6767
6768    fn mined_debug_account_info_at(
6769        &self,
6770        block: &Block,
6771        tx_index: Index,
6772        address: Address,
6773    ) -> Result<RpcAccountInfo, BlockchainError> {
6774        let tx_index = tx_index.0;
6775        let transaction_count = block.body.transactions.len();
6776        if tx_index >= transaction_count {
6777            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
6778                "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
6779            ))));
6780        }
6781
6782        let trace = |parent_state: &StateDb| -> Result<RpcAccountInfo, BlockchainError> {
6783            let (mut cache_db, evm_env, hardfork) =
6784                self.prepare_block_replay_with_db(block, Box::new(parent_state))?;
6785            self.replay_mined_transaction_prefix(
6786                &mut cache_db,
6787                &evm_env,
6788                hardfork,
6789                block,
6790                tx_index + 1,
6791            )?;
6792            let account = revm::DatabaseRef::basic_ref(&cache_db, address)?.unwrap_or_default();
6793            let code = self.get_code_with_state(&cache_db, address)?;
6794            Ok(RpcAccountInfo { balance: account.balance, nonce: account.nonce, code })
6795        };
6796
6797        let read_guard = self.states.upgradable_read();
6798        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
6799            trace(state)
6800        } else {
6801            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6802            let state = write_guard
6803                .get_on_disk_state(&block.header.parent_hash)
6804                .ok_or(BlockchainError::BlockNotFound)?;
6805            trace(state)
6806        }
6807    }
6808
6809    /// Rollback the chain to a common height.
6810    ///
6811    /// The state of the chain is rewound using `rewind` to the common block, including the db,
6812    /// storage, and env.
6813    pub async fn rollback(&self, common_block: Block) -> Result<(), BlockchainError> {
6814        let hash = common_block.header.hash_slow();
6815
6816        // Get the database at the common block
6817        let common_state = {
6818            let return_state_or_throw_err =
6819                |db: Option<&StateDb>| -> Result<AddressMap<DbAccount>, BlockchainError> {
6820                    let state_db = db.ok_or(BlockchainError::DataUnavailable)?;
6821                    let db_full =
6822                        state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
6823                    Ok(db_full.clone())
6824                };
6825
6826            let read_guard = self.states.upgradable_read();
6827            if let Some(db) = read_guard.get_state(&hash) {
6828                return_state_or_throw_err(Some(db))?
6829            } else {
6830                let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6831                return_state_or_throw_err(write_guard.get_on_disk_state(&hash))?
6832            }
6833        };
6834
6835        {
6836            // Collect the logs of the blocks that are about to be removed from the canonical
6837            // chain, while their transactions and receipts are still in storage
6838            let removed_logs = self.removed_logs_since(common_block.header.number());
6839
6840            // Unwind the storage back to the common ancestor first
6841            let removed_blocks =
6842                self.blockchain.storage.write().unwind_to(common_block.header.number(), hash);
6843
6844            // Clean up in-memory and on-disk states for removed blocks
6845            let removed_hashes: Vec<_> =
6846                removed_blocks.iter().map(|b| b.header.hash_slow()).collect();
6847            self.states.write().remove_block_states(&removed_hashes);
6848
6849            // Notify all log subscriptions and filters about the removed logs, so they receive
6850            // them again marked as removed, before any new chain notifications are emitted
6851            if !removed_logs.is_empty() {
6852                self.notify_on_removed_logs(removed_logs);
6853            }
6854
6855            // Set environment back to common block
6856            let mut env = self.evm_env.write();
6857            env.block_env.number = U256::from(common_block.header.number());
6858            env.block_env.timestamp = U256::from(common_block.header.timestamp());
6859            env.block_env.gas_limit = common_block.header.gas_limit();
6860            env.block_env.difficulty = common_block.header.difficulty();
6861            env.block_env.prevrandao = common_block.header.mix_hash();
6862
6863            self.time.reset(env.block_env.timestamp.saturating_to());
6864            // drop any pending next-block prevrandao override so it does not leak into a block
6865            self.cheats.clear_next_block_prevrandao();
6866        }
6867
6868        {
6869            // Collect block hashes before acquiring db lock to avoid holding blockchain storage
6870            // lock across await. Only collect the last 256 blocks since that's all BLOCKHASH can
6871            // access.
6872            let block_hashes: Vec<_> = {
6873                let storage = self.blockchain.storage.read();
6874                let min_block = common_block.header.number().saturating_sub(256);
6875                storage
6876                    .hashes
6877                    .iter()
6878                    .filter(|(num, _)| **num >= min_block)
6879                    .map(|(&num, &hash)| (num, hash))
6880                    .collect()
6881            };
6882
6883            // Acquire db lock once for the entire restore operation to reduce lock churn.
6884            let mut db = self.db.write().await;
6885            db.clear();
6886
6887            // Insert account info before storage to prevent fork-mode RPC fetches after clear.
6888            for (address, acc) in common_state {
6889                db.insert_account(address, acc.info);
6890                for (key, value) in acc.storage {
6891                    db.set_storage_at(address, key.into(), value.into())?;
6892                }
6893            }
6894
6895            // Restore block hashes from blockchain storage (now unwound, contains only valid
6896            // blocks).
6897            for (block_num, hash) in block_hashes {
6898                db.insert_block_hash(U256::from(block_num), hash);
6899            }
6900        }
6901
6902        Ok(())
6903    }
6904
6905    /// Returns the traces for the given transaction
6906    pub async fn debug_trace_transaction(
6907        &self,
6908        hash: B256,
6909        opts: GethDebugTracingOptions,
6910    ) -> Result<GethTrace, BlockchainError> {
6911        #[cfg(feature = "js-tracer")]
6912        if let Some(tracer_type) = opts.tracer.as_ref()
6913            && tracer_type.is_js()
6914        {
6915            return self
6916                .trace_tx_with_js_tracer(hash, tracer_type.as_str().to_string(), opts.clone())
6917                .await;
6918        }
6919
6920        if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()).await {
6921            return trace;
6922        }
6923
6924        if let Some(fork) = self.get_fork() {
6925            return Ok(fork.debug_trace_transaction(hash, opts).await?);
6926        }
6927
6928        Err(BlockchainError::TransactionNotFound)
6929    }
6930
6931    /// Returns geth-style traces for all transactions in an RLP-encoded block.
6932    pub async fn debug_trace_block(
6933        &self,
6934        rlp_block: Bytes,
6935        opts: GethDebugTracingOptions,
6936    ) -> Result<Vec<TraceResult>, BlockchainError> {
6937        let mut rlp = rlp_block.as_ref();
6938        let block = Block::<FoundryTxEnvelope>::decode(&mut rlp).map_err(|err| {
6939            BlockchainError::RpcError(RpcError::invalid_params(format!(
6940                "failed to decode block: {err}"
6941            )))
6942        })?;
6943        if !rlp.is_empty() {
6944            return Err(BlockchainError::RpcError(RpcError::invalid_params(
6945                "failed to decode block: trailing bytes".to_string(),
6946            )));
6947        }
6948
6949        self.debug_trace_block_by_hash(block.header.hash_slow(), opts).await
6950    }
6951
6952    /// Returns geth-style traces for all transactions in a block by hash.
6953    pub async fn debug_trace_block_by_hash(
6954        &self,
6955        block_hash: B256,
6956        opts: GethDebugTracingOptions,
6957    ) -> Result<Vec<TraceResult>, BlockchainError> {
6958        if let Some(block) = self.blockchain.get_block_by_hash(&block_hash) {
6959            let mut traces = Vec::new();
6960            for tx in &block.body.transactions {
6961                let tx_hash = tx.hash();
6962                match self.debug_trace_transaction(tx_hash, opts.clone()).await {
6963                    Ok(trace) => {
6964                        traces.push(TraceResult::Success { result: trace, tx_hash: Some(tx_hash) });
6965                    }
6966                    Err(error) => {
6967                        traces.push(TraceResult::Error {
6968                            error: error.to_string(),
6969                            tx_hash: Some(tx_hash),
6970                        });
6971                    }
6972                }
6973            }
6974            return Ok(traces);
6975        }
6976
6977        if let Some(fork) = self.get_fork() {
6978            return Ok(fork.debug_trace_block_by_hash(block_hash, opts).await?);
6979        }
6980
6981        Err(BlockchainError::BlockNotFound)
6982    }
6983
6984    /// Returns geth-style traces for all transactions in a block by number.
6985    pub async fn debug_trace_block_by_number(
6986        &self,
6987        block_number: BlockNumber,
6988        opts: GethDebugTracingOptions,
6989    ) -> Result<Vec<TraceResult>, BlockchainError> {
6990        let number = self.convert_block_number(Some(block_number));
6991
6992        if let Some(block) = self.get_block(BlockId::Number(BlockNumber::Number(number))) {
6993            let mut traces = Vec::new();
6994            for tx in &block.body.transactions {
6995                let tx_hash = tx.hash();
6996                match self.debug_trace_transaction(tx_hash, opts.clone()).await {
6997                    Ok(trace) => {
6998                        traces.push(TraceResult::Success { result: trace, tx_hash: Some(tx_hash) });
6999                    }
7000                    Err(error) => {
7001                        traces.push(TraceResult::Error {
7002                            error: error.to_string(),
7003                            tx_hash: Some(tx_hash),
7004                        });
7005                    }
7006                }
7007            }
7008            return Ok(traces);
7009        }
7010
7011        if let Some(fork) = self.get_fork() {
7012            return Ok(fork.debug_trace_block_by_number(number, opts).await?);
7013        }
7014
7015        Err(BlockchainError::BlockNotFound)
7016    }
7017
7018    fn geth_trace(
7019        &self,
7020        tx: &MinedTransaction<N>,
7021        opts: GethDebugTracingOptions,
7022    ) -> Result<GethTrace, BlockchainError> {
7023        let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts;
7024
7025        if let Some(tracer) = tracer {
7026            match tracer {
7027                GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
7028                    GethDebugBuiltInTracerType::FourByteTracer => {
7029                        let inspector = FourByteInspector::default();
7030                        let res = self.replay_tx_with_inspector(
7031                            tx.info.transaction_hash,
7032                            inspector,
7033                            |_, _, inspector, _, _| FourByteFrame::from(inspector).into(),
7034                        )?;
7035                        return Ok(res);
7036                    }
7037                    GethDebugBuiltInTracerType::CallTracer => {
7038                        return match call_config_from_tracer_config(tracer_config) {
7039                            Ok(call_config) => {
7040                                let inspector = TracingInspector::new(
7041                                    TracingInspectorConfig::from_geth_call_config(&call_config),
7042                                );
7043                                let frame = self.replay_tx_with_inspector(
7044                                    tx.info.transaction_hash,
7045                                    inspector,
7046                                    |_, _, inspector, _, _| {
7047                                        inspector
7048                                            .geth_builder()
7049                                            .geth_call_traces(call_config, tx.info.gas_used)
7050                                            .into()
7051                                    },
7052                                )?;
7053                                Ok(frame)
7054                            }
7055                            Err(e) => Err(RpcError::invalid_params(e.to_string()).into()),
7056                        };
7057                    }
7058                    GethDebugBuiltInTracerType::PreStateTracer => {
7059                        return match tracer_config.into_pre_state_config() {
7060                            Ok(pre_state_config) => {
7061                                let inspector = TracingInspector::new(
7062                                    TracingInspectorConfig::from_geth_prestate_config(
7063                                        &pre_state_config,
7064                                    ),
7065                                );
7066                                let frame = self.replay_tx_with_inspector(
7067                                    tx.info.transaction_hash,
7068                                    inspector,
7069                                    |state, db, inspector, _, _| {
7070                                        inspector.geth_builder().geth_prestate_traces(
7071                                            &state,
7072                                            &pre_state_config,
7073                                            db,
7074                                        )
7075                                    },
7076                                )??;
7077                                Ok(frame.into())
7078                            }
7079                            Err(e) => Err(RpcError::invalid_params(e.to_string()).into()),
7080                        };
7081                    }
7082                    GethDebugBuiltInTracerType::NoopTracer
7083                    | GethDebugBuiltInTracerType::MuxTracer
7084                    | GethDebugBuiltInTracerType::Erc7562Tracer
7085                    | GethDebugBuiltInTracerType::FlatCallTracer
7086                    | GethDebugBuiltInTracerType::StateGasTracer => {}
7087                },
7088                GethDebugTracerType::JsTracer(_code) => {}
7089            }
7090
7091            return Ok(NoopFrame::default().into());
7092        }
7093
7094        // default structlog tracer
7095        Ok(GethTraceBuilder::new(tx.info.traces.clone())
7096            .geth_traces(tx.info.gas_used, tx.info.out.clone().unwrap_or_default(), config)
7097            .into())
7098    }
7099
7100    async fn mined_geth_trace_transaction(
7101        &self,
7102        hash: B256,
7103        opts: GethDebugTracingOptions,
7104    ) -> Option<Result<GethTrace, BlockchainError>> {
7105        self.blockchain.storage.read().transactions.get(&hash).map(|tx| self.geth_trace(tx, opts))
7106    }
7107
7108    pub async fn transaction_receipt(
7109        &self,
7110        hash: B256,
7111    ) -> Result<Option<FoundryTxReceipt>, BlockchainError> {
7112        if let Some(receipt) = self.mined_transaction_receipt(hash) {
7113            return Ok(Some(receipt.inner));
7114        }
7115
7116        if let Some(fork) = self.get_fork() {
7117            let receipt = fork.transaction_receipt(hash).await?;
7118            let number = self.convert_block_number(
7119                receipt.clone().and_then(|r| r.block_number()).map(BlockNumber::from),
7120            );
7121
7122            if fork.predates_fork_inclusive(number) {
7123                return Ok(receipt);
7124            }
7125        }
7126
7127        Ok(None)
7128    }
7129
7130    /// Returns all transaction receipts of the block
7131    pub fn mined_block_receipts(&self, id: impl Into<BlockId>) -> Option<Vec<FoundryTxReceipt>> {
7132        let storage = self.blockchain.storage.read();
7133        let hash = match id.into() {
7134            BlockId::Hash(hash) => hash.block_hash,
7135            BlockId::Number(number) => storage.hash(number, self.slots_in_an_epoch)?,
7136        };
7137        let block = storage.blocks.get(&hash)?.clone();
7138
7139        if block.body.transactions.iter().enumerate().any(|(index, transaction)| {
7140            storage.transactions.get(&transaction.hash()).is_none_or(|transaction| {
7141                transaction.block_hash != hash
7142                    || transaction.info.transaction_index as usize != index
7143            })
7144        }) {
7145            drop(storage);
7146            return block
7147                .body
7148                .transactions
7149                .into_iter()
7150                .map(|transaction| {
7151                    self.mined_transaction_receipt(transaction.hash()).map(|receipt| receipt.inner)
7152                })
7153                .collect();
7154        }
7155
7156        let mut receipts = Vec::with_capacity(block.body.transactions.len());
7157        let mut next_log_index = 0;
7158
7159        for block_transaction in &block.body.transactions {
7160            let transaction = storage.transactions.get(&block_transaction.hash())?;
7161            let log_count = transaction.receipt.logs().len();
7162            let receipt = self.build_mined_transaction_receipt(
7163                &transaction.info,
7164                transaction.receipt.clone(),
7165                transaction.block_hash,
7166                &block,
7167                next_log_index,
7168            );
7169            receipts.push(receipt.inner);
7170            next_log_index += log_count;
7171        }
7172
7173        Some(receipts)
7174    }
7175
7176    /// Returns the transaction receipt for the given hash
7177    pub(crate) fn mined_transaction_receipt(
7178        &self,
7179        hash: B256,
7180    ) -> Option<MinedTransactionReceipt<FoundryNetwork>> {
7181        let storage = self.blockchain.storage.read();
7182        let transaction = storage.transactions.get(&hash)?;
7183
7184        let index = transaction.info.transaction_index as usize;
7185        let block = storage.blocks.get(&transaction.block_hash)?;
7186        let mut next_log_index = 0;
7187        for block_transaction in &block.body.transactions[..index] {
7188            next_log_index +=
7189                storage.transactions.get(&block_transaction.hash())?.receipt.logs().len();
7190        }
7191
7192        Some(self.build_mined_transaction_receipt(
7193            &transaction.info,
7194            transaction.receipt.clone(),
7195            transaction.block_hash,
7196            block,
7197            next_log_index,
7198        ))
7199    }
7200
7201    fn build_mined_transaction_receipt(
7202        &self,
7203        info: &TransactionInfo,
7204        tx_receipt: FoundryReceiptEnvelope,
7205        block_hash: B256,
7206        block: &Block,
7207        next_log_index: usize,
7208    ) -> MinedTransactionReceipt<FoundryNetwork> {
7209        let transaction = block.body.transactions[info.transaction_index as usize].clone();
7210
7211        // Cancun specific
7212        let excess_blob_gas = block.header.excess_blob_gas();
7213        let blob_gas_price =
7214            alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas.unwrap_or_default());
7215        let blob_gas_used = transaction.blob_gas_used();
7216
7217        let effective_gas_price = transaction.effective_gas_price(block.header.base_fee_per_gas());
7218
7219        let tx_receipt = tx_receipt.convert_logs_rpc(
7220            BlockNumHash::new(block.header.number(), block_hash),
7221            block.header.timestamp(),
7222            info.transaction_hash,
7223            info.transaction_index,
7224            next_log_index,
7225        );
7226
7227        let receipt = TransactionReceipt {
7228            inner: tx_receipt,
7229            transaction_hash: info.transaction_hash,
7230            transaction_index: Some(info.transaction_index),
7231            block_number: Some(block.header.number()),
7232            gas_used: info.gas_used,
7233            contract_address: info.contract_address,
7234            effective_gas_price,
7235            block_hash: Some(block_hash),
7236            from: info.from,
7237            to: info.to,
7238            blob_gas_price: Some(blob_gas_price),
7239            blob_gas_used,
7240        };
7241
7242        // Include timestamp in receipt to avoid extra block lookups (e.g., in Otterscan API)
7243        let mut inner = FoundryTxReceipt::with_timestamp(receipt, block.header.timestamp());
7244        if self.is_tempo() {
7245            let fee_payer = match &*transaction {
7246                FoundryTxEnvelope::Tempo(tx) => match tx.tx().recover_fee_payer(info.from) {
7247                    Ok(fee_payer) => fee_payer,
7248                    Err(error) => {
7249                        warn!(
7250                            target: "backend",
7251                            %error,
7252                            tx_hash = ?info.transaction_hash,
7253                            "failed to recover Tempo fee payer for mined receipt"
7254                        );
7255                        info.from
7256                    }
7257                },
7258                _ => info.from,
7259            };
7260            inner = inner.with_fee_payer(fee_payer);
7261
7262            // Match Tempo's receipt conversion: the final log of every non-free
7263            // transaction is the fee token transfer to TIPFeeManager.
7264            if inner.effective_gas_price() > 0
7265                && inner.gas_used() > 0
7266                && let Some(fee_token) = inner.0.inner.logs().last().map(|log| log.address())
7267            {
7268                inner = inner.with_fee_token(fee_token);
7269            }
7270        }
7271        MinedTransactionReceipt { inner, out: info.out.clone() }
7272    }
7273
7274    /// Returns the blocks receipts for the given number
7275    pub async fn block_receipts(
7276        &self,
7277        number: BlockId,
7278    ) -> Result<Option<Vec<FoundryTxReceipt>>, BlockchainError> {
7279        if let Some(receipts) = self.mined_block_receipts(number) {
7280            return Ok(Some(receipts));
7281        }
7282
7283        if let Some(fork) = self.get_fork() {
7284            let number = match self.ensure_block_number(Some(number)).await {
7285                Err(_) => return Ok(None),
7286                Ok(n) => n,
7287            };
7288
7289            if fork.predates_fork_inclusive(number) {
7290                let receipts = fork.block_receipts(number).await?;
7291
7292                return Ok(receipts);
7293            }
7294        }
7295
7296        Ok(None)
7297    }
7298}
7299
7300impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> Backend<N> {
7301    /// Get the current state.
7302    pub async fn serialized_state(
7303        &self,
7304        preserve_historical_states: bool,
7305    ) -> Result<SerializableState, BlockchainError> {
7306        let at = self.evm_env.read().block_env.clone();
7307        #[cfg(feature = "monad")]
7308        let mut monad_block_participants = BTreeMap::new();
7309        #[cfg(feature = "monad")]
7310        let mut monad_block_replay_profiles = BTreeMap::new();
7311        let (best_number, blocks, transactions) = {
7312            let storage = self.blockchain.storage.read();
7313            #[cfg(feature = "monad")]
7314            if self.is_monad() {
7315                monad_block_participants = storage
7316                    .monad_block_participants
7317                    .iter()
7318                    .filter(|(hash, _)| storage.blocks.contains_key(*hash))
7319                    .map(|(hash, participants)| (*hash, participants.iter().copied().collect()))
7320                    .collect();
7321                monad_block_replay_profiles = storage
7322                    .monad_block_replay_profiles
7323                    .iter()
7324                    .filter(|(hash, _)| storage.blocks.contains_key(*hash))
7325                    .map(|(hash, profile)| (*hash, *profile))
7326                    .collect();
7327            }
7328            (storage.best_number, storage.serialized_blocks(), storage.serialized_transactions())
7329        };
7330        let historical_states =
7331            preserve_historical_states.then(|| self.states.write().serialized_states());
7332
7333        let state = self
7334            .db
7335            .read()
7336            .await
7337            .dump_state(at, best_number, blocks, transactions, historical_states)?
7338            .ok_or_else(|| {
7339                BlockchainError::RpcError(RpcError::invalid_params(
7340                    "Dumping state not supported with the current configuration",
7341                ))
7342            })?;
7343        #[cfg(feature = "monad")]
7344        let state = {
7345            let mut state = state;
7346            state.monad_block_participants = monad_block_participants;
7347            state.monad_block_replay_profiles = monad_block_replay_profiles;
7348            state
7349        };
7350        Ok(state)
7351    }
7352
7353    /// Write all chain data to serialized bytes buffer
7354    pub async fn dump_state(
7355        &self,
7356        preserve_historical_states: bool,
7357    ) -> Result<Bytes, BlockchainError> {
7358        let state = self.serialized_state(preserve_historical_states).await?;
7359        let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
7360        encoder
7361            .write_all(&serde_json::to_vec(&state).unwrap_or_default())
7362            .map_err(|_| BlockchainError::DataUnavailable)?;
7363        Ok(encoder.finish().unwrap_or_default().into())
7364    }
7365
7366    /// Apply [SerializableState] data to the backend storage.
7367    pub async fn load_state(&self, mut state: SerializableState) -> Result<bool, BlockchainError> {
7368        let _mining_guard = self.mining.lock().await;
7369        let mut block_env = state.block.take();
7370        let mut selected_head = None;
7371        let mut selected_header = None;
7372        let mut checkpoint = None;
7373        let fork_head = self.get_fork().map(|f| (f.block_number(), f.block_hash(), f.timestamp()));
7374        if let Some(block) = &mut block_env {
7375            if self.is_tempo() && self.is_fork() && block.beneficiary.is_zero() {
7376                block.beneficiary = TIP_FEE_MANAGER_ADDRESS;
7377            }
7378            // Set the current best block number.
7379            // Defaults to block number for compatibility with existing state files.
7380            let best_number = state.best_block_number.unwrap_or(block.number.saturating_to());
7381            let (selected_best_number, selected_best_hash) = if let Some((number, hash, _)) =
7382                fork_head
7383            {
7384                trace!(target: "backend", state_block_number=?best_number, fork_block_number=?number);
7385                // If the state.block_number is greater than the fork block number, set best number
7386                // to the state block number.
7387                // Ref: https://github.com/foundry-rs/foundry/issues/9539
7388                if best_number > number {
7389                    (best_number, None)
7390                } else {
7391                    // If loading state file on a fork, set best number to the fork block number.
7392                    // Ref: https://github.com/foundry-rs/foundry/pull/9215#issue-2618681838
7393                    (number, Some(hash))
7394                }
7395            } else {
7396                (best_number, None)
7397            };
7398
7399            let best_hash = if let Some(hash) = selected_best_hash {
7400                selected_header = state
7401                    .blocks
7402                    .iter()
7403                    .rev()
7404                    .find(|block| block.header.hash_slow() == hash)
7405                    .map(|block| block.header.clone());
7406                hash
7407            } else if state.blocks.is_empty() {
7408                let spec_id = self.spec_id();
7409                let is_cancun = spec_id >= SpecId::CANCUN;
7410                let parent_hash = selected_best_number
7411                    .checked_sub(1)
7412                    .and_then(|number| self.blockchain.storage.read().hashes.get(&number).copied())
7413                    .unwrap_or_default();
7414                let header = Header {
7415                    parent_hash,
7416                    beneficiary: block.beneficiary,
7417                    difficulty: block.difficulty,
7418                    number: selected_best_number,
7419                    gas_limit: block.gas_limit,
7420                    timestamp: block.timestamp.saturating_to(),
7421                    mix_hash: block.prevrandao.unwrap_or_default(),
7422                    base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(block.basefee),
7423                    parent_beacon_block_root: is_cancun.then_some(Default::default()),
7424                    blob_gas_used: is_cancun.then_some(0),
7425                    excess_blob_gas: if is_cancun { block.blob_excess_gas() } else { None },
7426                    withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
7427                    requests_hash: (spec_id >= SpecId::PRAGUE).then_some(EMPTY_REQUESTS_HASH),
7428                    ..Default::default()
7429                };
7430                let header = foundry_header(&self.networks, header);
7431                let best_hash = header.hash_slow();
7432                selected_header = Some(header.clone());
7433                checkpoint = Some(create_block(
7434                    header,
7435                    Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
7436                ));
7437                warn!(
7438                    target: "backend",
7439                    block_number = selected_best_number,
7440                    "state dump has no block history; created a synthetic checkpoint block"
7441                );
7442                best_hash
7443            } else if let Some(header) = state
7444                .blocks
7445                .iter()
7446                .rev()
7447                .find(|block| block.header.number() == selected_best_number)
7448                .map(|block| block.header.clone())
7449            {
7450                let best_hash = header.hash_slow();
7451                selected_header = Some(header);
7452                best_hash
7453            } else {
7454                return Err(BlockchainError::RpcError(RpcError::internal_error_with(format!(
7455                    "Best hash not found for best number {selected_best_number}",
7456                ))));
7457            };
7458
7459            selected_head = Some((selected_best_number, best_hash));
7460        }
7461
7462        // Stage the complete chain update first. Besides keeping blocks and transactions atomic for
7463        // concurrent readers, this ensures validation failures cannot leave a partially loaded
7464        // chain behind.
7465        let blocks = std::mem::take(&mut state.blocks);
7466        let transactions = std::mem::take(&mut state.transactions);
7467        let mut storage = self.blockchain.storage.read().clone();
7468        storage.load_blocks(blocks);
7469        storage.load_transactions(transactions);
7470        if let Some(checkpoint) = checkpoint {
7471            storage.insert_block(checkpoint);
7472        }
7473        if let Some((number, hash)) = selected_head {
7474            storage.hashes.insert(number, hash);
7475            storage.best_number = number;
7476            storage.best_hash = hash;
7477        }
7478
7479        #[cfg(feature = "monad")]
7480        if self.is_monad() {
7481            for (hash, profile) in &state.monad_block_replay_profiles {
7482                if storage.blocks.contains_key(hash) {
7483                    storage.monad_block_replay_profiles.insert(*hash, *profile);
7484                }
7485            }
7486            for (hash, participants) in &state.monad_block_participants {
7487                if storage.blocks.contains_key(hash) {
7488                    storage
7489                        .monad_block_participants
7490                        .insert(*hash, participants.iter().copied().collect());
7491                }
7492            }
7493            self.rebuild_monad_block_participant_cache(&mut storage)?;
7494            // Reject state that cannot supply the ancestor metadata required by the next block
7495            // before changing the live chain, EVM environment, or database.
7496            self.monad_context_for_child_of_in_storage(&storage, storage.best_hash)?;
7497        }
7498
7499        // Re-anchor block time to the canonical head selected above so the next blocks continue
7500        // its timeline: the saved one when the loaded head stays canonical, the fork's when the
7501        // state file is at or below the fork block. Resolve the timestamp from staged storage so a
7502        // later validation or database failure cannot modify the live clock.
7503        let canonical_timestamp = match fork_head {
7504            Some((_, fork_hash, fork_timestamp)) if storage.best_hash == fork_hash => {
7505                Some(fork_timestamp)
7506            }
7507            _ => storage.blocks.get(&storage.best_hash).map(|block| block.header.timestamp),
7508        };
7509
7510        if let Some(block) = block_env.as_mut() {
7511            // Keep NUMBER aligned with the canonical local head chosen above. Arbitrum state dumps
7512            // can intentionally keep BlockEnv.number distinct from the best L2 block number.
7513            if !is_arbitrum(self.chain_id().to())
7514                && let Some((number, _)) = selected_head
7515            {
7516                block.number = U256::from(number);
7517            }
7518        }
7519
7520        let next_fees = selected_header.as_ref().map(|header| {
7521            let parent_fees = self.fees.get_parent_header_fees(header);
7522            let next_block_excess_blob_gas = self.networks.next_block_blob_excess_gas(
7523                self.fees.blob_params(),
7524                header.excess_blob_gas().unwrap_or_default(),
7525                header.blob_gas_used().unwrap_or_default(),
7526                header.base_fee_per_gas().unwrap_or_default(),
7527            );
7528            let blob_excess_gas_and_price = BlobExcessGasAndPrice::new(
7529                next_block_excess_blob_gas,
7530                get_blob_base_fee_update_fraction(
7531                    self.evm_env.read().cfg_env.chain_id,
7532                    header.timestamp,
7533                ),
7534            );
7535            (parent_fees, blob_excess_gas_and_price)
7536        });
7537
7538        let historical_states = state.historical_states.take();
7539        if !self.db.write().await.load_state(state)? {
7540            return Err(RpcError::invalid_params(
7541                "Loading state not supported with the current configuration",
7542            )
7543            .into());
7544        }
7545
7546        // Backfill the EVM-level block hash cache from the freshly loaded blocks so that the
7547        // BLOCKHASH opcode stays consistent after loading state. Reuses the hashes already
7548        // computed by `load_blocks` above. Only collect the last 256 blocks since that's all
7549        // BLOCKHASH can access.
7550        let block_hashes = {
7551            let min_block = storage.best_number.saturating_sub(256);
7552            storage
7553                .hashes
7554                .iter()
7555                .filter(|(num, _)| (min_block..=storage.best_number).contains(*num))
7556                .map(|(&num, &hash)| (U256::from(num), hash))
7557                .collect()
7558        };
7559
7560        *self.blockchain.storage.write() = storage;
7561        if let Some(timestamp) = canonical_timestamp {
7562            self.time.reset(timestamp);
7563        }
7564        if let Some(block_env) = block_env {
7565            self.evm_env.write().block_env = block_env;
7566        }
7567        if let Some((parent_fees, blob_excess_gas_and_price)) = next_fees {
7568            #[cfg(feature = "optimism")]
7569            if self.is_optimism() {
7570                self.fees.set_optimism_base_fee_rules(&parent_fees.extra_data);
7571            }
7572            self.fees.set_base_fee(parent_fees.base_fee);
7573            self.fees.set_blob_excess_gas_and_price(blob_excess_gas_and_price);
7574        }
7575
7576        self.db.write().await.set_block_hashes(block_hashes);
7577
7578        if let Some(historical_states) = historical_states {
7579            self.states.write().load_states(historical_states);
7580        }
7581
7582        Ok(true)
7583    }
7584
7585    /// Deserialize and add all chain data to the backend storage
7586    pub async fn load_state_bytes(&self, buf: Bytes) -> Result<bool, BlockchainError> {
7587        let orig_buf = &buf.0[..];
7588        let mut decoder = GzDecoder::new(orig_buf);
7589        let mut decoded_data = Vec::new();
7590
7591        let state: SerializableState = serde_json::from_slice(if decoder.header().is_some() {
7592            decoder
7593                .read_to_end(decoded_data.as_mut())
7594                .map_err(|_| BlockchainError::FailedToDecodeStateDump)?;
7595            &decoded_data
7596        } else {
7597            &buf.0
7598        })
7599        .map_err(|_| BlockchainError::FailedToDecodeStateDump)?;
7600
7601        self.load_state(state).await
7602    }
7603}
7604
7605impl Backend<FoundryNetwork> {
7606    /// Simulates a bundle of signed transactions and returns Flashbots-compatible results.
7607    pub async fn call_bundle(
7608        &self,
7609        bundle: EthCallBundle,
7610        transactions: Vec<PendingTransaction<FoundryTxEnvelope>>,
7611        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7612    ) -> Result<EthCallBundleResponse, BlockchainError> {
7613        let EthCallBundle {
7614            block_number,
7615            coinbase,
7616            timestamp,
7617            gas_limit,
7618            difficulty,
7619            base_fee,
7620            ..
7621        } = bundle;
7622
7623        let blob_gas_used = transactions
7624            .iter()
7625            .filter_map(|transaction| transaction.transaction.blob_gas_used())
7626            .sum::<u64>();
7627        let max_blob_gas = self.blob_params().max_blob_gas_per_block();
7628        if blob_gas_used > max_blob_gas {
7629            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
7630                "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
7631            ))));
7632        }
7633
7634        self.with_database_at_and_context(
7635            block_request,
7636            |state, mut block_env, mut monad_context| {
7637                let state_block_number = block_env.number.to::<u64>();
7638                block_env.number = U256::from(block_number);
7639                block_env.timestamp = timestamp
7640                    .map(U256::from)
7641                    .unwrap_or_else(|| block_env.timestamp.saturating_add(U256::from(12)));
7642                if let Some(coinbase) = coinbase {
7643                    block_env.beneficiary = coinbase;
7644                }
7645                if let Some(gas_limit) = gas_limit {
7646                    block_env.gas_limit = gas_limit;
7647                }
7648                if let Some(difficulty) = difficulty {
7649                    block_env.difficulty = difficulty;
7650                }
7651                if let Some(base_fee) = base_fee {
7652                    block_env.basefee = base_fee.try_into().unwrap_or(u64::MAX);
7653                }
7654
7655                let mut evm_env = self.evm_env.read().clone();
7656                evm_env.block_env = block_env;
7657                let coinbase = evm_env.block_env.beneficiary;
7658                let base_fee = evm_env.block_env.basefee;
7659                let mut cache_db = CacheDB::new(state);
7660                let initial_coinbase = revm::DatabaseRef::basic_ref(&cache_db, coinbase)?
7661                    .map(|account| account.balance)
7662                    .unwrap_or_default();
7663                let mut coinbase_balance_before_tx = initial_coinbase;
7664                let mut coinbase_balance_after_tx = initial_coinbase;
7665                let mut total_gas_used = 0u64;
7666                let mut total_gas_fees = U256::ZERO;
7667                let mut bundle_hash = alloy_primitives::Keccak256::new();
7668                let mut results = Vec::with_capacity(transactions.len());
7669
7670                for transaction in transactions {
7671                    let sender = *transaction.sender();
7672                    let tx = transaction.transaction.as_ref();
7673                    let tx_hash = tx.hash();
7674                    bundle_hash.update(tx_hash);
7675
7676                    let mut inspector = self.build_inspector();
7677                    let (ResultAndState { result, state }, _) = self
7678                        .transact_envelope_with_inspector_ref_and_context(
7679                            &cache_db,
7680                            &evm_env,
7681                            &mut inspector,
7682                            &transaction,
7683                            monad_context.as_mut().map(next_monad_context),
7684                        )?;
7685
7686                    let gas_price = tx.effective_tip_per_gas(base_fee).unwrap_or_default();
7687                    let gas_used = result.tx_gas_used();
7688                    let gas_fees = U256::from(gas_used) * U256::from(gas_price);
7689                    total_gas_used += gas_used;
7690                    total_gas_fees += gas_fees;
7691
7692                    coinbase_balance_after_tx = state
7693                        .get(&coinbase)
7694                        .map(|account| account.info.balance)
7695                        .unwrap_or(coinbase_balance_before_tx);
7696                    let coinbase_diff =
7697                        coinbase_balance_after_tx.saturating_sub(coinbase_balance_before_tx);
7698                    let eth_sent_to_coinbase = coinbase_diff.saturating_sub(gas_fees);
7699                    coinbase_balance_before_tx = coinbase_balance_after_tx;
7700
7701                    let output = result.output().cloned().unwrap_or_default();
7702                    let (value, revert) = if result.is_success() {
7703                        (Some(output), None)
7704                    } else {
7705                        (None, Some(output))
7706                    };
7707
7708                    results.push(EthCallBundleTransactionResult {
7709                        coinbase_diff,
7710                        eth_sent_to_coinbase,
7711                        from_address: sender,
7712                        gas_fees,
7713                        gas_price: U256::from(gas_price),
7714                        gas_used,
7715                        to_address: tx.to(),
7716                        tx_hash,
7717                        value,
7718                        revert,
7719                    });
7720                    cache_db.commit(state);
7721                }
7722
7723                let coinbase_diff = coinbase_balance_after_tx.saturating_sub(initial_coinbase);
7724                let eth_sent_to_coinbase = coinbase_diff.saturating_sub(total_gas_fees);
7725                let bundle_gas_price =
7726                    coinbase_diff.checked_div(U256::from(total_gas_used)).unwrap_or_default();
7727
7728                Ok(EthCallBundleResponse {
7729                    bundle_hash: bundle_hash.finalize(),
7730                    bundle_gas_price,
7731                    coinbase_diff,
7732                    eth_sent_to_coinbase,
7733                    gas_fees: total_gas_fees,
7734                    results,
7735                    state_block_number,
7736                    total_gas_used,
7737                })
7738            },
7739        )
7740        .await
7741    }
7742
7743    /// Executes bundles of call requests and returns each call output.
7744    pub async fn call_many(
7745        &self,
7746        bundles: Vec<Bundle<WithOtherFields<TransactionRequest>>>,
7747        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7748        state_override: Option<alloy_rpc_types::state::StateOverride>,
7749    ) -> Result<Vec<Vec<EthCallResponse>>, BlockchainError> {
7750        if bundles.is_empty() {
7751            return Err(BlockchainError::RpcError(RpcError::invalid_params(
7752                "bundles are empty.".to_string(),
7753            )));
7754        }
7755
7756        self.with_database_at_and_context(
7757            block_request,
7758            |state, mut block_env, mut monad_context| {
7759                let mut cache_db = CacheDB::new(state);
7760                if let Some(state_override) = state_override {
7761                    apply_state_overrides(state_override, &mut cache_db)?;
7762                }
7763
7764                let mut results = Vec::with_capacity(bundles.len());
7765                for bundle in bundles {
7766                    let Bundle { transactions, block_override } = bundle;
7767                    if let Some(block_override) = block_override {
7768                        cache_db.apply_block_overrides(block_override, &mut block_env);
7769                    }
7770
7771                    let mut bundle_results = Vec::with_capacity(transactions.len());
7772                    for request in transactions {
7773                        let fee_details = FeeDetails::new(
7774                            request.gas_price,
7775                            request.max_fee_per_gas,
7776                            request.max_priority_fee_per_gas,
7777                            request.max_fee_per_blob_gas,
7778                        )?
7779                        .or_zero_fees();
7780                        let PreparedCall { evm_env, mut tx_env, simulated_tempo_tx } = self
7781                            .prepare_call_env(&cache_db, request, fee_details, block_env.clone())?;
7782                        apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
7783
7784                        let mut inspector = self.build_inspector();
7785                        let ResultAndState { result, state } = self
7786                            .transact_call_with_inspector_ref(
7787                                &cache_db,
7788                                &evm_env,
7789                                &mut inspector,
7790                                tx_env,
7791                                monad_context.as_mut().map(next_monad_context),
7792                            )?;
7793
7794                        let output = result.output().cloned().unwrap_or_default();
7795                        let response = if result.is_success() {
7796                            EthCallResponse { value: Some(output), error: None }
7797                        } else {
7798                            let error = RevertDecoder::new()
7799                                .maybe_decode(&output, None)
7800                                .unwrap_or_else(|| "execution failed".to_string());
7801                            EthCallResponse { value: None, error: Some(error) }
7802                        };
7803
7804                        cache_db.commit(state);
7805                        bundle_results.push(response);
7806                    }
7807
7808                    results.push(bundle_results);
7809                    block_env.number = block_env.number.saturating_add(U256::ONE);
7810                    block_env.timestamp = block_env.timestamp.saturating_add(U256::ONE);
7811                    #[cfg(feature = "monad")]
7812                    self::monad::advance_block_context(&mut monad_context);
7813                }
7814
7815                Ok(results)
7816            },
7817        )
7818        .await
7819    }
7820
7821    /// Simulates the payload by executing the calls in request.
7822    pub async fn simulate(
7823        &self,
7824        request: SimulatePayload,
7825        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7826        block_interval: u64,
7827    ) -> Result<Vec<SimulatedBlock<AnyRpcBlock>>, BlockchainError> {
7828        self.simulate_raw(
7829            preserve_simulation_request_fields(request),
7830            block_request,
7831            block_interval,
7832        )
7833        .await
7834    }
7835
7836    /// Simulates a payload while preserving transaction extension fields.
7837    pub(crate) async fn simulate_raw(
7838        &self,
7839        request: SimulatePayload<WithOtherFields<TransactionRequest>>,
7840        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7841        block_interval: u64,
7842    ) -> Result<Vec<SimulatedBlock<AnyRpcBlock>>, BlockchainError> {
7843        let simulate_at = |state: Box<dyn MaybeFullDatabase + '_>,
7844                           base_block_env: BlockEnv,
7845                           base_number,
7846                           base_timestamp,
7847                           base_hash,
7848                           base_fee,
7849                           mut monad_context: Option<MonadReplayContext>,
7850                           base_base_fee_per_gas,
7851                           base_excess_blob_gas,
7852                           base_blob_gas_used,
7853                           base_fee_extra_data: Bytes,
7854                           optimism_jovian: bool| {
7855            let SimulatePayload {
7856                block_state_calls,
7857                trace_transfers,
7858                validation,
7859                return_full_transactions,
7860            } = request;
7861            let block_state_calls = sanitize_simulation_blocks(
7862                block_state_calls,
7863                base_number,
7864                base_timestamp,
7865                block_interval,
7866            )?;
7867            let mut cache_db = CacheDB::new(state);
7868            cache_db.cache.block_hashes.insert(U256::from(base_number), base_hash);
7869            let mut block_res = Vec::with_capacity(block_state_calls.len());
7870            let mut parent_hash = base_hash;
7871            let mut next_base_fee = base_fee;
7872            let mut inherited_block_env = base_block_env;
7873            let (is_merge, is_cancun, is_amsterdam, tx_gas_limit_cap) = {
7874                let cfg_env = &self.evm_env.read().cfg_env;
7875                (
7876                    cfg_env.spec >= SpecId::MERGE,
7877                    cfg_env.spec >= SpecId::CANCUN,
7878                    cfg_env.spec >= SpecId::AMSTERDAM,
7879                    cfg_env.tx_gas_limit_cap(),
7880                )
7881            };
7882            let mut parent_base_fee_per_gas = base_base_fee_per_gas;
7883            let mut parent_excess_blob_gas = base_excess_blob_gas;
7884            let mut parent_blob_gas_used = base_blob_gas_used;
7885            let mut rpc_gas_budget = SIMULATE_GAS_CAP;
7886
7887            // execute the blocks
7888            for block in block_state_calls {
7889                let SimBlock { block_overrides, state_overrides, calls } = block;
7890                let mut block_env = inherited_block_env.clone();
7891                let overridden_beacon_root =
7892                    block_overrides.as_ref().and_then(|overrides| overrides.beacon_root);
7893                let block_timestamp = block_overrides
7894                    .as_ref()
7895                    .and_then(|overrides| overrides.time)
7896                    .unwrap_or_else(|| block_env.timestamp.saturating_to());
7897                let blob_params = self.simulation_blob_params_at_timestamp(block_timestamp);
7898                if is_cancun {
7899                    let excess_blob_gas = self.networks.next_block_blob_excess_gas(
7900                        blob_params,
7901                        parent_excess_blob_gas,
7902                        parent_blob_gas_used,
7903                        parent_base_fee_per_gas,
7904                    );
7905                    block_env.set_blob_excess_gas_and_price(
7906                        excess_blob_gas,
7907                        blob_params.update_fraction as u64,
7908                    );
7909                } else {
7910                    block_env.blob_excess_gas_and_price = None;
7911                }
7912                block_env.basefee = if validation { next_base_fee } else { 0 };
7913                block_env.prevrandao = Some(B256::ZERO);
7914                if is_merge && !is_arbitrum(self.protocol_chain_id()) {
7915                    block_env.difficulty = U256::ZERO;
7916                }
7917                let mut call_res = Vec::with_capacity(calls.len());
7918                let mut log_index = 0;
7919                let mut cumulative_gas_used = 0;
7920                let mut block_regular_gas_used = 0;
7921                let mut block_state_gas_used = 0;
7922                let mut block_blob_gas_used = 0u64;
7923                let mut transactions = Vec::with_capacity(calls.len());
7924                let mut transaction_envelopes = Vec::with_capacity(calls.len());
7925                let mut receipts = Vec::with_capacity(calls.len());
7926                let overridden_block_hashes = block_overrides
7927                    .as_ref()
7928                    .and_then(|overrides| overrides.block_hash.as_ref())
7929                    .map(|overrides| {
7930                        overrides
7931                            .keys()
7932                            .map(|number| {
7933                                let number = U256::from(*number);
7934                                (number, cache_db.cache.block_hashes.get(&number).copied())
7935                            })
7936                            .collect::<Vec<_>>()
7937                    })
7938                    .unwrap_or_default();
7939
7940                if let Some(block_overrides) = block_overrides {
7941                    cache_db.apply_block_overrides(block_overrides, &mut block_env);
7942                }
7943                let simulation_evm_env =
7944                    EvmEnv::new(self.evm_env.read().cfg_env.clone(), block_env.clone());
7945                let spec_id = *simulation_evm_env.spec_id();
7946                let ethereum_transitions = self
7947                    .ethereum_block_transitions(self.hardfork(), None, BlockExecutionKind::Complete)
7948                    .map(|mut transitions| {
7949                        transitions.parent_beacon_block_root = (transitions.hardfork
7950                            >= EthereumHardfork::Cancun)
7951                            .then_some(overridden_beacon_root.unwrap_or_default());
7952                        transitions
7953                    });
7954                let precompile_overrides = self.simulation_precompile_overrides(
7955                    state_overrides.as_ref(),
7956                    &simulation_evm_env,
7957                )?;
7958
7959                // Apply state overrides after validating precompile moves against this block's
7960                // active precompile set.
7961                if let Some(mut state_overrides) = state_overrides {
7962                    state_overrides.retain(|_, account| {
7963                        account.balance.is_some()
7964                            || account.nonce.is_some()
7965                            || account.code.is_some()
7966                            || account.state.is_some()
7967                            || account
7968                                .state_diff
7969                                .as_ref()
7970                                .is_some_and(|state_diff| !state_diff.is_empty())
7971                    });
7972                    let previously_deleted = previously_deleted_accounts(
7973                        &cache_db.cache.accounts,
7974                        state_overrides.keys().copied(),
7975                    );
7976                    apply_state_overrides(state_overrides, &mut cache_db)?;
7977                    preserve_deleted_storage(&mut cache_db.cache.accounts, previously_deleted);
7978                }
7979
7980                if let Some(transitions) = ethereum_transitions {
7981                    self.apply_simulation_pre_execution_changes(
7982                        &mut cache_db,
7983                        &simulation_evm_env,
7984                        parent_hash,
7985                        transitions,
7986                    )?;
7987                }
7988
7989                // execute all calls in that block
7990                for (req_idx, mut request) in calls.into_iter().enumerate() {
7991                    let classified_request = self.parse_transaction_request(request.clone())?;
7992                    let is_ethereum_request = classified_request.is_ethereum();
7993                    let mut parsed_request = self.is_tempo().then_some(classified_request);
7994                    if is_ethereum_request {
7995                        request.populate_blob_hashes();
7996                        let preferred_type = request.preferred_type();
7997                        request.transaction_type = Some(preferred_type as u8);
7998                        request.trim_conflicting_keys();
7999                        request.populate_blob_hashes();
8000                    }
8001                    let request_blob_gas_used = if is_ethereum_request && !optimism_jovian {
8002                        u64::try_from(request.blob_versioned_hashes.as_ref().map_or(0, Vec::len))
8003                            .unwrap_or(u64::MAX)
8004                            .saturating_mul(DATA_GAS_PER_BLOB)
8005                    } else {
8006                        0
8007                    };
8008                    let max_blob_gas = block_blob_gas_limit(
8009                        optimism_jovian,
8010                        block_env.gas_limit,
8011                        blob_params.max_blob_gas_per_block(),
8012                    );
8013                    if !optimism_jovian
8014                        && block_blob_gas_used.saturating_add(request_blob_gas_used) > max_blob_gas
8015                    {
8016                        return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
8017                            "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
8018                        ))));
8019                    }
8020                    block_blob_gas_used = block_blob_gas_used.saturating_add(request_blob_gas_used);
8021
8022                    let inner = request.as_ref();
8023                    let remaining_regular_gas =
8024                        block_env.gas_limit.saturating_sub(block_regular_gas_used);
8025                    let remaining_state_gas =
8026                        block_env.gas_limit.saturating_sub(block_state_gas_used);
8027                    let remaining_gas = if is_amsterdam {
8028                        remaining_regular_gas.min(remaining_state_gas)
8029                    } else {
8030                        block_env.gas_limit.saturating_sub(cumulative_gas_used)
8031                    };
8032                    let requested_gas = inner.gas.unwrap_or(remaining_gas);
8033                    let exceeds_gas_limit = if is_amsterdam {
8034                        let requested_regular_gas = requested_gas.min(tx_gas_limit_cap);
8035                        requested_regular_gas > remaining_regular_gas
8036                            || requested_gas > remaining_state_gas
8037                    } else {
8038                        requested_gas > remaining_gas
8039                    };
8040                    if exceeds_gas_limit {
8041                        return Err(BlockchainError::RpcError(RpcError {
8042                            code: ErrorCode::ServerError(-38015),
8043                            message: format!(
8044                                "block gas limit exceeded: remaining {remaining_gas}, requested {requested_gas}"
8045                            )
8046                            .into(),
8047                            data: None,
8048                        }));
8049                    }
8050                    let execution_gas_limit = requested_gas.min(rpc_gas_budget);
8051                    let preserve_signed_gas = matches!(
8052                        &parsed_request,
8053                        Some(FoundryTransactionRequest::Tempo(request))
8054                            if request.fee_payer_signature.is_some()
8055                    );
8056                    request.gas = Some(execution_gas_limit);
8057                    if !preserve_signed_gas && let Some(parsed_request) = &mut parsed_request {
8058                        parsed_request.as_mut().gas = Some(execution_gas_limit);
8059                    }
8060
8061                    let caller = request.from.unwrap_or_default();
8062                    let caller_nonce = RevmDatabase::basic(&mut cache_db, caller)?
8063                        .map(|account| account.nonce)
8064                        .unwrap_or_default();
8065                    let tempo_nonce_key =
8066                        parsed_request.as_ref().and_then(|request| match request {
8067                            FoundryTransactionRequest::Tempo(request) => request.nonce_key,
8068                            _ => None,
8069                        });
8070                    if request.nonce.is_none() {
8071                        let nonce = tempo_nonce_key.map_or(Ok(caller_nonce), |nonce_key| {
8072                            tempo_nonce(&cache_db, caller, nonce_key)
8073                        })?;
8074                        request.nonce = Some(nonce);
8075                        if let Some(parsed_request) = &mut parsed_request {
8076                            parsed_request.as_mut().nonce = Some(nonce);
8077                        }
8078                    }
8079
8080                    if is_ethereum_request {
8081                        let mut canonical_request =
8082                            FoundryTransactionRequest::Ethereum(request.inner.clone());
8083                        canonical_request.prep_for_submission();
8084                        request.inner = canonical_request.as_ref().clone();
8085                        if let Some(parsed_request) = &mut parsed_request {
8086                            *parsed_request = canonical_request;
8087                        }
8088                    }
8089
8090                    let fee_details = FeeDetails::new(
8091                        request.gas_price,
8092                        request.max_fee_per_gas,
8093                        request.max_priority_fee_per_gas,
8094                        request.max_fee_per_blob_gas,
8095                    )?
8096                    .or_zero_fees();
8097
8098                    let PreparedCall { mut evm_env, mut tx_env, simulated_tempo_tx } =
8099                        if let Some(parsed_request) = parsed_request {
8100                            self.prepare_typed_call_env(
8101                                &cache_db,
8102                                parsed_request,
8103                                fee_details,
8104                                block_env.clone(),
8105                            )?
8106                        } else {
8107                            self.prepare_call_env(
8108                                &cache_db,
8109                                request.clone(),
8110                                fee_details,
8111                                block_env.clone(),
8112                            )?
8113                        };
8114                    tx_env.base_mut().gas_limit = execution_gas_limit;
8115                    apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
8116                    if !validation
8117                        && tempo_nonce_key.is_none_or(|key| key.is_zero())
8118                        && request.nonce == Some(u64::MAX)
8119                    {
8120                        tx_env.base_mut().nonce = 0;
8121                    }
8122                    let uses_protocol_call_nonce = tx_env.uses_protocol_call_nonce();
8123                    let simulated_envelope = simulated_tempo_tx.map(FoundryTxEnvelope::Tempo);
8124
8125                    if is_amsterdam {
8126                        // Ensure simulated Amsterdam calls use EIP-8037's split gas schedule.
8127                        let spec = evm_env.cfg_env.spec;
8128                        evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec);
8129                    }
8130
8131                    // Always disable EIP-3607
8132                    evm_env.cfg_env.disable_eip3607 = true;
8133
8134                    if validation {
8135                        evm_env.cfg_env.disable_nonce_check = false;
8136                        evm_env.cfg_env.disable_base_fee = false;
8137                        evm_env.cfg_env.disable_block_gas_limit = false;
8138                    }
8139
8140                    let mut inspector = self.build_inspector();
8141
8142                    // transact
8143                    inspector = inspector.with_simulation_logs(trace_transfers);
8144                    trace!(target: "backend", env=?evm_env, spec=?evm_env.spec_id(),"simulate evm env");
8145                    let execution_result = match tx_env {
8146                        CallTxEnv::Eth(tx_env)
8147                            if !validation
8148                                && tx_env.tx_type == 3
8149                                && tx_env.max_fee_per_blob_gas == 0 =>
8150                        {
8151                            self.transact_eth_simulation_with_inspector_ref(
8152                                &cache_db,
8153                                &evm_env,
8154                                &mut inspector,
8155                                tx_env,
8156                                &precompile_overrides,
8157                            )
8158                        }
8159                        tx_env if precompile_overrides.moves.is_empty() => self
8160                            .transact_call_with_inspector_ref(
8161                                &cache_db,
8162                                &evm_env,
8163                                &mut inspector,
8164                                tx_env,
8165                                monad_context.as_mut().map(next_monad_context),
8166                            ),
8167                        tx_env => self.transact_eth_with_inspector_ref_and_precompile_overrides(
8168                            &cache_db,
8169                            &evm_env,
8170                            &mut inspector,
8171                            tx_env.into_base(),
8172                            &precompile_overrides,
8173                        ),
8174                    };
8175                    let ResultAndState { result, mut state } = match execution_result {
8176                        Err(BlockchainError::InvalidTransaction(error)) => {
8177                            return Err(simulate_transaction_error(error));
8178                        }
8179                        result => result?,
8180                    };
8181                    if !validation
8182                        && caller_nonce == u64::MAX
8183                        && uses_protocol_call_nonce
8184                        && let Some(account) = state.get_mut(&caller)
8185                    {
8186                        account.info.nonce = 0;
8187                    }
8188                    trace!(target: "backend", ?result, ?request, "simulate call");
8189
8190                    let canonical_logs = result.clone().into_logs();
8191                    let (response_logs, attempted_log_count) = inspector
8192                        .take_simulation_logs(&canonical_logs, result.is_success())
8193                        .expect("simulation log collector is installed");
8194                    inspector.print_logs();
8195                    if self.print_traces {
8196                        inspector.into_print_traces(self.call_trace_decoder());
8197                    }
8198
8199                    // REVM turns a previously deleted account into `Touched` when a later call
8200                    // recreates it without storage. Preserve the cleared-storage provenance so
8201                    // subsequent calls and the recursively merged state cannot reload old slots.
8202                    let previously_deleted = previously_deleted_accounts(
8203                        &cache_db.cache.accounts,
8204                        state.keys().copied(),
8205                    );
8206
8207                    rpc_gas_budget = rpc_gas_budget.saturating_sub(result.tx_gas_used());
8208                    cumulative_gas_used = cumulative_gas_used.saturating_add(result.tx_gas_used());
8209                    block_regular_gas_used = block_regular_gas_used
8210                        .saturating_add(result.gas().block_regular_gas_used());
8211                    block_state_gas_used =
8212                        block_state_gas_used.saturating_add(result.gas().block_state_gas_used());
8213
8214                    // create the transaction from a request
8215                    let from = caller;
8216                    request.sidecar = None;
8217                    let tx = if let Some(envelope) = simulated_envelope {
8218                        MaybeImpersonatedTransaction::impersonated(envelope, from)
8219                    } else {
8220                        if request.to.is_none() {
8221                            request.to = Some(TxKind::Create);
8222                        }
8223                        let mut request = self.parse_transaction_request(request)?;
8224                        request.prep_for_submission();
8225                        let typed_tx = request.build_unsigned().map_err(|e| {
8226                            BlockchainError::InvalidTransactionRequest(e.to_string())
8227                        })?;
8228                        MaybeImpersonatedTransaction::impersonated(
8229                            typed_tx.into_impersonated(),
8230                            from,
8231                        )
8232                    };
8233                    let tx_hash = tx.as_ref().hash();
8234                    #[cfg(feature = "optimism")]
8235                    if optimism_jovian {
8236                        let tx_blob_gas = crate::eth::backend::executor::optimism::blob_gas_used(
8237                            &mut cache_db,
8238                            tx.as_ref(),
8239                            true,
8240                        )
8241                        .map_err(|err| BlockchainError::Internal(err.to_string()))?;
8242                        if block_blob_gas_used.saturating_add(tx_blob_gas) > max_blob_gas {
8243                            return Err(BlockchainError::RpcError(RpcError::invalid_params(
8244                                format!(
8245                                    "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
8246                                ),
8247                            )));
8248                        }
8249                        block_blob_gas_used = block_blob_gas_used.saturating_add(tx_blob_gas);
8250                    }
8251
8252                    // Commit after calculating the footprint so the scalar comes from pre-tx
8253                    // state, matching the upstream OP block executor.
8254                    cache_db.commit(state);
8255                    preserve_deleted_storage(&mut cache_db.cache.accounts, previously_deleted);
8256                    #[cfg(feature = "optimism")]
8257                    let receipt = if matches!(tx.as_ref(), FoundryTxEnvelope::Deposit(_)) {
8258                        crate::eth::backend::executor::optimism::build_simulated_deposit_receipt(
8259                            self.hardfork(),
8260                            caller_nonce,
8261                            &result,
8262                            canonical_logs.clone(),
8263                            cumulative_gas_used,
8264                        )
8265                    } else {
8266                        FoundryReceiptBuilder::build_simulated_receipt(
8267                            tx.as_ref().tx_type(),
8268                            &result,
8269                            canonical_logs.clone(),
8270                            cumulative_gas_used,
8271                        )
8272                    };
8273                    #[cfg(not(feature = "optimism"))]
8274                    let receipt = FoundryReceiptBuilder::build_simulated_receipt(
8275                        tx.as_ref().tx_type(),
8276                        &result,
8277                        canonical_logs.clone(),
8278                        cumulative_gas_used,
8279                    );
8280                    receipts.push(receipt);
8281                    transaction_envelopes.push(tx.as_ref().clone());
8282                    let rpc_tx =
8283                        transaction_build(Some(tx_hash), tx, None, None, Some(block_env.basefee));
8284                    transactions.push(rpc_tx);
8285
8286                    let return_data = if result.is_success() {
8287                        result.output().cloned().unwrap_or_default()
8288                    } else {
8289                        Bytes::new()
8290                    };
8291                    let sim_res = SimCallResult {
8292                        return_data,
8293                        gas_used: result.tx_gas_used(),
8294                        max_used_gas: Some(
8295                            result.gas().total_gas_spent().max(result.gas().floor_gas()),
8296                        ),
8297                        status: result.is_success(),
8298                        error: match &result {
8299                            ExecutionResult::Success { .. } => None,
8300                            ExecutionResult::Revert { output, .. } => {
8301                                let message = RevertDecoder::new()
8302                                    .maybe_decode(output, None)
8303                                    .map(|reason| format!("execution reverted: {reason}"))
8304                                    .unwrap_or_else(|| "execution reverted".to_string());
8305                                Some(SimulateError {
8306                                    code: SimulateError::EXECUTION_REVERTED_CODE,
8307                                    message,
8308                                    data: Some(output.clone()),
8309                                })
8310                            }
8311                            ExecutionResult::Halt { reason, .. } => Some(SimulateError {
8312                                code: SimulateError::VM_EXECUTION_ERROR_CODE,
8313                                message: if matches!(reason, HaltReason::OutOfGas(_)) {
8314                                    "out of gas".to_string()
8315                                } else {
8316                                    format!("vm execution error: {reason}")
8317                                },
8318                                data: None,
8319                            }),
8320                        },
8321                        logs: response_logs
8322                            .into_iter()
8323                            .map(|(idx, log)| Log {
8324                                inner: log,
8325                                block_number: Some(block_env.number.saturating_to()),
8326                                block_timestamp: Some(block_env.timestamp.saturating_to()),
8327                                transaction_index: Some(req_idx as u64),
8328                                log_index: Some(idx + log_index),
8329                                removed: false,
8330
8331                                block_hash: None,
8332                                transaction_hash: Some(tx_hash),
8333                            })
8334                            .collect(),
8335                    };
8336                    log_index += attempted_log_count;
8337                    call_res.push(sim_res);
8338                }
8339
8340                for (number, hash) in overridden_block_hashes {
8341                    if let Some(hash) = hash {
8342                        cache_db.cache.block_hashes.insert(number, hash);
8343                    } else {
8344                        cache_db.cache.block_hashes.remove(&number);
8345                    }
8346                }
8347
8348                let gas_used = if is_amsterdam {
8349                    block_regular_gas_used.max(block_state_gas_used)
8350                } else {
8351                    cumulative_gas_used
8352                };
8353                let requests = if let Some(transitions) = ethereum_transitions {
8354                    self.apply_simulation_post_execution_changes(
8355                        &mut cache_db,
8356                        &simulation_evm_env,
8357                        transitions,
8358                        &receipts,
8359                    )?
8360                } else {
8361                    Default::default()
8362                };
8363
8364                // Fork databases are partial, so their synthetic blocks use a zero state root.
8365                let state_root = cache_db
8366                    .maybe_full_db()
8367                    .map(|accounts| state_root(&accounts))
8368                    .unwrap_or_default();
8369                let header = Header {
8370                    logs_bloom: receipts.iter().fold(Bloom::ZERO, |mut bloom, receipt| {
8371                        bloom.accrue_bloom(receipt.logs_bloom());
8372                        bloom
8373                    }),
8374                    transactions_root: calculate_transaction_root(&transaction_envelopes),
8375                    receipts_root: calculate_receipt_root(&receipts),
8376                    parent_hash,
8377                    beneficiary: block_env.beneficiary,
8378                    state_root,
8379                    difficulty: block_env.difficulty,
8380                    number: block_env.number.saturating_to(),
8381                    gas_limit: block_env.gas_limit,
8382                    gas_used,
8383                    timestamp: block_env.timestamp.saturating_to(),
8384                    extra_data: base_fee_extra_data.clone(),
8385                    mix_hash: block_env.prevrandao.unwrap_or_default(),
8386                    nonce: Default::default(),
8387                    base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(block_env.basefee),
8388                    withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
8389                    blob_gas_used: is_cancun.then_some(block_blob_gas_used),
8390                    excess_blob_gas: if is_cancun { block_env.blob_excess_gas() } else { None },
8391                    parent_beacon_block_root: ethereum_transitions.and_then(|transitions| {
8392                        (transitions.hardfork >= EthereumHardfork::Cancun)
8393                            .then_some(transitions.parent_beacon_block_root.unwrap_or_default())
8394                    }),
8395                    requests_hash: ethereum_transitions.and_then(|transitions| {
8396                        (transitions.hardfork >= EthereumHardfork::Prague)
8397                            .then(|| requests.requests_hash())
8398                    }),
8399                    ..Default::default()
8400                };
8401                let block_hash = header.hash_slow();
8402                for (transaction_index, transaction) in transactions.iter_mut().enumerate() {
8403                    transaction.block_hash = Some(block_hash);
8404                    transaction.block_number = Some(header.number);
8405                    transaction.transaction_index = Some(transaction_index as u64);
8406                    transaction.block_timestamp = Some(header.timestamp);
8407                }
8408                let mut block = alloy_rpc_types::Block {
8409                    header: AnyRpcHeader {
8410                        hash: block_hash,
8411                        inner: header.into(),
8412                        total_difficulty: None,
8413                        size: None,
8414                    },
8415                    uncles: vec![],
8416                    transactions: BlockTransactions::Full(transactions),
8417                    withdrawals: (spec_id >= SpecId::SHANGHAI).then_some(Default::default()),
8418                };
8419
8420                if !return_full_transactions {
8421                    block.transactions.convert_to_hashes();
8422                }
8423
8424                for res in &mut call_res {
8425                    res.logs.iter_mut().for_each(|log| {
8426                        log.block_hash = Some(block.header.hash);
8427                    });
8428                }
8429
8430                let simulated_block = SimulatedBlock {
8431                    inner: AnyRpcBlock::new(WithOtherFields::new(block)),
8432                    calls: call_res,
8433                };
8434
8435                parent_hash = block_hash;
8436                cache_db.cache.block_hashes.insert(block_env.number, block_hash);
8437                inherited_block_env.beneficiary = block_env.beneficiary;
8438                inherited_block_env.difficulty = block_env.difficulty;
8439                inherited_block_env.gas_limit = block_env.gas_limit;
8440                // Route through the fee manager so Tempo chains use their own base fee rules.
8441                let header = &simulated_block.inner.header;
8442                next_base_fee = self.fees.calculate_next_block_base_fee_from_header(&header.inner);
8443                parent_base_fee_per_gas = header.base_fee_per_gas().unwrap_or_default();
8444                parent_excess_blob_gas = header.excess_blob_gas().unwrap_or_default();
8445                parent_blob_gas_used = header.blob_gas_used().unwrap_or_default();
8446
8447                block_res.push(simulated_block);
8448                #[cfg(feature = "monad")]
8449                self::monad::advance_block_context(&mut monad_context);
8450            }
8451
8452            Ok(block_res)
8453        };
8454
8455        match block_request {
8456            Some(BlockRequest::Pending(pool_transactions)) => {
8457                self.with_pending_block(pool_transactions, |state, block| {
8458                    let header = &block.block.header;
8459                    let parent_fees = self.fees.calculate_parent_header_fees(header);
8460                    let optimism_jovian =
8461                        self.is_optimism_jovian_at_header(header, parent_fees.optimism_jovian);
8462                    let monad_context = self.active_monad_context_before_mined_transaction(
8463                        &block.block,
8464                        block.block.body.transactions.len(),
8465                    )?;
8466                    #[cfg(feature = "monad")]
8467                    let monad_context = {
8468                        let mut monad_context = monad_context;
8469                        self::monad::advance_block_context(&mut monad_context);
8470                        monad_context
8471                    };
8472                    simulate_at(
8473                        state,
8474                        block_env_from_header(header),
8475                        header.number(),
8476                        header.timestamp(),
8477                        header.hash_slow(),
8478                        parent_fees.base_fee,
8479                        monad_context,
8480                        header.base_fee_per_gas().unwrap_or_default(),
8481                        header.excess_blob_gas().unwrap_or_default(),
8482                        header.blob_gas_used().unwrap_or_default(),
8483                        parent_fees.extra_data,
8484                        optimism_jovian,
8485                    )
8486                })
8487                .await
8488            }
8489            block_request => {
8490                let base_block_number = match block_request.as_ref() {
8491                    Some(BlockRequest::Number(number)) => BlockNumber::Number(*number),
8492                    Some(BlockRequest::Pending(_)) => unreachable!(),
8493                    None => BlockNumber::Latest,
8494                };
8495                let base_block = self
8496                    .block_by_number(base_block_number)
8497                    .await?
8498                    .ok_or(BlockchainError::BlockNotFound)?;
8499                let base_number = base_block.header.number();
8500                let base_timestamp = base_block.header.timestamp();
8501                let base_hash = base_block.header.hash;
8502                let parent_fees = self.fees.calculate_parent_header_fees(&base_block.header.inner);
8503                let optimism_jovian = self.is_optimism_jovian_at_header(
8504                    &base_block.header.inner,
8505                    parent_fees.optimism_jovian,
8506                );
8507
8508                #[cfg(feature = "monad")]
8509                let monad_context = if self.is_monad() {
8510                    Some(self.monad_context_for_child_of_block_number(base_number).await?)
8511                } else {
8512                    None
8513                };
8514                #[cfg(not(feature = "monad"))]
8515                let monad_context = None;
8516
8517                self.with_database_at(block_request, |state, block_env| {
8518                    simulate_at(
8519                        state,
8520                        block_env,
8521                        base_number,
8522                        base_timestamp,
8523                        base_hash,
8524                        parent_fees.base_fee,
8525                        monad_context,
8526                        base_block.header.base_fee_per_gas().unwrap_or_default(),
8527                        base_block.header.excess_blob_gas().unwrap_or_default(),
8528                        base_block.header.blob_gas_used().unwrap_or_default(),
8529                        parent_fees.extra_data,
8530                        optimism_jovian,
8531                    )
8532                })
8533                .await?
8534            }
8535        }
8536    }
8537
8538    pub fn get_blob_by_tx_hash(&self, hash: B256) -> Result<Option<Vec<alloy_consensus::Blob>>> {
8539        let storage = self.blockchain.storage.read();
8540        Ok(storage.transactions.get(&hash).and_then(|mined| {
8541            storage
8542                .blocks
8543                .get(&mined.block_hash)?
8544                .body
8545                .transactions
8546                .get(mined.info.transaction_index as usize)?
8547                .as_ref()
8548                .sidecar()
8549                .map(|sidecar| sidecar.sidecar.blobs().to_vec())
8550        }))
8551    }
8552
8553    /// Sets the fee token for a user address (Tempo-only).
8554    pub async fn set_fee_token(&self, user: Address, token: Address) -> DatabaseResult<()> {
8555        self.with_tempo_storage(|| {
8556            let mut fee_manager = TipFeeManager::new();
8557            fee_manager
8558                .set_user_token(user, IFeeManager::setUserTokenCall { token })
8559                .map_err(tempo_db_err)
8560        })
8561        .await
8562    }
8563
8564    /// Sets the fee token for a validator address (Tempo-only).
8565    pub async fn set_validator_fee_token(
8566        &self,
8567        validator: Address,
8568        token: Address,
8569    ) -> DatabaseResult<()> {
8570        self.with_tempo_storage(|| {
8571            let mut fee_manager = TipFeeManager::new();
8572            // Use Address::ZERO as beneficiary so the check `sender != beneficiary` passes
8573            fee_manager
8574                .set_validator_token(
8575                    validator,
8576                    IFeeManager::setValidatorTokenCall { token },
8577                    Address::ZERO,
8578                )
8579                .map_err(tempo_db_err)
8580        })
8581        .await
8582    }
8583
8584    /// Mints FeeAMM liquidity for a token pair (Tempo-only).
8585    pub async fn set_fee_amm_liquidity(
8586        &self,
8587        user_token: Address,
8588        validator_token: Address,
8589        amount: U256,
8590    ) -> DatabaseResult<()> {
8591        // T3+ rejects minting to the zero address.
8592        let admin = Address::repeat_byte(0x11);
8593        self.with_tempo_storage(|| {
8594            // Mint the required tokens to admin so it can provide liquidity.
8595            // grant_role_internal bypasses the caller check, matching genesis seeding.
8596            for &token_address in &[user_token, validator_token] {
8597                let mut token = TIP20Token::from_address(token_address).map_err(tempo_db_err)?;
8598                token.grant_role_internal(admin, *ISSUER_ROLE).map_err(tempo_db_err)?;
8599                token.mint(admin, ITIP20::mintCall { to: admin, amount }).map_err(tempo_db_err)?;
8600            }
8601            let mut fee_manager = TipFeeManager::new();
8602            fee_manager
8603                .mint(admin, user_token, validator_token, amount, admin)
8604                .map_err(tempo_db_err)?;
8605            Ok(())
8606        })
8607        .await
8608    }
8609
8610    /// Sets an account's balance for a deployed TIP-20 token (Tempo-only).
8611    pub async fn set_tip20_balance(
8612        &self,
8613        address: Address,
8614        token_address: Address,
8615        balance: U256,
8616    ) -> DatabaseResult<()> {
8617        if self.try_set_tip20_balance(address, token_address, balance).await? {
8618            return Ok(());
8619        }
8620
8621        Err(tempo_db_err(format!("address {token_address} is not a deployed TIP-20 token")))
8622    }
8623
8624    /// Sets an account's balance if the address is a deployed TIP-20 token (Tempo-only).
8625    pub async fn try_set_tip20_balance(
8626        &self,
8627        address: Address,
8628        token_address: Address,
8629        balance: U256,
8630    ) -> DatabaseResult<bool> {
8631        self.with_tempo_storage(|| {
8632            if !TIP20Factory::new().is_tip20(token_address).map_err(tempo_db_err)? {
8633                return Ok(false);
8634            }
8635
8636            let mut token = TIP20Token::from_address(token_address).map_err(tempo_db_err)?;
8637            token.balances[address].write(balance).map_err(tempo_db_err)?;
8638            Ok(true)
8639        })
8640        .await
8641    }
8642
8643    /// Runs `f` inside a Tempo storage context initialized from the current state
8644    /// (Tempo-only).
8645    async fn with_tempo_storage<R>(&self, f: impl FnOnce() -> R) -> R {
8646        let hardfork = self.hardfork();
8647        // One consistent snapshot of the current env to build the storage context.
8648        let (chain_id, timestamp, block_number) = {
8649            let env = self.evm_env.read();
8650            (
8651                env.cfg_env.chain_id,
8652                U256::from(env.block_env.timestamp),
8653                env.block_env.number.to::<u64>(),
8654            )
8655        };
8656        let mut db = self.db.write().await;
8657        let mut storage = AnvilStorageProvider::new(
8658            &mut **db,
8659            chain_id,
8660            timestamp,
8661            block_number,
8662            hardfork.into(),
8663        );
8664        StorageCtx::enter(&mut storage, f)
8665    }
8666}
8667
8668/// Converts a Tempo error into an anvil [`DatabaseError`].
8669fn tempo_db_err<E: std::fmt::Display>(e: E) -> DatabaseError {
8670    DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))
8671}
8672
8673/// Get max nonce from transaction pool by address.
8674fn get_pool_transactions_nonce(
8675    pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
8676    address: Address,
8677) -> Option<u64> {
8678    if let Some(highest_nonce) = pool_transactions
8679        .iter()
8680        .filter(|tx| {
8681            *tx.pending_transaction.sender() == address
8682                && !tx.pending_transaction.transaction.as_ref().has_nonzero_tempo_nonce_key()
8683        })
8684        .map(|tx| tx.pending_transaction.nonce())
8685        .max()
8686    {
8687        let tx_count = highest_nonce.saturating_add(1);
8688        return Some(tx_count);
8689    }
8690    None
8691}
8692
8693impl<N: Network> Backend<N>
8694where
8695    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
8696{
8697    /// Validates a transaction candidate selected for mining.
8698    fn validate_mining_pool_transaction_for(
8699        &self,
8700        pool_tx: &PoolTransaction<FoundryTxEnvelope>,
8701        account: &AccountInfo,
8702        evm_env: &EvmEnv,
8703    ) -> Result<(), InvalidTransactionError> {
8704        #[cfg(feature = "monad")]
8705        if self.validate_monad_mining_pool_transaction_for(pool_tx, account, evm_env)? {
8706            return Ok(());
8707        }
8708
8709        self.validate_pool_transaction_for(&pool_tx.pending_transaction, account, evm_env)
8710    }
8711}
8712
8713#[async_trait::async_trait]
8714impl<N: Network> TransactionValidator<FoundryTxEnvelope> for Backend<N>
8715where
8716    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
8717{
8718    async fn validate_pool_transaction(
8719        &self,
8720        tx: &PendingTransaction<FoundryTxEnvelope>,
8721    ) -> Result<(), BlockchainError> {
8722        let address = *tx.sender();
8723        let account = self.get_account(address).await?;
8724        let evm_env = self.next_evm_env();
8725
8726        // Tempo AA: validate time bounds and fee token balance (async checks)
8727        if let FoundryTxEnvelope::Tempo(aa_tx) = tx.transaction.as_ref() {
8728            let tempo_tx = aa_tx.tx();
8729            let current_time = evm_env.block_env.timestamp.saturating_to::<u64>();
8730
8731            // Reject if valid_before is expired or too close to current time (< 3 seconds)
8732            const AA_VALID_BEFORE_MIN_SECS: u64 = 3;
8733            if let Some(valid_before) = tempo_tx.valid_before.map(|v| v.get()) {
8734                let min_allowed = current_time.saturating_add(AA_VALID_BEFORE_MIN_SECS);
8735                if valid_before <= min_allowed {
8736                    return Err(InvalidTransactionError::TempoValidBeforeExpired {
8737                        valid_before,
8738                        min_allowed,
8739                    }
8740                    .into());
8741                }
8742            }
8743
8744            // Reject if valid_after is too far in the future (> 1 hour)
8745            const AA_VALID_AFTER_MAX_SECS: u64 = 3600;
8746            if let Some(valid_after) = tempo_tx.valid_after.map(|v| v.get()) {
8747                let max_allowed = current_time.saturating_add(AA_VALID_AFTER_MAX_SECS);
8748                if valid_after > max_allowed {
8749                    return Err(InvalidTransactionError::TempoValidAfterTooFar {
8750                        valid_after,
8751                        max_allowed,
8752                    }
8753                    .into());
8754                }
8755            }
8756
8757            // Fee token balance check
8758            let fee_payer = tempo_tx.recover_fee_payer(address).unwrap_or(address);
8759            let fee_token =
8760                tempo_tx.fee_token.unwrap_or(foundry_evm::core::tempo::PATH_USD_ADDRESS);
8761
8762            // gas_limit * max_fee_per_gas in wei, scaled to 6-decimal token units
8763            let required_wei =
8764                U256::from(tempo_tx.gas_limit).saturating_mul(U256::from(tempo_tx.max_fee_per_gas));
8765            let required = required_wei / U256::from(10u64.pow(12));
8766
8767            let balance = self.get_fee_token_balance(fee_token, fee_payer).await?;
8768            if balance < required {
8769                return Err(InvalidTransactionError::TempoInsufficientFeeTokenBalance {
8770                    balance,
8771                    required,
8772                }
8773                .into());
8774            }
8775        }
8776
8777        Ok(self.validate_pool_transaction_for(tx, &account, &evm_env)?)
8778    }
8779
8780    fn validate_pool_transaction_for(
8781        &self,
8782        pending: &PendingTransaction<FoundryTxEnvelope>,
8783        account: &AccountInfo,
8784        evm_env: &EvmEnv,
8785    ) -> Result<(), InvalidTransactionError> {
8786        let tx = &pending.transaction;
8787
8788        if let Some(tx_chain_id) = tx.chain_id() {
8789            let chain_id = self.chain_id();
8790            if chain_id.to::<u64>() != tx_chain_id {
8791                if let FoundryTxEnvelope::Legacy(tx) = tx.as_ref() {
8792                    // <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md>
8793                    if evm_env.cfg_env.spec >= SpecId::SPURIOUS_DRAGON && tx.chain_id().is_none() {
8794                        debug!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V");
8795                        return Err(InvalidTransactionError::IncompatibleEIP155);
8796                    }
8797                } else {
8798                    debug!(target: "backend", ?chain_id, ?tx_chain_id, "invalid chain id");
8799                    return Err(InvalidTransactionError::InvalidChainId);
8800                }
8801            }
8802        }
8803
8804        // Reject native value transfers on Tempo networks
8805        if self.is_tempo() && !tx.value().is_zero() {
8806            warn!(target: "backend", "[{:?}] native value transfer not allowed in Tempo mode", tx.hash());
8807            return Err(InvalidTransactionError::TempoNativeValueTransfer);
8808        }
8809
8810        // Tempo AA T5: cap authorization list size
8811        if self.is_tempo_hardfork_active(TempoHardfork::T5)
8812            && let FoundryTxEnvelope::Tempo(aa_tx) = tx.as_ref()
8813        {
8814            const MAX_TEMPO_AUTHORIZATIONS: usize = 16;
8815            let auth_count = aa_tx.tx().tempo_authorization_list.len();
8816            if auth_count > MAX_TEMPO_AUTHORIZATIONS {
8817                warn!(target: "backend", "[{:?}] Tempo tx has too many authorizations: {}", tx.hash(), auth_count);
8818                return Err(InvalidTransactionError::TempoTooManyAuthorizations {
8819                    count: auth_count,
8820                    max: MAX_TEMPO_AUTHORIZATIONS,
8821                });
8822            }
8823        }
8824
8825        // Nonce validation — skip for deposits (L1→L2) and Tempo txs (2D nonce system)
8826        #[cfg(feature = "optimism")]
8827        let is_deposit_tx = pending.transaction.as_ref().is_deposit();
8828        #[cfg(not(feature = "optimism"))]
8829        let is_deposit_tx = false;
8830        let is_tempo_tx = pending.transaction.as_ref().is_tempo();
8831        let nonce = tx.nonce();
8832        if nonce < account.nonce && !is_deposit_tx && !is_tempo_tx {
8833            debug!(target: "backend", "[{:?}] nonce too low", tx.hash());
8834            return Err(InvalidTransactionError::NonceTooLow);
8835        }
8836
8837        #[cfg(feature = "monad")]
8838        self.validate_monad_transaction_type(tx)?;
8839
8840        // EIP-4844 structural validation
8841        if evm_env.cfg_env.spec >= SpecId::CANCUN && tx.is_eip4844() {
8842            // Heavy (blob validation) checks
8843            let blob_tx = match tx.as_ref() {
8844                FoundryTxEnvelope::Eip4844(tx) => tx.tx(),
8845                _ => unreachable!(),
8846            };
8847
8848            let blob_count = blob_tx.tx().blob_versioned_hashes.len();
8849
8850            // Ensure there are blob hashes.
8851            if blob_count == 0 {
8852                return Err(InvalidTransactionError::NoBlobHashes);
8853            }
8854
8855            // Ensure the tx does not exceed the max blobs per transaction.
8856            let max_blobs_per_tx = self.blob_params().max_blobs_per_tx as usize;
8857            if blob_count > max_blobs_per_tx {
8858                return Err(InvalidTransactionError::TooManyBlobs(blob_count, max_blobs_per_tx));
8859            }
8860
8861            // Check for any blob validation errors if not impersonating.
8862            if !self.skip_blob_validation(Some(*pending.sender()))
8863                && let Err(err) = blob_tx.validate(EnvKzgSettings::default().get())
8864            {
8865                return Err(InvalidTransactionError::BlobTransactionValidationError(err));
8866            }
8867        }
8868
8869        // EIP-3860 initcode size validation, respects --code-size-limit / --disable-code-size-limit
8870        if evm_env.cfg_env.spec >= SpecId::SHANGHAI && tx.kind() == TxKind::Create {
8871            let max_initcode_size = evm_env
8872                .cfg_env
8873                .limit_contract_code_size
8874                .map(|limit| limit.saturating_mul(2))
8875                .unwrap_or_else(|| self.max_initcode_size(evm_env));
8876            if tx.input().len() > max_initcode_size {
8877                return Err(InvalidTransactionError::MaxInitCodeSizeExceeded);
8878            }
8879        }
8880
8881        // Balance and fee related checks
8882        if !self.disable_pool_balance_checks {
8883            // Gas limit validation
8884            if tx.gas_limit() < MIN_TRANSACTION_GAS as u64 {
8885                debug!(target: "backend", "[{:?}] gas too low", tx.hash());
8886                return Err(InvalidTransactionError::GasTooLow);
8887            }
8888
8889            // Check tx gas limit against block gas limit, if block gas limit is set.
8890            if !evm_env.cfg_env.disable_block_gas_limit
8891                && tx.gas_limit() > evm_env.block_env.gas_limit
8892            {
8893                debug!(target: "backend", "[{:?}] gas too high", tx.hash());
8894                return Err(InvalidTransactionError::GasTooHigh(ErrDetail {
8895                    detail: String::from("tx.gas_limit > env.block.gas_limit"),
8896                }));
8897            }
8898
8899            // Check tx gas limit against tx gas limit cap (Osaka hard fork and later).
8900            if evm_env.cfg_env.tx_gas_limit_cap.is_none()
8901                && tx.gas_limit() > self.tx_gas_limit_cap(evm_env)
8902            {
8903                debug!(target: "backend", "[{:?}] gas too high", tx.hash());
8904                return Err(InvalidTransactionError::GasTooHigh(ErrDetail {
8905                    detail: String::from("tx.gas_limit > resolved tx gas limit cap"),
8906                }));
8907            }
8908
8909            // EIP-1559 fee validation (London hard fork and later).
8910            if evm_env.cfg_env.spec >= SpecId::LONDON {
8911                if tx.max_fee_per_gas() < evm_env.block_env.basefee.into() && !is_deposit_tx {
8912                    debug!(target: "backend", "max fee per gas={}, too low, block basefee={}", tx.max_fee_per_gas(), evm_env.block_env.basefee);
8913                    return Err(InvalidTransactionError::FeeCapTooLow);
8914                }
8915
8916                if !evm_env.cfg_env.disable_priority_fee_check
8917                    && let (Some(max_priority_fee_per_gas), max_fee_per_gas) =
8918                        (tx.as_ref().max_priority_fee_per_gas(), tx.as_ref().max_fee_per_gas())
8919                    && max_priority_fee_per_gas > max_fee_per_gas
8920                {
8921                    debug!(target: "backend", "max priority fee per gas={}, too high, max fee per gas={}", max_priority_fee_per_gas, max_fee_per_gas);
8922                    return Err(InvalidTransactionError::TipAboveFeeCap);
8923                }
8924            }
8925
8926            // EIP-4844 blob fee validation
8927            if evm_env.cfg_env.spec >= SpecId::CANCUN
8928                && tx.is_eip4844()
8929                && let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas()
8930                && let Some(blob_gas_and_price) = &evm_env.block_env.blob_excess_gas_and_price
8931                && max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice
8932            {
8933                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);
8934                return Err(InvalidTransactionError::BlobFeeCapTooLow(
8935                    max_fee_per_blob_gas,
8936                    blob_gas_and_price.blob_gasprice,
8937                ));
8938            }
8939
8940            let value = tx.value();
8941            match tx.as_ref() {
8942                #[cfg(feature = "optimism")]
8943                FoundryTxEnvelope::Deposit(deposit_tx) => {
8944                    // Deposit transactions
8945                    // https://specs.optimism.io/protocol/deposits.html#execution
8946                    // 1. no gas cost check required since already have prepaid gas from L1
8947                    // 2. increment account balance by deposited amount before checking for
8948                    //    sufficient funds `tx.value <= existing account value + deposited value`
8949                    if value > account.balance + U256::from(deposit_tx.mint) {
8950                        debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + U256::from(deposit_tx.mint), value, *pending.sender());
8951                        return Err(InvalidTransactionError::InsufficientFunds);
8952                    }
8953                }
8954                FoundryTxEnvelope::Tempo(_) => {
8955                    // Tempo AA transactions pay gas with fee tokens, not ETH.
8956                    // Fee token balance is validated in validate_pool_transaction (async).
8957                }
8958                #[cfg(feature = "monad")]
8959                _ if self.validate_monad_transaction_funds(pending, account, evm_env)? => {}
8960                _ => {
8961                    let max_cost = (tx.gas_limit() as u128)
8962                        .saturating_mul(tx.max_fee_per_gas())
8963                        .saturating_add(
8964                            tx.blob_gas_used()
8965                                .map(|g| g as u128)
8966                                .unwrap_or(0)
8967                                .mul(tx.max_fee_per_blob_gas().unwrap_or(0)),
8968                        );
8969                    // check sufficient funds: `gas * price + value`
8970                    let req_funds =
8971                        max_cost.checked_add(value.saturating_to()).ok_or_else(|| {
8972                            debug!(target: "backend", "[{:?}] cost too high", tx.hash());
8973                            InvalidTransactionError::InsufficientFunds
8974                        })?;
8975                    if account.balance < U256::from(req_funds) {
8976                        debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender());
8977                        return Err(InvalidTransactionError::InsufficientFunds);
8978                    }
8979                }
8980            }
8981        }
8982        Ok(())
8983    }
8984
8985    fn validate_for(
8986        &self,
8987        tx: &PendingTransaction<FoundryTxEnvelope>,
8988        account: &AccountInfo,
8989        evm_env: &EvmEnv,
8990    ) -> Result<(), InvalidTransactionError> {
8991        self.validate_pool_transaction_for(tx, account, evm_env)?;
8992        if tx.nonce() > account.nonce {
8993            return Err(InvalidTransactionError::NonceTooHigh);
8994        }
8995        Ok(())
8996    }
8997}
8998
8999/// Replaces the cached hash of a [`Signed`] transaction, preserving the inner tx and signature.
9000fn rehash<T>(signed: Signed<T>, hash: B256) -> Signed<T>
9001where
9002    T: alloy_consensus::transaction::RlpEcdsaEncodableTx,
9003{
9004    let (t, sig, _) = signed.into_parts();
9005    Signed::new_unchecked(t, sig, hash)
9006}
9007
9008fn build_rpc_transaction(
9009    envelope: AnyTxEnvelope,
9010    from: Address,
9011    block: Option<&Block>,
9012    info: Option<&TransactionInfo>,
9013    effective_gas_price: Option<u128>,
9014) -> AnyRpcTransaction {
9015    let tx = Transaction {
9016        inner: Recovered::new_unchecked(envelope, from),
9017        block_hash: block.map(|block| block.header.hash_slow()),
9018        block_number: block.map(|block| block.header.number()),
9019        transaction_index: info.map(|info| info.transaction_index),
9020        effective_gas_price,
9021        block_timestamp: block.map(|block| block.header.timestamp()),
9022    };
9023    AnyRpcTransaction::from(WithOtherFields::new(tx))
9024}
9025
9026/// Creates a `AnyRpcTransaction` as it's expected for the `eth` RPC api from storage data
9027pub fn transaction_build(
9028    tx_hash: Option<B256>,
9029    eth_transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>,
9030    block: Option<&Block>,
9031    info: Option<TransactionInfo>,
9032    base_fee: Option<u64>,
9033) -> AnyRpcTransaction {
9034    let mined_from = info.as_ref().map(|info| info.from);
9035
9036    #[cfg(feature = "optimism")]
9037    if let FoundryTxEnvelope::Deposit(deposit_tx) = eth_transaction.as_ref() {
9038        let dep_tx = deposit_tx;
9039
9040        let ser = serde_json::to_value(dep_tx).expect("could not serialize TxDeposit");
9041        let maybe_deposit_fields = OtherFields::try_from(ser);
9042
9043        match maybe_deposit_fields {
9044            Ok(mut fields) => {
9045                // Add zeroed signature fields for backwards compatibility
9046                // https://specs.optimism.io/protocol/deposits.html#the-deposited-transaction-type
9047                fields.insert("v".to_string(), serde_json::to_value("0x0").unwrap());
9048                fields.insert("r".to_string(), serde_json::to_value(B256::ZERO).unwrap());
9049                fields.insert(String::from("s"), serde_json::to_value(B256::ZERO).unwrap());
9050                fields.insert(String::from("nonce"), serde_json::to_value("0x0").unwrap());
9051
9052                let inner = UnknownTypedTransaction {
9053                    ty: AnyTxType(DEPOSIT_TX_TYPE_ID),
9054                    fields,
9055                    memo: Default::default(),
9056                };
9057
9058                let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
9059                    hash: tx_hash.unwrap_or_else(|| eth_transaction.hash()),
9060                    inner,
9061                });
9062
9063                return build_rpc_transaction(
9064                    envelope,
9065                    mined_from.unwrap_or(deposit_tx.from),
9066                    block,
9067                    info.as_ref(),
9068                    None,
9069                );
9070            }
9071            Err(_) => {
9072                error!(target: "backend", "failed to serialize deposit transaction");
9073            }
9074        }
9075    }
9076
9077    if let FoundryTxEnvelope::Tempo(tempo_tx) = eth_transaction.as_ref() {
9078        let from = mined_from.unwrap_or_else(|| eth_transaction.recover().unwrap_or_default());
9079        let ser = serde_json::to_value(tempo_tx).expect("could not serialize Tempo transaction");
9080        let maybe_tempo_fields = OtherFields::try_from(ser);
9081
9082        match maybe_tempo_fields {
9083            Ok(fields) => {
9084                let inner = UnknownTypedTransaction {
9085                    ty: AnyTxType(TEMPO_TX_TYPE_ID),
9086                    fields,
9087                    memo: Default::default(),
9088                };
9089
9090                let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
9091                    hash: tx_hash.unwrap_or_else(|| eth_transaction.hash()),
9092                    inner,
9093                });
9094
9095                return build_rpc_transaction(envelope, from, block, info.as_ref(), None);
9096            }
9097            Err(_) => {
9098                error!(target: "backend", "failed to serialize tempo transaction");
9099            }
9100        }
9101    }
9102
9103    let from = mined_from.unwrap_or_else(|| eth_transaction.recover().unwrap_or_default());
9104    let effective_gas_price = eth_transaction.effective_gas_price(base_fee);
9105
9106    // if a specific hash was provided we update the transaction's hash
9107    // This is important for impersonated transactions since they all use the
9108    // `BYPASS_SIGNATURE` which would result in different hashes
9109    // Note: for impersonated transactions this only concerns pending transactions because
9110    // there's no `info` yet.
9111    let hash = tx_hash.unwrap_or_else(|| eth_transaction.hash());
9112
9113    let eth_envelope = FoundryTxEnvelope::from(eth_transaction)
9114        .try_into_eth()
9115        .expect("non-standard transactions are handled above");
9116
9117    let envelope = match eth_envelope {
9118        TxEnvelope::Legacy(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Legacy(rehash(s, hash))),
9119        TxEnvelope::Eip1559(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip1559(rehash(s, hash))),
9120        TxEnvelope::Eip2930(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip2930(rehash(s, hash))),
9121        TxEnvelope::Eip4844(s) => {
9122            let s = if block.is_some() { s.map(TxEip4844Variant::drop_sidecar) } else { s };
9123            AnyTxEnvelope::Ethereum(TxEnvelope::Eip4844(rehash(s, hash)))
9124        }
9125        TxEnvelope::Eip7702(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(rehash(s, hash))),
9126    };
9127
9128    build_rpc_transaction(envelope, from, block, info.as_ref(), Some(effective_gas_price))
9129}
9130
9131/// Prove a storage key's existence or nonexistence in the account's storage trie.
9132///
9133/// `storage_key` is the hash of the desired storage key, meaning
9134/// this will only work correctly under a secure trie.
9135/// `storage_key` == keccak(key)
9136pub fn prove_storage(
9137    storage: &alloy_primitives::map::U256Map<U256>,
9138    keys: &[B256],
9139) -> (B256, Vec<Vec<Bytes>>) {
9140    let keys: Vec<_> = keys.iter().map(|key| Nibbles::unpack(keccak256(key))).collect();
9141
9142    let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(keys.clone()));
9143
9144    for (key, value) in trie_storage(storage) {
9145        builder.add_leaf(key, &value);
9146    }
9147
9148    let root = builder.root();
9149
9150    let mut proofs = Vec::new();
9151    let all_proof_nodes = builder.take_proof_nodes();
9152
9153    for proof_key in keys {
9154        // Iterate over all proof nodes and find the matching ones.
9155        // The filtered results are guaranteed to be in order.
9156        let matching_proof_nodes =
9157            all_proof_nodes.matching_nodes_sorted(&proof_key).into_iter().map(|(_, node)| node);
9158        proofs.push(matching_proof_nodes.collect());
9159    }
9160
9161    (root, proofs)
9162}
9163
9164pub fn is_arbitrum(chain_id: u64) -> bool {
9165    if let Ok(chain) = NamedChain::try_from(chain_id) {
9166        return chain.is_arbitrum();
9167    }
9168    false
9169}
9170
9171/// Commits a fully executed candidate cache to the live database.
9172fn commit_cache(db: &mut dyn Db, cache: revm::database::Cache) -> Result<(), BlockchainError> {
9173    let revm::database::Cache { accounts, contracts, .. } = cache;
9174    let mut changes = EvmState::default();
9175    for (address, db_account) in accounts {
9176        if db_account.account_state == AccountState::None {
9177            continue;
9178        }
9179
9180        let DbAccount { mut info, account_state, storage } = db_account;
9181        // `CacheDB` also records absent-account reads as `NotExisting`. They are not state changes
9182        // and must not become synthetic selfdestructs in the live database.
9183        if account_state == AccountState::NotExisting && db.basic(address)?.is_none() {
9184            continue;
9185        }
9186        if info.code.is_none() {
9187            info.code = contracts.get(&info.code_hash).cloned();
9188        }
9189        let mut account = Account::from(info);
9190        account.mark_touch();
9191        match account_state {
9192            AccountState::NotExisting => account.mark_selfdestruct(),
9193            AccountState::StorageCleared => account.mark_created(),
9194            AccountState::Touched => {}
9195            AccountState::None => unreachable!(),
9196        }
9197        for (slot, value) in storage {
9198            let original = if account_state == AccountState::StorageCleared {
9199                U256::ZERO
9200            } else {
9201                db.storage(address, slot)?
9202            };
9203            account
9204                .storage
9205                .insert(slot, EvmStorageSlot::new_changed(original, value, TransactionId::ZERO));
9206        }
9207        changes.insert(address, account);
9208    }
9209    db.commit(changes);
9210    Ok(())
9211}
9212
9213fn simulate_rpc_error(code: i64, message: impl Into<String>) -> BlockchainError {
9214    BlockchainError::RpcError(RpcError {
9215        code: ErrorCode::from(code),
9216        message: message.into().into(),
9217        data: None,
9218    })
9219}
9220
9221fn previously_deleted_accounts(
9222    accounts: &AddressMap<DbAccount>,
9223    addresses: impl IntoIterator<Item = Address>,
9224) -> Vec<Address> {
9225    addresses
9226        .into_iter()
9227        .filter(|address| {
9228            accounts
9229                .get(address)
9230                .is_some_and(|account| account.account_state == AccountState::NotExisting)
9231        })
9232        .collect()
9233}
9234
9235fn preserve_deleted_storage(
9236    accounts: &mut AddressMap<DbAccount>,
9237    previously_deleted: Vec<Address>,
9238) {
9239    for address in previously_deleted {
9240        if let Some(account) = accounts.get_mut(&address)
9241            && account.account_state != AccountState::NotExisting
9242        {
9243            account.account_state = AccountState::StorageCleared;
9244        }
9245    }
9246}
9247
9248fn simulate_transaction_error(error: InvalidTransactionError) -> BlockchainError {
9249    let code = match &error {
9250        InvalidTransactionError::NonceTooLow => -38010,
9251        InvalidTransactionError::NonceTooHigh => -38011,
9252        InvalidTransactionError::NonceMaxValue => -32603,
9253        InvalidTransactionError::FeeCapTooLow => -38012,
9254        InvalidTransactionError::GasTooLow | InvalidTransactionError::GasTooHigh(_) => -38013,
9255        InvalidTransactionError::InsufficientFunds
9256        | InvalidTransactionError::InsufficientFundsForTransfer => -38014,
9257        _ => return BlockchainError::InvalidTransaction(error),
9258    };
9259
9260    simulate_rpc_error(code, format!("err: {error}"))
9261}
9262
9263pub(in crate::eth) fn sanitize_simulation_blocks<T>(
9264    blocks: Vec<SimBlock<T>>,
9265    base_number: u64,
9266    base_timestamp: u64,
9267    block_interval: u64,
9268) -> Result<Vec<SimBlock<T>>, BlockchainError> {
9269    let block_interval = block_interval.max(1);
9270    let mut sanitized = Vec::with_capacity(blocks.len());
9271    let mut previous_number = base_number;
9272    let mut previous_timestamp = base_timestamp;
9273
9274    for mut block in blocks {
9275        let mut overrides = block.block_overrides.take().unwrap_or_default();
9276        let default_number = previous_number.checked_add(1).ok_or_else(|| {
9277            simulate_rpc_error(-38020, "block number overflow while constructing sequence")
9278        })?;
9279        let number =
9280            overrides.number.map(|number| number.saturating_to()).unwrap_or(default_number);
9281
9282        if number <= previous_number {
9283            return Err(simulate_rpc_error(
9284                -38020,
9285                format!("block numbers must be in order: {number} <= {previous_number}"),
9286            ));
9287        }
9288
9289        let gap = number - previous_number - 1;
9290        let remaining = MAX_SIMULATE_BLOCKS as usize - sanitized.len();
9291        if gap as usize >= remaining {
9292            return Err(simulate_rpc_error(-38026, "too many blocks"));
9293        }
9294
9295        for offset in 0..gap {
9296            let timestamp = previous_timestamp.checked_add(block_interval).ok_or_else(|| {
9297                simulate_rpc_error(-38021, "block timestamp overflow while filling number gap")
9298            })?;
9299            sanitized.push(SimBlock {
9300                block_overrides: Some(BlockOverrides {
9301                    number: Some(U256::from(default_number + offset)),
9302                    time: Some(timestamp),
9303                    ..Default::default()
9304                }),
9305                state_overrides: None,
9306                calls: Vec::new(),
9307            });
9308            previous_timestamp = timestamp;
9309        }
9310
9311        let timestamp = match overrides.time {
9312            Some(timestamp) => timestamp,
9313            None => previous_timestamp.checked_add(block_interval).ok_or_else(|| {
9314                simulate_rpc_error(-38021, "block timestamp overflow while constructing sequence")
9315            })?,
9316        };
9317        if timestamp <= previous_timestamp {
9318            return Err(simulate_rpc_error(
9319                -38021,
9320                format!("block timestamps must be in order: {timestamp} <= {previous_timestamp}"),
9321            ));
9322        }
9323
9324        overrides.number = Some(U256::from(number));
9325        overrides.time = Some(timestamp);
9326        block.block_overrides = Some(overrides);
9327        sanitized.push(block);
9328        previous_number = number;
9329        previous_timestamp = timestamp;
9330    }
9331
9332    Ok(sanitized)
9333}
9334
9335/// Unpacks an [`ExecutionResult`] into its exit reason, gas used, output, and logs.
9336fn unpack_execution_result<H: IntoInstructionResult>(
9337    result: ExecutionResult<H>,
9338) -> (InstructionResult, u64, Option<Output>, Vec<revm::primitives::Log>) {
9339    match result {
9340        ExecutionResult::Success { reason, gas, output, logs, .. } => {
9341            (reason.into(), gas.tx_gas_used(), Some(output), logs)
9342        }
9343        ExecutionResult::Revert { gas, output, logs, .. } => {
9344            (InstructionResult::Revert, gas.tx_gas_used(), Some(Output::Call(output)), logs)
9345        }
9346        ExecutionResult::Halt { reason, gas, logs, .. } => {
9347            (reason.into_instruction_result(), gas.tx_gas_used(), None, logs)
9348        }
9349    }
9350}
9351
9352fn arbitrum_replay_block_number(block: &AnyRpcBlock) -> U256 {
9353    block
9354        .other
9355        .get("l1BlockNumber")
9356        .cloned()
9357        .and_then(|number| serde_json::from_value(number).ok())
9358        .unwrap_or_else(|| U256::from(block.header().number()))
9359}
9360
9361/// Converts a halt reason into an [`InstructionResult`].
9362///
9363/// Abstracts over network-specific halt reason types (`HaltReason`, `OpHaltReason`)
9364/// so that anvil code doesn't need to match on each variant directly.
9365pub use foundry_evm::core::evm::IntoInstructionResult;
9366
9367/// Creates an Ethereum-shaped genesis header from the EVM environment.
9368fn genesis_header(
9369    evm_env: &EvmEnv,
9370    base_fee: Option<u64>,
9371    timestamp: u64,
9372    genesis_number: u64,
9373) -> Header {
9374    let spec_id = *evm_env.spec_id();
9375    Header {
9376        timestamp,
9377        base_fee_per_gas: base_fee,
9378        gas_limit: evm_env.block_env.gas_limit,
9379        beneficiary: evm_env.block_env.beneficiary,
9380        difficulty: evm_env.block_env.difficulty,
9381        blob_gas_used: evm_env.block_env.blob_excess_gas_and_price.as_ref().map(|_| 0),
9382        excess_blob_gas: evm_env.block_env.blob_excess_gas(),
9383        number: genesis_number,
9384        parent_beacon_block_root: (spec_id >= SpecId::CANCUN).then_some(Default::default()),
9385        withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
9386        requests_hash: (spec_id >= SpecId::PRAGUE).then_some(EMPTY_REQUESTS_HASH),
9387        ..Default::default()
9388    }
9389}
9390
9391/// Wraps an Ethereum-shaped header in the selected network's consensus header.
9392fn foundry_header(networks: &NetworkConfigs, header: Header) -> FoundryHeader {
9393    if networks.is_tempo() { FoundryHeader::tempo(header) } else { header.into() }
9394}
9395
9396#[cfg(test)]
9397mod tests {
9398    use super::{
9399        ForkCacheNamespace, ForkCacheSource, StagedForkCacheLease, StagedForkDbUser,
9400        arbitrum_replay_block_number,
9401    };
9402    use crate::{NodeConfig, config::ForkTransactionReplay, spawn};
9403    use alloy_network::{AnyHeader, AnyRpcBlock, AnyRpcHeader, TransactionBuilder};
9404    use alloy_primitives::{B256, Bytes, U256};
9405    use alloy_provider::Provider;
9406    use alloy_rpc_types::{Block, BlockTransactions, TransactionRequest, state::EvmOverrides};
9407    use alloy_serde::WithOtherFields;
9408    use foundry_config::NamedChain;
9409    use foundry_evm::{
9410        backend::{BlockchainDb, BlockchainDbMeta},
9411        hardfork::{EthereumHardfork, FoundryHardfork},
9412    };
9413    use foundry_evm_networks::arbitrum;
9414    use std::sync::Arc;
9415    use tempfile::tempdir;
9416
9417    fn test_cache_db(cache_path: std::path::PathBuf) -> BlockchainDb {
9418        let db = BlockchainDb::new(BlockchainDbMeta::default(), Some(cache_path));
9419        db.block_hashes().write().insert(U256::ZERO, B256::repeat_byte(0x11));
9420        db.cache().flush();
9421        db
9422    }
9423
9424    #[test]
9425    fn arbitrum_transaction_replay_uses_l1_block_number() {
9426        let header = AnyHeader { number: 75_219_831, ..Default::default() };
9427        let mut block = AnyRpcBlock::new(
9428            Block::new(
9429                AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
9430                BlockTransactions::Full(Vec::new()),
9431            )
9432            .into(),
9433        );
9434        block.other.insert("l1BlockNumber".to_string(), serde_json::json!("0x10276d3"));
9435
9436        assert_eq!(arbitrum_replay_block_number(&block), U256::from(16_938_707));
9437    }
9438
9439    #[tokio::test]
9440    async fn fork_arbitrum_transaction_replay_preserves_rpc_block_number() {
9441        const GENESIS_BLOCK: u64 = 101;
9442        const L1_BLOCK: u64 = 10;
9443        const REPLAY_BLOCK: u64 = GENESIS_BLOCK + 1;
9444
9445        let config = || {
9446            NodeConfig::test()
9447                .with_chain_id(Some(NamedChain::Arbitrum as u64))
9448                .with_genesis_block_number(Some(GENESIS_BLOCK))
9449        };
9450        let (_source_api, source_handle) = spawn(config()).await;
9451        let source_provider = source_handle.http_provider();
9452        let sender = source_provider.get_accounts().await.unwrap()[0];
9453        let receipt = source_provider
9454            .send_transaction(WithOtherFields::new(
9455                TransactionRequest::default()
9456                    .with_from(sender)
9457                    .with_to(arbitrum::ARB_SYS_ADDRESS)
9458                    .with_input(Bytes::copy_from_slice(&arbitrum::ARB_BLOCK_NUMBER_SELECTOR)),
9459            ))
9460            .await
9461            .unwrap()
9462            .get_receipt()
9463            .await
9464            .unwrap();
9465        let mut source_block = source_provider
9466            .get_block_by_hash(receipt.block_hash.unwrap())
9467            .full()
9468            .await
9469            .unwrap()
9470            .unwrap();
9471        source_block
9472            .other
9473            .insert("l1BlockNumber".to_string(), serde_json::json!(format!("0x{L1_BLOCK:x}")));
9474
9475        let (replay_api, _replay_handle) = spawn(config()).await;
9476        replay_api
9477            .backend
9478            .apply_fork_transaction_replay(ForkTransactionReplay { source_block, target_index: 0 })
9479            .await
9480            .unwrap();
9481
9482        let expected = arbitrum::arb_block_number_output(REPLAY_BLOCK);
9483        let replayed =
9484            replay_api.backend.mined_transaction_receipt(receipt.transaction_hash).unwrap();
9485        assert_eq!(replayed.out.unwrap(), expected);
9486        assert_eq!(replay_api.block_number().unwrap(), U256::from(REPLAY_BLOCK));
9487
9488        let output = replay_api
9489            .call(
9490                WithOtherFields::new(
9491                    TransactionRequest::default()
9492                        .with_to(arbitrum::ARB_SYS_ADDRESS)
9493                        .with_input(Bytes::copy_from_slice(&arbitrum::ARB_BLOCK_NUMBER_SELECTOR)),
9494                ),
9495                None,
9496                EvmOverrides::default(),
9497            )
9498            .await
9499            .unwrap();
9500        assert_eq!(output, expected);
9501    }
9502
9503    fn test_endpoint_identity(
9504        hardfork: Option<FoundryHardfork>,
9505        instance_id: Option<B256>,
9506    ) -> super::ForkEndpointIdentity {
9507        super::ForkEndpointIdentity {
9508            execution_chain_id: 1,
9509            source_chain_id: 1,
9510            network: None,
9511            network_profile: None,
9512            hardfork,
9513            instance_id,
9514            source_fork_block_number: None,
9515            source_fork_block_hash: None,
9516        }
9517    }
9518
9519    #[tokio::test]
9520    async fn missing_snapshot_block_does_not_change_head() {
9521        let (api, _handle) = spawn(NodeConfig::test()).await;
9522        let snapshot_hash = api.backend.best_hash();
9523        let snapshot = api.backend.create_state_snapshot().await;
9524        api.mine_one().await.unwrap();
9525        let best_number = api.backend.best_number();
9526        let best_hash = api.backend.best_hash();
9527        let head_wall_time = api.backend.time().last_block_wall_time();
9528
9529        api.backend.blockchain.storage.write().blocks.remove(&snapshot_hash);
9530        let err = api.backend.revert_state_snapshot(snapshot).await.unwrap_err();
9531
9532        assert!(matches!(err, super::BlockchainError::BlockNotFound));
9533        assert_eq!(api.backend.best_number(), best_number);
9534        assert_eq!(api.backend.best_hash(), best_hash);
9535        assert_eq!(api.backend.time().last_block_wall_time(), head_wall_time);
9536    }
9537
9538    struct CacheFlushingDb(BlockchainDb);
9539
9540    impl Drop for CacheFlushingDb {
9541        fn drop(&mut self) {
9542            self.0.block_hashes().write().insert(U256::from(1), B256::repeat_byte(0x22));
9543            self.0.cache().flush();
9544        }
9545    }
9546
9547    #[test]
9548    fn staged_fork_cache_lease_waits_for_last_owner() {
9549        let root = tempdir().unwrap();
9550        let block_cache_dir = root.path().join("1");
9551        let cache_path = block_cache_dir.join("storage.json");
9552        let sibling_cache_path = block_cache_dir.join("storage-sibling.json");
9553        let db = test_cache_db(cache_path.clone());
9554        std::fs::write(&sibling_cache_path, b"sibling").unwrap();
9555        let lease = StagedForkCacheLease::new(db.clone(), Some(cache_path.clone()));
9556        let last_user = lease.clone();
9557
9558        lease.rollback().unwrap();
9559        assert!(!db.block_hashes().read().is_empty());
9560        drop(lease);
9561        assert!(!db.block_hashes().read().is_empty());
9562
9563        drop(last_user);
9564        assert!(db.block_hashes().read().is_empty());
9565        assert!(!cache_path.exists());
9566        assert!(sibling_cache_path.exists());
9567    }
9568
9569    #[tokio::test]
9570    async fn staged_fork_cache_lease_survives_task_cancellation() {
9571        let root = tempdir().unwrap();
9572        let block_cache_dir = root.path().join("1");
9573        let cache_path = block_cache_dir.join("storage.json");
9574        let cache_db = test_cache_db(cache_path.clone());
9575        let lease = StagedForkCacheLease::new(cache_db.clone(), Some(cache_path.clone()));
9576        let db = Arc::new(tokio::sync::RwLock::new(CacheFlushingDb(cache_db.clone())));
9577        let task_user = StagedForkDbUser { db: Some(Arc::clone(&db)), cache_lease: lease.clone() };
9578        drop(db);
9579        let task = tokio::spawn(async move {
9580            let _db = task_user.db().read().await;
9581            std::future::pending::<()>().await;
9582        });
9583        tokio::task::yield_now().await;
9584
9585        // Precise closure capture must not detach the lease from the task's database handle.
9586        assert_eq!(Arc::strong_count(lease.0.as_ref().unwrap()), 2);
9587        lease.rollback().unwrap();
9588        drop(lease);
9589
9590        task.abort();
9591        assert!(task.await.unwrap_err().is_cancelled());
9592
9593        assert!(cache_db.block_hashes().read().is_empty());
9594        assert!(!cache_path.exists());
9595    }
9596
9597    #[test]
9598    fn staged_fork_cache_lease_disarm_preserves_committed_cache() {
9599        let root = tempdir().unwrap();
9600        let block_cache_dir = root.path().join("1");
9601        let cache_path = block_cache_dir.join("storage.json");
9602        let db = test_cache_db(cache_path.clone());
9603        let lease = StagedForkCacheLease::new(db.clone(), Some(cache_path.clone()));
9604
9605        lease.disarm();
9606        drop(lease);
9607
9608        assert!(!db.block_hashes().read().is_empty());
9609        assert!(cache_path.exists());
9610    }
9611
9612    #[test]
9613    fn fork_cache_namespace_preserves_sibling_endpoints() {
9614        let root = tempdir().unwrap();
9615        let target_file = "storage-target.json";
9616        let sibling_file = "storage-sibling.json";
9617        let regular_cache_entry = root.path().join("latest.json");
9618        std::fs::write(&regular_cache_entry, b"ordinary RPC cache entry").unwrap();
9619        for block in ["1", "2"] {
9620            let block_dir = root.path().join(block);
9621            std::fs::create_dir_all(&block_dir).unwrap();
9622            std::fs::write(block_dir.join(target_file), b"target").unwrap();
9623            std::fs::write(block_dir.join(sibling_file), b"sibling").unwrap();
9624        }
9625        let namespace = ForkCacheNamespace {
9626            chain_cache_dir: root.path().to_path_buf(),
9627            file_name: target_file.to_string(),
9628        };
9629
9630        namespace.invalidate().unwrap();
9631
9632        for block in ["1", "2"] {
9633            let block_dir = root.path().join(block);
9634            assert!(!block_dir.join(target_file).exists());
9635            assert!(block_dir.join(sibling_file).exists());
9636        }
9637        assert!(regular_cache_entry.exists());
9638    }
9639
9640    #[test]
9641    fn startup_fork_cache_user_rolls_back_after_database_flush() {
9642        let root = tempdir().unwrap();
9643        let block_cache_dir = root.path().join("1");
9644        let cache_path = block_cache_dir.join("storage.json");
9645        let cache_db = test_cache_db(cache_path.clone());
9646        let user = StagedForkDbUser {
9647            db: Some(Arc::new(tokio::sync::RwLock::new(CacheFlushingDb(cache_db.clone())))),
9648            cache_lease: StagedForkCacheLease::new(cache_db.clone(), Some(cache_path.clone())),
9649        };
9650
9651        drop(user);
9652
9653        assert!(cache_db.block_hashes().read().is_empty());
9654        assert!(!cache_path.exists());
9655    }
9656
9657    #[test]
9658    fn startup_fork_cache_user_preserves_cache_after_commit() {
9659        let root = tempdir().unwrap();
9660        let block_cache_dir = root.path().join("1");
9661        let cache_path = block_cache_dir.join("storage.json");
9662        let cache_db = test_cache_db(cache_path.clone());
9663        let user = StagedForkDbUser {
9664            db: Some(Arc::new(tokio::sync::RwLock::new(CacheFlushingDb(cache_db.clone())))),
9665            cache_lease: StagedForkCacheLease::new(cache_db.clone(), Some(cache_path.clone())),
9666        };
9667        user.cache_lease.disarm();
9668
9669        drop(user);
9670
9671        assert_eq!(
9672            cache_db.block_hashes().read().get(&U256::from(1)),
9673            Some(&B256::repeat_byte(0x22))
9674        );
9675        assert!(cache_path.exists());
9676    }
9677
9678    #[test]
9679    fn fork_cache_source_invalidates_only_same_url_authoritative_replacements() {
9680        let anonymous = test_endpoint_identity(None, None);
9681        let anonymous_source = ForkCacheSource {
9682            rpc_url: "http://localhost".to_string(),
9683            endpoint_identity: anonymous,
9684        };
9685        let mut changed_anonymous = anonymous;
9686        changed_anonymous.source_chain_id = 2;
9687
9688        assert!(
9689            !anonymous_source
9690                .authoritative_identity_changed_at_same_url("http://localhost", changed_anonymous)
9691        );
9692        assert!(
9693            !anonymous_source
9694                .authoritative_identity_changed_at_same_url("http://mirror", anonymous)
9695        );
9696
9697        let hardfork = Some(FoundryHardfork::Ethereum(EthereumHardfork::Prague));
9698        let authoritative = test_endpoint_identity(hardfork, Some(B256::repeat_byte(0x22)));
9699        let authoritative_source = ForkCacheSource {
9700            rpc_url: "http://localhost".to_string(),
9701            endpoint_identity: authoritative,
9702        };
9703        let replacement = test_endpoint_identity(hardfork, Some(B256::repeat_byte(0x33)));
9704
9705        assert!(
9706            !authoritative_source
9707                .authoritative_identity_changed_at_same_url("http://localhost", authoritative)
9708        );
9709        assert!(
9710            authoritative_source
9711                .authoritative_identity_changed_at_same_url("http://localhost", replacement)
9712        );
9713        assert!(
9714            anonymous_source
9715                .authoritative_identity_changed_at_same_url("http://localhost", authoritative)
9716        );
9717        assert!(
9718            authoritative_source
9719                .authoritative_identity_changed_at_same_url("http://localhost", anonymous)
9720        );
9721    }
9722
9723    #[tokio::test]
9724    async fn test_deterministic_block_mining() {
9725        // Test that mine_block produces deterministic block hashes with same initial conditions
9726        let genesis_timestamp = 1743944919u64;
9727
9728        // Create two identical backends
9729        let config_a = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into());
9730        let config_b = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into());
9731
9732        let (api_a, _handle_a) = spawn(config_a).await;
9733        let (api_b, _handle_b) = spawn(config_b).await;
9734
9735        // Mine empty blocks (no transactions) on both backends
9736        let outcome_a_1 = api_a.backend.mine_block(vec![]).await.unwrap();
9737        let outcome_b_1 = api_b.backend.mine_block(vec![]).await.unwrap();
9738
9739        // Both should mine the same block number
9740        assert_eq!(outcome_a_1.block_number, outcome_b_1.block_number);
9741
9742        // Get the actual blocks to compare hashes
9743        let block_a_1 =
9744            api_a.block_by_number(outcome_a_1.block_number.into()).await.unwrap().unwrap();
9745        let block_b_1 =
9746            api_b.block_by_number(outcome_b_1.block_number.into()).await.unwrap().unwrap();
9747
9748        // The block hashes should be identical
9749        assert_eq!(
9750            block_a_1.header.hash, block_b_1.header.hash,
9751            "Block hashes should be deterministic. Got {} vs {}",
9752            block_a_1.header.hash, block_b_1.header.hash
9753        );
9754
9755        // Mine another block to ensure it remains deterministic
9756        let outcome_a_2 = api_a.backend.mine_block(vec![]).await.unwrap();
9757        let outcome_b_2 = api_b.backend.mine_block(vec![]).await.unwrap();
9758
9759        let block_a_2 =
9760            api_a.block_by_number(outcome_a_2.block_number.into()).await.unwrap().unwrap();
9761        let block_b_2 =
9762            api_b.block_by_number(outcome_b_2.block_number.into()).await.unwrap().unwrap();
9763
9764        assert_eq!(
9765            block_a_2.header.hash, block_b_2.header.hash,
9766            "Second block hashes should also be deterministic. Got {} vs {}",
9767            block_a_2.header.hash, block_b_2.header.hash
9768        );
9769
9770        // Ensure the blocks are different (sanity check)
9771        assert_ne!(
9772            block_a_1.header.hash, block_a_2.header.hash,
9773            "Different blocks should have different hashes"
9774        );
9775    }
9776
9777    #[cfg(feature = "monad")]
9778    #[tokio::test]
9779    async fn monad_load_state_rebuilds_participant_cache() {
9780        use alloy_network::TransactionBuilder as _;
9781        use alloy_provider::Provider as _;
9782
9783        let config = || {
9784            NodeConfig::test_monad()
9785                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
9786        };
9787        let (api, handle) = spawn(config()).await;
9788        let provider = handle.http_provider();
9789        let accounts = provider.get_accounts().await.unwrap();
9790        let sender = accounts[0];
9791
9792        let receipt = provider
9793            .send_transaction(
9794                alloy_rpc_types::TransactionRequest::default()
9795                    .with_from(sender)
9796                    .with_to(accounts[1])
9797                    .with_value(U256::from(1))
9798                    .into(),
9799            )
9800            .await
9801            .unwrap()
9802            .get_receipt()
9803            .await
9804            .unwrap();
9805        let block_hash = receipt.block_hash.unwrap();
9806        let state = api.serialized_state(false).await.unwrap();
9807
9808        let (loaded_api, _handle) = spawn(config()).await;
9809        loaded_api.backend.load_state(state).await.unwrap();
9810
9811        let storage = loaded_api.backend.blockchain.storage.read();
9812        let participants = storage.monad_block_participants.get(&block_hash).unwrap();
9813        assert!(participants.contains(&sender));
9814    }
9815
9816    #[cfg(feature = "monad")]
9817    #[tokio::test]
9818    async fn monad_load_state_restores_pruned_participant_cache() {
9819        use alloy_consensus::BlockHeader as _;
9820        use alloy_network::TransactionBuilder as _;
9821        use alloy_provider::Provider as _;
9822
9823        let config = || {
9824            NodeConfig::test_monad()
9825                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
9826                .with_transaction_block_keeper(Some(1usize))
9827        };
9828        let (api, handle) = spawn(config()).await;
9829        let provider = handle.http_provider();
9830        let accounts = provider.get_accounts().await.unwrap();
9831        let sender = accounts[0];
9832
9833        let first_receipt = provider
9834            .send_transaction(
9835                alloy_rpc_types::TransactionRequest::default()
9836                    .with_from(sender)
9837                    .with_to(accounts[1])
9838                    .with_value(U256::from(1))
9839                    .into(),
9840            )
9841            .await
9842            .unwrap()
9843            .get_receipt()
9844            .await
9845            .unwrap();
9846        let second_receipt = provider
9847            .send_transaction(
9848                alloy_rpc_types::TransactionRequest::default()
9849                    .with_from(sender)
9850                    .with_to(accounts[1])
9851                    .with_value(U256::from(1))
9852                    .into(),
9853            )
9854            .await
9855            .unwrap()
9856            .get_receipt()
9857            .await
9858            .unwrap();
9859        let first_block_hash = first_receipt.block_hash.unwrap();
9860        let second_block_hash = second_receipt.block_hash.unwrap();
9861
9862        {
9863            let storage = api.backend.blockchain.storage.read();
9864            let first_block = storage.blocks.get(&first_block_hash).unwrap();
9865            assert!(first_block.body.transactions.is_empty());
9866            assert_ne!(
9867                first_block.header.transactions_root(),
9868                alloy_consensus::constants::EMPTY_ROOT_HASH
9869            );
9870            assert!(storage.monad_block_participants[&first_block_hash].contains(&sender));
9871            assert!(!storage.blocks[&second_block_hash].body.transactions.is_empty());
9872        }
9873
9874        let state = api.serialized_state(false).await.unwrap();
9875        assert!(state.monad_block_participants[&first_block_hash].contains(&sender));
9876        assert_eq!(state.monad_block_participants.len(), 2);
9877        assert!(state.monad_block_participants[&second_block_hash].contains(&sender));
9878
9879        let (loaded_api, _handle) = spawn(config()).await;
9880        loaded_api.backend.load_state(state).await.unwrap();
9881
9882        {
9883            let storage = loaded_api.backend.blockchain.storage.read();
9884            assert!(storage.monad_block_participants[&first_block_hash].contains(&sender));
9885            assert!(storage.monad_block_participants[&second_block_hash].contains(&sender));
9886        }
9887
9888        let outcome = loaded_api.backend.mine_block(vec![]).await.unwrap();
9889        assert_eq!(outcome.block_number, 3);
9890    }
9891
9892    #[cfg(feature = "monad")]
9893    #[tokio::test]
9894    async fn monad_load_state_rejection_is_atomic() {
9895        use alloy_consensus::BlockHeader as _;
9896        use alloy_network::TransactionBuilder as _;
9897        use alloy_provider::Provider as _;
9898
9899        let config = || {
9900            NodeConfig::test_monad()
9901                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
9902                .with_transaction_block_keeper(Some(1usize))
9903        };
9904        let (source_api, source_handle) = spawn(config()).await;
9905        let source_provider = source_handle.http_provider();
9906        let accounts = source_provider.get_accounts().await.unwrap();
9907        let sentinel = alloy_primitives::Address::repeat_byte(0x77);
9908
9909        source_api.anvil_set_balance(sentinel, U256::from(123)).await.unwrap();
9910        for nonce in 0..2 {
9911            source_provider
9912                .send_transaction(
9913                    alloy_rpc_types::TransactionRequest::default()
9914                        .with_from(accounts[0])
9915                        .with_to(accounts[1])
9916                        .with_nonce(nonce)
9917                        .with_value(U256::from(1))
9918                        .into(),
9919                )
9920                .await
9921                .unwrap()
9922                .get_receipt()
9923                .await
9924                .unwrap();
9925        }
9926
9927        let mut invalid_state = source_api.serialized_state(false).await.unwrap();
9928        let pruned_block_hash = invalid_state
9929            .blocks
9930            .iter()
9931            .find(|block| {
9932                block.transactions.is_empty()
9933                    && block.header.transactions_root()
9934                        != alloy_consensus::constants::EMPTY_ROOT_HASH
9935            })
9936            .unwrap()
9937            .header
9938            .hash_slow();
9939        assert!(invalid_state.monad_block_participants.remove(&pruned_block_hash).is_some());
9940
9941        let (target_api, _handle) = spawn(config()).await;
9942        target_api.anvil_set_balance(sentinel, U256::from(7)).await.unwrap();
9943        target_api.mine_one().await.unwrap();
9944        let original_best_hash = target_api.backend.best_hash();
9945        let original_best_number = target_api.backend.best_number();
9946        let original_block_env = target_api.backend.evm_env.read().block_env.clone();
9947        let original_balance = target_api.backend.current_balance(sentinel).await.unwrap();
9948
9949        let err = target_api.backend.load_state(invalid_state).await.unwrap_err();
9950        assert!(matches!(err, super::BlockchainError::DataUnavailable));
9951        assert_eq!(target_api.backend.best_hash(), original_best_hash);
9952        assert_eq!(target_api.backend.best_number(), original_best_number);
9953        assert_eq!(target_api.backend.evm_env.read().block_env, original_block_env);
9954        assert_eq!(target_api.backend.current_balance(sentinel).await.unwrap(), original_balance);
9955
9956        let outcome = target_api.backend.mine_block(vec![]).await.unwrap();
9957        assert_eq!(outcome.block_number, original_best_number + 1);
9958    }
9959
9960    #[tokio::test]
9961    async fn trace_decoder_follows_executed_hardfork_for_cross_namespace_override() {
9962        let (api, _) = spawn(
9963            NodeConfig::test_tempo()
9964                .with_hardfork(Some(FoundryHardfork::Ethereum(EthereumHardfork::Prague))),
9965        )
9966        .await;
9967
9968        let decoder = api.backend.call_trace_decoder();
9969        assert_eq!(decoder.hardfork(), Some(FoundryHardfork::Tempo(api.backend.tempo_hardfork())));
9970        // The refresh compares the same coerced value, so repeated calls stay stable.
9971        assert!(Arc::ptr_eq(&decoder, &api.backend.call_trace_decoder()));
9972    }
9973
9974    #[cfg(feature = "monad")]
9975    #[tokio::test]
9976    async fn monad_trace_decoder_follows_resolved_hardfork() {
9977        let (monad_eight, _) = spawn(
9978            NodeConfig::test_monad()
9979                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadEight.into())),
9980        )
9981        .await;
9982        let stale_monad_nine = foundry_evm::traces::CallTraceDecoderBuilder::new()
9983            .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
9984            .build();
9985        *monad_eight.backend.call_trace_decoder.write() = Arc::new(stale_monad_nine);
9986
9987        let decoder = monad_eight.backend.call_trace_decoder();
9988        assert_eq!(
9989            decoder.hardfork(),
9990            Some(foundry_evm::hardfork::MonadHardfork::MonadEight.into())
9991        );
9992        assert!(
9993            !decoder
9994                .labels
9995                .contains_key(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
9996        );
9997
9998        let (monad_nine, _) = spawn(
9999            NodeConfig::test_monad()
10000                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into())),
10001        )
10002        .await;
10003        let stale_monad_eight = foundry_evm::traces::CallTraceDecoderBuilder::new()
10004            .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadEight.into()))
10005            .build();
10006        *monad_nine.backend.call_trace_decoder.write() = Arc::new(stale_monad_eight);
10007
10008        let decoder = monad_nine.backend.call_trace_decoder();
10009        assert_eq!(
10010            decoder.hardfork(),
10011            Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into())
10012        );
10013        assert_eq!(
10014            decoder
10015                .labels
10016                .get(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
10017                .map(String::as_str),
10018            Some("ReserveBalance")
10019        );
10020    }
10021}