Skip to main content

anvil/eth/backend/mem/
mod.rs

1//! In-memory blockchain backend.
2use self::state::trie_storage;
3
4use crate::{
5    ForkChoice, NodeConfig, PrecompileFactory,
6    config::{ForkTransactionReplay, PruneStateHistoryConfig},
7    eth::{
8        backend::{
9            cheats::{CheatEcrecover, CheatsManager},
10            db::{AnvilCacheDB, Db, MaybeFullDatabase, SerializableState, StateDb},
11            executor::{
12                AnvilBlockExecutor, BlockExecutionKind, EthereumBlockTransitions,
13                ExecutedPoolTransactions, FoundryReceiptBuilder, PoolTxGasConfig,
14                apply_ethereum_post_execution_changes, apply_ethereum_pre_execution_changes,
15                execute_pool_transactions,
16            },
17            fork::ClientFork,
18            genesis::GenesisConfig,
19            mem::{
20                state::{state_root, storage_root, trie_accounts},
21                storage::MinedTransactionReceipt,
22            },
23            notifications::{ChainNotification, ChainNotifications, NewBlockNotification},
24            replay::{
25                ExecutedHistoricalReplay, HistoricalReplayTransaction,
26                PreparedForkTransactionReplay, execute_historical_replay,
27                prepare_fork_transaction_replay,
28            },
29            tempo::AnvilStorageProvider,
30            time::{TimeManager, utc_from_secs},
31            validate::TransactionValidator,
32        },
33        error::{BlockchainError, ErrDetail, InvalidTransactionError},
34        fees::{FeeDetails, FeeManager, MIN_SUGGESTED_PRIORITY_FEE},
35        macros::node_info,
36        pool::transactions::PoolTransaction,
37        preserve_simulation_request_fields,
38    },
39    mem::{
40        inspector::{AnvilInspector, InspectorTxConfig},
41        storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome},
42    },
43};
44use alloy_chains::NamedChain;
45use alloy_consensus::{
46    Blob, BlockHeader, EnvKzgSettings, Header, Signed, Transaction as TransactionTrait,
47    TransactionEnvelope, TrieAccount, TxEip4844Variant, TxEnvelope, TxReceipt, Typed2718,
48    constants::EMPTY_WITHDRAWALS,
49    proofs::{calculate_receipt_root, calculate_transaction_root},
50    transaction::Recovered,
51};
52use alloy_eips::{
53    BlockNumHash, Encodable2718, eip2935, eip4788,
54    eip4844::{DATA_GAS_PER_BLOB, kzg_to_versioned_hash},
55    eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS,
56    eip7002, eip7251,
57    eip7685::EMPTY_REQUESTS_HASH,
58    eip7840::BlobParams,
59    eip7910::SystemContract,
60};
61use alloy_evm::{
62    Database, EthEvmFactory, Evm, EvmEnv, EvmFactory, FromTxWithEncoded,
63    block::{BlockExecutionResult, BlockExecutor, StateDB},
64    eth::EthEvmContext,
65    overrides::{OverrideBlockHashes, apply_state_overrides},
66    precompiles::{DynPrecompile, MovePrecompileError, Precompile, PrecompilesMap},
67};
68use alloy_network::{
69    AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, Network,
70    NetworkTransactionBuilder, ReceiptResponse, UnknownTxEnvelope, UnknownTypedTransaction,
71};
72#[cfg(feature = "optimism")]
73use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx};
74use alloy_primitives::{
75    Address, B256, Bloom, Bytes, Signature, TxHash, TxKind, U64, U256, address, hex, keccak256,
76    map::{AddressMap, HashMap, HashSet},
77};
78use alloy_rlp::Decodable;
79use alloy_rpc_types::{
80    AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides,
81    BlockTransactions, EIP1186AccountProofResponse as AccountProof,
82    EIP1186StorageProof as StorageProof, Filter, Header as AlloyHeader, Index, Log, Transaction,
83    TransactionReceipt,
84    anvil::Forking,
85    request::TransactionRequest,
86    serde_helpers::JsonStorageKey,
87    simulate::{
88        MAX_SIMULATE_BLOCKS, SimBlock, SimCallResult, SimulateError, SimulatePayload,
89        SimulatedBlock,
90    },
91    state::{EvmOverrides, StateOverride},
92    trace::{
93        filter::TraceFilter,
94        geth::{
95            CallConfig, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig,
96            GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
97            NoopFrame, TraceResult,
98        },
99        opcode::{BlockOpcodeGas, TransactionOpcodeGas},
100        parity::{
101            LocalizedTransactionTrace, TraceResults, TraceResultsWithTransactionHash, TraceType,
102        },
103    },
104};
105use alloy_rpc_types_eth::{AccountInfo as RpcAccountInfo, Bundle, EthCallResponse};
106use alloy_rpc_types_mev::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult};
107use alloy_serde::{OtherFields, WithOtherFields};
108use alloy_trie::{HashBuilder, Nibbles, proof::ProofRetainer};
109use anvil_core::eth::{
110    block::{Block, BlockInfo, canonical_block, create_block},
111    transaction::{MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo},
112};
113use anvil_rpc::error::{ErrorCode, RpcError};
114use chrono::Datelike;
115use eyre::{Context, Result};
116use flate2::{Compression, read::GzDecoder, write::GzEncoder};
117#[cfg(feature = "optimism")]
118use foundry_evm::hardfork::OpHardfork;
119use foundry_evm::{
120    backend::{DatabaseError, DatabaseResult, RevertStateSnapshotAction},
121    constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE,
122    core::{
123        evm::{EvmEnvFor, TempoEvmNetwork},
124        precompiles::EC_RECOVER,
125    },
126    decode::RevertDecoder,
127    hardfork::{EthereumHardfork, FoundryHardfork},
128    inspectors::AccessListInspector,
129    traces::{
130        CallTraceDecoder, FourByteInspector, GethTraceBuilder, TracingInspector,
131        TracingInspectorConfig,
132    },
133    utils::{
134        apply_chain_specific_tx_replay_env_changes, block_env_from_header,
135        get_blob_base_fee_update_fraction, get_blob_base_fee_update_fraction_by_spec_id,
136        get_blob_params_by_spec_id,
137    },
138};
139use foundry_evm_networks::{NetworkConfigs, arbitrum};
140#[cfg(feature = "optimism")]
141use foundry_primitives::get_deposit_tx_parts;
142use foundry_primitives::{
143    FoundryHeader, FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest,
144    FoundryTxEnvelope, FoundryTxReceipt, TempoTransactionRequest,
145};
146use futures::channel::mpsc::{UnboundedSender, unbounded};
147#[cfg(feature = "optimism")]
148use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, POST_EXEC_TX_TYPE_ID};
149#[cfg(feature = "optimism")]
150use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts};
151
152/// Side-channel container for OP-specific deposit info produced by
153/// [`Backend::build_call_env`] and consumed by the OP transact path.
154///
155/// When the `optimism` feature is enabled, this is an alias for
156/// `op_revm::DepositTransactionParts`. When disabled, it is a zero-sized
157/// stand-in so the eth/tempo dispatch chain still type-checks.
158#[cfg(feature = "optimism")]
159type OpCallDepositInfo = DepositTransactionParts;
160#[cfg(not(feature = "optimism"))]
161#[derive(Default, Clone, Debug)]
162struct OpCallDepositInfo;
163
164/// Maximum cumulative gas available to one `eth_simulateV1` request.
165const SIMULATE_GAS_CAP: u64 = 50_000_000;
166const SEPOLIA_DEPOSIT_CONTRACT_ADDRESS: Address =
167    address!("7f02c3e3c98b133055b8b348b2ac625669ed295d");
168const HOLESKY_DEPOSIT_CONTRACT_ADDRESS: Address =
169    address!("4242424242424242424242424242424242424242");
170
171/// Fixed transaction context for direct Tempo RPC simulations.
172const TEMPO_RPC_SIMULATION_CONTEXT: B256 = B256::new(*b"TEMPO_RPC_SIMULATION_MPP_CONTEXT");
173
174/// Ethereum handler that skips blob fee cap validation for non-validating calls with a zero cap.
175struct SimulationHandler<EVM, ERROR, FRAME> {
176    _phantom: PhantomData<(EVM, ERROR, FRAME)>,
177}
178
179impl<EVM, ERROR, FRAME> Default for SimulationHandler<EVM, ERROR, FRAME> {
180    fn default() -> Self {
181        Self { _phantom: PhantomData }
182    }
183}
184
185impl<EVM, ERROR, FRAME> EvmHandler for SimulationHandler<EVM, ERROR, FRAME>
186where
187    EVM: EvmTr<
188            Context: ContextTr<
189                Block = BlockEnv,
190                Tx = TxEnv,
191                Journal: JournalTr<State = EvmState>,
192            > + ContextSetters,
193            Frame = FRAME,
194        >,
195    ERROR: EvmTrError<EVM>,
196    FRAME: FrameTr<FrameResult = FrameResult, FrameInit = FrameInit>,
197{
198    type Evm = EVM;
199    type Error = ERROR;
200    type HaltReason = HaltReason;
201
202    fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
203        let skip_blob_fee_check = evm.ctx_ref().cfg().is_base_fee_check_disabled()
204            && evm.ctx_ref().tx().tx_type == 3
205            && evm.ctx_ref().tx().max_fee_per_blob_gas == 0;
206        if !skip_blob_fee_check {
207            return validation::validate_env(evm.ctx());
208        }
209
210        let block = evm.ctx_ref().block().clone();
211        let mut validation_block = block.clone();
212        if let Some(blob_gas_and_price) = &mut validation_block.blob_excess_gas_and_price {
213            blob_gas_and_price.blob_gasprice = 0;
214        }
215        evm.ctx().set_block(validation_block);
216        let result = validation::validate_env(evm.ctx());
217        evm.ctx().set_block(block);
218        result
219    }
220}
221
222impl<EVM, ERROR> InspectorHandler for SimulationHandler<EVM, ERROR, EthFrame<EthInterpreter>>
223where
224    EVM: InspectorEvmTr<
225            Context: ContextTr<
226                Block = BlockEnv,
227                Tx = TxEnv,
228                Journal: JournalTr<State = EvmState>,
229            > + ContextSetters,
230            Frame = EthFrame<EthInterpreter>,
231            Inspector: Inspector<<EVM as EvmTr>::Context, EthInterpreter>,
232        >,
233    ERROR: EvmTrError<EVM>,
234{
235    type IT = EthInterpreter;
236}
237
238#[derive(Clone)]
239enum CallTxEnv {
240    Eth(TxEnv),
241    #[cfg(feature = "optimism")]
242    Op(OpTransaction<TxEnv>),
243    Tempo(TempoTxEnv),
244}
245
246impl CallTxEnv {
247    #[cfg_attr(not(feature = "js-tracer"), allow(dead_code))]
248    const fn base(&self) -> &TxEnv {
249        match self {
250            Self::Eth(tx) => tx,
251            #[cfg(feature = "optimism")]
252            Self::Op(tx) => &tx.base,
253            Self::Tempo(tx) => &tx.inner,
254        }
255    }
256
257    const fn base_mut(&mut self) -> &mut TxEnv {
258        match self {
259            Self::Eth(tx) => tx,
260            #[cfg(feature = "optimism")]
261            Self::Op(tx) => &mut tx.base,
262            Self::Tempo(tx) => &mut tx.inner,
263        }
264    }
265
266    fn into_base(self) -> TxEnv {
267        match self {
268            Self::Eth(tx) => tx,
269            #[cfg(feature = "optimism")]
270            Self::Op(tx) => tx.base,
271            Self::Tempo(tx) => tx.inner,
272        }
273    }
274
275    fn uses_protocol_call_nonce(&self) -> bool {
276        match self {
277            Self::Eth(tx) => matches!(tx.kind, TxKind::Call(_)),
278            #[cfg(feature = "optimism")]
279            Self::Op(tx) => matches!(tx.base.kind, TxKind::Call(_)),
280            Self::Tempo(tx) => tx.tempo_tx_env.as_ref().map_or_else(
281                || matches!(tx.inner.kind, TxKind::Call(_)),
282                |aa| {
283                    aa.nonce_key.is_zero()
284                        && aa
285                            .aa_calls
286                            .first()
287                            .is_some_and(|call| matches!(call.to, TxKind::Call(_)))
288                },
289            ),
290        }
291    }
292}
293
294fn apply_tempo_envelope_identity(tx_env: &mut CallTxEnv, simulated_tx: Option<&AASigned>) {
295    if let (CallTxEnv::Tempo(tx_env), Some(simulated_tx)) = (tx_env, simulated_tx) {
296        tx_env.unique_tx_identifier = Some(simulated_tx.expiring_nonce_hash(tx_env.inner.caller));
297        if let Some(batch) = &mut tx_env.tempo_tx_env {
298            batch.tx_hash = *simulated_tx.hash();
299        }
300    }
301}
302
303struct PreparedCall {
304    evm_env: EvmEnv,
305    tx_env: CallTxEnv,
306    simulated_tempo_tx: Option<AASigned>,
307}
308
309#[derive(Default)]
310struct TypedCallOverrides {
311    gas_limit: Option<u64>,
312    access_list: Option<AccessList>,
313    disable_fee_charge: bool,
314}
315
316/// Marker trait that abstracts over the per-network inspector trait bounds
317/// required by the in-memory backend. The OP bound is only included when the
318/// `optimism` feature is enabled.
319#[cfg(feature = "optimism")]
320pub trait BackendInspector<DB: Database>:
321    Inspector<EthEvmContext<DB>> + Inspector<OpEvmContext<DB>> + Inspector<TempoContext<DB>>
322{
323}
324#[cfg(feature = "optimism")]
325impl<DB: Database, T> BackendInspector<DB> for T where
326    T: Inspector<EthEvmContext<DB>> + Inspector<OpEvmContext<DB>> + Inspector<TempoContext<DB>>
327{
328}
329#[cfg(not(feature = "optimism"))]
330pub trait BackendInspector<DB: Database>:
331    Inspector<EthEvmContext<DB>> + Inspector<TempoContext<DB>>
332{
333}
334#[cfg(not(feature = "optimism"))]
335impl<DB: Database, T> BackendInspector<DB> for T where
336    T: Inspector<EthEvmContext<DB>> + Inspector<TempoContext<DB>>
337{
338}
339use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
340use revm::{
341    Database as RevmDatabase, DatabaseCommit, Inspector,
342    context::{Block as RevmBlock, BlockEnv, Cfg, ContextSetters, ContextTr, TxEnv},
343    context_interface::{
344        JournalTr,
345        block::BlobExcessGasAndPrice,
346        result::{ExecutionResult, HaltReason, Output, ResultAndState},
347    },
348    database::{AccountState, CacheDB, DbAccount, WrapDatabaseRef},
349    handler::{
350        EthFrame, EvmTr, EvmTrError, FrameResult, FrameTr, Handler as EvmHandler, validation,
351    },
352    inspector::{InspectorEvmTr, InspectorHandler},
353    interpreter::{InstructionResult, interpreter::EthInterpreter, interpreter_action::FrameInit},
354    precompile::{PrecompileSpecId, Precompiles},
355    primitives::{KECCAK_EMPTY, hardfork::SpecId},
356    state::{Account, AccountInfo, EvmState, EvmStorageSlot, TransactionId},
357};
358use revm_inspectors::opcode::OpcodeGasInspector;
359use std::{
360    collections::BTreeMap,
361    fmt::{self, Debug},
362    io::{Read, Write},
363    marker::PhantomData,
364    ops::Mul,
365    path::PathBuf,
366    sync::Arc,
367    time::Duration,
368};
369use storage::{Blockchain, DEFAULT_HISTORY_LIMIT, MinedTransaction};
370use tempo_evm::evm::TempoEvmFactory;
371use tempo_hardfork::TempoHardfork;
372use tempo_precompiles::{
373    NONCE_PRECOMPILE_ADDRESS, TIP_FEE_MANAGER_ADDRESS, extend_tempo_precompiles,
374    nonce::NonceManager,
375    storage::{Handler, StorageActions, StorageCtx},
376    tip_fee_manager::{IFeeManager, TipFeeManager},
377    tip20::{ISSUER_ROLE, ITIP20, TIP20Token},
378    tip20_factory::TIP20Factory,
379};
380use tempo_primitives::{
381    AASigned, SignatureType, TEMPO_TX_TYPE_ID, TempoSignature,
382    transaction::{
383        Call, KeychainSignature, PrimitiveSignature, RecoveredTempoAuthorization,
384        tt_signature::{P256SignatureWithPreHash, WebAuthnSignature},
385    },
386};
387use tempo_revm::{
388    TempoBatchCallEnv, TempoBlockEnv, TempoHaltReason, TempoTxEnv, evm::TempoContext,
389    gas_params::tempo_gas_params,
390};
391use tokio::sync::RwLock as AsyncRwLock;
392
393pub mod cache;
394pub mod fork_db;
395pub mod in_memory_db;
396pub mod inspector;
397#[cfg(feature = "optimism")]
398pub mod optimism;
399pub mod state;
400pub mod storage;
401
402/// Helper trait that combines revm::DatabaseRef with Debug.
403/// This is needed because alloy-evm requires Debug on Database implementations.
404/// With trait upcasting now stable, we can now upcast from this trait to revm::DatabaseRef.
405pub trait DatabaseRef: revm::DatabaseRef<Error = DatabaseError> + Debug {}
406impl<T> DatabaseRef for T where T: revm::DatabaseRef<Error = DatabaseError> + Debug {}
407impl DatabaseRef for dyn crate::eth::backend::db::Db {}
408
409// Gas per transaction not creating a contract.
410pub const MIN_TRANSACTION_GAS: u128 = 21000;
411// Gas per transaction creating a contract.
412pub const MIN_CREATE_GAS: u128 = 53000;
413
414fn tempo_nonce(
415    state: &dyn DatabaseRef,
416    caller: Address,
417    nonce_key: U256,
418) -> Result<u64, BlockchainError> {
419    if nonce_key.is_zero() {
420        return Ok(state.basic_ref(caller)?.map(|account| account.nonce).unwrap_or_default());
421    }
422    if nonce_key == U256::MAX {
423        return Ok(0);
424    }
425    let slot = NonceManager::new().nonces[caller][nonce_key].slot();
426    Ok(state.storage_ref(NONCE_PRECOMPILE_ADDRESS, slot)?.saturating_to())
427}
428
429fn mock_tempo_signature(
430    key_type: SignatureType,
431    key_data: Option<Bytes>,
432    key_id: Option<Address>,
433    caller: Address,
434    is_t1c: bool,
435) -> TempoSignature {
436    let signature = match key_type {
437        SignatureType::Secp256k1 => {
438            PrimitiveSignature::Secp256k1(Signature::new(U256::ZERO, U256::ZERO, false))
439        }
440        SignatureType::P256 => PrimitiveSignature::P256(P256SignatureWithPreHash {
441            r: B256::ZERO,
442            s: B256::ZERO,
443            pub_key_x: B256::ZERO,
444            pub_key_y: B256::ZERO,
445            pre_hash: false,
446        }),
447        SignatureType::WebAuthn => {
448            const CLIENT_JSON: &str = r#"{"type":"webauthn.get","challenge":"","origin":""}"#;
449            const AUTH_DATA_SIZE: usize = 37;
450            const MIN_SIZE: usize = AUTH_DATA_SIZE + CLIENT_JSON.len();
451            const DEFAULT_SIZE: usize = 800;
452            const MAX_SIZE: usize = 8192;
453
454            let size = key_data
455                .as_deref()
456                .and_then(|data| match data.len() {
457                    1 => Some(data[0] as usize),
458                    2 => Some(u16::from_be_bytes([data[0], data[1]]) as usize),
459                    4 => Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize),
460                    _ => None,
461                })
462                .unwrap_or(DEFAULT_SIZE)
463                .clamp(MIN_SIZE, MAX_SIZE);
464            let mut webauthn_data = vec![0u8; AUTH_DATA_SIZE];
465            webauthn_data[32] = 0x01;
466            let padding = "x".repeat(size - MIN_SIZE);
467            webauthn_data.extend_from_slice(
468                format!(r#"{{"type":"webauthn.get","challenge":"","origin":"{padding}"}}"#)
469                    .as_bytes(),
470            );
471            PrimitiveSignature::WebAuthn(WebAuthnSignature {
472                webauthn_data: webauthn_data.into(),
473                r: B256::ZERO,
474                s: B256::ZERO,
475                pub_key_x: B256::ZERO,
476                pub_key_y: B256::ZERO,
477            })
478        }
479    };
480
481    if key_id.is_some() {
482        let signature = if is_t1c {
483            KeychainSignature::new(caller, signature)
484        } else {
485            KeychainSignature::new_v1(caller, signature)
486        };
487        TempoSignature::Keychain(signature)
488    } else {
489        TempoSignature::Primitive(signature)
490    }
491}
492
493fn call_config_from_tracer_config(
494    tracer_config: GethDebugTracerConfig,
495) -> Result<CallConfig, serde_json::Error> {
496    let mut tracer_config = tracer_config.into_json();
497    if let Some(config) = tracer_config.as_object_mut()
498        && !config.contains_key("onlyTopCall")
499        && let Some(only_top_level_call) = config.remove("onlyTopLevelCall")
500    {
501        config.insert("onlyTopCall".to_string(), only_top_level_call);
502    }
503
504    GethDebugTracerConfig(tracer_config).into_call_config()
505}
506
507pub type State = foundry_evm::utils::StateChangeset;
508
509#[derive(Clone, Debug, Default)]
510struct SimulationPrecompileOverrides {
511    moves: Vec<(Address, Address)>,
512}
513
514/// A block request, which includes the Pool Transactions if it's Pending
515pub enum BlockRequest<T> {
516    Pending(Vec<Arc<PoolTransaction<T>>>),
517    Number(u64),
518}
519
520impl<T> fmt::Debug for BlockRequest<T> {
521    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522        match self {
523            Self::Pending(txs) => f.debug_tuple("Pending").field(&txs.len()).finish(),
524            Self::Number(n) => f.debug_tuple("Number").field(n).finish(),
525        }
526    }
527}
528
529impl<T> BlockRequest<T> {
530    pub const fn block_number(&self) -> BlockNumber {
531        match *self {
532            Self::Pending(_) => BlockNumber::Pending,
533            Self::Number(n) => BlockNumber::Number(n),
534        }
535    }
536}
537
538/// Gives access to the [revm::Database]
539pub struct Backend<N: Network> {
540    /// Access to [`revm::Database`] abstraction.
541    ///
542    /// This will be used in combination with [`alloy_evm::Evm`] and is responsible for feeding
543    /// data to the evm during its execution.
544    ///
545    /// At time of writing, there are two different types of `Db`:
546    ///   - [`MemDb`](crate::mem::in_memory_db::MemDb): everything is stored in memory
547    ///   - [`ForkDb`](crate::mem::fork_db::ForkedDatabase): forks off a remote client, missing
548    ///     data is retrieved via RPC-calls
549    ///
550    /// In order to commit changes to the [`revm::Database`], the [`alloy_evm::Evm`] requires
551    /// mutable access, which requires a write-lock from this `db`. In forking mode, the time
552    /// during which the write-lock is active depends on whether the `ForkDb` can provide all
553    /// requested data from memory or whether it has to retrieve it via RPC calls first. This
554    /// means that it potentially blocks for some time, even taking into account the rate
555    /// limits of RPC endpoints. Therefore the `Db` is guarded by a `tokio::sync::RwLock` here
556    /// so calls that need to read from it, while it's currently written to, don't block. E.g.
557    /// a new block is currently mined and a new [`Self::set_storage_at()`] request is being
558    /// executed.
559    db: Arc<AsyncRwLock<Box<dyn Db>>>,
560    /// stores all block related data in memory.
561    blockchain: Blockchain<N>,
562    /// Historic states of previous blocks.
563    states: Arc<RwLock<InMemoryBlockStates>>,
564    /// EVM environment data of the chain (block env, cfg env).
565    evm_env: Arc<RwLock<EvmEnv>>,
566    /// Network configuration (optimism, custom precompiles, etc.)
567    networks: NetworkConfigs,
568    /// The active hardfork.
569    hardfork: FoundryHardfork,
570    /// This is set if this is currently forked off another client.
571    fork: Arc<RwLock<Option<ClientFork>>>,
572    /// Provides time related info, like timestamp.
573    time: TimeManager,
574    /// Contains state of custom overrides.
575    cheats: CheatsManager,
576    /// Contains fee data.
577    fees: FeeManager,
578    /// Initialised genesis.
579    genesis: GenesisConfig,
580    /// Listeners for new blocks that get notified when a new block was imported or when logs were
581    /// removed from the canonical chain due to a reorg.
582    new_block_listeners: Arc<Mutex<Vec<UnboundedSender<ChainNotification>>>>,
583    /// Keeps track of active state snapshots at a specific block.
584    active_state_snapshots: Arc<Mutex<HashMap<U256, (u64, B256)>>>,
585    enable_steps_tracing: bool,
586    print_logs: bool,
587    print_traces: bool,
588    /// Recorder used for decoding traces, used together with print_traces
589    call_trace_decoder: Arc<CallTraceDecoder>,
590    /// How to keep history state
591    prune_state_history_config: PruneStateHistoryConfig,
592    /// max number of blocks with transactions in memory
593    transaction_block_keeper: Option<usize>,
594    pub(crate) node_config: Arc<AsyncRwLock<NodeConfig>>,
595    /// Slots in an epoch
596    slots_in_an_epoch: u64,
597    /// Precompiles to inject to the EVM.
598    precompile_factory: Option<Arc<dyn PrecompileFactory>>,
599    /// Prevent race conditions during mining
600    mining: Arc<tokio::sync::Mutex<()>>,
601    /// Disable pool balance checks
602    disable_pool_balance_checks: bool,
603}
604
605impl<N: Network> Clone for Backend<N> {
606    fn clone(&self) -> Self {
607        Self {
608            db: self.db.clone(),
609            blockchain: self.blockchain.clone(),
610            states: self.states.clone(),
611            evm_env: self.evm_env.clone(),
612            networks: self.networks,
613            hardfork: self.hardfork,
614            fork: self.fork.clone(),
615            time: self.time.clone(),
616            cheats: self.cheats.clone(),
617            fees: self.fees.clone(),
618            genesis: self.genesis.clone(),
619            new_block_listeners: self.new_block_listeners.clone(),
620            active_state_snapshots: self.active_state_snapshots.clone(),
621            enable_steps_tracing: self.enable_steps_tracing,
622            print_logs: self.print_logs,
623            print_traces: self.print_traces,
624            call_trace_decoder: self.call_trace_decoder.clone(),
625            prune_state_history_config: self.prune_state_history_config,
626            transaction_block_keeper: self.transaction_block_keeper,
627            node_config: self.node_config.clone(),
628            slots_in_an_epoch: self.slots_in_an_epoch,
629            precompile_factory: self.precompile_factory.clone(),
630            mining: self.mining.clone(),
631            disable_pool_balance_checks: self.disable_pool_balance_checks,
632        }
633    }
634}
635
636impl<N: Network> fmt::Debug for Backend<N> {
637    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
638        f.debug_struct("Backend").finish_non_exhaustive()
639    }
640}
641
642// Methods that are generic over any Network.
643impl<N: Network> Backend<N> {
644    /// Sets the account to impersonate
645    ///
646    /// Returns `true` if the account is already impersonated
647    pub fn impersonate(&self, addr: Address) -> bool {
648        if self.cheats.impersonated_accounts().contains(&addr) {
649            return true;
650        }
651        // Ensure EIP-3607 is disabled
652        self.evm_env.write().cfg_env.disable_eip3607 = true;
653        self.cheats.impersonate(addr)
654    }
655
656    /// Removes the account that from the impersonated set
657    ///
658    /// If the impersonated `addr` is a contract then we also reset the code here
659    pub fn stop_impersonating(&self, addr: Address) {
660        self.cheats.stop_impersonating(&addr);
661    }
662
663    /// If set to true will make every account impersonated
664    pub fn auto_impersonate_account(&self, enabled: bool) {
665        self.cheats.set_auto_impersonate_account(enabled);
666    }
667
668    /// Returns the configured fork, if any
669    pub fn get_fork(&self) -> Option<ClientFork> {
670        self.fork.read().clone()
671    }
672
673    /// Returns the database
674    pub fn get_db(&self) -> &Arc<AsyncRwLock<Box<dyn Db>>> {
675        &self.db
676    }
677
678    /// Returns the `AccountInfo` from the database
679    pub async fn get_account(&self, address: Address) -> DatabaseResult<AccountInfo> {
680        Ok(self.db.read().await.basic_ref(address)?.unwrap_or_default())
681    }
682
683    /// Whether we're forked off some remote client
684    pub fn is_fork(&self) -> bool {
685        self.fork.read().is_some()
686    }
687
688    /// Writes the CREATE2 deployer code directly to the database at the address provided.
689    pub async fn set_create2_deployer(&self, address: Address) -> DatabaseResult<()> {
690        self.set_code(address, Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE)).await?;
691        Ok(())
692    }
693
694    /// Updates memory limits that should be more strict when auto-mine is enabled
695    pub(crate) fn update_interval_mine_block_time(&self, block_time: Duration) {
696        self.states.write().update_interval_mine_block_time(block_time)
697    }
698
699    /// Returns the `TimeManager` responsible for timestamps
700    pub const fn time(&self) -> &TimeManager {
701        &self.time
702    }
703
704    /// Returns the `CheatsManager` responsible for executing cheatcodes
705    pub const fn cheats(&self) -> &CheatsManager {
706        &self.cheats
707    }
708
709    /// Whether to skip blob validation
710    pub fn skip_blob_validation(&self, impersonator: Option<Address>) -> bool {
711        self.cheats().auto_impersonate_accounts()
712            || impersonator
713                .is_some_and(|addr| self.cheats().impersonated_accounts().contains(&addr))
714    }
715
716    /// Returns the `FeeManager` that manages fee/pricings
717    pub const fn fees(&self) -> &FeeManager {
718        &self.fees
719    }
720
721    /// The EVM environment data of the blockchain
722    pub const fn evm_env(&self) -> &Arc<RwLock<EvmEnv>> {
723        &self.evm_env
724    }
725
726    /// Returns the current best hash of the chain
727    pub fn best_hash(&self) -> B256 {
728        self.blockchain.storage.read().best_hash
729    }
730
731    /// Returns the current best number of the chain
732    pub fn best_number(&self) -> u64 {
733        self.blockchain.storage.read().best_number
734    }
735
736    /// Sets the block number
737    pub fn set_block_number(&self, number: u64) {
738        self.evm_env.write().block_env.number = U256::from(number);
739    }
740
741    /// Returns the client coinbase address.
742    pub fn coinbase(&self) -> Address {
743        self.evm_env.read().block_env.beneficiary
744    }
745
746    /// Returns the client coinbase address.
747    pub fn chain_id(&self) -> U256 {
748        U256::from(self.evm_env.read().cfg_env.chain_id)
749    }
750
751    pub fn set_chain_id(&self, chain_id: u64) {
752        self.evm_env.write().cfg_env.chain_id = chain_id;
753    }
754
755    /// Returns the genesis data for the Beacon API.
756    pub const fn genesis_time(&self) -> u64 {
757        self.genesis.timestamp
758    }
759
760    /// Returns the configured genesis block number.
761    pub const fn genesis_number(&self) -> u64 {
762        self.genesis.number
763    }
764
765    /// Returns balance of the given account.
766    pub async fn current_balance(&self, address: Address) -> DatabaseResult<U256> {
767        Ok(self.get_account(address).await?.balance)
768    }
769
770    /// Returns balance of the given account.
771    pub async fn current_nonce(&self, address: Address) -> DatabaseResult<u64> {
772        Ok(self.get_account(address).await?.nonce)
773    }
774
775    /// Sets the coinbase address
776    pub fn set_coinbase(&self, address: Address) {
777        self.evm_env.write().block_env.beneficiary = address;
778    }
779
780    /// Sets the `prevrandao` value to use for the next mined block.
781    ///
782    /// This is a one-shot override that is consumed by the next block; afterwards anvil resumes its
783    /// default per-block `prevrandao` derivation.
784    pub fn set_next_block_prevrandao(&self, prevrandao: B256) {
785        self.cheats.set_next_block_prevrandao(prevrandao);
786    }
787
788    /// Sets the nonce of the given address
789    pub async fn set_nonce(&self, address: Address, nonce: U256) -> DatabaseResult<()> {
790        self.db.write().await.set_nonce(address, nonce.try_into().unwrap_or(u64::MAX))
791    }
792
793    /// Sets the balance of the given address
794    pub async fn set_balance(&self, address: Address, balance: U256) -> DatabaseResult<()> {
795        self.db.write().await.set_balance(address, balance)
796    }
797
798    /// Sets the code of the given address
799    pub async fn set_code(&self, address: Address, code: Bytes) -> DatabaseResult<()> {
800        self.db.write().await.set_code(address, code)
801    }
802
803    /// Sets the value for the given slot of the given address
804    pub async fn set_storage_at(
805        &self,
806        address: Address,
807        slot: U256,
808        val: B256,
809    ) -> DatabaseResult<()> {
810        self.db.write().await.set_storage_at(address, slot.into(), val)
811    }
812
813    /// Returns the configured specid
814    pub fn spec_id(&self) -> SpecId {
815        *self.evm_env.read().spec_id()
816    }
817
818    /// Returns true for post London
819    pub fn is_eip1559(&self) -> bool {
820        (self.spec_id() as u8) >= (SpecId::LONDON as u8)
821    }
822
823    /// Returns true for post Merge
824    pub fn is_eip3675(&self) -> bool {
825        (self.spec_id() as u8) >= (SpecId::MERGE as u8)
826    }
827
828    /// Returns true for post Berlin
829    pub fn is_eip2930(&self) -> bool {
830        (self.spec_id() as u8) >= (SpecId::BERLIN as u8)
831    }
832
833    /// Returns true for post Cancun
834    pub fn is_eip4844(&self) -> bool {
835        (self.spec_id() as u8) >= (SpecId::CANCUN as u8)
836    }
837
838    /// Returns true for post Prague
839    pub fn is_eip7702(&self) -> bool {
840        (self.spec_id() as u8) >= (SpecId::PRAGUE as u8)
841    }
842
843    /// Returns true if op-stack deposits are active
844    #[cfg(feature = "optimism")]
845    pub const fn is_optimism(&self) -> bool {
846        self.networks.is_optimism()
847    }
848
849    /// Returns true if op-stack deposits are active.
850    ///
851    /// Always `false` when built without the `optimism` feature.
852    #[cfg(not(feature = "optimism"))]
853    pub const fn is_optimism(&self) -> bool {
854        false
855    }
856
857    /// Returns true if Tempo network mode is active
858    pub const fn is_tempo(&self) -> bool {
859        self.networks.is_tempo()
860    }
861
862    /// Returns the active hardfork.
863    pub fn hardfork(&self) -> FoundryHardfork {
864        if let Some(hardfork) =
865            self.fork.read().as_ref().and_then(|fork| fork.config.read().hardfork)
866        {
867            return hardfork;
868        }
869        self.hardfork
870    }
871
872    /// Returns canonical Ethereum transition configuration only for an Ethereum network.
873    fn ethereum_block_transitions(
874        &self,
875        hardfork: FoundryHardfork,
876        parent_beacon_block_root: Option<B256>,
877        execution_kind: BlockExecutionKind,
878    ) -> Option<EthereumBlockTransitions> {
879        if self.is_optimism() || self.is_tempo() {
880            return None;
881        }
882        let FoundryHardfork::Ethereum(hardfork) = hardfork else { return None };
883        Some(EthereumBlockTransitions {
884            hardfork,
885            deposit_contract_address: self.ethereum_deposit_contract_address(),
886            parent_beacon_block_root,
887            execution_kind,
888        })
889    }
890
891    /// Returns the configured deposit contract, then the canonical address for known chains.
892    fn ethereum_deposit_contract_address(&self) -> Address {
893        if let Some(address) = self
894            .genesis
895            .genesis_init
896            .as_ref()
897            .and_then(|genesis| genesis.config.deposit_contract_address)
898        {
899            return address;
900        }
901
902        match NamedChain::try_from(self.evm_env.read().cfg_env.chain_id) {
903            Ok(NamedChain::Sepolia) => SEPOLIA_DEPOSIT_CONTRACT_ADDRESS,
904            Ok(NamedChain::Holesky) => HOLESKY_DEPOSIT_CONTRACT_ADDRESS,
905            // Hoodi shares the mainnet address; other chains use Alloy's mainnet fallback.
906            _ => MAINNET_DEPOSIT_CONTRACT_ADDRESS,
907        }
908    }
909
910    /// Returns the active Tempo hardfork.
911    pub fn tempo_hardfork(&self) -> TempoHardfork {
912        TempoHardfork::from(self.hardfork())
913    }
914
915    /// Returns whether a Tempo hardfork is active on this backend.
916    pub fn is_tempo_hardfork_active(&self, hardfork: TempoHardfork) -> bool {
917        self.is_tempo() && self.tempo_hardfork() >= hardfork
918    }
919
920    /// Returns the precompiles for the current spec.
921    pub fn precompiles(&self) -> BTreeMap<String, Address> {
922        let spec_id = self.spec_id();
923        let mut precompiles =
924            PrecompilesMap::from_static(Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)));
925        let (chain_id, timestamp) = {
926            let evm_env = self.evm_env.read();
927            (evm_env.cfg_env.chain_id, evm_env.block_env.timestamp.saturating_to())
928        };
929        self.networks.inject_chain_precompiles(&mut precompiles, chain_id, timestamp);
930
931        let mut precompiles_map = BTreeMap::<String, Address>::default();
932        for address in precompiles.addresses() {
933            let precompile = precompiles.get(address).expect("precompile address must resolve");
934            precompiles_map.insert(precompile.precompile_id().name().to_string(), *address);
935        }
936
937        // Extend with configured network precompiles.
938        precompiles_map
939            .extend(self.networks.precompiles(self.is_tempo().then(|| self.tempo_hardfork())));
940
941        if let Some(factory) = &self.precompile_factory {
942            for (address, precompile) in factory.precompiles() {
943                precompiles_map.insert(precompile.precompile_id().to_string(), address);
944            }
945        }
946
947        precompiles_map
948    }
949
950    /// Returns the system contracts for the current spec.
951    pub fn system_contracts(&self) -> BTreeMap<SystemContract, Address> {
952        let mut system_contracts = BTreeMap::<SystemContract, Address>::default();
953
954        let spec_id = self.spec_id();
955
956        if spec_id >= SpecId::CANCUN {
957            system_contracts.extend(SystemContract::cancun());
958        }
959
960        if spec_id >= SpecId::PRAGUE {
961            system_contracts.extend(SystemContract::prague(None));
962        }
963
964        system_contracts
965    }
966
967    /// Returns [`BlobParams`] corresponding to the current spec.
968    pub fn blob_params(&self) -> BlobParams {
969        get_blob_params_by_spec_id(self.spec_id())
970    }
971
972    fn simulation_blob_params_at_timestamp(&self, timestamp: u64) -> BlobParams {
973        let spec_id = self.spec_id();
974        let configured_hardfork = self.hardfork();
975        if let FoundryHardfork::Ethereum(
976            configured
977            @ (EthereumHardfork::Osaka | EthereumHardfork::Bpo1 | EthereumHardfork::Bpo2),
978        ) = configured_hardfork
979            && let Some(hardfork) = FoundryHardfork::from_chain_and_timestamp(
980                self.evm_env.read().cfg_env.chain_id,
981                timestamp,
982            )
983            && let FoundryHardfork::Ethereum(
984                scheduled @ (EthereumHardfork::Osaka
985                | EthereumHardfork::Bpo1
986                | EthereumHardfork::Bpo2),
987            ) = hardfork
988        {
989            let hardfork = match (configured, scheduled) {
990                (EthereumHardfork::Bpo2, _) | (_, EthereumHardfork::Bpo2) => EthereumHardfork::Bpo2,
991                (EthereumHardfork::Bpo1, _) | (_, EthereumHardfork::Bpo1) => EthereumHardfork::Bpo1,
992                _ => EthereumHardfork::Osaka,
993            };
994            return Self::simulation_blob_params_for_hardfork(hardfork.into(), spec_id);
995        }
996        Self::simulation_blob_params_for_hardfork(configured_hardfork, spec_id)
997    }
998
999    fn simulation_blob_params_for_hardfork(
1000        hardfork: FoundryHardfork,
1001        spec_id: SpecId,
1002    ) -> BlobParams {
1003        match hardfork {
1004            FoundryHardfork::Ethereum(EthereumHardfork::Prague) => BlobParams::prague(),
1005            FoundryHardfork::Ethereum(EthereumHardfork::Osaka) => BlobParams::osaka(),
1006            FoundryHardfork::Ethereum(EthereumHardfork::Bpo1) => BlobParams::bpo1(),
1007            FoundryHardfork::Ethereum(EthereumHardfork::Bpo2) => BlobParams::bpo2(),
1008            FoundryHardfork::Ethereum(
1009                EthereumHardfork::Bpo3
1010                | EthereumHardfork::Bpo4
1011                | EthereumHardfork::Bpo5
1012                | EthereumHardfork::Amsterdam,
1013            ) => BlobParams::bpo2(),
1014            _ => get_blob_params_by_spec_id(spec_id),
1015        }
1016    }
1017
1018    /// Returns an error if EIP1559 is not active (pre Berlin)
1019    pub fn ensure_eip1559_active(&self) -> Result<(), BlockchainError> {
1020        if self.is_eip1559() {
1021            return Ok(());
1022        }
1023        Err(BlockchainError::EIP1559TransactionUnsupportedAtHardfork)
1024    }
1025
1026    /// Returns an error if EIP1559 is not active (pre muirGlacier)
1027    pub fn ensure_eip2930_active(&self) -> Result<(), BlockchainError> {
1028        if self.is_eip2930() {
1029            return Ok(());
1030        }
1031        Err(BlockchainError::EIP2930TransactionUnsupportedAtHardfork)
1032    }
1033
1034    pub fn ensure_eip4844_active(&self) -> Result<(), BlockchainError> {
1035        if self.is_eip4844() {
1036            return Ok(());
1037        }
1038        Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork)
1039    }
1040
1041    pub fn ensure_eip7702_active(&self) -> Result<(), BlockchainError> {
1042        if self.is_eip7702() {
1043            return Ok(());
1044        }
1045        Err(BlockchainError::EIP7702TransactionUnsupportedAtHardfork)
1046    }
1047
1048    /// Returns an error if op-stack deposits are not active
1049    #[cfg(feature = "optimism")]
1050    pub const fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> {
1051        if self.is_optimism() {
1052            return Ok(());
1053        }
1054        Err(BlockchainError::DepositTransactionUnsupported)
1055    }
1056
1057    /// Returns an error if Tempo transactions are not active
1058    pub const fn ensure_tempo_active(&self) -> Result<(), BlockchainError> {
1059        if self.is_tempo() {
1060            return Ok(());
1061        }
1062        Err(BlockchainError::TempoTransactionUnsupported)
1063    }
1064
1065    /// Builds the [`InspectorTxConfig`] from the backend's current settings.
1066    fn inspector_tx_config(&self) -> InspectorTxConfig {
1067        InspectorTxConfig {
1068            print_traces: self.print_traces,
1069            print_logs: self.print_logs,
1070            enable_steps_tracing: self.enable_steps_tracing,
1071            call_trace_decoder: self.call_trace_decoder.clone(),
1072        }
1073    }
1074
1075    /// Builds the [`PoolTxGasConfig`] from the given EVM environment.
1076    fn pool_tx_gas_config(&self, evm_env: &EvmEnv) -> PoolTxGasConfig {
1077        let spec_id = *evm_env.spec_id();
1078        let is_cancun = spec_id >= SpecId::CANCUN;
1079        let blob_params = self.blob_params();
1080        PoolTxGasConfig {
1081            disable_block_gas_limit: evm_env.cfg_env.disable_block_gas_limit,
1082            tx_gas_limit_cap: evm_env.cfg_env.tx_gas_limit_cap,
1083            tx_gas_limit_cap_resolved: evm_env.cfg_env.tx_gas_limit_cap(),
1084            max_blob_gas_per_block: blob_params.max_blob_gas_per_block(),
1085            is_cancun,
1086        }
1087    }
1088
1089    /// Returns the block gas limit
1090    pub fn gas_limit(&self) -> u64 {
1091        self.evm_env.read().block_env.gas_limit
1092    }
1093
1094    /// Sets the block gas limit
1095    pub fn set_gas_limit(&self, gas_limit: u64) {
1096        self.evm_env.write().block_env.gas_limit = gas_limit;
1097    }
1098
1099    /// Returns the current base fee
1100    pub fn base_fee(&self) -> u64 {
1101        self.fees.base_fee()
1102    }
1103
1104    /// Returns whether the minimum suggested priority fee is enforced
1105    pub const fn is_min_priority_fee_enforced(&self) -> bool {
1106        self.fees.is_min_priority_fee_enforced()
1107    }
1108
1109    pub fn excess_blob_gas_and_price(&self) -> Option<BlobExcessGasAndPrice> {
1110        self.fees.excess_blob_gas_and_price()
1111    }
1112
1113    /// Sets the current basefee
1114    pub fn set_base_fee(&self, basefee: u64) {
1115        self.fees.set_base_fee(basefee)
1116    }
1117
1118    /// Sets the gas price
1119    pub fn set_gas_price(&self, price: u128) {
1120        self.fees.set_gas_price(price)
1121    }
1122
1123    pub fn elasticity(&self) -> f64 {
1124        self.fees.elasticity()
1125    }
1126
1127    /// Returns the total difficulty of the chain until this block
1128    ///
1129    /// Note: this will always be `0` in memory mode
1130    /// In forking mode this will always be the total difficulty of the forked block
1131    pub fn total_difficulty(&self) -> U256 {
1132        self.blockchain.storage.read().total_difficulty
1133    }
1134
1135    /// Creates a new `evm_snapshot` at the current height.
1136    ///
1137    /// Returns the id of the snapshot created.
1138    pub async fn create_state_snapshot(&self) -> U256 {
1139        let num = self.best_number();
1140        let hash = self.best_hash();
1141        let id = self.db.write().await.snapshot_state();
1142        trace!(target: "backend", "creating snapshot {} at {}", id, num);
1143        self.active_state_snapshots.lock().insert(id, (num, hash));
1144        id
1145    }
1146
1147    pub fn list_state_snapshots(&self) -> BTreeMap<U256, (u64, B256)> {
1148        self.active_state_snapshots.lock().clone().into_iter().collect()
1149    }
1150
1151    /// Returns the environment for the next block
1152    fn next_evm_env(&self) -> EvmEnv {
1153        let mut evm_env = self.evm_env.read().clone();
1154        // increase block number for this block
1155        evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1));
1156        evm_env.block_env.basefee = self.base_fee();
1157        evm_env.block_env.blob_excess_gas_and_price = self.excess_blob_gas_and_price();
1158        evm_env.block_env.timestamp = U256::from(self.time.current_call_timestamp());
1159        evm_env
1160    }
1161
1162    /// Returns the environment for replaying transactions from a historical block.
1163    fn tx_replay_evm_env(&self, header: &impl BlockHeader) -> EvmEnv {
1164        let mut evm_env = self.evm_env.read().clone();
1165        evm_env.block_env = block_env_from_header(header);
1166        apply_chain_specific_tx_replay_env_changes(&mut evm_env);
1167        evm_env
1168    }
1169
1170    /// Creates the database and environment for replaying a locally mined block.
1171    ///
1172    /// An empty block execution applies protocol-level pre-execution changes, such as the
1173    /// EIP-2935 parent hash system call, through the same network-specific executor used while
1174    /// mining.
1175    fn prepare_block_replay<'a>(
1176        &self,
1177        block: &Block,
1178        parent_state: &'a StateDb,
1179    ) -> Result<(CacheDB<&'a StateDb>, EvmEnv), BlockchainError> {
1180        let mut cache_db = AnvilCacheDB::new(parent_state);
1181        let evm_env = self.tx_replay_evm_env(&block.header);
1182        let spec_id = *evm_env.spec_id();
1183        let inspector_tx_config = self.inspector_tx_config();
1184        let gas_config = self.pool_tx_gas_config(&evm_env);
1185
1186        self.execute_with_block_executor(
1187            &mut cache_db,
1188            &evm_env,
1189            block.header.parent_hash,
1190            spec_id,
1191            block.header.parent_beacon_block_root,
1192            BlockExecutionKind::TransactionPrefix,
1193            &[],
1194            &gas_config,
1195            &inspector_tx_config,
1196            &|_, _| Ok(()),
1197        )?;
1198
1199        Ok((cache_db.0, evm_env))
1200    }
1201
1202    /// Builds [`Inspector`] with the configured options.
1203    fn build_inspector(&self) -> AnvilInspector {
1204        let mut inspector = AnvilInspector::default();
1205
1206        if self.print_logs {
1207            inspector = inspector.with_log_collector();
1208        }
1209        if self.print_traces {
1210            inspector = inspector.with_trace_printer();
1211        }
1212
1213        inspector
1214    }
1215
1216    /// Builds an inspector configured for block mining (tracing always enabled).
1217    fn build_mining_inspector(&self) -> AnvilInspector {
1218        let mut inspector = AnvilInspector::default().with_tracing();
1219        if self.enable_steps_tracing {
1220            inspector = inspector.with_steps_tracing();
1221        }
1222        if self.print_logs {
1223            inspector = inspector.with_log_collector();
1224        }
1225        if self.print_traces {
1226            inspector = inspector.with_trace_printer();
1227        }
1228        inspector
1229    }
1230
1231    /// Returns a new block event stream that yields Notifications when a new block was added or
1232    /// when logs were removed from the canonical chain due to a reorg
1233    pub fn new_block_notifications(&self) -> ChainNotifications {
1234        let (tx, rx) = unbounded();
1235        self.new_block_listeners.lock().push(tx);
1236        trace!(target: "backed", "added new block listener");
1237        rx
1238    }
1239
1240    /// Returns the number of new-block listeners. Closed listeners are pruned lazily on the next
1241    /// new block notification.
1242    pub fn new_block_listeners_count(&self) -> usize {
1243        self.new_block_listeners.lock().len()
1244    }
1245
1246    /// Notifies all `new_block_listeners` about the new block
1247    fn notify_on_new_block(&self, header: Header, hash: B256) {
1248        // cleanup closed notification streams first, if the channel is closed we can remove the
1249        // sender half for the set
1250        self.new_block_listeners.lock().retain(|tx| !tx.is_closed());
1251
1252        let notification =
1253            ChainNotification::Block(NewBlockNotification { hash, header: Arc::new(header) });
1254
1255        self.new_block_listeners
1256            .lock()
1257            .retain(|tx| tx.unbounded_send(notification.clone()).is_ok());
1258    }
1259
1260    /// Notifies all `new_block_listeners` about the logs that were removed from the canonical
1261    /// chain due to a reorg.
1262    fn notify_on_removed_logs(&self, logs: Vec<Log>) {
1263        // cleanup closed notification streams first, if the channel is closed we can remove the
1264        // sender half for the set
1265        self.new_block_listeners.lock().retain(|tx| !tx.is_closed());
1266
1267        let notification = ChainNotification::RemovedLogs(Arc::new(logs));
1268
1269        self.new_block_listeners
1270            .lock()
1271            .retain(|tx| tx.unbounded_send(notification.clone()).is_ok());
1272    }
1273
1274    /// Returns the block number for the given block id
1275    pub fn convert_block_number(&self, block: Option<BlockNumber>) -> u64 {
1276        let current = self.best_number();
1277        match block.unwrap_or(BlockNumber::Latest) {
1278            BlockNumber::Latest | BlockNumber::Pending => current,
1279            BlockNumber::Earliest => 0,
1280            BlockNumber::Number(num) => num,
1281            BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch),
1282            BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2),
1283        }
1284    }
1285
1286    /// Returns the canonical hash for the given block number.
1287    pub(crate) fn block_hash_by_number(&self, number: u64) -> Option<B256> {
1288        self.blockchain.hash(BlockNumber::Number(number).into(), self.slots_in_an_epoch)
1289    }
1290
1291    /// Returns the block and its hash for the given id
1292    pub(crate) fn get_block_with_hash(&self, id: impl Into<BlockId>) -> Option<(Block, B256)> {
1293        let hash = self.blockchain.hash(id.into(), self.slots_in_an_epoch)?;
1294        let block = self.get_block_by_hash(hash)?;
1295        Some((block, hash))
1296    }
1297
1298    pub fn get_block(&self, id: impl Into<BlockId>) -> Option<Block> {
1299        self.get_block_with_hash(id).map(|(block, _)| block)
1300    }
1301
1302    pub fn get_block_by_hash(&self, hash: B256) -> Option<Block> {
1303        self.blockchain.get_block_by_hash(&hash)
1304    }
1305
1306    /// Returns the base fees for the block after a fee history range.
1307    ///
1308    /// Mining publishes a new canonical block before advancing the fee manager. Holding the mining
1309    /// lock makes choosing between an existing child and the current head's pending fees atomic
1310    /// with that publication sequence.
1311    pub(crate) async fn fee_history_next_fees(&self, highest: u64) -> Option<(u128, u128)> {
1312        let _mining_guard = self.mining.lock().await;
1313        let next_number = highest.checked_add(1)?;
1314        if let Some(block) = self.get_block(next_number) {
1315            Some((
1316                block.header.base_fee_per_gas.unwrap_or_default() as u128,
1317                block.header.blob_fee(self.blob_params()).unwrap_or_default(),
1318            ))
1319        } else if highest == self.best_number() {
1320            Some((self.fees().base_fee() as u128, self.fees().base_fee_per_blob_gas()))
1321        } else {
1322            None
1323        }
1324    }
1325
1326    /// Returns the traces for the given transaction
1327    pub(crate) fn mined_parity_trace_transaction(
1328        &self,
1329        hash: B256,
1330    ) -> Option<Vec<LocalizedTransactionTrace>> {
1331        self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.parity_traces())
1332    }
1333
1334    /// Returns the traces for the given block
1335    pub(crate) fn mined_parity_trace_block(
1336        &self,
1337        block: u64,
1338    ) -> Option<Vec<LocalizedTransactionTrace>> {
1339        let block = self.get_block(block)?;
1340        let mut traces = vec![];
1341        let storage = self.blockchain.storage.read();
1342        for tx in block.body.transactions {
1343            if let Some(mined_tx) = storage.transactions.get(&tx.hash()) {
1344                traces.extend(mined_tx.parity_traces());
1345            }
1346        }
1347        Some(traces)
1348    }
1349
1350    /// Returns the mined transaction for the given hash
1351    pub(crate) fn mined_transaction(&self, hash: B256) -> Option<MinedTransaction<N>> {
1352        self.blockchain.storage.read().transactions.get(&hash).cloned()
1353    }
1354
1355    /// Overrides the given signature to impersonate the specified address during ecrecover.
1356    pub async fn impersonate_signature(
1357        &self,
1358        signature: Bytes,
1359        address: Address,
1360    ) -> Result<(), BlockchainError> {
1361        self.cheats.add_recover_override(signature, address);
1362        Ok(())
1363    }
1364
1365    /// Returns code by its hash
1366    pub async fn debug_code_by_hash(
1367        &self,
1368        code_hash: B256,
1369        block_id: Option<BlockId>,
1370    ) -> Result<Option<Bytes>, BlockchainError> {
1371        if let Ok(code) = self.db.read().await.code_by_hash_ref(code_hash) {
1372            return Ok(Some(code.original_bytes()));
1373        }
1374        if let Some(fork) = self.get_fork() {
1375            return Ok(fork.debug_code_by_hash(code_hash, block_id).await?);
1376        }
1377
1378        Ok(None)
1379    }
1380
1381    /// Returns the value associated with a key from the database
1382    /// Currently only supports bytecode lookups.
1383    ///
1384    /// Based on Reth implementation: <https://github.com/paradigmxyz/reth/blob/66cfa9ed1a8c4bc2424aacf6fb2c1e67a78ee9a2/crates/rpc/rpc/src/debug.rs#L1146-L1178>
1385    ///
1386    /// Key should be: 0x63 (1-byte prefix) + 32 bytes (code_hash)
1387    /// Total key length must be 33 bytes.
1388    pub async fn debug_db_get(&self, key: String) -> Result<Option<Bytes>, BlockchainError> {
1389        let key_bytes = if key.starts_with("0x") {
1390            hex::decode(&key)
1391                .map_err(|_| BlockchainError::Message("Invalid hex key".to_string()))?
1392        } else {
1393            key.into_bytes()
1394        };
1395
1396        // Validate key length: must be 33 bytes (1 byte prefix + 32 bytes code hash)
1397        if key_bytes.len() != 33 {
1398            return Err(BlockchainError::Message(format!(
1399                "Invalid key length: expected 33 bytes, got {}",
1400                key_bytes.len()
1401            )));
1402        }
1403
1404        // Check for bytecode prefix (0x63 = 'c' in ASCII)
1405        if key_bytes[0] != 0x63 {
1406            return Err(BlockchainError::Message(
1407                "Key prefix must be 0x63 for code hash lookups".to_string(),
1408            ));
1409        }
1410
1411        let code_hash = B256::from_slice(&key_bytes[1..33]);
1412
1413        // Use the existing debug_code_by_hash method to retrieve the bytecode
1414        self.debug_code_by_hash(code_hash, None).await
1415    }
1416
1417    fn mined_block_by_hash(&self, hash: B256) -> Option<AnyRpcBlock> {
1418        let block = self.blockchain.get_block_by_hash(&hash)?;
1419        Some(self.convert_block_with_hash(block, Some(hash)))
1420    }
1421
1422    pub(crate) async fn mined_transactions_by_block_number(
1423        &self,
1424        number: BlockNumber,
1425    ) -> Option<Vec<AnyRpcTransaction>> {
1426        if let Some(block) = self.get_block(number) {
1427            return self.mined_transactions_in_block(&block);
1428        }
1429        None
1430    }
1431
1432    /// Returns all transactions given a block
1433    pub(crate) fn mined_transactions_in_block(
1434        &self,
1435        block: &Block,
1436    ) -> Option<Vec<AnyRpcTransaction>> {
1437        let mut transactions = Vec::with_capacity(block.body.transactions.len());
1438        let base_fee = block.header.base_fee_per_gas();
1439        let storage = self.blockchain.storage.read();
1440        for hash in block.body.transactions.iter().map(|tx| tx.hash()) {
1441            let info = storage.transactions.get(&hash)?.info.clone();
1442            let tx = block.body.transactions.get(info.transaction_index as usize)?.clone();
1443
1444            let tx = transaction_build(Some(hash), tx, Some(block), Some(info), base_fee);
1445            transactions.push(tx);
1446        }
1447        Some(transactions)
1448    }
1449
1450    pub fn mined_block_by_number(&self, number: BlockNumber) -> Option<AnyRpcBlock> {
1451        let (block, hash) = self.get_block_with_hash(number)?;
1452        let mut block = self.convert_block_with_hash(block, Some(hash));
1453        block.transactions.convert_to_hashes();
1454        Some(block)
1455    }
1456
1457    pub fn get_full_block(&self, id: impl Into<BlockId>) -> Option<AnyRpcBlock> {
1458        let (block, hash) = self.get_block_with_hash(id)?;
1459        let transactions = self.mined_transactions_in_block(&block)?;
1460        let mut block = self.convert_block_with_hash(block, Some(hash));
1461        block.inner.transactions = BlockTransactions::Full(transactions);
1462        Some(block)
1463    }
1464
1465    /// Takes a block as it's stored internally and returns the eth api conform block format.
1466    pub fn convert_block(&self, block: Block) -> AnyRpcBlock {
1467        self.convert_block_with_hash(block, None)
1468    }
1469
1470    /// Takes a block as it's stored internally and returns the eth api conform block format.
1471    /// If `known_hash` is provided, it will be used instead of computing `hash_slow()`.
1472    pub fn convert_block_with_hash(&self, block: Block, known_hash: Option<B256>) -> AnyRpcBlock {
1473        let size = U256::from(alloy_rlp::encode(canonical_block(block.clone())).len() as u32);
1474
1475        let header = block.header.clone();
1476        let transactions = block.body.transactions;
1477
1478        let hash = known_hash.unwrap_or_else(|| header.hash_slow());
1479        let number = header.number();
1480        let withdrawals_root = header.withdrawals_root();
1481        let tempo_fields = header
1482            .as_tempo()
1483            .map(|header| {
1484                (
1485                    header.timestamp_millis(),
1486                    header.general_gas_limit,
1487                    header.shared_gas_limit,
1488                    header.timestamp_millis_part,
1489                )
1490            })
1491            .or_else(|| {
1492                self.is_tempo()
1493                    .then(|| (header.timestamp().saturating_mul(1000), header.gas_limit(), 0, 0))
1494            });
1495
1496        let block = AlloyBlock {
1497            header: AlloyHeader {
1498                inner: AnyHeader::from(header.into_inner()),
1499                hash,
1500                total_difficulty: Some(self.total_difficulty()),
1501                size: Some(size),
1502            },
1503            transactions: alloy_rpc_types::BlockTransactions::Hashes(
1504                transactions.into_iter().map(|tx| tx.hash()).collect(),
1505            ),
1506            uncles: vec![],
1507            withdrawals: withdrawals_root.map(|_| Default::default()),
1508        };
1509
1510        let mut block = WithOtherFields::new(block);
1511
1512        // If Arbitrum, apply chain specifics to converted block.
1513        if is_arbitrum(self.chain_id().to::<u64>()) {
1514            // Set `l1BlockNumber` field.
1515            block.other.insert("l1BlockNumber".to_string(), number.into());
1516        }
1517
1518        if let Some((
1519            timestamp_millis,
1520            general_gas_limit,
1521            shared_gas_limit,
1522            timestamp_millis_part,
1523        )) = tempo_fields
1524        {
1525            block.other.insert(
1526                "timestampMillis".to_string(),
1527                serde_json::Value::String(format!("0x{timestamp_millis:x}")),
1528            );
1529            block.other.insert(
1530                "mainBlockGeneralGasLimit".to_string(),
1531                serde_json::Value::String(format!("0x{general_gas_limit:x}")),
1532            );
1533            block.other.insert(
1534                "sharedGasLimit".to_string(),
1535                serde_json::Value::String(format!("0x{shared_gas_limit:x}")),
1536            );
1537            block.other.insert(
1538                "timestampMillisPart".to_string(),
1539                serde_json::Value::String(format!("0x{timestamp_millis_part:x}")),
1540            );
1541        }
1542
1543        AnyRpcBlock::from(block)
1544    }
1545
1546    pub async fn block_by_hash(&self, hash: B256) -> Result<Option<AnyRpcBlock>, BlockchainError> {
1547        trace!(target: "backend", "get block by hash {:?}", hash);
1548        if let tx @ Some(_) = self.mined_block_by_hash(hash) {
1549            return Ok(tx);
1550        }
1551
1552        if let Some(fork) = self.get_fork() {
1553            return Ok(fork.block_by_hash(hash).await?);
1554        }
1555
1556        Ok(None)
1557    }
1558
1559    pub async fn block_by_hash_full(
1560        &self,
1561        hash: B256,
1562    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
1563        trace!(target: "backend", "get block by hash {:?}", hash);
1564        if let tx @ Some(_) = self.get_full_block(hash) {
1565            return Ok(tx);
1566        }
1567
1568        if let Some(fork) = self.get_fork() {
1569            return Ok(fork.block_by_hash_full(hash).await?);
1570        }
1571
1572        Ok(None)
1573    }
1574
1575    pub async fn block_by_number(
1576        &self,
1577        number: BlockNumber,
1578    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
1579        trace!(target: "backend", "get block by number {:?}", number);
1580        if let tx @ Some(_) = self.mined_block_by_number(number) {
1581            return Ok(tx);
1582        }
1583
1584        if let Some(fork) = self.get_fork() {
1585            let number = self.convert_block_number(Some(number));
1586            if fork.predates_fork_inclusive(number) {
1587                return Ok(fork.block_by_number(number).await?);
1588            }
1589        }
1590
1591        Ok(None)
1592    }
1593
1594    pub async fn block_by_number_full(
1595        &self,
1596        number: BlockNumber,
1597    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
1598        trace!(target: "backend", "get block by number {:?}", number);
1599        if let tx @ Some(_) = self.get_full_block(number) {
1600            return Ok(tx);
1601        }
1602
1603        if let Some(fork) = self.get_fork() {
1604            let number = self.convert_block_number(Some(number));
1605            if fork.predates_fork_inclusive(number) {
1606                return Ok(fork.block_by_number_full(number).await?);
1607            }
1608        }
1609
1610        Ok(None)
1611    }
1612
1613    /// Converts the `BlockNumber` into a numeric value
1614    ///
1615    /// # Errors
1616    ///
1617    /// returns an error if the requested number is larger than the current height
1618    pub async fn ensure_block_number<T: Into<BlockId>>(
1619        &self,
1620        block_id: Option<T>,
1621    ) -> Result<u64, BlockchainError> {
1622        let current = self.best_number();
1623        let requested =
1624            match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) {
1625                BlockId::Hash(hash) => {
1626                    self.block_by_hash(hash.block_hash)
1627                        .await?
1628                        .ok_or(BlockchainError::BlockNotFound)?
1629                        .header
1630                        .number
1631                }
1632                BlockId::Number(num) => match num {
1633                    BlockNumber::Latest | BlockNumber::Pending => current,
1634                    BlockNumber::Earliest => U64::ZERO.to::<u64>(),
1635                    BlockNumber::Number(num) => num,
1636                    BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch),
1637                    BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2),
1638                },
1639            };
1640
1641        if requested > current {
1642            Err(BlockchainError::BlockOutOfRange(current, requested))
1643        } else {
1644            Ok(requested)
1645        }
1646    }
1647
1648    /// Injects all configured precompiles into the given precompile map.
1649    ///
1650    /// This applies four layers:
1651    /// 1. Network-specific precompiles (e.g. Tempo, OP)
1652    /// 2. Chain- and timestamp-specific precompiles
1653    /// 3. User-provided precompiles via [`PrecompileFactory`]
1654    /// 4. Cheatcode ecrecover overrides (if active)
1655    fn inject_precompiles(&self, precompiles: &mut PrecompilesMap, evm_env: &EvmEnv) {
1656        self.networks.inject_precompiles(precompiles);
1657        self.networks.inject_chain_precompiles(
1658            precompiles,
1659            evm_env.cfg_env.chain_id,
1660            evm_env.block_env.timestamp.saturating_to(),
1661        );
1662
1663        if let Some(factory) = &self.precompile_factory {
1664            factory.install(precompiles);
1665        }
1666
1667        let cheats = Arc::new(self.cheats.clone());
1668        if cheats.has_recover_overrides() {
1669            let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats));
1670            precompiles.apply_precompile(&EC_RECOVER, move |_| {
1671                Some(DynPrecompile::new_stateful(
1672                    cheat_ecrecover.precompile_id().clone(),
1673                    move |input| cheat_ecrecover.call(input),
1674                ))
1675            });
1676        }
1677    }
1678
1679    fn inject_arbitrum_precompile(&self, precompiles: &mut PrecompilesMap, evm_env: &EvmEnv) {
1680        let Some(block_number) = self.arbitrum_block_number(evm_env) else { return };
1681        precompiles.apply_precompile(&arbitrum::ARB_SYS_ADDRESS, move |_| {
1682            Some(arbitrum::arb_sys_precompile(block_number))
1683        });
1684    }
1685
1686    fn simulation_precompile_overrides(
1687        &self,
1688        state_overrides: Option<&StateOverride>,
1689        evm_env: &EvmEnv,
1690    ) -> Result<SimulationPrecompileOverrides, BlockchainError> {
1691        let mut moves = state_overrides
1692            .into_iter()
1693            .flatten()
1694            .filter_map(|(source, account)| {
1695                account.move_precompile_to.map(|destination| (*source, destination))
1696            })
1697            .collect::<Vec<_>>();
1698        moves.sort_unstable();
1699        if moves.is_empty() {
1700            return Ok(SimulationPrecompileOverrides::default());
1701        }
1702        if self.is_optimism() || self.is_tempo() {
1703            return Err(simulate_rpc_error(
1704                -32000,
1705                "precompile moves are not supported on this network",
1706            ));
1707        }
1708
1709        let mut precompiles = PrecompilesMap::from_static(Precompiles::new(
1710            PrecompileSpecId::from_spec_id(*evm_env.spec_id()),
1711        ));
1712        self.inject_precompiles(&mut precompiles, evm_env);
1713        self.inject_arbitrum_precompile(&mut precompiles, evm_env);
1714        let precompile_addresses = precompiles.addresses().copied().collect::<HashSet<_>>();
1715
1716        // Validate every source first so invalid-source errors take precedence over the more
1717        // specific move errors below.
1718        for (source, _) in &moves {
1719            if !precompile_addresses.contains(source) {
1720                return Err(simulate_rpc_error(
1721                    -32000,
1722                    format!("account {source} is not a precompile"),
1723                ));
1724            }
1725        }
1726        for (source, destination) in &moves {
1727            if source == destination {
1728                return Err(simulate_rpc_error(
1729                    -38022,
1730                    format!("cannot move precompile {source} to itself"),
1731                ));
1732            }
1733        }
1734        let mut destinations = Vec::with_capacity(moves.len());
1735        for (_, destination) in &moves {
1736            if destinations.contains(destination) {
1737                return Err(simulate_rpc_error(
1738                    -38023,
1739                    format!("multiple precompiles moved to {destination}"),
1740                ));
1741            }
1742            destinations.push(*destination);
1743        }
1744
1745        Ok(SimulationPrecompileOverrides { moves })
1746    }
1747
1748    fn apply_simulation_precompile_overrides(
1749        &self,
1750        precompiles: &mut PrecompilesMap,
1751        overrides: &SimulationPrecompileOverrides,
1752    ) -> Result<alloy_primitives::map::AddressSet, BlockchainError> {
1753        let warm_addresses = precompiles.addresses().copied().collect();
1754        precompiles.move_precompiles(overrides.moves.iter().copied()).map_err(
1755            |MovePrecompileError::NotAPrecompile(address)| {
1756                simulate_rpc_error(-32000, format!("account {address} is not a precompile"))
1757            },
1758        )?;
1759
1760        // A dynamic lookup must not restore a precompile removed from its protocol address.
1761        let moved_sources =
1762            Arc::new(overrides.moves.iter().map(|(source, _)| *source).collect::<HashSet<_>>());
1763        precompiles.map_precompile_lookup(move |address, previous| {
1764            if moved_sources.contains(address) {
1765                None
1766            } else {
1767                previous.and_then(|lookup| lookup.lookup(address))
1768            }
1769        });
1770        Ok(warm_addresses)
1771    }
1772
1773    fn inject_tempo_precompiles<DB, I>(
1774        &self,
1775        evm: &mut tempo_evm::evm::TempoEvm<DB, I>,
1776        evm_env: &EvmEnv,
1777    ) where
1778        DB: Database,
1779        I: Inspector<TempoContext<DB>>,
1780    {
1781        self.inject_precompiles(evm.precompiles_mut(), evm_env);
1782        // Re-extend Tempo precompiles, preserving shared non-creditable slots.
1783        let cfg = evm.ctx().cfg.clone();
1784        let non_creditable_slots = evm.non_creditable_slots();
1785        extend_tempo_precompiles(
1786            evm.precompiles_mut(),
1787            &cfg,
1788            StorageActions::disabled(),
1789            non_creditable_slots,
1790        );
1791    }
1792
1793    /// Executes a call with the Ethereum EVM.
1794    ///
1795    /// Creates an Ethereum EVM, injects precompiles, and transacts with a
1796    /// plain [`TxEnv`].
1797    fn transact_eth_with_inspector_ref<'db, I, DB>(
1798        &self,
1799        db: &'db DB,
1800        evm_env: &EvmEnv,
1801        inspector: &mut I,
1802        tx_env: TxEnv,
1803    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
1804    where
1805        DB: DatabaseRef + ?Sized,
1806        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
1807        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
1808    {
1809        self.transact_eth_with_inspector_ref_and_precompile_overrides(
1810            db,
1811            evm_env,
1812            inspector,
1813            tx_env,
1814            &SimulationPrecompileOverrides::default(),
1815        )
1816    }
1817
1818    fn transact_eth_with_inspector_ref_and_precompile_overrides<'db, I, DB>(
1819        &self,
1820        db: &'db DB,
1821        evm_env: &EvmEnv,
1822        inspector: &mut I,
1823        tx_env: TxEnv,
1824        overrides: &SimulationPrecompileOverrides,
1825    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
1826    where
1827        DB: DatabaseRef + ?Sized,
1828        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
1829        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
1830    {
1831        let mut evm = EthEvmFactory::default().create_evm_with_inspector(
1832            WrapDatabaseRef(db),
1833            evm_env.clone(),
1834            inspector,
1835        );
1836        self.inject_precompiles(evm.precompiles_mut(), evm_env);
1837        self.inject_arbitrum_precompile(evm.precompiles_mut(), evm_env);
1838        if !overrides.moves.is_empty() {
1839            let warm_addresses =
1840                self.apply_simulation_precompile_overrides(evm.precompiles_mut(), overrides)?;
1841            // EIP-2929 warms protocol precompile addresses, not simulation-only destinations.
1842            evm.ctx_mut().journal_mut().warm_precompiles(&warm_addresses);
1843        }
1844        Ok(evm.transact(tx_env)?)
1845    }
1846
1847    fn transact_eth_simulation_with_inspector_ref<'db, I, DB>(
1848        &self,
1849        db: &'db DB,
1850        evm_env: &EvmEnv,
1851        inspector: &mut I,
1852        tx_env: TxEnv,
1853        overrides: &SimulationPrecompileOverrides,
1854    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
1855    where
1856        DB: DatabaseRef + ?Sized,
1857        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
1858        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
1859    {
1860        let mut evm = EthEvmFactory::default().create_evm_with_inspector(
1861            WrapDatabaseRef(db),
1862            evm_env.clone(),
1863            inspector,
1864        );
1865        self.inject_precompiles(evm.precompiles_mut(), evm_env);
1866        self.inject_arbitrum_precompile(evm.precompiles_mut(), evm_env);
1867        if !overrides.moves.is_empty() {
1868            let warm_addresses =
1869                self.apply_simulation_precompile_overrides(evm.precompiles_mut(), overrides)?;
1870            evm.ctx_mut().journal_mut().warm_precompiles(&warm_addresses);
1871        }
1872
1873        let mut evm = evm.into_inner();
1874        evm.ctx_mut().set_tx(tx_env);
1875        let mut handler = SimulationHandler::<
1876            _,
1877            revm::context::result::EVMError<DatabaseError>,
1878            EthFrame<EthInterpreter>,
1879        >::default();
1880        let result = handler.inspect_run(&mut evm)?;
1881        let state = evm.ctx_mut().journal_mut().finalize();
1882        Ok(ResultAndState { result, state })
1883    }
1884
1885    /// Builds the appropriate tx env from a [`FoundryTxEnvelope`], executes via the correct
1886    /// EVM backend (Op/Tempo/Eth), and returns both the result and the base [`TxEnv`].
1887    fn transact_envelope_with_inspector_ref<'db, I, DB>(
1888        &self,
1889        db: &'db DB,
1890        evm_env: &EvmEnv,
1891        inspector: &mut I,
1892        tx: &FoundryTxEnvelope,
1893        sender: Address,
1894    ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
1895    where
1896        DB: DatabaseRef + ?Sized,
1897        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
1898        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
1899    {
1900        if tx.is_tempo() {
1901            let tx_env: TempoTxEnv =
1902                FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
1903            let base = tx_env.inner.clone();
1904            let result = self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)?;
1905            return Ok((result, base));
1906        }
1907        #[cfg(feature = "optimism")]
1908        if self.is_optimism() {
1909            let op_tx: OpTransaction<TxEnv> =
1910                FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
1911            let base = op_tx.base.clone();
1912            let result = self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx)?;
1913            return Ok((result, base));
1914        }
1915        let tx_env: TxEnv =
1916            FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
1917        let base = tx_env.clone();
1918        let result = self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?;
1919        Ok((result, base))
1920    }
1921
1922    /// Builds the Tempo [`EvmEnv`] (spec, gas params, [`TempoBlockEnv`]) from a base
1923    /// env.
1924    fn build_tempo_evm_env(&self, evm_env: &EvmEnv) -> EvmEnvFor<TempoEvmNetwork> {
1925        let hardfork = self.tempo_hardfork();
1926        EvmEnv::new(
1927            evm_env.cfg_env.clone().with_spec_and_gas_params(hardfork, tempo_gas_params(hardfork)),
1928            TempoBlockEnv {
1929                inner: evm_env.block_env.clone(),
1930                timestamp_millis_part: 0,
1931                ..Default::default()
1932            },
1933        )
1934    }
1935
1936    /// Creates a Tempo EVM, injects precompiles, and transacts with a native [`TempoTxEnv`].
1937    fn transact_tempo_with_inspector_ref<'db, I, DB>(
1938        &self,
1939        db: &'db DB,
1940        evm_env: &EvmEnv,
1941        inspector: &mut I,
1942        tx_env: TempoTxEnv,
1943    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
1944    where
1945        DB: DatabaseRef + ?Sized,
1946        I: Inspector<TempoContext<WrapDatabaseRef<&'db DB>>>,
1947        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
1948    {
1949        let tempo_env = self.build_tempo_evm_env(evm_env);
1950        let mut evm = TempoEvmFactory::default().create_evm_with_inspector(
1951            WrapDatabaseRef(db),
1952            tempo_env,
1953            inspector,
1954        );
1955        self.inject_tempo_precompiles(&mut evm, evm_env);
1956        let result = evm.transact(tx_env)?;
1957        Ok(ResultAndState {
1958            result: result.result.map_haltreason(|h| match h {
1959                TempoHaltReason::Ethereum(eth) => eth,
1960                _ => HaltReason::PrecompileError,
1961            }),
1962            state: result.state,
1963        })
1964    }
1965
1966    /// Creates a concrete EVM + [`AnvilBlockExecutor`], runs pre-execution changes, and
1967    /// executes pool transactions. Returns the execution results and drops the EVM.
1968    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
1969    fn execute_with_block_executor<DB>(
1970        &self,
1971        db: DB,
1972        evm_env: &EvmEnv,
1973        parent_hash: B256,
1974        spec_id: SpecId,
1975        parent_beacon_block_root: Option<B256>,
1976        execution_kind: BlockExecutionKind,
1977        pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
1978        gas_config: &PoolTxGasConfig,
1979        inspector_tx_config: &InspectorTxConfig,
1980        validator: &dyn Fn(
1981            &PoolTransaction<FoundryTxEnvelope>,
1982            &AccountInfo,
1983        ) -> Result<(), InvalidTransactionError>,
1984    ) -> Result<
1985        (ExecutedPoolTransactions<FoundryTxEnvelope>, BlockExecutionResult<FoundryReceiptEnvelope>),
1986        BlockchainError,
1987    >
1988    where
1989        DB: StateDB<Error = DatabaseError>,
1990    {
1991        let inspector = self.build_mining_inspector();
1992        let ethereum_transitions = self.ethereum_block_transitions(
1993            self.hardfork(),
1994            parent_beacon_block_root,
1995            execution_kind,
1996        );
1997
1998        macro_rules! run {
1999            ($evm:expr) => {{
2000                self.inject_precompiles($evm.precompiles_mut(), evm_env);
2001                self.inject_arbitrum_precompile($evm.precompiles_mut(), evm_env);
2002                let mut executor =
2003                    AnvilBlockExecutor::new($evm, parent_hash, spec_id, ethereum_transitions);
2004                executor
2005                    .apply_pre_execution_changes()
2006                    .map_err(|err| BlockchainError::Internal(err.to_string()))?;
2007                let pool_result = execute_pool_transactions(
2008                    &mut executor,
2009                    pool_transactions,
2010                    gas_config,
2011                    inspector_tx_config,
2012                    self.cheats(),
2013                    validator,
2014                );
2015                let (evm, block_result) =
2016                    executor.finish().map_err(|err| BlockchainError::Internal(err.to_string()))?;
2017                drop(evm);
2018                Ok((pool_result, block_result))
2019            }};
2020        }
2021
2022        #[cfg(feature = "optimism")]
2023        if self.is_optimism() {
2024            let op_env = EvmEnv::new(
2025                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(self.hardfork.into()),
2026                evm_env.block_env.clone(),
2027            );
2028            let mut evm =
2029                OpEvmFactory::<OpTx>::default().create_evm_with_inspector(db, op_env, inspector);
2030            return run!(evm);
2031        }
2032
2033        if self.is_tempo() {
2034            let tempo_env = self.build_tempo_evm_env(evm_env);
2035            let mut evm =
2036                TempoEvmFactory::default().create_evm_with_inspector(db, tempo_env, inspector);
2037            run!(evm)
2038        } else {
2039            let mut evm =
2040                EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
2041            run!(evm)
2042        }
2043    }
2044
2045    /// Applies Ethereum block-start transitions to a disposable simulation candidate.
2046    fn apply_simulation_pre_execution_changes<DB>(
2047        &self,
2048        db: DB,
2049        evm_env: &EvmEnv,
2050        parent_hash: B256,
2051        transitions: EthereumBlockTransitions,
2052    ) -> Result<(), BlockchainError>
2053    where
2054        DB: StateDB<Error = DatabaseError>,
2055    {
2056        let inspector = self.build_mining_inspector();
2057        let mut evm =
2058            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
2059        self.inject_precompiles(evm.precompiles_mut(), evm_env);
2060        self.inject_arbitrum_precompile(evm.precompiles_mut(), evm_env);
2061        apply_ethereum_pre_execution_changes(&mut evm, parent_hash, transitions)
2062            .map_err(|err| BlockchainError::Internal(err.to_string()))
2063    }
2064
2065    /// Applies Ethereum post-block transitions to a disposable simulation candidate.
2066    fn apply_simulation_post_execution_changes<DB>(
2067        &self,
2068        db: DB,
2069        evm_env: &EvmEnv,
2070        transitions: EthereumBlockTransitions,
2071        receipts: &[FoundryReceiptEnvelope],
2072    ) -> Result<alloy_eips::eip7685::Requests, BlockchainError>
2073    where
2074        DB: StateDB<Error = DatabaseError>,
2075    {
2076        let inspector = self.build_mining_inspector();
2077        let mut evm =
2078            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
2079        self.inject_precompiles(evm.precompiles_mut(), evm_env);
2080        self.inject_arbitrum_precompile(evm.precompiles_mut(), evm_env);
2081        apply_ethereum_post_execution_changes(&mut evm, transitions, receipts)
2082            .map_err(|err| BlockchainError::Internal(err.to_string()))
2083    }
2084
2085    /// ## EVM settings
2086    ///
2087    /// 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>:
2088    ///
2089    ///  - `disable_eip3607` is set to `true`
2090    ///  - `disable_base_fee` is set to `true`
2091    ///  - `tx_gas_limit_cap` is set to `Some(u64::MAX)` indicating no gas limit cap
2092    ///  - `nonce` check is skipped
2093    fn build_call_env(
2094        &self,
2095        request: WithOtherFields<TransactionRequest>,
2096        fee_details: FeeDetails,
2097        block_env: BlockEnv,
2098    ) -> (EvmEnv, TxEnv, OpCallDepositInfo) {
2099        let tx_type = request.minimal_tx_type() as u8;
2100
2101        let WithOtherFields::<TransactionRequest> {
2102            inner:
2103                TransactionRequest {
2104                    from,
2105                    to,
2106                    gas,
2107                    value,
2108                    input,
2109                    access_list,
2110                    blob_versioned_hashes,
2111                    authorization_list,
2112                    nonce,
2113                    sidecar: _,
2114                    chain_id,
2115                    .. // Rest of the gas fees related fields are taken from `fee_details`
2116                },
2117            other,
2118        } = request;
2119
2120        let FeeDetails {
2121            gas_price,
2122            max_fee_per_gas,
2123            max_priority_fee_per_gas,
2124            max_fee_per_blob_gas,
2125        } = fee_details;
2126
2127        let gas_limit = gas.unwrap_or(block_env.gas_limit);
2128        let mut evm_env = self.evm_env.read().clone();
2129        evm_env.block_env = block_env;
2130        // we want to disable this in eth_call, since this is common practice used by other node
2131        // impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
2132        evm_env.cfg_env.disable_block_gas_limit = true;
2133        evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX);
2134
2135        // The basefee should be ignored for calls against state for
2136        // - eth_call
2137        // - eth_estimateGas
2138        // - eth_createAccessList
2139        // - tracing
2140        evm_env.cfg_env.disable_base_fee = true;
2141
2142        // Disable nonce check in revm
2143        evm_env.cfg_env.disable_nonce_check = true;
2144
2145        let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| {
2146            self.fees().raw_gas_price().saturating_add(MIN_SUGGESTED_PRIORITY_FEE)
2147        });
2148        let caller = from.unwrap_or_default();
2149        let to = to.as_ref().and_then(TxKind::to);
2150        let blob_hashes = blob_versioned_hashes.unwrap_or_default();
2151        let mut tx_env = TxEnv {
2152            caller,
2153            gas_limit,
2154            gas_price,
2155            gas_priority_fee: max_priority_fee_per_gas,
2156            max_fee_per_blob_gas: max_fee_per_blob_gas
2157                .or_else(|| {
2158                    if blob_hashes.is_empty() { Some(0) } else { evm_env.block_env.blob_gasprice() }
2159                })
2160                .unwrap_or_default(),
2161            kind: match to {
2162                Some(addr) => TxKind::Call(*addr),
2163                None => TxKind::Create,
2164            },
2165            tx_type,
2166            value: value.unwrap_or_default(),
2167            data: input.into_input().unwrap_or_default(),
2168            chain_id: Some(chain_id.unwrap_or(self.chain_id().to::<u64>())),
2169            access_list: access_list.unwrap_or_default(),
2170            blob_hashes,
2171            ..Default::default()
2172        };
2173        tx_env.set_signed_authorization(authorization_list.unwrap_or_default());
2174
2175        if let Some(nonce) = nonce {
2176            tx_env.nonce = nonce;
2177        }
2178
2179        if evm_env.block_env.basefee == 0 {
2180            // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee`
2181            // 0 is only possible if it's manually set
2182            evm_env.cfg_env.disable_base_fee = true;
2183        }
2184
2185        // Deposit transaction? (only valid when op-stack deposits are active)
2186        #[cfg(feature = "optimism")]
2187        let op_deposit = if self.ensure_op_deposits_active().is_ok()
2188            && let Ok(deposit) = get_deposit_tx_parts(&other)
2189        {
2190            deposit
2191        } else {
2192            OpCallDepositInfo::default()
2193        };
2194        #[cfg(not(feature = "optimism"))]
2195        let op_deposit = {
2196            // `other` carries OP-only deposit fields; consumed only when feature is enabled.
2197            let _ = &other;
2198            OpCallDepositInfo
2199        };
2200
2201        (evm_env, tx_env, op_deposit)
2202    }
2203
2204    fn prepare_call_env(
2205        &self,
2206        state: &dyn DatabaseRef,
2207        request: WithOtherFields<TransactionRequest>,
2208        fee_details: FeeDetails,
2209        block_env: BlockEnv,
2210    ) -> Result<PreparedCall, BlockchainError> {
2211        let request = self.parse_transaction_request(request)?;
2212        self.prepare_typed_call_env(state, request, fee_details, block_env)
2213    }
2214
2215    fn prepare_base_call_env(
2216        &self,
2217        request: WithOtherFields<TransactionRequest>,
2218        fee_details: FeeDetails,
2219        block_env: BlockEnv,
2220    ) -> PreparedCall {
2221        let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block_env);
2222        #[cfg(feature = "optimism")]
2223        let tx_env = if self.is_optimism() {
2224            CallTxEnv::Op(OpTransaction { base: tx_env, deposit: op_deposit, ..Default::default() })
2225        } else if self.is_tempo() {
2226            CallTxEnv::Tempo(TempoTxEnv::from(tx_env))
2227        } else {
2228            CallTxEnv::Eth(tx_env)
2229        };
2230        #[cfg(not(feature = "optimism"))]
2231        let tx_env = {
2232            let _ = op_deposit;
2233            if self.is_tempo() {
2234                CallTxEnv::Tempo(TempoTxEnv::from(tx_env))
2235            } else {
2236                CallTxEnv::Eth(tx_env)
2237            }
2238        };
2239        PreparedCall { evm_env, tx_env, simulated_tempo_tx: None }
2240    }
2241
2242    /// Classifies an RPC request according to the active network.
2243    pub(crate) fn parse_transaction_request(
2244        &self,
2245        request: WithOtherFields<TransactionRequest>,
2246    ) -> Result<FoundryTransactionRequest, BlockchainError> {
2247        let transaction_type = request.transaction_type;
2248        if !self.is_tempo() && transaction_type != Some(TEMPO_TX_TYPE_ID) {
2249            #[cfg(feature = "optimism")]
2250            if transaction_type == Some(DEPOSIT_TX_TYPE_ID)
2251                || transaction_type == Some(POST_EXEC_TX_TYPE_ID)
2252                || get_deposit_tx_parts(&request.other).is_ok()
2253            {
2254                return Ok(FoundryTransactionRequest::Op(request));
2255            }
2256            return Ok(FoundryTransactionRequest::Ethereum(request.into_inner()));
2257        }
2258
2259        let parsed: FoundryTransactionRequest =
2260            request.try_into().map_err(|err: serde_json::Error| {
2261                BlockchainError::InvalidTransactionRequest(err.to_string())
2262            })?;
2263        if parsed.is_tempo() {
2264            self.ensure_tempo_active()?;
2265        }
2266        if parsed.is_tempo()
2267            && self.is_tempo()
2268            && transaction_type.is_some_and(|ty| ty != TEMPO_TX_TYPE_ID)
2269        {
2270            return Err(BlockchainError::FailedToDecodeTransaction);
2271        }
2272        Ok(parsed)
2273    }
2274
2275    fn build_tempo_request_env(
2276        &self,
2277        request: TempoTransactionRequest,
2278        mut base: TxEnv,
2279    ) -> Result<(TempoTxEnv, AASigned), BlockchainError> {
2280        let fee_payer = request.fee_payer_signature.map(|_| {
2281            request.clone().build_aa().ok().and_then(|tx| tx.recover_fee_payer(base.caller).ok())
2282        });
2283
2284        // Build the response representation separately so the mocked execution signature does not
2285        // leak into RPC output.
2286        let mut response_request = request.clone();
2287        response_request.inner.from = Some(base.caller);
2288        response_request.inner.gas = Some(base.gas_limit);
2289        response_request.inner.nonce = Some(base.nonce);
2290        response_request.inner.chain_id = base.chain_id;
2291        response_request.inner.max_fee_per_gas = Some(base.gas_price);
2292        response_request.inner.max_priority_fee_per_gas =
2293            Some(base.gas_priority_fee.unwrap_or_default());
2294        response_request.inner.access_list = Some(base.access_list.clone());
2295        if response_request.calls.is_empty()
2296            && response_request.inner.to.is_none()
2297            && !base.data.is_empty()
2298        {
2299            response_request.inner.to = Some(base.kind);
2300        }
2301        let response_tx = response_request
2302            .build_aa()
2303            .map_err(|err| BlockchainError::InvalidTransactionRequest(err.to_string()))?;
2304        let response_tx = response_tx.into_signed(TempoSignature::default());
2305        let key_type = request.key_type.unwrap_or(SignatureType::Secp256k1);
2306        let key_data = request.key_data.clone();
2307        let key_id = request.key_id;
2308        let signature = mock_tempo_signature(
2309            key_type,
2310            key_data,
2311            key_id,
2312            base.caller,
2313            self.tempo_hardfork().is_t1c(),
2314        );
2315        let mut calls = request.calls;
2316        if let Some(to) = request.inner.to {
2317            calls.push(Call {
2318                to,
2319                value: request.inner.value.unwrap_or_default(),
2320                input: request.inner.input.into_input().unwrap_or_default(),
2321            });
2322        } else if calls.is_empty() && !base.data.is_empty() {
2323            // Alloy represents an omitted top-level `to` as `None`; preserve Ethereum CREATE
2324            // semantics by materializing it as the final Tempo call.
2325            calls.push(Call { to: base.kind, value: base.value, input: base.data.clone() });
2326        }
2327        if let Some(first_call) = calls.first() {
2328            base.kind = first_call.to;
2329            base.value = first_call.value;
2330            base.data = first_call.input.clone();
2331        }
2332        let tx_env = TempoTxEnv {
2333            fee_token: request.fee_token,
2334            is_system_tx: false,
2335            unique_tx_identifier: Some(TEMPO_RPC_SIMULATION_CONTEXT),
2336            fee_payer,
2337            tempo_tx_env: Some(Box::new(TempoBatchCallEnv {
2338                aa_calls: calls,
2339                signature,
2340                tempo_authorization_list: request
2341                    .tempo_authorization_list
2342                    .into_iter()
2343                    .map(RecoveredTempoAuthorization::new)
2344                    .collect(),
2345                nonce_key: request.nonce_key.unwrap_or_default(),
2346                key_authorization: request.key_authorization,
2347                signature_hash: B256::ZERO,
2348                tx_hash: B256::ZERO,
2349                valid_before: request.valid_before.map(|value| value.get()),
2350                valid_after: request.valid_after.map(|value| value.get()),
2351                subblock_transaction: false,
2352                override_key_id: key_id,
2353                expiring_nonce_idx: None,
2354            })),
2355            inner: base,
2356        };
2357        Ok((tx_env, response_tx))
2358    }
2359
2360    fn prepare_typed_call_env(
2361        &self,
2362        state: &dyn DatabaseRef,
2363        request: FoundryTransactionRequest,
2364        fee_details: FeeDetails,
2365        block_env: BlockEnv,
2366    ) -> Result<PreparedCall, BlockchainError> {
2367        match request {
2368            FoundryTransactionRequest::Tempo(tempo_request) => {
2369                self.ensure_tempo_active()?;
2370                let mut tempo_request = *tempo_request;
2371                if tempo_request.inner.nonce.is_none() {
2372                    let caller = tempo_request.inner.from.unwrap_or_default();
2373                    tempo_request.inner.nonce = Some(tempo_nonce(
2374                        state,
2375                        caller,
2376                        tempo_request.nonce_key.unwrap_or_default(),
2377                    )?);
2378                }
2379                let inner = WithOtherFields::new(tempo_request.inner.clone());
2380                let (evm_env, base, _) = self.build_call_env(inner, fee_details, block_env);
2381                let (tx_env, simulated_tempo_tx) =
2382                    self.build_tempo_request_env(tempo_request, base)?;
2383                Ok(PreparedCall {
2384                    evm_env,
2385                    tx_env: CallTxEnv::Tempo(tx_env),
2386                    simulated_tempo_tx: Some(simulated_tempo_tx),
2387                })
2388            }
2389            FoundryTransactionRequest::Ethereum(request) => Ok(self.prepare_base_call_env(
2390                WithOtherFields::new(request),
2391                fee_details,
2392                block_env,
2393            )),
2394            #[cfg(feature = "optimism")]
2395            FoundryTransactionRequest::Op(request) => {
2396                Ok(self.prepare_base_call_env(request, fee_details, block_env))
2397            }
2398        }
2399    }
2400
2401    fn transact_call_with_inspector_ref<'db, I, DB>(
2402        &self,
2403        db: &'db DB,
2404        evm_env: &EvmEnv,
2405        inspector: &mut I,
2406        tx_env: CallTxEnv,
2407    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2408    where
2409        DB: DatabaseRef + ?Sized,
2410        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
2411        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2412    {
2413        match tx_env {
2414            CallTxEnv::Eth(tx_env) => {
2415                self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)
2416            }
2417            #[cfg(feature = "optimism")]
2418            CallTxEnv::Op(tx_env) => {
2419                self.transact_op_with_inspector_ref(db, evm_env, inspector, tx_env)
2420            }
2421            CallTxEnv::Tempo(tx_env) => {
2422                self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)
2423            }
2424        }
2425    }
2426
2427    pub fn call_with_state(
2428        &self,
2429        state: &dyn DatabaseRef,
2430        request: WithOtherFields<TransactionRequest>,
2431        fee_details: FeeDetails,
2432        block_env: BlockEnv,
2433    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
2434        let mut inspector = self.build_inspector();
2435        let PreparedCall { evm_env, tx_env, .. } =
2436            self.prepare_call_env(state, request, fee_details, block_env)?;
2437        let ResultAndState { result, state } =
2438            self.transact_call_with_inspector_ref(state, &evm_env, &mut inspector, tx_env)?;
2439
2440        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
2441        inspector.print_logs();
2442
2443        if self.print_traces {
2444            inspector.into_print_traces(self.call_trace_decoder.clone());
2445        }
2446
2447        Ok((exit_reason, out, gas_used as u128, state))
2448    }
2449
2450    pub(crate) fn call_with_state_typed_gas_limit(
2451        &self,
2452        state: &dyn DatabaseRef,
2453        request: FoundryTransactionRequest,
2454        fee_details: FeeDetails,
2455        block_env: BlockEnv,
2456        gas_limit: u64,
2457        disable_fee_charge: bool,
2458    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
2459        self.call_with_state_typed_inner(
2460            state,
2461            request,
2462            fee_details,
2463            block_env,
2464            TypedCallOverrides {
2465                gas_limit: Some(gas_limit),
2466                disable_fee_charge,
2467                ..Default::default()
2468            },
2469        )
2470    }
2471
2472    pub(crate) fn call_with_state_typed_access_list(
2473        &self,
2474        state: &dyn DatabaseRef,
2475        request: FoundryTransactionRequest,
2476        fee_details: FeeDetails,
2477        block_env: BlockEnv,
2478        access_list: AccessList,
2479    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
2480        self.call_with_state_typed_inner(
2481            state,
2482            request,
2483            fee_details,
2484            block_env,
2485            TypedCallOverrides { access_list: Some(access_list), ..Default::default() },
2486        )
2487    }
2488
2489    fn call_with_state_typed_inner(
2490        &self,
2491        state: &dyn DatabaseRef,
2492        request: FoundryTransactionRequest,
2493        fee_details: FeeDetails,
2494        block_env: BlockEnv,
2495        overrides: TypedCallOverrides,
2496    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
2497        let mut inspector = self.build_inspector();
2498        let PreparedCall { mut evm_env, mut tx_env, .. } =
2499            self.prepare_typed_call_env(state, request, fee_details, block_env)?;
2500        evm_env.cfg_env.disable_fee_charge = overrides.disable_fee_charge;
2501        if let Some(gas_limit) = overrides.gas_limit {
2502            tx_env.base_mut().gas_limit = gas_limit;
2503        }
2504        if let Some(access_list) = overrides.access_list {
2505            tx_env.base_mut().access_list = access_list;
2506        }
2507        let ResultAndState { result, state } =
2508            self.transact_call_with_inspector_ref(state, &evm_env, &mut inspector, tx_env)?;
2509        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
2510        inspector.print_logs();
2511        Ok((exit_reason, out, gas_used as u128, state))
2512    }
2513
2514    pub fn build_access_list_with_state(
2515        &self,
2516        state: &dyn DatabaseRef,
2517        request: WithOtherFields<TransactionRequest>,
2518        fee_details: FeeDetails,
2519        block_env: BlockEnv,
2520    ) -> Result<(InstructionResult, Option<Output>, u64, AccessList), BlockchainError> {
2521        let mut inspector =
2522            AccessListInspector::new(request.access_list.clone().unwrap_or_default());
2523
2524        let PreparedCall { evm_env, tx_env, .. } =
2525            self.prepare_call_env(state, request, fee_details, block_env)?;
2526        let ResultAndState { result, state: _ } =
2527            self.transact_call_with_inspector_ref(state, &evm_env, &mut inspector, tx_env)?;
2528        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
2529        let access_list = inspector.access_list();
2530        Ok((exit_reason, out, gas_used, access_list))
2531    }
2532
2533    fn arbitrum_block_number(&self, evm_env: &EvmEnv) -> Option<u64> {
2534        if !arbitrum::is_arbitrum_chain(evm_env.cfg_env.chain_id) {
2535            return None;
2536        }
2537
2538        let env_block = evm_env.block_env.number.saturating_to();
2539        Some(self.get_fork().map_or(env_block, |fork| fork.block_number().max(env_block)))
2540    }
2541
2542    pub fn get_code_with_state(
2543        &self,
2544        state: &dyn DatabaseRef,
2545        address: Address,
2546    ) -> Result<Bytes, BlockchainError> {
2547        trace!(target: "backend", "get code for {:?}", address);
2548        let account = state.basic_ref(address)?.unwrap_or_default();
2549        if account.code_hash == KECCAK_EMPTY {
2550            // if the code hash is `KECCAK_EMPTY`, we check no further
2551            return Ok(Default::default());
2552        }
2553        let code = if let Some(code) = account.code {
2554            code
2555        } else {
2556            state.code_by_hash_ref(account.code_hash)?
2557        };
2558        Ok(code.bytes()[..code.len()].to_vec().into())
2559    }
2560
2561    pub fn get_balance_with_state<D>(
2562        &self,
2563        state: D,
2564        address: Address,
2565    ) -> Result<U256, BlockchainError>
2566    where
2567        D: DatabaseRef,
2568    {
2569        trace!(target: "backend", "get balance for {:?}", address);
2570        Ok(state.basic_ref(address)?.unwrap_or_default().balance)
2571    }
2572
2573    pub async fn transaction_by_block_number_and_index(
2574        &self,
2575        number: BlockNumber,
2576        index: Index,
2577    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
2578        if let Some(block) = self.mined_block_by_number(number) {
2579            return Ok(self.mined_transaction_by_block_hash_and_index(block.header.hash, index));
2580        }
2581
2582        if let Some(fork) = self.get_fork() {
2583            let number = self.convert_block_number(Some(number));
2584            if fork.predates_fork(number) {
2585                return Ok(fork
2586                    .transaction_by_block_number_and_index(number, index.into())
2587                    .await?);
2588            }
2589        }
2590
2591        Ok(None)
2592    }
2593
2594    pub async fn transaction_by_block_hash_and_index(
2595        &self,
2596        hash: B256,
2597        index: Index,
2598    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
2599        if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) {
2600            return Ok(tx);
2601        }
2602
2603        if let Some(fork) = self.get_fork() {
2604            return Ok(fork.transaction_by_block_hash_and_index(hash, index.into()).await?);
2605        }
2606
2607        Ok(None)
2608    }
2609
2610    pub fn mined_transaction_by_block_hash_and_index(
2611        &self,
2612        block_hash: B256,
2613        index: Index,
2614    ) -> Option<AnyRpcTransaction> {
2615        let (info, block, tx) = {
2616            let storage = self.blockchain.storage.read();
2617            let block = storage.blocks.get(&block_hash).cloned()?;
2618            let index: usize = index.into();
2619            let tx = block.body.transactions.get(index)?.clone();
2620            let info = storage.transactions.get(&tx.hash())?.info.clone();
2621            (info, block, tx)
2622        };
2623
2624        Some(transaction_build(
2625            Some(info.transaction_hash),
2626            tx,
2627            Some(&block),
2628            Some(info),
2629            block.header.base_fee_per_gas(),
2630        ))
2631    }
2632
2633    pub async fn transaction_by_hash(
2634        &self,
2635        hash: B256,
2636    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
2637        trace!(target: "backend", "transaction_by_hash={:?}", hash);
2638        if let tx @ Some(_) = self.mined_transaction_by_hash(hash) {
2639            return Ok(tx);
2640        }
2641
2642        if let Some(fork) = self.get_fork() {
2643            return fork
2644                .transaction_by_hash(hash)
2645                .await
2646                .map_err(BlockchainError::AlloyForkProvider);
2647        }
2648
2649        Ok(None)
2650    }
2651
2652    pub fn mined_transaction_by_hash(&self, hash: B256) -> Option<AnyRpcTransaction> {
2653        let (info, block) = {
2654            let storage = self.blockchain.storage.read();
2655            let MinedTransaction { info, block_hash, .. } =
2656                storage.transactions.get(&hash)?.clone();
2657            let block = storage.blocks.get(&block_hash).cloned()?;
2658            (info, block)
2659        };
2660        let tx = block.body.transactions.get(info.transaction_index as usize)?.clone();
2661
2662        Some(transaction_build(
2663            Some(info.transaction_hash),
2664            tx,
2665            Some(&block),
2666            Some(info),
2667            block.header.base_fee_per_gas(),
2668        ))
2669    }
2670
2671    /// Returns the traces for the given transaction
2672    pub async fn trace_transaction(
2673        &self,
2674        hash: B256,
2675    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
2676        if let Some(traces) = self.mined_parity_trace_transaction(hash) {
2677            return Ok(traces);
2678        }
2679
2680        if let Some(fork) = self.get_fork() {
2681            return Ok(fork.trace_transaction(hash).await?);
2682        }
2683
2684        Ok(vec![])
2685    }
2686
2687    /// Returns a transaction trace at a given index.
2688    pub async fn trace_get(
2689        &self,
2690        hash: B256,
2691        indices: Vec<Index>,
2692    ) -> Result<Option<LocalizedTransactionTrace>, BlockchainError> {
2693        if indices.len() != 1 {
2694            return Ok(None);
2695        }
2696
2697        let index: usize = indices[0].into();
2698        if let Some(traces) = self.mined_parity_trace_transaction(hash) {
2699            return Ok(traces.into_iter().nth(index));
2700        }
2701
2702        if let Some(fork) = self.get_fork() {
2703            return Ok(fork.trace_get(hash, indices).await?);
2704        }
2705
2706        Ok(None)
2707    }
2708
2709    /// Returns the traces for the given block
2710    pub async fn trace_block(
2711        &self,
2712        block: BlockNumber,
2713    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
2714        let number = self.convert_block_number(Some(block));
2715        if let Some(traces) = self.mined_parity_trace_block(number) {
2716            return Ok(traces);
2717        }
2718
2719        if let Some(fork) = self.get_fork()
2720            && fork.predates_fork(number)
2721        {
2722            return Ok(fork.trace_block(number).await?);
2723        }
2724
2725        Ok(vec![])
2726    }
2727
2728    /// Executes a transaction call and returns requested parity trace results.
2729    pub async fn trace_call(
2730        &self,
2731        request: WithOtherFields<TransactionRequest>,
2732        fee_details: FeeDetails,
2733        trace_types: HashSet<TraceType>,
2734        block_request: BlockRequest<FoundryTxEnvelope>,
2735        block_id: BlockId,
2736    ) -> Result<TraceResults, BlockchainError>
2737    where
2738        Self: TransactionValidator<FoundryTxEnvelope>,
2739        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
2740    {
2741        if let BlockRequest::Number(number) = &block_request
2742            && let Some(fork) = self.get_fork()
2743            && fork.predates_fork(*number)
2744        {
2745            return Ok(fork.trace_call(request, trace_types, block_id).await?);
2746        }
2747
2748        self.with_database_at(Some(block_request), |state, block| {
2749            let cache_db = CacheDB::new(state);
2750            let mut inspector =
2751                TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
2752            let PreparedCall { evm_env, tx_env, .. } =
2753                self.prepare_call_env(&cache_db, request, fee_details, block)?;
2754            let result =
2755                self.transact_call_with_inspector_ref(&cache_db, &evm_env, &mut inspector, tx_env)?;
2756
2757            inspector
2758                .into_parity_builder()
2759                .into_trace_results_with_state(&result, &trace_types, &cache_db)
2760                .map_err(Into::into)
2761        })
2762        .await?
2763    }
2764
2765    /// Replays all transactions in a block and returns the requested traces for each transaction
2766    pub async fn trace_replay_block_transactions(
2767        &self,
2768        block: BlockNumber,
2769        trace_types: HashSet<TraceType>,
2770    ) -> Result<Vec<TraceResultsWithTransactionHash>, BlockchainError> {
2771        let block_number = self.convert_block_number(Some(block));
2772
2773        // Try mined blocks first
2774        if let Some(results) =
2775            self.mined_parity_trace_replay_block_transactions(block_number, &trace_types)?
2776        {
2777            return Ok(results);
2778        }
2779
2780        // Fallback to fork if block predates fork
2781        if let Some(fork) = self.get_fork()
2782            && fork.predates_fork(block_number)
2783        {
2784            return Ok(fork.trace_replay_block_transactions(block_number, trace_types).await?);
2785        }
2786
2787        Ok(vec![])
2788    }
2789
2790    /// Replays a mined transaction and returns the requested traces.
2791    pub async fn trace_replay_transaction(
2792        &self,
2793        hash: B256,
2794        trace_types: HashSet<TraceType>,
2795    ) -> Result<TraceResults, BlockchainError> {
2796        let block_number =
2797            self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.block_number);
2798
2799        // If the transaction was mined locally, replay it locally. Do not fall
2800        // through to the fork when the local replay fails; that would misreport
2801        // a local data problem as an upstream transaction lookup.
2802        if let Some(block_number) = block_number {
2803            let results = self
2804                .mined_parity_trace_replay_block_transactions(block_number, &trace_types)?
2805                .ok_or(BlockchainError::BlockNotFound)?;
2806
2807            return results
2808                .into_iter()
2809                .find(|result| result.transaction_hash == hash)
2810                .map(|result| result.full_trace)
2811                .ok_or_else(|| {
2812                    BlockchainError::Internal(format!(
2813                        "replayed block {block_number} for local transaction {hash:?}, \
2814                         but its trace was missing"
2815                    ))
2816                });
2817        }
2818
2819        // Not known locally: forward to the fork if present.
2820        if let Some(fork) = self.get_fork() {
2821            return Ok(fork.trace_replay_transaction(hash, trace_types).await?);
2822        }
2823
2824        Err(BlockchainError::TransactionNotFound)
2825    }
2826
2827    /// Traces a raw transaction without committing it to the chain state or mempool.
2828    pub async fn trace_raw_transaction(
2829        &self,
2830        pending_transaction: PendingTransaction<FoundryTxEnvelope>,
2831        trace_types: HashSet<TraceType>,
2832        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
2833    ) -> Result<TraceResults, BlockchainError>
2834    where
2835        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
2836    {
2837        let trace_config = TracingInspectorConfig::from_parity_config(&trace_types);
2838
2839        self.with_database_at(block_request, |state, block_env| {
2840            let cache_db = CacheDB::new(state);
2841            let mut evm_env = self.evm_env.read().clone();
2842            evm_env.block_env = block_env;
2843
2844            let mut inspector = TracingInspector::new(trace_config);
2845            let (result, _) = self.transact_envelope_with_inspector_ref(
2846                &cache_db,
2847                &evm_env,
2848                &mut inspector,
2849                pending_transaction.transaction.as_ref(),
2850                *pending_transaction.sender(),
2851            )?;
2852
2853            inspector
2854                .into_parity_builder()
2855                .into_trace_results_with_state(&result, &trace_types, &cache_db)
2856                .map_err(BlockchainError::from)
2857        })
2858        .await?
2859    }
2860
2861    /// Traces calls sequentially against a shared in-memory state.
2862    pub async fn trace_call_many(
2863        &self,
2864        calls: Vec<(WithOtherFields<TransactionRequest>, HashSet<TraceType>)>,
2865        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
2866    ) -> Result<Vec<TraceResults>, BlockchainError>
2867    where
2868        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
2869    {
2870        self.with_database_at(block_request, |state, block_env| {
2871            let mut cache_db = CacheDB::new(state);
2872            let mut results = Vec::with_capacity(calls.len());
2873            let mut calls = calls.into_iter().peekable();
2874
2875            while let Some((request, trace_types)) = calls.next() {
2876                let fee_details = FeeDetails::new(
2877                    request.gas_price,
2878                    request.max_fee_per_gas,
2879                    request.max_priority_fee_per_gas,
2880                    request.max_fee_per_blob_gas,
2881                )?
2882                .or_zero_fees();
2883                let PreparedCall { evm_env, mut tx_env, simulated_tempo_tx } =
2884                    self.prepare_call_env(&cache_db, request, fee_details, block_env.clone())?;
2885                apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
2886
2887                let trace_config = TracingInspectorConfig::from_parity_config(&trace_types);
2888                let mut inspector = TracingInspector::new(trace_config);
2889                let result = self.transact_call_with_inspector_ref(
2890                    &cache_db,
2891                    &evm_env,
2892                    &mut inspector,
2893                    tx_env,
2894                )?;
2895
2896                let trace_result = inspector
2897                    .into_parity_builder()
2898                    .into_trace_results_with_state(&result, &trace_types, &cache_db)
2899                    .map_err(BlockchainError::from)?;
2900                results.push(trace_result);
2901
2902                if calls.peek().is_some() {
2903                    cache_db.commit(result.state);
2904                }
2905            }
2906
2907            Ok(results)
2908        })
2909        .await?
2910    }
2911
2912    /// Returns the trace results for all transactions in a mined block by replaying them
2913    fn mined_parity_trace_replay_block_transactions(
2914        &self,
2915        block_number: u64,
2916        trace_types: &HashSet<TraceType>,
2917    ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, BlockchainError> {
2918        let Some(block) = self.get_block(block_number) else { return Ok(None) };
2919
2920        // Execute this in the context of the parent state
2921        let parent_hash = block.header.parent_hash;
2922        let trace_config = TracingInspectorConfig::from_parity_config(trace_types);
2923
2924        let read_guard = self.states.upgradable_read();
2925        if let Some(state) = read_guard.get_state(&parent_hash) {
2926            self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types)
2927                .map(Some)
2928        } else {
2929            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
2930            let Some(state) = write_guard.get_on_disk_state(&parent_hash) else {
2931                return Ok(None);
2932            };
2933            self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types)
2934                .map(Some)
2935        }
2936    }
2937
2938    /// Replays all transactions in a block with the tracing inspector to generate TraceResults
2939    fn replay_block_transactions_with_inspector(
2940        &self,
2941        block: &Block,
2942        parent_state: &StateDb,
2943        trace_config: TracingInspectorConfig,
2944        trace_types: &HashSet<TraceType>,
2945    ) -> Result<Vec<TraceResultsWithTransactionHash>, BlockchainError> {
2946        let (mut cache_db, evm_env) = self.prepare_block_replay(block, parent_state)?;
2947        let mut results = Vec::new();
2948
2949        // Execute each transaction in the block with tracing
2950        for tx_envelope in &block.body.transactions {
2951            let tx_hash = tx_envelope.hash();
2952
2953            // Create a fresh inspector for this transaction
2954            let mut inspector = TracingInspector::new(trace_config);
2955
2956            // Prepare transaction environment and execute
2957            let pending_tx = PendingTransaction::from_maybe_impersonated(tx_envelope.clone())?;
2958            let (result, _) = self.transact_envelope_with_inspector_ref(
2959                &cache_db,
2960                &evm_env,
2961                &mut inspector,
2962                pending_tx.transaction.as_ref(),
2963                *pending_tx.sender(),
2964            )?;
2965
2966            // Build TraceResults from the inspector and execution result
2967            let full_trace = inspector
2968                .into_parity_builder()
2969                .into_trace_results_with_state(&result, trace_types, &cache_db)
2970                .map_err(BlockchainError::from)?;
2971
2972            results.push(TraceResultsWithTransactionHash { transaction_hash: tx_hash, full_trace });
2973
2974            // Commit the state changes for the next transaction
2975            cache_db.commit(result.state);
2976        }
2977
2978        Ok(results)
2979    }
2980
2981    // Returns the traces matching a given filter
2982    pub async fn trace_filter(
2983        &self,
2984        filter: TraceFilter,
2985    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
2986        let matcher = filter.matcher();
2987        let start = filter.from_block.unwrap_or(0);
2988        let end = filter.to_block.unwrap_or_else(|| self.best_number());
2989
2990        if start > end {
2991            return Err(BlockchainError::RpcError(RpcError::invalid_params(
2992                "invalid block range, ensure that to block is greater than from block".to_string(),
2993            )));
2994        }
2995
2996        let dist = end - start;
2997        if dist > 300 {
2998            return Err(BlockchainError::RpcError(RpcError::invalid_params(
2999                "block range too large, currently limited to 300".to_string(),
3000            )));
3001        }
3002
3003        // Accumulate tasks for block range
3004        let mut trace_tasks = vec![];
3005        for num in start..=end {
3006            trace_tasks.push(self.trace_block(num.into()));
3007        }
3008
3009        // Execute tasks and filter traces
3010        let traces = futures::future::try_join_all(trace_tasks).await?;
3011        let filtered_traces =
3012            traces.into_iter().flatten().filter(|trace| matcher.matches(&trace.trace));
3013
3014        // Apply after and count
3015        let filtered_traces: Vec<_> = if let Some(after) = filter.after {
3016            filtered_traces.skip(after as usize).collect()
3017        } else {
3018            filtered_traces.collect()
3019        };
3020
3021        let filtered_traces: Vec<_> = if let Some(count) = filter.count {
3022            filtered_traces.into_iter().take(count as usize).collect()
3023        } else {
3024            filtered_traces
3025        };
3026
3027        Ok(filtered_traces)
3028    }
3029
3030    pub fn get_blobs_by_block_id(
3031        &self,
3032        id: impl Into<BlockId>,
3033        versioned_hashes: Vec<B256>,
3034    ) -> Result<Option<Vec<alloy_consensus::Blob>>> {
3035        Ok(self.get_block(id).map(|block| {
3036            block
3037                .body
3038                .transactions
3039                .iter()
3040                .filter_map(|tx| tx.as_ref().sidecar())
3041                .flat_map(|sidecar| {
3042                    sidecar.sidecar.blobs().iter().zip(sidecar.sidecar.commitments().iter())
3043                })
3044                .filter(|(_, commitment)| {
3045                    // Filter blobs by versioned_hashes if provided
3046                    versioned_hashes.is_empty()
3047                        || versioned_hashes.contains(&kzg_to_versioned_hash(commitment.as_slice()))
3048                })
3049                .map(|(blob, _)| *blob)
3050                .collect()
3051        }))
3052    }
3053
3054    #[allow(clippy::large_stack_frames)]
3055    pub fn get_blob_by_versioned_hash(&self, hash: B256) -> Result<Option<Blob>> {
3056        let storage = self.blockchain.storage.read();
3057        for block in storage.blocks.values() {
3058            for tx in &block.body.transactions {
3059                let typed_tx = tx.as_ref();
3060                if let Some(sidecar) = typed_tx.sidecar() {
3061                    for versioned_hash in sidecar.sidecar.versioned_hashes() {
3062                        if versioned_hash == hash
3063                            && let Some(index) =
3064                                sidecar.sidecar.commitments().iter().position(|commitment| {
3065                                    kzg_to_versioned_hash(commitment.as_slice()) == *hash
3066                                })
3067                            && let Some(blob) = sidecar.sidecar.blobs().get(index)
3068                        {
3069                            return Ok(Some(*blob));
3070                        }
3071                    }
3072                }
3073            }
3074        }
3075        Ok(None)
3076    }
3077
3078    /// Initialises the balance of the given accounts
3079    #[expect(clippy::too_many_arguments)]
3080    pub async fn with_genesis(
3081        db: Arc<AsyncRwLock<Box<dyn Db>>>,
3082        env: Arc<RwLock<EvmEnv>>,
3083        networks: NetworkConfigs,
3084        genesis: GenesisConfig,
3085        fees: FeeManager,
3086        fork: Arc<RwLock<Option<ClientFork>>>,
3087        enable_steps_tracing: bool,
3088        print_logs: bool,
3089        print_traces: bool,
3090        call_trace_decoder: Arc<CallTraceDecoder>,
3091        prune_state_history_config: PruneStateHistoryConfig,
3092        max_persisted_states: Option<usize>,
3093        transaction_block_keeper: Option<usize>,
3094        automine_block_time: Option<Duration>,
3095        cache_path: Option<PathBuf>,
3096        node_config: Arc<AsyncRwLock<NodeConfig>>,
3097    ) -> Result<Self> {
3098        // if this is a fork then adjust the blockchain storage
3099        let blockchain = if let Some(fork) = fork.read().as_ref() {
3100            trace!(target: "backend", "using forked blockchain at {}", fork.block_number());
3101            Blockchain::forked(fork.block_number(), fork.block_hash(), fork.total_difficulty())
3102        } else {
3103            Blockchain::new(
3104                &env.read(),
3105                fees.is_eip1559().then(|| fees.base_fee()),
3106                genesis.timestamp,
3107                genesis.number,
3108                networks.is_tempo(),
3109            )
3110        };
3111
3112        // Sync EVM block.number with genesis for non-fork mode.
3113        // Fork mode syncs in setup_fork_db_config() instead.
3114        if fork.read().is_none() {
3115            env.write().block_env.number = U256::from(genesis.number);
3116
3117            // The genesis block keeps its base fee, but the next block must already follow Tempo's
3118            // rules (e.g. T7 clamps the seed down to the cap). Fork mode seeds this from the fork
3119            // block instead.
3120            if fees.tempo_hardfork().is_some() {
3121                let env = env.read();
3122                let next_base_fee = fees.get_next_block_base_fee_per_gas(
3123                    0,
3124                    env.block_env.gas_limit,
3125                    env.block_env.basefee,
3126                );
3127                drop(env);
3128                fees.set_base_fee(next_base_fee);
3129            }
3130        }
3131
3132        let start_timestamp = if let Some(fork) = fork.read().as_ref() {
3133            fork.timestamp()
3134        } else {
3135            genesis.timestamp
3136        };
3137
3138        let mut states = if prune_state_history_config.is_config_enabled() {
3139            // if prune state history is enabled, configure the state cache only for memory
3140            prune_state_history_config
3141                .max_memory_history
3142                .map(|limit| InMemoryBlockStates::new(limit, 0))
3143                .unwrap_or_default()
3144                .memory_only()
3145        } else if max_persisted_states.is_some() {
3146            max_persisted_states
3147                .map(|limit| InMemoryBlockStates::new(DEFAULT_HISTORY_LIMIT, limit))
3148                .unwrap_or_default()
3149        } else {
3150            Default::default()
3151        };
3152
3153        if let Some(cache_path) = cache_path {
3154            states = states.disk_path(cache_path);
3155        }
3156
3157        let (slots_in_an_epoch, precompile_factory, disable_pool_balance_checks, hardfork) = {
3158            let cfg = node_config.read().await;
3159            (
3160                cfg.slots_in_an_epoch,
3161                cfg.precompile_factory.clone(),
3162                cfg.disable_pool_balance_checks,
3163                cfg.get_hardfork(),
3164            )
3165        };
3166
3167        let backend = Self {
3168            db,
3169            blockchain,
3170            states: Arc::new(RwLock::new(states)),
3171            evm_env: env,
3172            networks,
3173            hardfork,
3174            fork,
3175            time: TimeManager::new(start_timestamp),
3176            cheats: Default::default(),
3177            new_block_listeners: Default::default(),
3178            fees,
3179            genesis,
3180            active_state_snapshots: Arc::new(Mutex::new(Default::default())),
3181            enable_steps_tracing,
3182            print_logs,
3183            print_traces,
3184            call_trace_decoder,
3185            prune_state_history_config,
3186            transaction_block_keeper,
3187            node_config,
3188            slots_in_an_epoch,
3189            precompile_factory,
3190            mining: Arc::new(tokio::sync::Mutex::new(())),
3191            disable_pool_balance_checks,
3192        };
3193
3194        if let Some(interval_block_time) = automine_block_time {
3195            backend.update_interval_mine_block_time(interval_block_time);
3196        }
3197
3198        // Note: this can only fail in forking mode, in which case we can't recover
3199        backend.apply_genesis().await.wrap_err("failed to create genesis")?;
3200        Ok(backend)
3201    }
3202
3203    /// Applies the configured genesis settings
3204    ///
3205    /// This will fund, create the genesis accounts
3206    async fn apply_genesis(&self) -> Result<(), DatabaseError> {
3207        trace!(target: "backend", "setting genesis balances");
3208
3209        if self.fork.read().is_some() {
3210            // fetch all account first
3211            let mut genesis_accounts_futures = Vec::with_capacity(self.genesis.accounts.len());
3212            for address in self.genesis.accounts.iter().copied() {
3213                let db = Arc::clone(&self.db);
3214
3215                // The forking Database backend can handle concurrent requests, we can fetch all dev
3216                // accounts concurrently by spawning the job to a new task
3217                genesis_accounts_futures.push(tokio::task::spawn(async move {
3218                    let db = db.read().await;
3219                    let info = db.basic_ref(address)?.unwrap_or_default();
3220                    Ok::<_, DatabaseError>((address, info))
3221                }));
3222            }
3223
3224            let genesis_accounts = futures::future::join_all(genesis_accounts_futures).await;
3225
3226            let mut db = self.db.write().await;
3227
3228            for res in genesis_accounts {
3229                let (address, mut info) = res.unwrap()?;
3230                info.balance = self.genesis.balance;
3231                db.insert_account(address, info.clone());
3232            }
3233        } else {
3234            let mut db = self.db.write().await;
3235            for (account, info) in self.genesis.account_infos() {
3236                db.insert_account(account, info);
3237            }
3238
3239            // insert the new genesis hash to the database so it's available for the next block in
3240            // the evm
3241            db.insert_block_hash(U256::from(self.best_number()), self.best_hash());
3242
3243            if let Some(transitions) =
3244                self.ethereum_block_transitions(self.hardfork(), None, BlockExecutionKind::Complete)
3245            {
3246                if transitions.hardfork >= EthereumHardfork::Cancun {
3247                    db.set_code(eip4788::BEACON_ROOTS_ADDRESS, eip4788::BEACON_ROOTS_CODE.clone())?;
3248                }
3249                if transitions.hardfork >= EthereumHardfork::Prague {
3250                    db.set_code(
3251                        eip2935::HISTORY_STORAGE_ADDRESS,
3252                        eip2935::HISTORY_STORAGE_CODE.clone(),
3253                    )?;
3254                    db.set_code(
3255                        eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
3256                        eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone(),
3257                    )?;
3258                    db.set_code(
3259                        eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
3260                        eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_CODE.clone(),
3261                    )?;
3262                }
3263            }
3264        }
3265
3266        let db = self.db.write().await;
3267        // apply the genesis.json alloc
3268        self.genesis.apply_genesis_json_alloc(db)?;
3269
3270        // Initialize Tempo precompiles and fee tokens when in Tempo mode (not in fork mode).
3271        // In fork mode, precompiles are inherited from the forked origin.
3272        if self.networks.is_tempo() && !self.is_fork() {
3273            let chain_id = self.evm_env.read().cfg_env.chain_id;
3274            let timestamp = self.genesis.timestamp;
3275            let test_accounts: Vec<Address> = self.genesis.accounts.clone();
3276            let hardfork = self.tempo_hardfork();
3277            let mut db = self.db.write().await;
3278            crate::eth::backend::tempo::initialize_tempo_precompiles(
3279                &mut **db,
3280                chain_id,
3281                timestamp,
3282                &test_accounts,
3283                hardfork,
3284            )
3285            .map_err(|e| {
3286                tracing::error!(target: "backend", "failed to initialize Tempo precompiles: {e}");
3287                DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))
3288            })?;
3289            trace!(target: "backend", "initialized Tempo precompiles and fee tokens for {} accounts", test_accounts.len());
3290        }
3291
3292        trace!(target: "backend", "set genesis balances");
3293
3294        Ok(())
3295    }
3296
3297    /// Resets the fork to a fresh state
3298    pub async fn reset_fork(&self, forking: Forking) -> Result<(), BlockchainError> {
3299        if !self.is_fork() {
3300            if let Some(eth_rpc_url) = forking.json_rpc_url.clone() {
3301                let mut evm_env = self.evm_env.read().clone();
3302                let mut node_config = self.node_config.read().await.clone();
3303
3304                // we want to force the correct base fee for the next block during
3305                // `setup_fork_db_config`
3306                node_config.base_fee.take();
3307                node_config.fork_urls = vec![eth_rpc_url.clone()];
3308                node_config.apply_tempo_fork_beneficiary_default(&mut evm_env);
3309
3310                let (db, config) =
3311                    node_config.setup_fork_db_config(eth_rpc_url, &mut evm_env, &self.fees).await?;
3312
3313                *self.db.write().await = Box::new(db);
3314
3315                let fork = ClientFork::new(config, Arc::clone(&self.db));
3316
3317                *self.node_config.write().await = node_config;
3318                *self.evm_env.write() = evm_env;
3319                *self.fork.write() = Some(fork);
3320            } else {
3321                return Err(RpcError::invalid_params(
3322                    "Forking not enabled and RPC URL not provided to start forking",
3323                )
3324                .into());
3325            }
3326        }
3327
3328        if let Some(fork) = self.get_fork() {
3329            let block_number =
3330                forking.block_number.map(BlockNumber::from).unwrap_or(BlockNumber::Latest);
3331            // reset the fork entirely and reapply the genesis config
3332            let reset_urls =
3333                forking.json_rpc_url.as_ref().map(|url| vec![url.clone()]).unwrap_or_default();
3334            let target_rpc_url = forking.json_rpc_url.clone().or_else(|| fork.eth_rpc_url());
3335            let rpc_url_changed = target_rpc_url != fork.database_rpc_url();
3336            fork.prepare_reset(reset_urls, block_number.into()).await?;
3337            if rpc_url_changed {
3338                // Clear state fetched from the previous RPC URL before persisting the cache.
3339                fork.database.write().await.clear_into_state_snapshot();
3340            }
3341            // Persist fetched remote state before rebuilding the fork database so the new
3342            // block-specific database can load it from disk.
3343            fork.database.read().await.maybe_flush_cache().map_err(BlockchainError::Internal)?;
3344            let fork_block_number = fork.block_number();
3345            if rpc_url_changed {
3346                let cache_dir = {
3347                    let config = self.node_config.read().await;
3348                    if config.no_storage_caching || config.fork_urls.is_empty() {
3349                        None
3350                    } else {
3351                        foundry_config::Config::foundry_chain_cache_dir(config.get_chain_id())
3352                    }
3353                };
3354                if let Some(cache_dir) = cache_dir
3355                    && let Err(err) = std::fs::remove_dir_all(&cache_dir)
3356                    && err.kind() != std::io::ErrorKind::NotFound
3357                {
3358                    return Err(BlockchainError::Internal(format!(
3359                        "failed to invalidate fork cache at {}: {err}",
3360                        cache_dir.display()
3361                    )));
3362                }
3363            }
3364            let fork_block = fork
3365                .block_by_number(fork_block_number)
3366                .await?
3367                .ok_or(BlockchainError::BlockNotFound)?;
3368            // update all settings related to the forked block
3369            {
3370                if let Some(fork_url) = forking.json_rpc_url {
3371                    self.reset_block_number(fork_url, fork_block_number, true).await?;
3372                } else {
3373                    // If rpc url is unspecified, then update the fork with the new block number and
3374                    // existing rpc url, this updates the cache path
3375                    if let Some(fork_url) = target_rpc_url.clone() {
3376                        self.reset_block_number(fork_url, fork_block_number, false).await?;
3377                    }
3378
3379                    let gas_limit = self.node_config.read().await.fork_gas_limit(&fork_block);
3380                    let mut env = self.evm_env.write();
3381
3382                    env.cfg_env.chain_id = fork.chain_id();
3383                    env.block_env = BlockEnv {
3384                        number: U256::from(fork_block_number),
3385                        timestamp: U256::from(fork_block.header.timestamp()),
3386                        gas_limit,
3387                        difficulty: fork_block.header.difficulty(),
3388                        prevrandao: Some(fork_block.header.mix_hash().unwrap_or_default()),
3389                        // Keep previous `beneficiary` and `basefee` value
3390                        beneficiary: env.block_env.beneficiary,
3391                        basefee: env.block_env.basefee,
3392                        ..env.block_env.clone()
3393                    };
3394
3395                    // this is the base fee of the current block, but we need the base fee of
3396                    // the next block
3397                    let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas(
3398                        fork_block.header.gas_used(),
3399                        gas_limit,
3400                        fork_block.header.base_fee_per_gas().unwrap_or_default(),
3401                    );
3402
3403                    self.fees.set_base_fee(next_block_base_fee);
3404                }
3405
3406                // reset the time to the timestamp of the forked block
3407                self.time.reset(fork_block.header.timestamp());
3408                // drop any pending next-block prevrandao override so it does not leak into a block
3409                self.cheats.clear_next_block_prevrandao();
3410
3411                // also reset the total difficulty
3412                self.blockchain.storage.write().total_difficulty = fork.total_difficulty();
3413            }
3414            // reset storage
3415            *self.blockchain.storage.write() = BlockchainStorage::forked(
3416                fork.block_number(),
3417                fork.block_hash(),
3418                fork.total_difficulty(),
3419            );
3420            self.states.write().clear();
3421            self.apply_genesis().await?;
3422            fork.set_database_rpc_url(target_rpc_url);
3423
3424            trace!(target: "backend", "reset fork");
3425
3426            Ok(())
3427        } else {
3428            Err(RpcError::invalid_params("Forking not enabled").into())
3429        }
3430    }
3431
3432    /// Resets the backend to a fresh in-memory state, clearing all existing data
3433    pub async fn reset_to_in_mem(&self) -> Result<(), BlockchainError> {
3434        // Clear the fork if any exists
3435        *self.fork.write() = None;
3436
3437        let genesis_timestamp = self.genesis.timestamp;
3438        let genesis_number = self.genesis.number;
3439
3440        // Tempo chains seed the hardfork's own fixed base fee on reset; other chains keep the
3441        // pre-reset base fee for the genesis block, as before. Computed up front so the env,
3442        // storage, and fee manager all agree.
3443        let reset_base_fee = self.fees.tempo_hardfork().map(crate::config::tempo_default_base_fee);
3444        let genesis_base_fee = reset_base_fee.unwrap_or_else(|| self.fees.base_fee());
3445
3446        // Reset environment to genesis state
3447        {
3448            let mut env = self.evm_env.write();
3449            env.block_env.number = U256::from(genesis_number);
3450            env.block_env.timestamp = U256::from(genesis_timestamp);
3451            // Reset other block env fields to their defaults
3452            env.block_env.basefee = genesis_base_fee;
3453            env.block_env.prevrandao = Some(B256::ZERO);
3454        }
3455
3456        // Clear all storage and reinitialize with genesis
3457        let base_fee = self.fees.is_eip1559().then_some(genesis_base_fee);
3458        *self.blockchain.storage.write() = BlockchainStorage::new(
3459            &self.evm_env.read(),
3460            base_fee,
3461            genesis_timestamp,
3462            genesis_number,
3463            self.is_tempo(),
3464        );
3465        self.states.write().clear();
3466
3467        // Clear the database
3468        self.db.write().await.clear();
3469
3470        // Reset time manager
3471        self.time.reset(genesis_timestamp);
3472        // drop any pending next-block prevrandao override so it does not leak into a block
3473        self.cheats.clear_next_block_prevrandao();
3474
3475        // Seed the next block's base fee. On Tempo the genesis keeps its fixed default while the
3476        // next block already follows the hardfork's rule (e.g. T7 clamps to the cap); other chains
3477        // use Anvil's Ethereum default.
3478        if self.fees.is_eip1559() {
3479            let next_base_fee = match reset_base_fee {
3480                Some(genesis_base_fee) => {
3481                    // Seed the fixed genesis fee first so the next-block computation is not
3482                    // affected by any manually zeroed base fee, then advance one block per Tempo.
3483                    self.fees.set_base_fee(genesis_base_fee);
3484                    let gas_limit = self.evm_env.read().block_env.gas_limit;
3485                    self.fees.get_next_block_base_fee_per_gas(0, gas_limit, genesis_base_fee)
3486                }
3487                None => crate::eth::fees::INITIAL_BASE_FEE,
3488            };
3489            self.fees.set_base_fee(next_base_fee);
3490        }
3491
3492        self.fees.set_gas_price(crate::eth::fees::INITIAL_GAS_PRICE);
3493
3494        // Reapply genesis configuration
3495        self.apply_genesis().await?;
3496
3497        trace!(target: "backend", "reset to fresh in-memory state");
3498
3499        Ok(())
3500    }
3501
3502    async fn reset_block_number(
3503        &self,
3504        fork_url: String,
3505        fork_block_number: u64,
3506        validated_url: bool,
3507    ) -> Result<(), BlockchainError> {
3508        let mut node_config = self.node_config.read().await.clone();
3509        node_config.fork_choice = Some(ForkChoice::Block(fork_block_number as i128));
3510        let override_chain_id =
3511            self.get_fork().and_then(|fork| fork.config.read().override_chain_id);
3512        node_config.set_chain_id(override_chain_id);
3513        if validated_url {
3514            node_config.fork_chain_id = None;
3515        }
3516        // Update fork_urls so setup_fork_db_config uses the correct URL set
3517        node_config.fork_urls = vec![fork_url.clone()];
3518
3519        let mut evm_env = self.evm_env.read().clone();
3520        let (forked_db, client_fork_config) =
3521            node_config.setup_fork_db_config(fork_url, &mut evm_env, &self.fees).await?;
3522
3523        *self.db.write().await = Box::new(forked_db);
3524        let fork = ClientFork::new(client_fork_config, Arc::clone(&self.db));
3525        *self.node_config.write().await = node_config;
3526        *self.fork.write() = Some(fork);
3527        *self.evm_env.write() = evm_env;
3528
3529        Ok(())
3530    }
3531
3532    /// Reverts the state to the state snapshot identified by the given `id`.
3533    pub async fn revert_state_snapshot(&self, id: U256) -> Result<bool, BlockchainError> {
3534        let block = { self.active_state_snapshots.lock().remove(&id) };
3535        if let Some((num, hash)) = block {
3536            let best_block_hash = {
3537                // revert the storage that's newer than the snapshot
3538                let current_height = self.best_number();
3539                let mut storage = self.blockchain.storage.write();
3540
3541                for n in ((num + 1)..=current_height).rev() {
3542                    trace!(target: "backend", "reverting block {}", n);
3543                    if let Some(hash) = storage.hashes.remove(&n)
3544                        && let Some(block) = storage.blocks.remove(&hash)
3545                    {
3546                        for tx in block.body.transactions {
3547                            let _ = storage.transactions.remove(&tx.hash());
3548                        }
3549                    }
3550                }
3551
3552                storage.best_number = num;
3553                storage.best_hash = hash;
3554                hash
3555            };
3556            let block =
3557                self.block_by_hash(best_block_hash).await?.ok_or(BlockchainError::BlockNotFound)?;
3558
3559            let reset_time = block.header.timestamp();
3560            self.time.reset(reset_time);
3561            // drop any pending next-block prevrandao override so it does not leak into a block
3562            self.cheats.clear_next_block_prevrandao();
3563
3564            let mut env = self.evm_env.write();
3565            env.block_env = BlockEnv {
3566                number: U256::from(num),
3567                timestamp: U256::from(block.header.timestamp()),
3568                difficulty: block.header.difficulty(),
3569                // ensures prevrandao is set
3570                prevrandao: Some(block.header.mix_hash().unwrap_or_default()),
3571                gas_limit: block.header.gas_limit(),
3572                // Keep previous `beneficiary` and `basefee` value
3573                beneficiary: env.block_env.beneficiary,
3574                basefee: env.block_env.basefee,
3575                ..Default::default()
3576            }
3577        }
3578        Ok(self.db.write().await.revert_state(id, RevertStateSnapshotAction::RevertRemove))
3579    }
3580
3581    /// executes the transactions without writing to the underlying database
3582    pub async fn inspect_tx(
3583        &self,
3584        tx: Arc<PoolTransaction<FoundryTxEnvelope>>,
3585    ) -> Result<
3586        (InstructionResult, Option<Output>, u64, State, Vec<revm::primitives::Log>),
3587        BlockchainError,
3588    > {
3589        let evm_env = self.next_evm_env();
3590        let db = self.db.read().await;
3591        let mut inspector = self.build_inspector();
3592        let (ResultAndState { result, state }, _) = self.transact_envelope_with_inspector_ref(
3593            &**db,
3594            &evm_env,
3595            &mut inspector,
3596            tx.pending_transaction.transaction.as_ref(),
3597            *tx.pending_transaction.sender(),
3598        )?;
3599        let (exit_reason, gas_used, out, logs) = unpack_execution_result(result);
3600
3601        inspector.print_logs();
3602
3603        if self.print_traces {
3604            inspector.print_traces(self.call_trace_decoder.clone());
3605        }
3606
3607        Ok((exit_reason, out, gas_used, state, logs))
3608    }
3609}
3610
3611impl<N: Network> Backend<N>
3612where
3613    N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
3614{
3615    /// Returns all `Log`s mined by the node that were emitted in the `block` and match the `Filter`
3616    fn mined_logs_for_block(&self, filter: Filter, block: Block, block_hash: B256) -> Vec<Log> {
3617        let mut all_logs = Vec::new();
3618        let mut block_log_index = 0u32;
3619
3620        let storage = self.blockchain.storage.read();
3621
3622        for tx in block.body.transactions {
3623            let Some(tx) = storage.transactions.get(&tx.hash()) else {
3624                continue;
3625            };
3626
3627            let logs = tx.receipt.logs();
3628            let transaction_hash = tx.info.transaction_hash;
3629
3630            for log in logs {
3631                if filter.matches(log) {
3632                    all_logs.push(Log {
3633                        inner: log.clone(),
3634                        block_hash: Some(block_hash),
3635                        block_number: Some(block.header.number()),
3636                        block_timestamp: Some(block.header.timestamp()),
3637                        transaction_hash: Some(transaction_hash),
3638                        transaction_index: Some(tx.info.transaction_index),
3639                        log_index: Some(block_log_index as u64),
3640                        removed: false,
3641                    });
3642                }
3643                block_log_index += 1;
3644            }
3645        }
3646        all_logs
3647    }
3648
3649    /// Returns all logs of the blocks with a number greater than `block_number`, marked as
3650    /// removed.
3651    ///
3652    /// This is used during a reorg to capture the logs of the blocks that are about to be
3653    /// unwound before their transactions and receipts are cleared from storage, so they can be
3654    /// re-delivered to log subscriptions and filters with `removed: true`.
3655    fn removed_logs_since(&self, block_number: u64) -> Vec<Log> {
3656        let storage = self.blockchain.storage.read();
3657        let mut all_logs = Vec::new();
3658
3659        for num in (block_number + 1)..=storage.best_number {
3660            if let Some(hash) = storage.hashes.get(&num)
3661                && let Some(block) = storage.blocks.get(hash)
3662            {
3663                let mut block_log_index = 0u64;
3664                for tx in &block.body.transactions {
3665                    if let Some(tx) = storage.transactions.get(&tx.hash()) {
3666                        for log in tx.receipt.logs() {
3667                            all_logs.push(Log {
3668                                inner: log.clone(),
3669                                block_hash: Some(*hash),
3670                                block_number: Some(num),
3671                                block_timestamp: Some(block.header.timestamp()),
3672                                transaction_hash: Some(tx.info.transaction_hash),
3673                                transaction_index: Some(tx.info.transaction_index),
3674                                log_index: Some(block_log_index),
3675                                removed: true,
3676                            });
3677                            block_log_index += 1;
3678                        }
3679                    }
3680                }
3681            }
3682        }
3683
3684        all_logs
3685    }
3686
3687    /// Returns the logs of the block that match the filter
3688    async fn logs_for_block(
3689        &self,
3690        filter: Filter,
3691        hash: B256,
3692    ) -> Result<Vec<Log>, BlockchainError> {
3693        if let Some(block) = self.blockchain.get_block_by_hash(&hash) {
3694            return Ok(self.mined_logs_for_block(filter, block, hash));
3695        }
3696
3697        if let Some(fork) = self.get_fork() {
3698            return Ok(fork.logs(&filter).await?);
3699        }
3700
3701        Err(BlockchainError::UnknownBlock)
3702    }
3703
3704    /// Returns the logs that match the filter in the given range of blocks
3705    async fn logs_for_range(
3706        &self,
3707        filter: &Filter,
3708        mut from: u64,
3709        to: u64,
3710    ) -> Result<Vec<Log>, BlockchainError> {
3711        let mut all_logs = Vec::new();
3712
3713        // get the range that predates the fork if any
3714        if let Some(fork) = self.get_fork() {
3715            let to_on_fork = if fork.predates_fork(to) {
3716                to
3717            } else {
3718                // adjust the ranges
3719                fork.block_number()
3720            };
3721
3722            if fork.predates_fork_inclusive(from) {
3723                // this data is only available on the forked client
3724                let filter = filter.clone().from_block(from).to_block(to_on_fork);
3725                all_logs = fork.logs(&filter).await?;
3726
3727                // update the range
3728                from = fork.block_number() + 1;
3729            }
3730        }
3731
3732        for number in from..=to {
3733            if let Some((block, hash)) = self.get_block_with_hash(number) {
3734                all_logs.extend(self.mined_logs_for_block(filter.clone(), block, hash));
3735            }
3736        }
3737
3738        Ok(all_logs)
3739    }
3740
3741    /// Returns the logs according to the filter
3742    pub async fn logs(&self, filter: Filter) -> Result<Vec<Log>, BlockchainError> {
3743        trace!(target: "backend", "get logs [{:?}]", filter);
3744        if let Some(hash) = filter.get_block_hash() {
3745            self.logs_for_block(filter, hash).await
3746        } else {
3747            let best = self.best_number();
3748            let to_block =
3749                self.convert_block_number(filter.block_option.get_to_block().copied()).min(best);
3750            let from_block =
3751                self.convert_block_number(filter.block_option.get_from_block().copied());
3752            if from_block > best {
3753                return Err(BlockchainError::BlockOutOfRange(best, from_block));
3754            }
3755
3756            self.logs_for_range(&filter, from_block, to_block).await
3757        }
3758    }
3759
3760    /// Returns all receipts of the block
3761    pub fn mined_receipts(&self, hash: B256) -> Option<Vec<N::ReceiptEnvelope>> {
3762        let storage = self.blockchain.storage.read();
3763        let block = storage.blocks.get(&hash)?;
3764        block
3765            .body
3766            .transactions
3767            .iter()
3768            .map(|transaction| {
3769                storage.transactions.get(&transaction.hash()).map(|tx| tx.receipt.clone())
3770            })
3771            .collect()
3772    }
3773}
3774
3775// Mining methods — generic over N: Network, with Foundry-associated-type bounds for now.
3776impl<N: Network> Backend<N>
3777where
3778    Self: TransactionValidator<FoundryTxEnvelope>,
3779    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
3780{
3781    /// Mines a new block and stores it.
3782    ///
3783    /// this will execute all transaction in the order they come in and return all the markers they
3784    /// provide.
3785    pub async fn mine_block(
3786        &self,
3787        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
3788    ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
3789        self.do_mine_block(pool_transactions).await
3790    }
3791
3792    /// Replays a transaction-hash fork prefix before the live pool and miner are created.
3793    pub(crate) async fn apply_fork_transaction_replay(
3794        &self,
3795        replay: ForkTransactionReplay,
3796    ) -> Result<()> {
3797        let PreparedForkTransactionReplay { transactions, timestamp, parent_beacon_block_root } =
3798            prepare_fork_transaction_replay(replay)?;
3799        eyre::ensure!(!transactions.is_empty(), "fork transaction replay prefix is empty");
3800        let next_timestamp = timestamp.checked_add(1).ok_or_else(|| {
3801            eyre::eyre!("fork transaction replay timestamp cannot be incremented")
3802        })?;
3803
3804        let _mining_guard = self.mining.lock().await;
3805        let current_base_fee = self.base_fee();
3806        let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price();
3807        let mut evm_env = self.evm_env.read().clone();
3808        if evm_env.block_env.basefee == 0 {
3809            evm_env.cfg_env.disable_base_fee = true;
3810        }
3811
3812        let block_number = self.blockchain.storage.read().best_number.saturating_add(1);
3813        if is_arbitrum(evm_env.cfg_env.chain_id) {
3814            evm_env.block_env.number = U256::from(block_number);
3815        } else {
3816            evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1));
3817        }
3818        evm_env.block_env.basefee = current_base_fee;
3819        evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price;
3820        evm_env.block_env.timestamp = U256::from(timestamp);
3821
3822        let best_hash = self.blockchain.storage.read().best_hash;
3823        let mut prevrandao_input = [0u8; 40];
3824        prevrandao_input[..32].copy_from_slice(best_hash.as_slice());
3825        prevrandao_input[32..].copy_from_slice(&block_number.to_le_bytes());
3826        evm_env.block_env.prevrandao = Some(
3827            self.cheats.take_next_block_prevrandao().unwrap_or_else(|| keccak256(prevrandao_input)),
3828        );
3829
3830        let mut replay_env = evm_env.clone();
3831        apply_chain_specific_tx_replay_env_changes(&mut replay_env);
3832        let inspector_tx_config = self.inspector_tx_config();
3833
3834        // Resolve the hardfork from the source block itself, not the backend's configured one,
3835        // since the two can diverge at a fork boundary.
3836        let hardfork =
3837            FoundryHardfork::from_chain_and_timestamp(evm_env.cfg_env.chain_id, timestamp)
3838                .unwrap_or_else(|| self.hardfork());
3839        if !self.is_optimism() && !self.is_tempo() {
3840            replay_env.cfg_env.spec = SpecId::from(hardfork);
3841            // Cancun requires blob excess gas even for non-blob txs.
3842            if replay_env.cfg_env.spec >= SpecId::CANCUN
3843                && replay_env.block_env.blob_excess_gas_and_price.is_none()
3844            {
3845                replay_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(
3846                    0,
3847                    get_blob_base_fee_update_fraction_by_spec_id(replay_env.cfg_env.spec),
3848                ));
3849            }
3850        }
3851
3852        let (block_info, state_changes, block_hash) = {
3853            let db = self.db.read().await;
3854            let mut overlay = AnvilCacheDB::new(&**db);
3855            let ExecutedHistoricalReplay {
3856                block_result,
3857                transactions,
3858                transaction_infos,
3859                state_changes,
3860            } = self.execute_with_replay_block_executor(
3861                &mut overlay,
3862                &replay_env,
3863                best_hash,
3864                hardfork,
3865                parent_beacon_block_root,
3866                &transactions,
3867                &inspector_tx_config,
3868            )?;
3869            let state_root = overlay.maybe_state_root().unwrap_or_default();
3870            let block_info = self.build_block_info(
3871                &replay_env,
3872                best_hash,
3873                block_number,
3874                state_root,
3875                block_result,
3876                transactions,
3877                transaction_infos,
3878                parent_beacon_block_root,
3879            );
3880            let block_hash = block_info.block.header.hash_slow();
3881            (block_info, state_changes, block_hash)
3882        };
3883
3884        if self.prune_state_history_config.is_state_history_supported() {
3885            let state = self.db.read().await.current_state();
3886            self.states.write().insert(best_hash, state);
3887        }
3888
3889        {
3890            let mut db = self.db.write().await;
3891            for state in state_changes {
3892                db.commit(state);
3893            }
3894            db.insert_block_hash(U256::from(block_number), block_hash);
3895        }
3896
3897        let BlockInfo { block, transactions, receipts } = block_info;
3898        let header = block.header.clone();
3899        {
3900            let mut storage = self.blockchain.storage.write();
3901            storage.best_number = block_number;
3902            storage.best_hash = block_hash;
3903            if !self.is_eip3675() {
3904                storage.total_difficulty =
3905                    storage.total_difficulty.saturating_add(header.difficulty);
3906            }
3907            storage.blocks.insert(block_hash, block);
3908            storage.hashes.insert(block_number, block_hash);
3909            for (info, receipt) in transactions.into_iter().zip(receipts) {
3910                let mined_tx = MinedTransaction { info, receipt, block_hash, block_number };
3911                storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx);
3912            }
3913
3914            if let Some(transaction_block_keeper) = self.transaction_block_keeper
3915                && storage.blocks.len() > transaction_block_keeper
3916            {
3917                let to_clear = block_number
3918                    .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX));
3919                storage.remove_block_transactions_by_number(to_clear)
3920            }
3921        }
3922
3923        evm_env.block_env.difficulty = U256::ZERO;
3924        *self.evm_env.write() = evm_env;
3925        self.time.reset(timestamp);
3926        self.time.set_next_block_timestamp(next_timestamp)?;
3927
3928        let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas(
3929            header.gas_used,
3930            header.gas_limit,
3931            header.base_fee_per_gas.unwrap_or_default(),
3932        );
3933        let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas(
3934            header.excess_blob_gas.unwrap_or_default(),
3935            header.blob_gas_used.unwrap_or_default(),
3936        );
3937        self.fees.set_base_fee(next_block_base_fee);
3938        self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
3939            next_block_excess_blob_gas,
3940            get_blob_base_fee_update_fraction_by_spec_id(*self.evm_env.read().spec_id()),
3941        ));
3942        self.notify_on_new_block(header.into_inner(), block_hash);
3943
3944        Ok(())
3945    }
3946
3947    #[allow(clippy::too_many_arguments)]
3948    fn execute_with_replay_block_executor<DB>(
3949        &self,
3950        db: DB,
3951        evm_env: &EvmEnv,
3952        parent_hash: B256,
3953        hardfork: FoundryHardfork,
3954        parent_beacon_block_root: Option<B256>,
3955        transactions: &[HistoricalReplayTransaction],
3956        inspector_tx_config: &InspectorTxConfig,
3957    ) -> Result<ExecutedHistoricalReplay>
3958    where
3959        DB: StateDB<Error = DatabaseError>,
3960    {
3961        let inspector = self.build_mining_inspector();
3962        let ethereum_transitions = self.ethereum_block_transitions(
3963            hardfork,
3964            parent_beacon_block_root,
3965            BlockExecutionKind::TransactionPrefix,
3966        );
3967
3968        macro_rules! run {
3969            ($evm:expr) => {{
3970                self.inject_precompiles($evm.precompiles_mut(), evm_env);
3971                self.inject_arbitrum_precompile($evm.precompiles_mut(), evm_env);
3972                let mut executor = AnvilBlockExecutor::new(
3973                    $evm,
3974                    parent_hash,
3975                    *evm_env.spec_id(),
3976                    ethereum_transitions,
3977                )
3978                .with_state_changes();
3979                executor
3980                    .apply_pre_execution_changes()
3981                    .wrap_err("failed to apply replay block-start transitions")?;
3982                let (stored_transactions, transaction_infos) =
3983                    execute_historical_replay(&mut executor, transactions, inspector_tx_config)?;
3984                let state_changes = executor.take_state_changes();
3985                let (evm, block_result) =
3986                    executor.finish().wrap_err("failed to finish replay block execution")?;
3987                drop(evm);
3988                Ok(ExecutedHistoricalReplay {
3989                    block_result,
3990                    transactions: stored_transactions,
3991                    transaction_infos,
3992                    state_changes,
3993                })
3994            }};
3995        }
3996
3997        #[cfg(feature = "optimism")]
3998        if self.is_optimism() {
3999            let op_env = EvmEnv::new(
4000                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(self.hardfork.into()),
4001                evm_env.block_env.clone(),
4002            );
4003            let mut evm =
4004                OpEvmFactory::<OpTx>::default().create_evm_with_inspector(db, op_env, inspector);
4005            return run!(evm);
4006        }
4007
4008        if self.is_tempo() {
4009            let tempo_env = self.build_tempo_evm_env(evm_env);
4010            let mut evm =
4011                TempoEvmFactory::default().create_evm_with_inspector(db, tempo_env, inspector);
4012            run!(evm)
4013        } else {
4014            let mut evm =
4015                EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
4016            run!(evm)
4017        }
4018    }
4019
4020    /// Builds a [`BlockInfo`] from the EVM environment, execution results, and transactions.
4021    #[allow(clippy::too_many_arguments)]
4022    fn build_block_info(
4023        &self,
4024        evm_env: &EvmEnv,
4025        parent_hash: B256,
4026        number: u64,
4027        state_root: B256,
4028        block_result: BlockExecutionResult<FoundryReceiptEnvelope>,
4029        transactions: Vec<MaybeImpersonatedTransaction<FoundryTxEnvelope>>,
4030        transaction_infos: Vec<TransactionInfo>,
4031        parent_beacon_block_root: Option<B256>,
4032    ) -> BlockInfo<N> {
4033        let spec_id = *evm_env.spec_id();
4034        let is_shanghai = spec_id >= SpecId::SHANGHAI;
4035        let is_cancun = spec_id >= SpecId::CANCUN;
4036        let is_prague = spec_id >= SpecId::PRAGUE;
4037
4038        let receipts_root = calculate_receipt_root(&block_result.receipts);
4039        let cumulative_blob_gas_used = is_cancun.then_some(block_result.blob_gas_used);
4040        let bloom = block_result.receipts.iter().fold(Bloom::default(), |mut b, r| {
4041            b.accrue_bloom(r.logs_bloom());
4042            b
4043        });
4044
4045        let header = Header {
4046            parent_hash,
4047            ommers_hash: Default::default(),
4048            beneficiary: evm_env.block_env.beneficiary,
4049            state_root,
4050            transactions_root: Default::default(),
4051            receipts_root,
4052            logs_bloom: bloom,
4053            difficulty: evm_env.block_env.difficulty,
4054            number,
4055            gas_limit: evm_env.block_env.gas_limit,
4056            gas_used: block_result.gas_used,
4057            timestamp: evm_env.block_env.timestamp.saturating_to(),
4058            extra_data: Default::default(),
4059            mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(),
4060            nonce: Default::default(),
4061            base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(evm_env.block_env.basefee),
4062            parent_beacon_block_root: is_cancun
4063                .then(|| parent_beacon_block_root.unwrap_or_default()),
4064            blob_gas_used: cumulative_blob_gas_used,
4065            excess_blob_gas: if is_cancun { evm_env.block_env.blob_excess_gas() } else { None },
4066            withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
4067            requests_hash: is_prague.then(|| block_result.requests.requests_hash()),
4068            block_access_list_hash: None,
4069            slot_number: None,
4070        };
4071
4072        let block = create_block(FoundryHeader::new(header, self.is_tempo()), transactions);
4073        BlockInfo { block, transactions: transaction_infos, receipts: block_result.receipts }
4074    }
4075
4076    async fn do_mine_block(
4077        &self,
4078        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
4079    ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
4080        let _mining_guard = self.mining.lock().await;
4081        trace!(target: "backend", "creating new block with {} transactions", pool_transactions.len());
4082
4083        let (outcome, header, block_hash) = {
4084            let current_base_fee = self.base_fee();
4085            let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price();
4086
4087            let mut evm_env = self.evm_env.read().clone();
4088
4089            if evm_env.block_env.basefee == 0 {
4090                // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee`
4091                // 0 is only possible if it's manually set
4092                evm_env.cfg_env.disable_base_fee = true;
4093            }
4094
4095            let block_number = self.blockchain.storage.read().best_number.saturating_add(1);
4096
4097            // increase block number for this block
4098            if is_arbitrum(evm_env.cfg_env.chain_id) {
4099                // Temporary set `env.block.number` to `block_number` for Arbitrum chains.
4100                evm_env.block_env.number = U256::from(block_number);
4101            } else {
4102                evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1));
4103            }
4104
4105            evm_env.block_env.basefee = current_base_fee;
4106            evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price;
4107
4108            let best_hash = self.blockchain.storage.read().best_hash;
4109
4110            let mut input = [0u8; 40];
4111            input[..32].copy_from_slice(best_hash.as_slice());
4112            input[32..].copy_from_slice(&block_number.to_le_bytes());
4113            // Use the `prevrandao` value set via `anvil_setNextBlockPrevRandao` for this block if
4114            // one was provided, otherwise derive it from the parent hash and block number. The
4115            // manual override is consumed here so it only applies to this single block.
4116            let next_prevrandao = self.cheats.prepare_next_block_prevrandao();
4117            evm_env.block_env.prevrandao =
4118                Some(next_prevrandao.map_or_else(|| keccak256(input), |pending| pending.value));
4119
4120            let (block_info, included, invalid, not_yet_valid, block_hash, parent_state) = {
4121                let mut db = self.db.write().await;
4122
4123                // finally set the next block timestamp, this is done just before execution, because
4124                // there can be concurrent requests that can delay acquiring the db lock and we want
4125                // to ensure the timestamp is as close as possible to the actual execution.
4126                let pending_timestamp = self.time.prepare_next_timestamp();
4127                evm_env.block_env.timestamp = U256::from(pending_timestamp.timestamp);
4128
4129                // Forced historical transactions bypass pool admission and are replayed while
4130                // mining. Keep this exception local to the disposable mining environment.
4131                let mut mining_evm_env = evm_env.clone();
4132                if pool_transactions.iter().any(|tx| tx.is_replay) {
4133                    apply_chain_specific_tx_replay_env_changes(&mut mining_evm_env);
4134                }
4135
4136                let spec_id = *mining_evm_env.spec_id();
4137
4138                let inspector_tx_config = self.inspector_tx_config();
4139                let gas_config = self.pool_tx_gas_config(&mining_evm_env);
4140
4141                let mut candidate_db = AnvilCacheDB::new(&**db);
4142                let (pool_result, block_result) = self.execute_with_block_executor(
4143                    &mut candidate_db,
4144                    &mining_evm_env,
4145                    best_hash,
4146                    spec_id,
4147                    Some(B256::ZERO),
4148                    BlockExecutionKind::Complete,
4149                    &pool_transactions,
4150                    &gas_config,
4151                    &inspector_tx_config,
4152                    &|pool_tx, account| {
4153                        let validation_env =
4154                            if pool_tx.is_replay { &mining_evm_env } else { &evm_env };
4155                        self.validate_pool_transaction_for(
4156                            &pool_tx.pending_transaction,
4157                            account,
4158                            validation_env,
4159                        )
4160                    },
4161                )?;
4162
4163                let included = pool_result.included;
4164                let invalid = pool_result.invalid;
4165                let not_yet_valid = pool_result.not_yet_valid;
4166
4167                let CacheDB { cache, db: _ } = candidate_db.0;
4168                let parent_state = self
4169                    .prune_state_history_config
4170                    .is_state_history_supported()
4171                    .then(|| db.current_state());
4172                commit_cache(&mut **db, cache)?;
4173                let state_root = db.maybe_state_root().unwrap_or_default();
4174                let block_info = self.build_block_info(
4175                    &mining_evm_env,
4176                    best_hash,
4177                    block_number,
4178                    state_root,
4179                    block_result,
4180                    pool_result.txs,
4181                    pool_result.tx_info,
4182                    Some(B256::ZERO),
4183                );
4184
4185                // Update the new blockhash in the db itself.
4186                let block_hash = block_info.block.header.hash_slow();
4187                db.insert_block_hash(U256::from(block_info.block.header.number()), block_hash);
4188                self.time.commit_next_timestamp(pending_timestamp);
4189                if let Some(pending) = next_prevrandao {
4190                    self.cheats.consume_next_block_prevrandao(pending);
4191                }
4192
4193                (block_info, included, invalid, not_yet_valid, block_hash, parent_state)
4194            };
4195
4196            // create the new block with the current timestamp
4197            let BlockInfo { block, transactions, receipts } = block_info;
4198
4199            let header = block.header.clone();
4200
4201            if let Some(parent_state) = parent_state {
4202                self.states.write().insert(best_hash, parent_state);
4203            }
4204
4205            trace!(
4206                target: "backend",
4207                "Mined block {} with {} tx {:?}",
4208                block_number,
4209                transactions.len(),
4210                transactions.iter().map(|tx| tx.transaction_hash).collect::<Vec<_>>()
4211            );
4212            let mut storage = self.blockchain.storage.write();
4213            // update block metadata
4214            storage.best_number = block_number;
4215            storage.best_hash = block_hash;
4216            // Difficulty is removed and not used after Paris (aka TheMerge). Value is replaced with
4217            // prevrandao. https://github.com/bluealloy/revm/blob/1839b3fce8eaeebb85025576f2519b80615aca1e/crates/interpreter/src/instructions/host_env.rs#L27
4218            if !self.is_eip3675() {
4219                storage.total_difficulty =
4220                    storage.total_difficulty.saturating_add(header.difficulty);
4221            }
4222
4223            storage.blocks.insert(block_hash, block);
4224            storage.hashes.insert(block_number, block_hash);
4225
4226            node_info!("");
4227            // insert all transactions
4228            for (info, receipt) in transactions.into_iter().zip(receipts) {
4229                // log some tx info
4230                node_info!("    Transaction: {:?}", info.transaction_hash);
4231                if let Some(contract) = &info.contract_address {
4232                    node_info!("    Contract created: {contract}");
4233                }
4234                node_info!("    Gas used: {}", info.gas_used);
4235                if !info.exit.is_ok() {
4236                    let r = RevertDecoder::new().decode(
4237                        info.out.as_ref().map(|b| &b[..]).unwrap_or_default(),
4238                        Some(info.exit),
4239                    );
4240                    node_info!("    Error: reverted with: {r}");
4241                }
4242                node_info!("");
4243
4244                let mined_tx = MinedTransaction { info, receipt, block_hash, block_number };
4245                storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx);
4246            }
4247
4248            // remove old transactions that exceed the transaction block keeper
4249            if let Some(transaction_block_keeper) = self.transaction_block_keeper
4250                && storage.blocks.len() > transaction_block_keeper
4251            {
4252                let to_clear = block_number
4253                    .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX));
4254                storage.remove_block_transactions_by_number(to_clear)
4255            }
4256
4257            // we intentionally set the difficulty to `0` for newer blocks
4258            evm_env.block_env.difficulty = U256::from(0);
4259
4260            // update env with new values
4261            *self.evm_env.write() = evm_env;
4262
4263            let timestamp = utc_from_secs(header.timestamp);
4264
4265            node_info!("    Block Number: {}", block_number);
4266            node_info!("    Block Hash: {:?}", block_hash);
4267            if timestamp.year() > 9999 {
4268                // rf2822 panics with more than 4 digits
4269                node_info!("    Block Time: {:?}\n", timestamp.to_rfc3339());
4270            } else {
4271                node_info!("    Block Time: {:?}\n", timestamp.to_rfc2822());
4272            }
4273
4274            let outcome = MinedBlockOutcome { block_number, included, invalid, not_yet_valid };
4275
4276            (outcome, header, block_hash)
4277        };
4278        let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas(
4279            header.gas_used,
4280            header.gas_limit,
4281            header.base_fee_per_gas.unwrap_or_default(),
4282        );
4283        let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas(
4284            header.excess_blob_gas.unwrap_or_default(),
4285            header.blob_gas_used.unwrap_or_default(),
4286        );
4287
4288        // update next base fee
4289        self.fees.set_base_fee(next_block_base_fee);
4290
4291        self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
4292            next_block_excess_blob_gas,
4293            get_blob_base_fee_update_fraction_by_spec_id(*self.evm_env.read().spec_id()),
4294        ));
4295
4296        // notify all listeners
4297        self.notify_on_new_block(header.into_inner(), block_hash);
4298
4299        Ok(outcome)
4300    }
4301
4302    /// Reorg the chain to a common height and execute blocks to build new chain.
4303    ///
4304    /// The state of the chain is rewound using `rewind` to the common block, including the db,
4305    /// storage, and env.
4306    ///
4307    /// Finally, `do_mine_block` is called to create the new chain.
4308    pub async fn reorg(
4309        &self,
4310        depth: u64,
4311        tx_pairs: HashMap<u64, Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>>,
4312        common_block: Block,
4313    ) -> Result<(), BlockchainError> {
4314        self.rollback(common_block).await?;
4315        // Create the new reorged chain, filling the blocks with transactions if supplied
4316        for i in 0..depth {
4317            let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new);
4318            let outcome = self.do_mine_block(to_be_mined).await?;
4319            node_info!(
4320                "    Mined reorg block number {}. With {} valid txs and with invalid {} txs",
4321                outcome.block_number,
4322                outcome.included.len(),
4323                outcome.invalid.len()
4324            );
4325        }
4326
4327        Ok(())
4328    }
4329
4330    /// Creates the pending block
4331    ///
4332    /// This will execute all transaction in the order they come but will not mine the block
4333    pub async fn pending_block(
4334        &self,
4335        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
4336    ) -> BlockInfo<N> {
4337        self.with_pending_block(pool_transactions, |_, block| block).await
4338    }
4339
4340    /// Creates the pending block
4341    ///
4342    /// This will execute all transaction in the order they come but will not mine the block
4343    pub async fn with_pending_block<F, T>(
4344        &self,
4345        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
4346        f: F,
4347    ) -> T
4348    where
4349        F: FnOnce(Box<dyn MaybeFullDatabase + '_>, BlockInfo<N>) -> T,
4350    {
4351        let db = self.db.read().await;
4352        let evm_env = self.next_evm_env();
4353
4354        let mut cache_db = AnvilCacheDB::new(&*db);
4355
4356        let parent_hash = self.blockchain.storage.read().best_hash;
4357
4358        let spec_id = *evm_env.spec_id();
4359
4360        let inspector_tx_config = self.inspector_tx_config();
4361        let gas_config = self.pool_tx_gas_config(&evm_env);
4362
4363        let (pool_result, block_result) = self
4364            .execute_with_block_executor(
4365                &mut cache_db,
4366                &evm_env,
4367                parent_hash,
4368                spec_id,
4369                Some(B256::ZERO),
4370                BlockExecutionKind::Complete,
4371                &pool_transactions,
4372                &gas_config,
4373                &inspector_tx_config,
4374                &|pool_tx, account| {
4375                    self.validate_pool_transaction_for(
4376                        &pool_tx.pending_transaction,
4377                        account,
4378                        &evm_env,
4379                    )
4380                },
4381            )
4382            .expect("pending block execution failed");
4383
4384        // Extract inner CacheDB (which implements MaybeFullDatabase)
4385        let cache_db = cache_db.0;
4386
4387        let state_root = cache_db.maybe_state_root().unwrap_or_default();
4388        let block_number = evm_env.block_env.number.saturating_to();
4389        let block_info = self.build_block_info(
4390            &evm_env,
4391            parent_hash,
4392            block_number,
4393            state_root,
4394            block_result,
4395            pool_result.txs,
4396            pool_result.tx_info,
4397            Some(B256::ZERO),
4398        );
4399
4400        f(Box::new(cache_db), block_info)
4401    }
4402
4403    /// Returns the ERC20/TIP20 token balance for an account.
4404    ///
4405    /// Calls `balanceOf(address)` on the token contract. Returns `U256::ZERO` if
4406    /// the call fails (e.g. the token contract doesn't exist).
4407    pub async fn get_fee_token_balance(
4408        &self,
4409        token: Address,
4410        account: Address,
4411    ) -> Result<U256, BlockchainError> {
4412        // balanceOf(address) selector: 0x70a08231
4413        let mut calldata = vec![0x70, 0xa0, 0x82, 0x31];
4414        // ABI-encode the address (left-padded to 32 bytes)
4415        calldata.extend_from_slice(&[0u8; 12]);
4416        calldata.extend_from_slice(account.as_slice());
4417
4418        let request = WithOtherFields::new(TransactionRequest {
4419            from: Some(Address::ZERO),
4420            to: Some(TxKind::Call(token)),
4421            input: calldata.into(),
4422            ..Default::default()
4423        });
4424
4425        let fee_details = FeeDetails::zero();
4426        let (exit, out, _, _) = self.call(request, fee_details, None, Default::default()).await?;
4427
4428        // Check if call succeeded
4429        if exit != InstructionResult::Return && exit != InstructionResult::Stop {
4430            // Return zero balance if call failed (token might not exist)
4431            return Ok(U256::ZERO);
4432        }
4433
4434        // Decode U256 from output
4435        match out {
4436            Some(Output::Call(data)) if data.len() >= 32 => Ok(U256::from_be_slice(&data[..32])),
4437            _ => Ok(U256::ZERO),
4438        }
4439    }
4440
4441    /// Executes the [TransactionRequest] without writing to the DB
4442    ///
4443    /// # Errors
4444    ///
4445    /// Returns an error if the `block_number` is greater than the current height
4446    pub async fn call(
4447        &self,
4448        request: WithOtherFields<TransactionRequest>,
4449        fee_details: FeeDetails,
4450        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4451        overrides: EvmOverrides,
4452    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
4453        self.with_database_at(block_request, |state, mut block| {
4454            let block_number = block.number;
4455            let (exit, out, gas, state) = {
4456                let mut cache_db = CacheDB::new(state);
4457                if let Some(state_overrides) = overrides.state {
4458                    apply_state_overrides(state_overrides.into_iter().collect(), &mut cache_db)?;
4459                }
4460                if let Some(block_overrides) = overrides.block {
4461                    cache_db.apply_block_overrides(*block_overrides, &mut block);
4462                }
4463                self.call_with_state(&cache_db, request, fee_details, block)
4464            }?;
4465            trace!(target: "backend", "call return {:?} out: {:?} gas {} on block {}", exit, out, gas, block_number);
4466            Ok((exit, out, gas, state))
4467        }).await?
4468    }
4469
4470    pub async fn call_with_tracing(
4471        &self,
4472        request: WithOtherFields<TransactionRequest>,
4473        fee_details: FeeDetails,
4474        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4475        opts: GethDebugTracingCallOptions,
4476    ) -> Result<GethTrace, BlockchainError> {
4477        let GethDebugTracingCallOptions {
4478            tracing_options,
4479            block_overrides,
4480            state_overrides,
4481            tx_index,
4482        } = opts;
4483
4484        if let Some(tx_index) = tx_index {
4485            return self
4486                .call_with_tracing_at_tx_index(
4487                    request,
4488                    fee_details,
4489                    block_request,
4490                    tx_index,
4491                    tracing_options,
4492                    state_overrides,
4493                    block_overrides,
4494                )
4495                .await;
4496        }
4497
4498        self.with_database_at(block_request, |state, block| {
4499            let cache_db = CacheDB::new(state);
4500            self.trace_call_with_state(
4501                request,
4502                fee_details,
4503                block,
4504                cache_db,
4505                tracing_options,
4506                state_overrides,
4507                block_overrides,
4508            )
4509        })
4510        .await?
4511    }
4512
4513    #[allow(clippy::too_many_arguments)]
4514    async fn call_with_tracing_at_tx_index(
4515        &self,
4516        request: WithOtherFields<TransactionRequest>,
4517        fee_details: FeeDetails,
4518        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4519        tx_index: u64,
4520        tracing_options: GethDebugTracingOptions,
4521        state_overrides: Option<StateOverride>,
4522        block_overrides: Option<BlockOverrides>,
4523    ) -> Result<GethTrace, BlockchainError> {
4524        let tx_index = usize::try_from(tx_index).map_err(|_| {
4525            BlockchainError::RpcError(RpcError::invalid_params(format!(
4526                "tx_index {tx_index} does not fit in usize"
4527            )))
4528        })?;
4529        let block_number = match block_request {
4530            Some(BlockRequest::Pending(_)) => {
4531                return Err(BlockchainError::RpcError(RpcError::invalid_params(
4532                    "tx_index is not supported for pending blocks".to_string(),
4533                )));
4534            }
4535            Some(BlockRequest::Number(number)) => number,
4536            None => self.best_number(),
4537        };
4538        let block_id = BlockId::Number(BlockNumber::Number(block_number));
4539
4540        if let Some(block) = self.get_block(block_id) {
4541            return self.mined_trace_call_at_tx_index(
4542                request,
4543                fee_details,
4544                &block,
4545                tx_index,
4546                tracing_options,
4547                state_overrides,
4548                block_overrides,
4549            );
4550        }
4551
4552        if let Some(fork) = self.get_fork()
4553            && fork.predates_fork_inclusive(block_number)
4554        {
4555            let opts = GethDebugTracingCallOptions {
4556                tracing_options,
4557                state_overrides,
4558                block_overrides,
4559                tx_index: Some(tx_index as u64),
4560            };
4561            return Ok(fork.debug_trace_call(request, block_id, opts).await?);
4562        }
4563
4564        Err(BlockchainError::BlockNotFound)
4565    }
4566
4567    #[allow(clippy::too_many_arguments)]
4568    fn mined_trace_call_at_tx_index(
4569        &self,
4570        request: WithOtherFields<TransactionRequest>,
4571        fee_details: FeeDetails,
4572        block: &Block,
4573        tx_index: usize,
4574        tracing_options: GethDebugTracingOptions,
4575        state_overrides: Option<StateOverride>,
4576        block_overrides: Option<BlockOverrides>,
4577    ) -> Result<GethTrace, BlockchainError> {
4578        let transaction_count = block.body.transactions.len();
4579        if tx_index >= transaction_count {
4580            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
4581                "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
4582            ))));
4583        }
4584
4585        let pool_txs: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>> = block.body.transactions
4586            [..tx_index]
4587            .iter()
4588            .map(|tx| {
4589                let pending_tx =
4590                    PendingTransaction::from_maybe_impersonated(tx.clone()).expect("is valid");
4591                Arc::new(PoolTransaction {
4592                    pending_transaction: pending_tx,
4593                    requires: vec![],
4594                    provides: vec![],
4595                    priority: crate::eth::pool::transactions::TransactionPriority(0),
4596                    is_replay: true,
4597                })
4598            })
4599            .collect();
4600
4601        let trace = |parent_state: &StateDb| -> Result<GethTrace, BlockchainError> {
4602            let mut cache_db =
4603                AnvilCacheDB::new(Box::new(parent_state) as Box<dyn MaybeFullDatabase + '_>);
4604
4605            let evm_env = self.tx_replay_evm_env(&block.header);
4606
4607            let spec_id = *evm_env.spec_id();
4608            let inspector_tx_config = self.inspector_tx_config();
4609            let gas_config = self.pool_tx_gas_config(&evm_env);
4610
4611            self.execute_with_block_executor(
4612                &mut cache_db,
4613                &evm_env,
4614                block.header.parent_hash,
4615                spec_id,
4616                block.header.parent_beacon_block_root,
4617                BlockExecutionKind::TransactionPrefix,
4618                &pool_txs,
4619                &gas_config,
4620                &inspector_tx_config,
4621                &|pool_tx, account| {
4622                    self.validate_pool_transaction_for(
4623                        &pool_tx.pending_transaction,
4624                        account,
4625                        &evm_env,
4626                    )
4627                },
4628            )?;
4629
4630            let cache_db = cache_db.0;
4631            self.trace_call_with_state(
4632                request,
4633                fee_details,
4634                evm_env.block_env,
4635                cache_db,
4636                tracing_options,
4637                state_overrides,
4638                block_overrides,
4639            )
4640        };
4641
4642        let read_guard = self.states.upgradable_read();
4643        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
4644            trace(state)
4645        } else {
4646            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
4647            let state = write_guard
4648                .get_on_disk_state(&block.header.parent_hash)
4649                .ok_or(BlockchainError::BlockNotFound)?;
4650            trace(state)
4651        }
4652    }
4653
4654    #[allow(clippy::too_many_arguments)]
4655    fn trace_call_with_state(
4656        &self,
4657        request: WithOtherFields<TransactionRequest>,
4658        fee_details: FeeDetails,
4659        mut block: BlockEnv,
4660        mut cache_db: CacheDB<Box<dyn MaybeFullDatabase + '_>>,
4661        tracing_options: GethDebugTracingOptions,
4662        state_overrides: Option<StateOverride>,
4663        block_overrides: Option<BlockOverrides>,
4664    ) -> Result<GethTrace, BlockchainError> {
4665        let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options;
4666        let block_number = block.number;
4667
4668        if let Some(state_overrides) = state_overrides {
4669            apply_state_overrides(state_overrides, &mut cache_db)?;
4670        }
4671        if let Some(block_overrides) = block_overrides {
4672            cache_db.apply_block_overrides(block_overrides, &mut block);
4673        }
4674
4675        if let Some(tracer) = tracer {
4676            return match tracer {
4677                GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
4678                    GethDebugBuiltInTracerType::CallTracer => {
4679                        let call_config = call_config_from_tracer_config(tracer_config)
4680                            .map_err(|e| RpcError::invalid_params(e.to_string()))?;
4681
4682                        let mut inspector = self.build_inspector().with_tracing_config(
4683                            TracingInspectorConfig::from_geth_call_config(&call_config),
4684                        );
4685
4686                        let PreparedCall { evm_env, tx_env, .. } =
4687                            self.prepare_call_env(&cache_db, request, fee_details, block)?;
4688                        let ResultAndState { result, state: _ } = self
4689                            .transact_call_with_inspector_ref(
4690                                &cache_db,
4691                                &evm_env,
4692                                &mut inspector,
4693                                tx_env,
4694                            )?;
4695
4696                        inspector.print_logs();
4697                        if self.print_traces {
4698                            inspector.print_traces(self.call_trace_decoder.clone());
4699                        }
4700
4701                        let tracing_inspector = inspector.tracer.expect("tracer disappeared");
4702
4703                        Ok(tracing_inspector
4704                            .into_geth_builder()
4705                            .geth_call_traces(call_config, result.tx_gas_used())
4706                            .into())
4707                    }
4708                    GethDebugBuiltInTracerType::PreStateTracer => {
4709                        let pre_state_config = tracer_config
4710                            .into_pre_state_config()
4711                            .map_err(|e| RpcError::invalid_params(e.to_string()))?;
4712
4713                        let mut inspector = TracingInspector::new(
4714                            TracingInspectorConfig::from_geth_prestate_config(&pre_state_config),
4715                        );
4716
4717                        let PreparedCall { evm_env, tx_env, .. } =
4718                            self.prepare_call_env(&cache_db, request, fee_details, block)?;
4719                        let result = self.transact_call_with_inspector_ref(
4720                            &cache_db,
4721                            &evm_env,
4722                            &mut inspector,
4723                            tx_env,
4724                        )?;
4725
4726                        Ok(inspector
4727                            .into_geth_builder()
4728                            .geth_prestate_traces(&result, &pre_state_config, cache_db)?
4729                            .into())
4730                    }
4731                    GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()),
4732                    GethDebugBuiltInTracerType::FourByteTracer
4733                    | GethDebugBuiltInTracerType::MuxTracer
4734                    | GethDebugBuiltInTracerType::FlatCallTracer
4735                    | GethDebugBuiltInTracerType::Erc7562Tracer => {
4736                        Err(RpcError::invalid_params("unsupported tracer type").into())
4737                    }
4738                },
4739                #[cfg(not(feature = "js-tracer"))]
4740                GethDebugTracerType::JsTracer(_) => {
4741                    Err(RpcError::invalid_params("unsupported tracer type").into())
4742                }
4743                #[cfg(feature = "js-tracer")]
4744                GethDebugTracerType::JsTracer(code) => {
4745                    let config = tracer_config.into_json();
4746                    let mut inspector =
4747                        revm_inspectors::tracing::js::JsInspector::new(code, config)
4748                            .map_err(|err| BlockchainError::Message(err.to_string()))?;
4749
4750                    let PreparedCall { evm_env, tx_env, .. } =
4751                        self.prepare_call_env(&cache_db, request, fee_details, block.clone())?;
4752                    let result = self.transact_call_with_inspector_ref(
4753                        &cache_db,
4754                        &evm_env,
4755                        &mut inspector,
4756                        tx_env.clone(),
4757                    )?;
4758                    let res = inspector
4759                        .json_result(result, tx_env.base(), &block, &cache_db)
4760                        .map_err(|err| BlockchainError::Message(err.to_string()))?;
4761
4762                    Ok(GethTrace::JS(res))
4763                }
4764            };
4765        }
4766
4767        // defaults to StructLog tracer used since no tracer is specified
4768        let mut inspector = self
4769            .build_inspector()
4770            .with_tracing_config(TracingInspectorConfig::from_geth_config(&config));
4771
4772        let PreparedCall { evm_env, tx_env, .. } =
4773            self.prepare_call_env(&cache_db, request, fee_details, block)?;
4774        let ResultAndState { result, state: _ } =
4775            self.transact_call_with_inspector_ref(&cache_db, &evm_env, &mut inspector, tx_env)?;
4776
4777        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
4778
4779        let tracing_inspector = inspector.tracer.expect("tracer disappeared");
4780        let return_value = out.as_ref().map(|o| o.data()).cloned().unwrap_or_default();
4781
4782        trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call");
4783
4784        let res = tracing_inspector
4785            .into_geth_builder()
4786            .geth_traces(gas_used, return_value, config)
4787            .into();
4788
4789        Ok(res)
4790    }
4791
4792    /// Helper function to execute a closure with the database at a specific block
4793    pub async fn with_database_at<F, T>(
4794        &self,
4795        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4796        f: F,
4797    ) -> Result<T, BlockchainError>
4798    where
4799        F: FnOnce(Box<dyn MaybeFullDatabase + '_>, BlockEnv) -> T,
4800    {
4801        let block_number = match block_request {
4802            Some(BlockRequest::Pending(pool_transactions)) => {
4803                let result = self
4804                    .with_pending_block(pool_transactions, |state, block| {
4805                        let block = block.block;
4806                        f(state, block_env_from_header(&block.header))
4807                    })
4808                    .await;
4809                return Ok(result);
4810            }
4811            Some(BlockRequest::Number(bn)) => Some(BlockNumber::Number(bn)),
4812            None => None,
4813        };
4814        let block_number = self.convert_block_number(block_number);
4815        let current_number = self.best_number();
4816
4817        // Reject requests for future blocks that don't exist yet
4818        if block_number > current_number {
4819            return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
4820        }
4821
4822        if block_number < current_number {
4823            if let Some((block_hash, block)) = self
4824                .block_by_number(BlockNumber::Number(block_number))
4825                .await?
4826                .map(|block| (block.header.hash, block))
4827            {
4828                let read_guard = self.states.upgradable_read();
4829                if let Some(state_db) = read_guard.get_state(&block_hash) {
4830                    return Ok(f(Box::new(state_db), block_env_from_header(&block.header)));
4831                }
4832
4833                let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
4834                if let Some(state) = write_guard.get_on_disk_state(&block_hash) {
4835                    return Ok(f(Box::new(state), block_env_from_header(&block.header)));
4836                }
4837            }
4838
4839            warn!(target: "backend", "Not historic state found for block={}", block_number);
4840            return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
4841        }
4842
4843        let db = self.db.read().await;
4844        let block = self.evm_env.read().block_env.clone();
4845        Ok(f(Box::new(&**db), block))
4846    }
4847
4848    pub async fn storage_at(
4849        &self,
4850        address: Address,
4851        index: U256,
4852        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4853    ) -> Result<B256, BlockchainError> {
4854        self.with_database_at(block_request, |db, _| {
4855            trace!(target: "backend", "get storage for {:?} at {:?}", address, index);
4856            let val = db.storage_ref(address, index)?;
4857            Ok(val.into())
4858        })
4859        .await?
4860    }
4861
4862    pub async fn tempo_nonce(
4863        &self,
4864        caller: Address,
4865        nonce_key: U256,
4866        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4867    ) -> Result<u64, BlockchainError> {
4868        self.with_database_at(block_request, |state, _| tempo_nonce(&state, caller, nonce_key))
4869            .await?
4870    }
4871
4872    /// Returns storage values for multiple accounts and slots in a single call.
4873    pub async fn storage_values(
4874        &self,
4875        requests: HashMap<Address, Vec<B256>>,
4876        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4877    ) -> Result<HashMap<Address, Vec<B256>>, BlockchainError> {
4878        self.with_database_at(block_request, |db, _| {
4879            trace!(target: "backend", "get storage values for {} addresses", requests.len());
4880            let mut result: HashMap<Address, Vec<B256>> = HashMap::default();
4881            for (address, slots) in &requests {
4882                let mut values = Vec::with_capacity(slots.len());
4883                for slot in slots {
4884                    let val = db.storage_ref(*address, (*slot).into())?;
4885                    values.push(val.into());
4886                }
4887                result.insert(*address, values);
4888            }
4889            Ok(result)
4890        })
4891        .await?
4892    }
4893
4894    /// Returns the code of the address
4895    ///
4896    /// If the code is not present and fork mode is enabled then this will try to fetch it from the
4897    /// forked client
4898    pub async fn get_code(
4899        &self,
4900        address: Address,
4901        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4902    ) -> Result<Bytes, BlockchainError> {
4903        self.with_database_at(block_request, |db, _| self.get_code_with_state(&db, address)).await?
4904    }
4905
4906    /// Returns the balance of the address
4907    ///
4908    /// If the requested number predates the fork then this will fetch it from the endpoint
4909    pub async fn get_balance(
4910        &self,
4911        address: Address,
4912        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4913    ) -> Result<U256, BlockchainError> {
4914        self.with_database_at(block_request, |db, _| self.get_balance_with_state(db, address))
4915            .await?
4916    }
4917
4918    pub async fn get_account_at_block(
4919        &self,
4920        address: Address,
4921        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4922    ) -> Result<TrieAccount, BlockchainError> {
4923        self.with_database_at(block_request, |block_db, _| {
4924            let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
4925            let account = db.get(&address).cloned().unwrap_or_default();
4926            let storage_root = storage_root(&account.storage);
4927            let code_hash = account.info.code_hash;
4928            let balance = account.info.balance;
4929            let nonce = account.info.nonce;
4930            Ok(TrieAccount { balance, nonce, code_hash, storage_root })
4931        })
4932        .await?
4933    }
4934
4935    /// Returns the nonce of the address
4936    ///
4937    /// If the requested number predates the fork then this will fetch it from the endpoint
4938    pub async fn get_nonce(
4939        &self,
4940        address: Address,
4941        block_request: BlockRequest<FoundryTxEnvelope>,
4942    ) -> Result<u64, BlockchainError> {
4943        if let BlockRequest::Pending(pool_transactions) = &block_request
4944            && let Some(value) = get_pool_transactions_nonce(pool_transactions, address)
4945        {
4946            return Ok(value);
4947        }
4948        let final_block_request = match block_request {
4949            BlockRequest::Pending(_) => BlockRequest::Number(self.best_number()),
4950            BlockRequest::Number(bn) => BlockRequest::Number(bn),
4951        };
4952
4953        self.with_database_at(Some(final_block_request), |db, _| {
4954            trace!(target: "backend", "get nonce for {:?}", address);
4955            Ok(db.basic_ref(address)?.unwrap_or_default().nonce)
4956        })
4957        .await?
4958    }
4959
4960    fn replay_tx_with_inspector<I, F, T>(
4961        &self,
4962        hash: B256,
4963        mut inspector: I,
4964        f: F,
4965    ) -> Result<T, BlockchainError>
4966    where
4967        for<'a> I: BackendInspector<WrapDatabaseRef<&'a CacheDB<Box<&'a StateDb>>>> + 'a,
4968        for<'a> F:
4969            FnOnce(ResultAndState<HaltReason>, CacheDB<Box<&'a StateDb>>, I, TxEnv, EvmEnv) -> T,
4970    {
4971        let block = {
4972            let storage = self.blockchain.storage.read();
4973            let MinedTransaction { block_hash, .. } = storage
4974                .transactions
4975                .get(&hash)
4976                .cloned()
4977                .ok_or(BlockchainError::TransactionNotFound)?;
4978
4979            storage.blocks.get(&block_hash).cloned().ok_or(BlockchainError::BlockNotFound)?
4980        };
4981
4982        let index = block
4983            .body
4984            .transactions
4985            .iter()
4986            .position(|tx| tx.hash() == hash)
4987            .expect("transaction not found in block");
4988
4989        let pool_txs: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>> = block.body.transactions
4990            [..index]
4991            .iter()
4992            .map(|tx| {
4993                let pending_tx =
4994                    PendingTransaction::from_maybe_impersonated(tx.clone()).expect("is valid");
4995                Arc::new(PoolTransaction {
4996                    pending_transaction: pending_tx,
4997                    requires: vec![],
4998                    provides: vec![],
4999                    priority: crate::eth::pool::transactions::TransactionPriority(0),
5000                    is_replay: true,
5001                })
5002            })
5003            .collect();
5004
5005        let trace = |parent_state: &StateDb| -> Result<T, BlockchainError> {
5006            let mut cache_db = AnvilCacheDB::new(Box::new(parent_state));
5007
5008            let evm_env = self.tx_replay_evm_env(&block.header);
5009
5010            let spec_id = *evm_env.spec_id();
5011
5012            let inspector_tx_config = self.inspector_tx_config();
5013            let gas_config = self.pool_tx_gas_config(&evm_env);
5014
5015            self.execute_with_block_executor(
5016                &mut cache_db,
5017                &evm_env,
5018                block.header.parent_hash,
5019                spec_id,
5020                block.header.parent_beacon_block_root,
5021                BlockExecutionKind::TransactionPrefix,
5022                &pool_txs,
5023                &gas_config,
5024                &inspector_tx_config,
5025                &|pool_tx, account| {
5026                    self.validate_pool_transaction_for(
5027                        &pool_tx.pending_transaction,
5028                        account,
5029                        &evm_env,
5030                    )
5031                },
5032            )?;
5033
5034            // Extract inner CacheDB to match the expected types for the target tx execution
5035            let cache_db = cache_db.0;
5036
5037            let target_tx = block.body.transactions[index].clone();
5038            let target_tx = PendingTransaction::from_maybe_impersonated(target_tx)?;
5039            let (result, base_tx_env) = self.transact_envelope_with_inspector_ref(
5040                &cache_db,
5041                &evm_env,
5042                &mut inspector,
5043                target_tx.transaction.as_ref(),
5044                *target_tx.sender(),
5045            )?;
5046
5047            Ok(f(result, cache_db, inspector, base_tx_env, evm_env))
5048        };
5049
5050        let read_guard = self.states.upgradable_read();
5051        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
5052            trace(state)
5053        } else {
5054            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
5055            let state = write_guard
5056                .get_on_disk_state(&block.header.parent_hash)
5057                .ok_or(BlockchainError::BlockNotFound)?;
5058            trace(state)
5059        }
5060    }
5061
5062    /// Traces the transaction with the js tracer
5063    #[cfg(feature = "js-tracer")]
5064    pub async fn trace_tx_with_js_tracer(
5065        &self,
5066        hash: B256,
5067        code: String,
5068        opts: GethDebugTracingOptions,
5069    ) -> Result<GethTrace, BlockchainError> {
5070        let GethDebugTracingOptions { tracer_config, .. } = opts;
5071        let config = tracer_config.into_json();
5072        let inspector = revm_inspectors::tracing::js::JsInspector::new(code, config)
5073            .map_err(|err| BlockchainError::Message(err.to_string()))?;
5074        let trace = self.replay_tx_with_inspector(
5075            hash,
5076            inspector,
5077            |result, cache_db, mut inspector, tx_env, evm_env| {
5078                inspector
5079                    .json_result(
5080                        result,
5081                        &alloy_evm::IntoTxEnv::into_tx_env(tx_env),
5082                        &evm_env.block_env,
5083                        &cache_db,
5084                    )
5085                    .map_err(|e| BlockchainError::Message(e.to_string()))
5086            },
5087        )??;
5088        Ok(GethTrace::JS(trace))
5089    }
5090
5091    /// Prove an account's existence or nonexistence in the state trie.
5092    ///
5093    /// Returns a merkle proof of the account's trie node, `account_key` == keccak(address)
5094    pub async fn prove_account_at(
5095        &self,
5096        address: Address,
5097        keys: Vec<B256>,
5098        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
5099    ) -> Result<AccountProof, BlockchainError> {
5100        let block_number = block_request.as_ref().map(|r| r.block_number());
5101
5102        self.with_database_at(block_request, |block_db, _| {
5103            trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number);
5104            let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
5105            let account = db.get(&address).cloned().unwrap_or_default();
5106
5107            let mut builder = HashBuilder::default()
5108                .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))]));
5109
5110            for (key, account) in trie_accounts(db) {
5111                builder.add_leaf(key, &account);
5112            }
5113
5114            let _ = builder.root();
5115
5116            let proof = builder
5117                .take_proof_nodes()
5118                .into_nodes_sorted()
5119                .into_iter()
5120                .map(|(_, v)| v)
5121                .collect();
5122            let (storage_hash, storage_proofs) = prove_storage(&account.storage, &keys);
5123
5124            let account_proof = AccountProof {
5125                address,
5126                balance: account.info.balance,
5127                nonce: account.info.nonce,
5128                code_hash: account.info.code_hash,
5129                storage_hash,
5130                account_proof: proof,
5131                storage_proof: keys
5132                    .into_iter()
5133                    .zip(storage_proofs)
5134                    .map(|(key, proof)| {
5135                        let storage_key: U256 = key.into();
5136                        let value = account.storage.get(&storage_key).copied().unwrap_or_default();
5137                        StorageProof { key: JsonStorageKey::Hash(key), value, proof }
5138                    })
5139                    .collect(),
5140            };
5141
5142            Ok(account_proof)
5143        })
5144        .await?
5145    }
5146}
5147
5148impl<N: Network> Backend<N>
5149where
5150    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
5151{
5152    /// Returns opcode gas usage for the given transaction.
5153    pub async fn trace_transaction_opcode_gas(
5154        &self,
5155        hash: B256,
5156    ) -> Result<Option<TransactionOpcodeGas>, BlockchainError> {
5157        match self.replay_tx_with_inspector(
5158            hash,
5159            OpcodeGasInspector::default(),
5160            move |_, _, inspector, _, _| TransactionOpcodeGas {
5161                transaction_hash: hash,
5162                opcode_gas: inspector.opcode_gas_iter().collect(),
5163            },
5164        ) {
5165            Ok(trace) => Ok(Some(trace)),
5166            Err(BlockchainError::TransactionNotFound) => {
5167                if let Some(fork) = self.get_fork() {
5168                    return Ok(fork.trace_transaction_opcode_gas(hash).await?);
5169                }
5170
5171                Ok(None)
5172            }
5173            Err(err) => Err(err),
5174        }
5175    }
5176
5177    /// Returns opcode gas usage for all transactions in the given block.
5178    pub async fn trace_block_opcode_gas(
5179        &self,
5180        block_id: BlockId,
5181    ) -> Result<Option<BlockOpcodeGas>, BlockchainError> {
5182        if let Some((block, block_hash)) = self.get_block_with_hash(block_id) {
5183            return self.mined_block_opcode_gas(&block, block_hash).map(Some);
5184        }
5185
5186        if let Some(fork) = self.get_fork() {
5187            let number = self.ensure_block_number(Some(block_id)).await?;
5188            if fork.predates_fork_inclusive(number) {
5189                return Ok(fork.trace_block_opcode_gas(block_id).await?);
5190            }
5191        }
5192
5193        Err(BlockchainError::BlockNotFound)
5194    }
5195
5196    fn mined_block_opcode_gas(
5197        &self,
5198        block: &Block,
5199        block_hash: B256,
5200    ) -> Result<BlockOpcodeGas, BlockchainError> {
5201        // Genesis has no parent state or protocol pre-execution to replay.
5202        if block.header.number() == self.genesis_number() {
5203            return Ok(BlockOpcodeGas {
5204                block_hash,
5205                block_number: block.header.number(),
5206                transactions: Vec::new(),
5207            });
5208        }
5209
5210        let parent_hash = block.header.parent_hash;
5211
5212        let trace = |parent_state: &StateDb| -> Result<Vec<TransactionOpcodeGas>, BlockchainError> {
5213            let (mut cache_db, evm_env) = self.prepare_block_replay(block, parent_state)?;
5214            let mut transactions = Vec::with_capacity(block.body.transactions.len());
5215
5216            for tx_envelope in &block.body.transactions {
5217                let mut inspector = OpcodeGasInspector::default();
5218                let pending_tx = PendingTransaction::from_maybe_impersonated(tx_envelope.clone())?;
5219                let (result, _) = self.transact_envelope_with_inspector_ref(
5220                    &cache_db,
5221                    &evm_env,
5222                    &mut inspector,
5223                    pending_tx.transaction.as_ref(),
5224                    *pending_tx.sender(),
5225                )?;
5226
5227                transactions.push(TransactionOpcodeGas {
5228                    transaction_hash: tx_envelope.hash(),
5229                    opcode_gas: inspector.opcode_gas_iter().collect(),
5230                });
5231
5232                cache_db.commit(result.state);
5233            }
5234
5235            Ok(transactions)
5236        };
5237
5238        let read_guard = self.states.upgradable_read();
5239        let transactions = if let Some(state) = read_guard.get_state(&parent_hash) {
5240            trace(state)?
5241        } else {
5242            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
5243            let state = write_guard
5244                .get_on_disk_state(&parent_hash)
5245                .ok_or(BlockchainError::BlockNotFound)?;
5246            trace(state)?
5247        };
5248
5249        Ok(BlockOpcodeGas { block_hash, block_number: block.header.number(), transactions })
5250    }
5251
5252    /// Returns account information after replaying a block through the transaction at `tx_index`.
5253    pub async fn debug_account_info_at(
5254        &self,
5255        block_id: BlockId,
5256        tx_index: Index,
5257        address: Address,
5258    ) -> Result<Option<RpcAccountInfo>, BlockchainError> {
5259        if let Some((block, _)) = self.get_block_with_hash(block_id) {
5260            return self.mined_debug_account_info_at(&block, tx_index, address).map(Some);
5261        }
5262
5263        if let Some(fork) = self.get_fork() {
5264            let number = self.ensure_block_number(Some(block_id)).await?;
5265            if fork.predates_fork_inclusive(number) {
5266                // Delegate the resolved block number so tags (`latest`/`pending`/`safe`/
5267                // `finalized`) are resolved against the fork's head instead of drifting with
5268                // the upstream chain. Hashes are forwarded unchanged.
5269                let resolved = match block_id {
5270                    BlockId::Hash(_) => block_id,
5271                    _ => BlockId::number(number),
5272                };
5273                return Ok(fork.debug_account_info_at(resolved, tx_index, address).await?);
5274            }
5275        }
5276
5277        Err(BlockchainError::BlockNotFound)
5278    }
5279
5280    fn mined_debug_account_info_at(
5281        &self,
5282        block: &Block,
5283        tx_index: Index,
5284        address: Address,
5285    ) -> Result<RpcAccountInfo, BlockchainError> {
5286        let tx_index = tx_index.0;
5287        let transaction_count = block.body.transactions.len();
5288        if tx_index >= transaction_count {
5289            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
5290                "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
5291            ))));
5292        }
5293
5294        let pool_txs: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>> = block.body.transactions
5295            [..=tx_index]
5296            .iter()
5297            .map(|tx| {
5298                let pending_tx =
5299                    PendingTransaction::from_maybe_impersonated(tx.clone()).expect("is valid");
5300                Arc::new(PoolTransaction {
5301                    pending_transaction: pending_tx,
5302                    requires: vec![],
5303                    provides: vec![],
5304                    priority: crate::eth::pool::transactions::TransactionPriority(0),
5305                    is_replay: true,
5306                })
5307            })
5308            .collect();
5309
5310        let trace = |parent_state: &StateDb| -> Result<RpcAccountInfo, BlockchainError> {
5311            let mut cache_db = AnvilCacheDB::new(Box::new(parent_state));
5312            let evm_env = self.tx_replay_evm_env(&block.header);
5313
5314            let spec_id = *evm_env.spec_id();
5315            let inspector_tx_config = self.inspector_tx_config();
5316            let gas_config = self.pool_tx_gas_config(&evm_env);
5317
5318            self.execute_with_block_executor(
5319                &mut cache_db,
5320                &evm_env,
5321                block.header.parent_hash,
5322                spec_id,
5323                block.header.parent_beacon_block_root,
5324                BlockExecutionKind::TransactionPrefix,
5325                &pool_txs,
5326                &gas_config,
5327                &inspector_tx_config,
5328                &|pool_tx, account| {
5329                    self.validate_pool_transaction_for(
5330                        &pool_tx.pending_transaction,
5331                        account,
5332                        &evm_env,
5333                    )
5334                },
5335            )?;
5336
5337            let cache_db = cache_db.0;
5338            let account = revm::DatabaseRef::basic_ref(&cache_db, address)?.unwrap_or_default();
5339            let code = self.get_code_with_state(&cache_db, address)?;
5340            Ok(RpcAccountInfo { balance: account.balance, nonce: account.nonce, code })
5341        };
5342
5343        let read_guard = self.states.upgradable_read();
5344        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
5345            trace(state)
5346        } else {
5347            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
5348            let state = write_guard
5349                .get_on_disk_state(&block.header.parent_hash)
5350                .ok_or(BlockchainError::BlockNotFound)?;
5351            trace(state)
5352        }
5353    }
5354
5355    /// Rollback the chain to a common height.
5356    ///
5357    /// The state of the chain is rewound using `rewind` to the common block, including the db,
5358    /// storage, and env.
5359    pub async fn rollback(&self, common_block: Block) -> Result<(), BlockchainError> {
5360        let hash = common_block.header.hash_slow();
5361
5362        // Get the database at the common block
5363        let common_state = {
5364            let return_state_or_throw_err =
5365                |db: Option<&StateDb>| -> Result<AddressMap<DbAccount>, BlockchainError> {
5366                    let state_db = db.ok_or(BlockchainError::DataUnavailable)?;
5367                    let db_full =
5368                        state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
5369                    Ok(db_full.clone())
5370                };
5371
5372            let read_guard = self.states.upgradable_read();
5373            if let Some(db) = read_guard.get_state(&hash) {
5374                return_state_or_throw_err(Some(db))?
5375            } else {
5376                let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
5377                return_state_or_throw_err(write_guard.get_on_disk_state(&hash))?
5378            }
5379        };
5380
5381        {
5382            // Collect the logs of the blocks that are about to be removed from the canonical
5383            // chain, while their transactions and receipts are still in storage
5384            let removed_logs = self.removed_logs_since(common_block.header.number());
5385
5386            // Unwind the storage back to the common ancestor first
5387            let removed_blocks =
5388                self.blockchain.storage.write().unwind_to(common_block.header.number(), hash);
5389
5390            // Clean up in-memory and on-disk states for removed blocks
5391            let removed_hashes: Vec<_> =
5392                removed_blocks.iter().map(|b| b.header.hash_slow()).collect();
5393            self.states.write().remove_block_states(&removed_hashes);
5394
5395            // Notify all log subscriptions and filters about the removed logs, so they receive
5396            // them again marked as removed, before any new chain notifications are emitted
5397            if !removed_logs.is_empty() {
5398                self.notify_on_removed_logs(removed_logs);
5399            }
5400
5401            // Set environment back to common block
5402            let mut env = self.evm_env.write();
5403            env.block_env.number = U256::from(common_block.header.number());
5404            env.block_env.timestamp = U256::from(common_block.header.timestamp());
5405            env.block_env.gas_limit = common_block.header.gas_limit();
5406            env.block_env.difficulty = common_block.header.difficulty();
5407            env.block_env.prevrandao = common_block.header.mix_hash();
5408
5409            self.time.reset(env.block_env.timestamp.saturating_to());
5410            // drop any pending next-block prevrandao override so it does not leak into a block
5411            self.cheats.clear_next_block_prevrandao();
5412        }
5413
5414        {
5415            // Collect block hashes before acquiring db lock to avoid holding blockchain storage
5416            // lock across await. Only collect the last 256 blocks since that's all BLOCKHASH can
5417            // access.
5418            let block_hashes: Vec<_> = {
5419                let storage = self.blockchain.storage.read();
5420                let min_block = common_block.header.number().saturating_sub(256);
5421                storage
5422                    .hashes
5423                    .iter()
5424                    .filter(|(num, _)| **num >= min_block)
5425                    .map(|(&num, &hash)| (num, hash))
5426                    .collect()
5427            };
5428
5429            // Acquire db lock once for the entire restore operation to reduce lock churn.
5430            let mut db = self.db.write().await;
5431            db.clear();
5432
5433            // Insert account info before storage to prevent fork-mode RPC fetches after clear.
5434            for (address, acc) in common_state {
5435                db.insert_account(address, acc.info);
5436                for (key, value) in acc.storage {
5437                    db.set_storage_at(address, key.into(), value.into())?;
5438                }
5439            }
5440
5441            // Restore block hashes from blockchain storage (now unwound, contains only valid
5442            // blocks).
5443            for (block_num, hash) in block_hashes {
5444                db.insert_block_hash(U256::from(block_num), hash);
5445            }
5446        }
5447
5448        Ok(())
5449    }
5450
5451    /// Returns the traces for the given transaction
5452    pub async fn debug_trace_transaction(
5453        &self,
5454        hash: B256,
5455        opts: GethDebugTracingOptions,
5456    ) -> Result<GethTrace, BlockchainError> {
5457        #[cfg(feature = "js-tracer")]
5458        if let Some(tracer_type) = opts.tracer.as_ref()
5459            && tracer_type.is_js()
5460        {
5461            return self
5462                .trace_tx_with_js_tracer(hash, tracer_type.as_str().to_string(), opts.clone())
5463                .await;
5464        }
5465
5466        if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()).await {
5467            return trace;
5468        }
5469
5470        if let Some(fork) = self.get_fork() {
5471            return Ok(fork.debug_trace_transaction(hash, opts).await?);
5472        }
5473
5474        Ok(GethTrace::Default(Default::default()))
5475    }
5476
5477    /// Returns geth-style traces for all transactions in an RLP-encoded block.
5478    pub async fn debug_trace_block(
5479        &self,
5480        rlp_block: Bytes,
5481        opts: GethDebugTracingOptions,
5482    ) -> Result<Vec<TraceResult>, BlockchainError> {
5483        let mut rlp = rlp_block.as_ref();
5484        let block = Block::<FoundryTxEnvelope>::decode(&mut rlp).map_err(|err| {
5485            BlockchainError::RpcError(RpcError::invalid_params(format!(
5486                "failed to decode block: {err}"
5487            )))
5488        })?;
5489        if !rlp.is_empty() {
5490            return Err(BlockchainError::RpcError(RpcError::invalid_params(
5491                "failed to decode block: trailing bytes".to_string(),
5492            )));
5493        }
5494
5495        self.debug_trace_block_by_hash(block.header.hash_slow(), opts).await
5496    }
5497
5498    /// Returns geth-style traces for all transactions in a block by hash.
5499    pub async fn debug_trace_block_by_hash(
5500        &self,
5501        block_hash: B256,
5502        opts: GethDebugTracingOptions,
5503    ) -> Result<Vec<TraceResult>, BlockchainError> {
5504        if let Some(block) = self.blockchain.get_block_by_hash(&block_hash) {
5505            let mut traces = Vec::new();
5506            for tx in &block.body.transactions {
5507                let tx_hash = tx.hash();
5508                match self.debug_trace_transaction(tx_hash, opts.clone()).await {
5509                    Ok(trace) => {
5510                        traces.push(TraceResult::Success { result: trace, tx_hash: Some(tx_hash) });
5511                    }
5512                    Err(error) => {
5513                        traces.push(TraceResult::Error {
5514                            error: error.to_string(),
5515                            tx_hash: Some(tx_hash),
5516                        });
5517                    }
5518                }
5519            }
5520            return Ok(traces);
5521        }
5522
5523        if let Some(fork) = self.get_fork() {
5524            return Ok(fork.debug_trace_block_by_hash(block_hash, opts).await?);
5525        }
5526
5527        Err(BlockchainError::BlockNotFound)
5528    }
5529
5530    /// Returns geth-style traces for all transactions in a block by number.
5531    pub async fn debug_trace_block_by_number(
5532        &self,
5533        block_number: BlockNumber,
5534        opts: GethDebugTracingOptions,
5535    ) -> Result<Vec<TraceResult>, BlockchainError> {
5536        let number = self.convert_block_number(Some(block_number));
5537
5538        if let Some(block) = self.get_block(BlockId::Number(BlockNumber::Number(number))) {
5539            let mut traces = Vec::new();
5540            for tx in &block.body.transactions {
5541                let tx_hash = tx.hash();
5542                match self.debug_trace_transaction(tx_hash, opts.clone()).await {
5543                    Ok(trace) => {
5544                        traces.push(TraceResult::Success { result: trace, tx_hash: Some(tx_hash) });
5545                    }
5546                    Err(error) => {
5547                        traces.push(TraceResult::Error {
5548                            error: error.to_string(),
5549                            tx_hash: Some(tx_hash),
5550                        });
5551                    }
5552                }
5553            }
5554            return Ok(traces);
5555        }
5556
5557        if let Some(fork) = self.get_fork() {
5558            return Ok(fork.debug_trace_block_by_number(number, opts).await?);
5559        }
5560
5561        Err(BlockchainError::BlockNotFound)
5562    }
5563
5564    fn geth_trace(
5565        &self,
5566        tx: &MinedTransaction<N>,
5567        opts: GethDebugTracingOptions,
5568    ) -> Result<GethTrace, BlockchainError> {
5569        let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts;
5570
5571        if let Some(tracer) = tracer {
5572            match tracer {
5573                GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
5574                    GethDebugBuiltInTracerType::FourByteTracer => {
5575                        let inspector = FourByteInspector::default();
5576                        let res = self.replay_tx_with_inspector(
5577                            tx.info.transaction_hash,
5578                            inspector,
5579                            |_, _, inspector, _, _| FourByteFrame::from(inspector).into(),
5580                        )?;
5581                        return Ok(res);
5582                    }
5583                    GethDebugBuiltInTracerType::CallTracer => {
5584                        return match call_config_from_tracer_config(tracer_config) {
5585                            Ok(call_config) => {
5586                                let inspector = TracingInspector::new(
5587                                    TracingInspectorConfig::from_geth_call_config(&call_config),
5588                                );
5589                                let frame = self.replay_tx_with_inspector(
5590                                    tx.info.transaction_hash,
5591                                    inspector,
5592                                    |_, _, inspector, _, _| {
5593                                        inspector
5594                                            .geth_builder()
5595                                            .geth_call_traces(call_config, tx.info.gas_used)
5596                                            .into()
5597                                    },
5598                                )?;
5599                                Ok(frame)
5600                            }
5601                            Err(e) => Err(RpcError::invalid_params(e.to_string()).into()),
5602                        };
5603                    }
5604                    GethDebugBuiltInTracerType::PreStateTracer => {
5605                        return match tracer_config.into_pre_state_config() {
5606                            Ok(pre_state_config) => {
5607                                let inspector = TracingInspector::new(
5608                                    TracingInspectorConfig::from_geth_prestate_config(
5609                                        &pre_state_config,
5610                                    ),
5611                                );
5612                                let frame = self.replay_tx_with_inspector(
5613                                    tx.info.transaction_hash,
5614                                    inspector,
5615                                    |state, db, inspector, _, _| {
5616                                        inspector.geth_builder().geth_prestate_traces(
5617                                            &state,
5618                                            &pre_state_config,
5619                                            db,
5620                                        )
5621                                    },
5622                                )??;
5623                                Ok(frame.into())
5624                            }
5625                            Err(e) => Err(RpcError::invalid_params(e.to_string()).into()),
5626                        };
5627                    }
5628                    GethDebugBuiltInTracerType::NoopTracer
5629                    | GethDebugBuiltInTracerType::MuxTracer
5630                    | GethDebugBuiltInTracerType::Erc7562Tracer
5631                    | GethDebugBuiltInTracerType::FlatCallTracer => {}
5632                },
5633                GethDebugTracerType::JsTracer(_code) => {}
5634            }
5635
5636            return Ok(NoopFrame::default().into());
5637        }
5638
5639        // default structlog tracer
5640        Ok(GethTraceBuilder::new(tx.info.traces.clone())
5641            .geth_traces(tx.info.gas_used, tx.info.out.clone().unwrap_or_default(), config)
5642            .into())
5643    }
5644
5645    async fn mined_geth_trace_transaction(
5646        &self,
5647        hash: B256,
5648        opts: GethDebugTracingOptions,
5649    ) -> Option<Result<GethTrace, BlockchainError>> {
5650        self.blockchain.storage.read().transactions.get(&hash).map(|tx| self.geth_trace(tx, opts))
5651    }
5652
5653    /// returns all receipts for the given transactions
5654    fn get_receipts(
5655        &self,
5656        tx_hashes: impl IntoIterator<Item = TxHash>,
5657    ) -> Vec<FoundryReceiptEnvelope> {
5658        let storage = self.blockchain.storage.read();
5659        let mut receipts = vec![];
5660
5661        for hash in tx_hashes {
5662            if let Some(tx) = storage.transactions.get(&hash) {
5663                receipts.push(tx.receipt.clone());
5664            }
5665        }
5666
5667        receipts
5668    }
5669
5670    pub async fn transaction_receipt(
5671        &self,
5672        hash: B256,
5673    ) -> Result<Option<FoundryTxReceipt>, BlockchainError> {
5674        if let Some(receipt) = self.mined_transaction_receipt(hash) {
5675            return Ok(Some(receipt.inner));
5676        }
5677
5678        if let Some(fork) = self.get_fork() {
5679            let receipt = fork.transaction_receipt(hash).await?;
5680            let number = self.convert_block_number(
5681                receipt.clone().and_then(|r| r.block_number()).map(BlockNumber::from),
5682            );
5683
5684            if fork.predates_fork_inclusive(number) {
5685                return Ok(receipt);
5686            }
5687        }
5688
5689        Ok(None)
5690    }
5691
5692    /// Returns all transaction receipts of the block
5693    pub fn mined_block_receipts(&self, id: impl Into<BlockId>) -> Option<Vec<FoundryTxReceipt>> {
5694        let storage = self.blockchain.storage.read();
5695        let hash = match id.into() {
5696            BlockId::Hash(hash) => hash.block_hash,
5697            BlockId::Number(number) => storage.hash(number, self.slots_in_an_epoch)?,
5698        };
5699        let block = storage.blocks.get(&hash)?.clone();
5700
5701        if block.body.transactions.iter().enumerate().any(|(index, transaction)| {
5702            storage.transactions.get(&transaction.hash()).is_none_or(|transaction| {
5703                transaction.block_hash != hash
5704                    || transaction.info.transaction_index as usize != index
5705            })
5706        }) {
5707            drop(storage);
5708            return block
5709                .body
5710                .transactions
5711                .into_iter()
5712                .map(|transaction| {
5713                    self.mined_transaction_receipt(transaction.hash()).map(|receipt| receipt.inner)
5714                })
5715                .collect();
5716        }
5717
5718        let mut receipts = Vec::with_capacity(block.body.transactions.len());
5719        let mut next_log_index = 0;
5720
5721        for block_transaction in &block.body.transactions {
5722            let transaction = storage.transactions.get(&block_transaction.hash())?;
5723            let log_count = transaction.receipt.logs().len();
5724            let receipt = self.build_mined_transaction_receipt(
5725                &transaction.info,
5726                transaction.receipt.clone(),
5727                transaction.block_hash,
5728                &block,
5729                next_log_index,
5730            );
5731            receipts.push(receipt.inner);
5732            next_log_index += log_count;
5733        }
5734
5735        Some(receipts)
5736    }
5737
5738    /// Returns the transaction receipt for the given hash
5739    pub(crate) fn mined_transaction_receipt(
5740        &self,
5741        hash: B256,
5742    ) -> Option<MinedTransactionReceipt<FoundryNetwork>> {
5743        let transaction = self.blockchain.get_transaction_by_hash(&hash)?;
5744
5745        let index = transaction.info.transaction_index as usize;
5746        let block = self.blockchain.get_block_by_hash(&transaction.block_hash)?;
5747        let receipts = self.get_receipts(block.body.transactions.iter().map(|tx| tx.hash()));
5748        let next_log_index = receipts[..index].iter().map(|r| r.logs().len()).sum::<usize>();
5749
5750        let MinedTransaction { info, receipt, block_hash, .. } = transaction;
5751        Some(self.build_mined_transaction_receipt(
5752            &info,
5753            receipt,
5754            block_hash,
5755            &block,
5756            next_log_index,
5757        ))
5758    }
5759
5760    fn build_mined_transaction_receipt(
5761        &self,
5762        info: &TransactionInfo,
5763        tx_receipt: FoundryReceiptEnvelope,
5764        block_hash: B256,
5765        block: &Block,
5766        next_log_index: usize,
5767    ) -> MinedTransactionReceipt<FoundryNetwork> {
5768        let transaction = block.body.transactions[info.transaction_index as usize].clone();
5769
5770        // Cancun specific
5771        let excess_blob_gas = block.header.excess_blob_gas();
5772        let blob_gas_price =
5773            alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas.unwrap_or_default());
5774        let blob_gas_used = transaction.blob_gas_used();
5775
5776        let effective_gas_price = transaction.effective_gas_price(block.header.base_fee_per_gas());
5777
5778        let tx_receipt = tx_receipt.convert_logs_rpc(
5779            BlockNumHash::new(block.header.number(), block_hash),
5780            block.header.timestamp(),
5781            info.transaction_hash,
5782            info.transaction_index,
5783            next_log_index,
5784        );
5785
5786        let receipt = TransactionReceipt {
5787            inner: tx_receipt,
5788            transaction_hash: info.transaction_hash,
5789            transaction_index: Some(info.transaction_index),
5790            block_number: Some(block.header.number()),
5791            gas_used: info.gas_used,
5792            contract_address: info.contract_address,
5793            effective_gas_price,
5794            block_hash: Some(block_hash),
5795            from: info.from,
5796            to: info.to,
5797            blob_gas_price: Some(blob_gas_price),
5798            blob_gas_used,
5799        };
5800
5801        // Include timestamp in receipt to avoid extra block lookups (e.g., in Otterscan API)
5802        let mut inner = FoundryTxReceipt::with_timestamp(receipt, block.header.timestamp());
5803        if self.is_tempo() {
5804            let fee_payer = match &*transaction {
5805                FoundryTxEnvelope::Tempo(tx) => match tx.tx().recover_fee_payer(info.from) {
5806                    Ok(fee_payer) => fee_payer,
5807                    Err(error) => {
5808                        warn!(
5809                            target: "backend",
5810                            %error,
5811                            tx_hash = ?info.transaction_hash,
5812                            "failed to recover Tempo fee payer for mined receipt"
5813                        );
5814                        info.from
5815                    }
5816                },
5817                _ => info.from,
5818            };
5819            inner = inner.with_fee_payer(fee_payer);
5820
5821            // Match Tempo's receipt conversion: the final log of every non-free
5822            // transaction is the fee token transfer to TIPFeeManager.
5823            if inner.effective_gas_price() > 0
5824                && inner.gas_used() > 0
5825                && let Some(fee_token) = inner.0.inner.logs().last().map(|log| log.address())
5826            {
5827                inner = inner.with_fee_token(fee_token);
5828            }
5829        }
5830        MinedTransactionReceipt { inner, out: info.out.clone() }
5831    }
5832
5833    /// Returns the blocks receipts for the given number
5834    pub async fn block_receipts(
5835        &self,
5836        number: BlockId,
5837    ) -> Result<Option<Vec<FoundryTxReceipt>>, BlockchainError> {
5838        if let Some(receipts) = self.mined_block_receipts(number) {
5839            return Ok(Some(receipts));
5840        }
5841
5842        if let Some(fork) = self.get_fork() {
5843            let number = match self.ensure_block_number(Some(number)).await {
5844                Err(_) => return Ok(None),
5845                Ok(n) => n,
5846            };
5847
5848            if fork.predates_fork_inclusive(number) {
5849                let receipts = fork.block_receipts(number).await?;
5850
5851                return Ok(receipts);
5852            }
5853        }
5854
5855        Ok(None)
5856    }
5857}
5858
5859impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> Backend<N> {
5860    /// Get the current state.
5861    pub async fn serialized_state(
5862        &self,
5863        preserve_historical_states: bool,
5864    ) -> Result<SerializableState, BlockchainError> {
5865        let at = self.evm_env.read().block_env.clone();
5866        let best_number = self.blockchain.storage.read().best_number;
5867        let blocks = self.blockchain.storage.read().serialized_blocks();
5868        let transactions = self.blockchain.storage.read().serialized_transactions();
5869        let historical_states =
5870            preserve_historical_states.then(|| self.states.write().serialized_states());
5871
5872        let state = self.db.read().await.dump_state(
5873            at,
5874            best_number,
5875            blocks,
5876            transactions,
5877            historical_states,
5878        )?;
5879        state.ok_or_else(|| {
5880            RpcError::invalid_params("Dumping state not supported with the current configuration")
5881                .into()
5882        })
5883    }
5884
5885    /// Write all chain data to serialized bytes buffer
5886    pub async fn dump_state(
5887        &self,
5888        preserve_historical_states: bool,
5889    ) -> Result<Bytes, BlockchainError> {
5890        let state = self.serialized_state(preserve_historical_states).await?;
5891        let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
5892        encoder
5893            .write_all(&serde_json::to_vec(&state).unwrap_or_default())
5894            .map_err(|_| BlockchainError::DataUnavailable)?;
5895        Ok(encoder.finish().unwrap_or_default().into())
5896    }
5897
5898    /// Apply [SerializableState] data to the backend storage.
5899    pub async fn load_state(&self, mut state: SerializableState) -> Result<bool, BlockchainError> {
5900        let _mining_guard = self.mining.lock().await;
5901        let mut block_env = state.block.take();
5902        let mut selected_head = None;
5903        let mut selected_header = None;
5904        let mut checkpoint = None;
5905        let fork_head = self.get_fork().map(|f| (f.block_number(), f.block_hash(), f.timestamp()));
5906        if let Some(block) = &mut block_env {
5907            if self.is_tempo() && self.is_fork() && block.beneficiary.is_zero() {
5908                block.beneficiary = TIP_FEE_MANAGER_ADDRESS;
5909            }
5910            // Set the current best block number.
5911            // Defaults to block number for compatibility with existing state files.
5912            let best_number = state.best_block_number.unwrap_or(block.number.saturating_to());
5913            let (selected_best_number, selected_best_hash) = if let Some((number, hash, _)) =
5914                fork_head
5915            {
5916                trace!(target: "backend", state_block_number=?best_number, fork_block_number=?number);
5917                // If the state.block_number is greater than the fork block number, set best number
5918                // to the state block number.
5919                // Ref: https://github.com/foundry-rs/foundry/issues/9539
5920                if best_number > number {
5921                    (best_number, None)
5922                } else {
5923                    // If loading state file on a fork, set best number to the fork block number.
5924                    // Ref: https://github.com/foundry-rs/foundry/pull/9215#issue-2618681838
5925                    (number, Some(hash))
5926                }
5927            } else {
5928                (best_number, None)
5929            };
5930
5931            let best_hash = if let Some(hash) = selected_best_hash {
5932                selected_header = state
5933                    .blocks
5934                    .iter()
5935                    .rev()
5936                    .find(|block| block.header.hash_slow() == hash)
5937                    .map(|block| block.header.clone());
5938                hash
5939            } else if state.blocks.is_empty() {
5940                let spec_id = self.spec_id();
5941                let is_cancun = spec_id >= SpecId::CANCUN;
5942                let parent_hash = selected_best_number
5943                    .checked_sub(1)
5944                    .and_then(|number| self.blockchain.storage.read().hashes.get(&number).copied())
5945                    .unwrap_or_default();
5946                let header = Header {
5947                    parent_hash,
5948                    beneficiary: block.beneficiary,
5949                    difficulty: block.difficulty,
5950                    number: selected_best_number,
5951                    gas_limit: block.gas_limit,
5952                    timestamp: block.timestamp.saturating_to(),
5953                    mix_hash: block.prevrandao.unwrap_or_default(),
5954                    base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(block.basefee),
5955                    parent_beacon_block_root: is_cancun.then_some(Default::default()),
5956                    blob_gas_used: is_cancun.then_some(0),
5957                    excess_blob_gas: if is_cancun { block.blob_excess_gas() } else { None },
5958                    withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
5959                    requests_hash: (spec_id >= SpecId::PRAGUE).then_some(EMPTY_REQUESTS_HASH),
5960                    ..Default::default()
5961                };
5962                let header = FoundryHeader::new(header, self.is_tempo());
5963                let best_hash = header.hash_slow();
5964                selected_header = Some(header.clone());
5965                checkpoint = Some(create_block(
5966                    header,
5967                    Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
5968                ));
5969                warn!(
5970                    target: "backend",
5971                    block_number = selected_best_number,
5972                    "state dump has no block history; created a synthetic checkpoint block"
5973                );
5974                best_hash
5975            } else if let Some(header) = state
5976                .blocks
5977                .iter()
5978                .rev()
5979                .find(|block| block.header.number() == selected_best_number)
5980                .map(|block| block.header.clone())
5981            {
5982                let best_hash = header.hash_slow();
5983                selected_header = Some(header);
5984                best_hash
5985            } else {
5986                return Err(BlockchainError::RpcError(RpcError::internal_error_with(format!(
5987                    "Best hash not found for best number {selected_best_number}",
5988                ))));
5989            };
5990
5991            selected_head = Some((selected_best_number, best_hash));
5992        }
5993
5994        // Apply the prepared chain data atomically so concurrent readers never observe blocks
5995        // without their transactions or a partially updated head.
5996        let canonical_timestamp = {
5997            let mut storage = self.blockchain.storage.write();
5998            storage.load_blocks(std::mem::take(&mut state.blocks));
5999            storage.load_transactions(std::mem::take(&mut state.transactions));
6000            if let Some(checkpoint) = checkpoint {
6001                storage.insert_block(checkpoint);
6002            }
6003            if let Some((number, hash)) = selected_head {
6004                storage.hashes.insert(number, hash);
6005                storage.best_number = number;
6006                storage.best_hash = hash;
6007            }
6008
6009            // Re-anchor block time to the canonical head selected above so the next blocks
6010            // continue its timeline: the saved one when the loaded head stays canonical, the
6011            // fork's when the state file is at or below the fork block. Resolving the head by
6012            // identity also keeps the timeline of stale blocks a state file can carry above
6013            // its own best block out of the anchor. A head rolled back to the fork block has
6014            // no header in local storage, so take the fork timestamp, as `reset_fork` does.
6015            match fork_head {
6016                Some((_, fork_hash, fork_timestamp)) if storage.best_hash == fork_hash => {
6017                    Some(fork_timestamp)
6018                }
6019                _ => storage.blocks.get(&storage.best_hash).map(|b| b.header.timestamp),
6020            }
6021        };
6022        if let Some(timestamp) = canonical_timestamp {
6023            self.time.reset(timestamp);
6024        }
6025
6026        if let Some(mut block) = block_env {
6027            // Keep NUMBER aligned with the canonical local head chosen above. Arbitrum state dumps
6028            // can intentionally keep BlockEnv.number distinct from the best L2 block number.
6029            if !is_arbitrum(self.chain_id().to())
6030                && let Some((number, _)) = selected_head
6031            {
6032                block.number = U256::from(number);
6033            }
6034            self.evm_env.write().block_env = block;
6035        }
6036
6037        if let Some(header) = selected_header.as_ref() {
6038            let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas(
6039                header.gas_used(),
6040                header.gas_limit(),
6041                header.base_fee_per_gas().unwrap_or_default(),
6042            );
6043            let next_block_excess_blob_gas =
6044                self.fees.blob_params().next_block_excess_blob_gas_osaka(
6045                    header.excess_blob_gas().unwrap_or_default(),
6046                    header.blob_gas_used().unwrap_or_default(),
6047                    header.base_fee_per_gas().unwrap_or_default(),
6048                );
6049
6050            // update next base fee
6051            self.fees.set_base_fee(next_block_base_fee);
6052
6053            self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
6054                next_block_excess_blob_gas,
6055                get_blob_base_fee_update_fraction(
6056                    self.evm_env.read().cfg_env.chain_id,
6057                    header.timestamp,
6058                ),
6059            ));
6060        }
6061
6062        let historical_states = state.historical_states.take();
6063        if !self.db.write().await.load_state(state)? {
6064            return Err(RpcError::invalid_params(
6065                "Loading state not supported with the current configuration",
6066            )
6067            .into());
6068        }
6069
6070        // Backfill the EVM-level block hash cache from the freshly loaded blocks so that the
6071        // BLOCKHASH opcode stays consistent after loading state. Reuses the hashes already
6072        // computed by `load_blocks` above. Only collect the last 256 blocks since that's all
6073        // BLOCKHASH can access.
6074        let block_hashes = {
6075            let storage = self.blockchain.storage.read();
6076            let min_block = storage.best_number.saturating_sub(256);
6077            storage
6078                .hashes
6079                .iter()
6080                .filter(|(num, _)| (min_block..=storage.best_number).contains(*num))
6081                .map(|(&num, &hash)| (U256::from(num), hash))
6082                .collect()
6083        };
6084        self.db.write().await.set_block_hashes(block_hashes);
6085
6086        if let Some(historical_states) = historical_states {
6087            self.states.write().load_states(historical_states);
6088        }
6089
6090        Ok(true)
6091    }
6092
6093    /// Deserialize and add all chain data to the backend storage
6094    pub async fn load_state_bytes(&self, buf: Bytes) -> Result<bool, BlockchainError> {
6095        let orig_buf = &buf.0[..];
6096        let mut decoder = GzDecoder::new(orig_buf);
6097        let mut decoded_data = Vec::new();
6098
6099        let state: SerializableState = serde_json::from_slice(if decoder.header().is_some() {
6100            decoder
6101                .read_to_end(decoded_data.as_mut())
6102                .map_err(|_| BlockchainError::FailedToDecodeStateDump)?;
6103            &decoded_data
6104        } else {
6105            &buf.0
6106        })
6107        .map_err(|_| BlockchainError::FailedToDecodeStateDump)?;
6108
6109        self.load_state(state).await
6110    }
6111}
6112
6113impl Backend<FoundryNetwork> {
6114    /// Simulates a bundle of signed transactions and returns Flashbots-compatible results.
6115    pub async fn call_bundle(
6116        &self,
6117        bundle: EthCallBundle,
6118        transactions: Vec<PendingTransaction<FoundryTxEnvelope>>,
6119        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6120    ) -> Result<EthCallBundleResponse, BlockchainError> {
6121        let EthCallBundle {
6122            block_number,
6123            coinbase,
6124            timestamp,
6125            gas_limit,
6126            difficulty,
6127            base_fee,
6128            ..
6129        } = bundle;
6130
6131        let blob_gas_used = transactions
6132            .iter()
6133            .filter_map(|transaction| transaction.transaction.blob_gas_used())
6134            .sum::<u64>();
6135        let max_blob_gas = self.blob_params().max_blob_gas_per_block();
6136        if blob_gas_used > max_blob_gas {
6137            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
6138                "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
6139            ))));
6140        }
6141
6142        self.with_database_at(block_request, |state, mut block_env| {
6143            let state_block_number = block_env.number.to::<u64>();
6144            block_env.number = U256::from(block_number);
6145            block_env.timestamp = timestamp
6146                .map(U256::from)
6147                .unwrap_or_else(|| block_env.timestamp.saturating_add(U256::from(12)));
6148            if let Some(coinbase) = coinbase {
6149                block_env.beneficiary = coinbase;
6150            }
6151            if let Some(gas_limit) = gas_limit {
6152                block_env.gas_limit = gas_limit;
6153            }
6154            if let Some(difficulty) = difficulty {
6155                block_env.difficulty = difficulty;
6156            }
6157            if let Some(base_fee) = base_fee {
6158                block_env.basefee = base_fee.try_into().unwrap_or(u64::MAX);
6159            }
6160
6161            let mut evm_env = self.evm_env.read().clone();
6162            evm_env.block_env = block_env;
6163            let coinbase = evm_env.block_env.beneficiary;
6164            let base_fee = evm_env.block_env.basefee;
6165            let mut cache_db = CacheDB::new(state);
6166            let initial_coinbase = revm::DatabaseRef::basic_ref(&cache_db, coinbase)?
6167                .map(|account| account.balance)
6168                .unwrap_or_default();
6169            let mut coinbase_balance_before_tx = initial_coinbase;
6170            let mut coinbase_balance_after_tx = initial_coinbase;
6171            let mut total_gas_used = 0u64;
6172            let mut total_gas_fees = U256::ZERO;
6173            let mut bundle_hash = alloy_primitives::Keccak256::new();
6174            let mut results = Vec::with_capacity(transactions.len());
6175
6176            for transaction in transactions {
6177                let sender = *transaction.sender();
6178                let tx = transaction.transaction.into_inner();
6179                let tx_hash = tx.hash();
6180                bundle_hash.update(tx_hash);
6181
6182                let mut inspector = self.build_inspector();
6183                let (ResultAndState { result, state }, _) = self
6184                    .transact_envelope_with_inspector_ref(
6185                        &cache_db,
6186                        &evm_env,
6187                        &mut inspector,
6188                        &tx,
6189                        sender,
6190                    )?;
6191
6192                let gas_price = tx.effective_tip_per_gas(base_fee).unwrap_or_default();
6193                let gas_used = result.tx_gas_used();
6194                let gas_fees = U256::from(gas_used) * U256::from(gas_price);
6195                total_gas_used += gas_used;
6196                total_gas_fees += gas_fees;
6197
6198                coinbase_balance_after_tx = state
6199                    .get(&coinbase)
6200                    .map(|account| account.info.balance)
6201                    .unwrap_or(coinbase_balance_before_tx);
6202                let coinbase_diff =
6203                    coinbase_balance_after_tx.saturating_sub(coinbase_balance_before_tx);
6204                let eth_sent_to_coinbase = coinbase_diff.saturating_sub(gas_fees);
6205                coinbase_balance_before_tx = coinbase_balance_after_tx;
6206
6207                let output = result.output().cloned().unwrap_or_default();
6208                let (value, revert) =
6209                    if result.is_success() { (Some(output), None) } else { (None, Some(output)) };
6210
6211                results.push(EthCallBundleTransactionResult {
6212                    coinbase_diff,
6213                    eth_sent_to_coinbase,
6214                    from_address: sender,
6215                    gas_fees,
6216                    gas_price: U256::from(gas_price),
6217                    gas_used,
6218                    to_address: tx.to(),
6219                    tx_hash,
6220                    value,
6221                    revert,
6222                });
6223                cache_db.commit(state);
6224            }
6225
6226            let coinbase_diff = coinbase_balance_after_tx.saturating_sub(initial_coinbase);
6227            let eth_sent_to_coinbase = coinbase_diff.saturating_sub(total_gas_fees);
6228            let bundle_gas_price =
6229                coinbase_diff.checked_div(U256::from(total_gas_used)).unwrap_or_default();
6230
6231            Ok(EthCallBundleResponse {
6232                bundle_hash: bundle_hash.finalize(),
6233                bundle_gas_price,
6234                coinbase_diff,
6235                eth_sent_to_coinbase,
6236                gas_fees: total_gas_fees,
6237                results,
6238                state_block_number,
6239                total_gas_used,
6240            })
6241        })
6242        .await?
6243    }
6244
6245    /// Executes bundles of call requests and returns each call output.
6246    pub async fn call_many(
6247        &self,
6248        bundles: Vec<Bundle<WithOtherFields<TransactionRequest>>>,
6249        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6250        state_override: Option<alloy_rpc_types::state::StateOverride>,
6251    ) -> Result<Vec<Vec<EthCallResponse>>, BlockchainError> {
6252        if bundles.is_empty() {
6253            return Err(BlockchainError::RpcError(RpcError::invalid_params(
6254                "bundles are empty.".to_string(),
6255            )));
6256        }
6257
6258        self.with_database_at(block_request, |state, block_env| {
6259            let mut cache_db = CacheDB::new(state);
6260            if let Some(state_override) = state_override {
6261                apply_state_overrides(state_override, &mut cache_db)?;
6262            }
6263
6264            let mut results = Vec::with_capacity(bundles.len());
6265            for bundle in bundles {
6266                let Bundle { transactions, block_override } = bundle;
6267                let mut bundle_block_env = block_env.clone();
6268                if let Some(block_override) = block_override {
6269                    cache_db.apply_block_overrides(block_override, &mut bundle_block_env);
6270                }
6271
6272                let mut bundle_results = Vec::with_capacity(transactions.len());
6273                for request in transactions {
6274                    let fee_details = FeeDetails::new(
6275                        request.gas_price,
6276                        request.max_fee_per_gas,
6277                        request.max_priority_fee_per_gas,
6278                        request.max_fee_per_blob_gas,
6279                    )?
6280                    .or_zero_fees();
6281                    let PreparedCall { evm_env, mut tx_env, simulated_tempo_tx } = self
6282                        .prepare_call_env(
6283                            &cache_db,
6284                            request,
6285                            fee_details,
6286                            bundle_block_env.clone(),
6287                        )?;
6288                    apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
6289
6290                    let mut inspector = self.build_inspector();
6291                    let ResultAndState { result, state } = self.transact_call_with_inspector_ref(
6292                        &cache_db,
6293                        &evm_env,
6294                        &mut inspector,
6295                        tx_env,
6296                    )?;
6297
6298                    let output = result.output().cloned().unwrap_or_default();
6299                    let response = if result.is_success() {
6300                        EthCallResponse { value: Some(output), error: None }
6301                    } else {
6302                        let error = RevertDecoder::new()
6303                            .maybe_decode(&output, None)
6304                            .unwrap_or_else(|| "execution failed".to_string());
6305                        EthCallResponse { value: None, error: Some(error) }
6306                    };
6307
6308                    cache_db.commit(state);
6309                    bundle_results.push(response);
6310                }
6311
6312                results.push(bundle_results);
6313            }
6314
6315            Ok(results)
6316        })
6317        .await?
6318    }
6319
6320    /// Simulates the payload by executing the calls in request.
6321    pub async fn simulate(
6322        &self,
6323        request: SimulatePayload,
6324        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6325        block_interval: u64,
6326    ) -> Result<Vec<SimulatedBlock<AnyRpcBlock>>, BlockchainError> {
6327        self.simulate_raw(
6328            preserve_simulation_request_fields(request),
6329            block_request,
6330            block_interval,
6331        )
6332        .await
6333    }
6334
6335    /// Simulates a payload while preserving transaction extension fields.
6336    pub(crate) async fn simulate_raw(
6337        &self,
6338        request: SimulatePayload<WithOtherFields<TransactionRequest>>,
6339        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6340        block_interval: u64,
6341    ) -> Result<Vec<SimulatedBlock<AnyRpcBlock>>, BlockchainError> {
6342        let simulate_at = |state: Box<dyn MaybeFullDatabase + '_>,
6343                           base_block_env: BlockEnv,
6344                           base_number,
6345                           base_timestamp,
6346                           base_hash,
6347                           base_fee,
6348                           base_base_fee_per_gas,
6349                           base_excess_blob_gas,
6350                           base_blob_gas_used| {
6351            let SimulatePayload {
6352                block_state_calls,
6353                trace_transfers,
6354                validation,
6355                return_full_transactions,
6356            } = request;
6357            let block_state_calls = sanitize_simulation_blocks(
6358                block_state_calls,
6359                base_number,
6360                base_timestamp,
6361                block_interval,
6362            )?;
6363            let mut cache_db = CacheDB::new(state);
6364            cache_db.cache.block_hashes.insert(U256::from(base_number), base_hash);
6365            let mut block_res = Vec::with_capacity(block_state_calls.len());
6366            let mut parent_hash = base_hash;
6367            let mut next_base_fee = base_fee;
6368            let mut inherited_block_env = base_block_env;
6369            let (is_cancun, is_amsterdam, tx_gas_limit_cap) = {
6370                let cfg_env = &self.evm_env.read().cfg_env;
6371                (
6372                    cfg_env.spec >= SpecId::CANCUN,
6373                    cfg_env.spec >= SpecId::AMSTERDAM,
6374                    cfg_env.tx_gas_limit_cap(),
6375                )
6376            };
6377            let mut parent_base_fee_per_gas = base_base_fee_per_gas;
6378            let mut parent_excess_blob_gas = base_excess_blob_gas;
6379            let mut parent_blob_gas_used = base_blob_gas_used;
6380            let mut rpc_gas_budget = SIMULATE_GAS_CAP;
6381
6382            // execute the blocks
6383            for block in block_state_calls {
6384                let SimBlock { block_overrides, state_overrides, calls } = block;
6385                let mut block_env = inherited_block_env.clone();
6386                let overridden_beacon_root =
6387                    block_overrides.as_ref().and_then(|overrides| overrides.beacon_root);
6388                let block_timestamp = block_overrides
6389                    .as_ref()
6390                    .and_then(|overrides| overrides.time)
6391                    .unwrap_or_else(|| block_env.timestamp.saturating_to());
6392                let blob_params = self.simulation_blob_params_at_timestamp(block_timestamp);
6393                if is_cancun {
6394                    let excess_blob_gas = blob_params.next_block_excess_blob_gas_osaka(
6395                        parent_excess_blob_gas,
6396                        parent_blob_gas_used,
6397                        parent_base_fee_per_gas,
6398                    );
6399                    block_env.set_blob_excess_gas_and_price(
6400                        excess_blob_gas,
6401                        blob_params.update_fraction as u64,
6402                    );
6403                } else {
6404                    block_env.blob_excess_gas_and_price = None;
6405                }
6406                block_env.basefee = if validation { next_base_fee } else { 0 };
6407                block_env.prevrandao = Some(B256::ZERO);
6408                let mut call_res = Vec::with_capacity(calls.len());
6409                let mut log_index = 0;
6410                let mut cumulative_gas_used = 0;
6411                let mut block_regular_gas_used = 0;
6412                let mut block_state_gas_used = 0;
6413                let mut block_blob_gas_used = 0u64;
6414                let mut transactions = Vec::with_capacity(calls.len());
6415                let mut transaction_envelopes = Vec::with_capacity(calls.len());
6416                let mut receipts = Vec::with_capacity(calls.len());
6417                let overridden_block_hashes = block_overrides
6418                    .as_ref()
6419                    .and_then(|overrides| overrides.block_hash.as_ref())
6420                    .map(|overrides| {
6421                        overrides
6422                            .keys()
6423                            .map(|number| {
6424                                let number = U256::from(*number);
6425                                (number, cache_db.cache.block_hashes.get(&number).copied())
6426                            })
6427                            .collect::<Vec<_>>()
6428                    })
6429                    .unwrap_or_default();
6430
6431                if let Some(block_overrides) = block_overrides {
6432                    cache_db.apply_block_overrides(block_overrides, &mut block_env);
6433                }
6434                let simulation_evm_env =
6435                    EvmEnv::new(self.evm_env.read().cfg_env.clone(), block_env.clone());
6436                let spec_id = *simulation_evm_env.spec_id();
6437                let ethereum_transitions = self
6438                    .ethereum_block_transitions(self.hardfork(), None, BlockExecutionKind::Complete)
6439                    .map(|mut transitions| {
6440                        transitions.parent_beacon_block_root = (transitions.hardfork
6441                            >= EthereumHardfork::Cancun)
6442                            .then_some(overridden_beacon_root.unwrap_or_default());
6443                        transitions
6444                    });
6445                let precompile_overrides = self.simulation_precompile_overrides(
6446                    state_overrides.as_ref(),
6447                    &simulation_evm_env,
6448                )?;
6449
6450                // Apply state overrides after validating precompile moves against this block's
6451                // active precompile set.
6452                if let Some(mut state_overrides) = state_overrides {
6453                    state_overrides.retain(|_, account| {
6454                        account.balance.is_some()
6455                            || account.nonce.is_some()
6456                            || account.code.is_some()
6457                            || account.state.is_some()
6458                            || account
6459                                .state_diff
6460                                .as_ref()
6461                                .is_some_and(|state_diff| !state_diff.is_empty())
6462                    });
6463                    let previously_deleted = previously_deleted_accounts(
6464                        &cache_db.cache.accounts,
6465                        state_overrides.keys().copied(),
6466                    );
6467                    apply_state_overrides(state_overrides, &mut cache_db)?;
6468                    preserve_deleted_storage(&mut cache_db.cache.accounts, previously_deleted);
6469                }
6470
6471                if let Some(transitions) = ethereum_transitions {
6472                    self.apply_simulation_pre_execution_changes(
6473                        &mut cache_db,
6474                        &simulation_evm_env,
6475                        parent_hash,
6476                        transitions,
6477                    )?;
6478                }
6479
6480                // execute all calls in that block
6481                for (req_idx, mut request) in calls.into_iter().enumerate() {
6482                    let classified_request = self.parse_transaction_request(request.clone())?;
6483                    let is_ethereum_request = classified_request.is_ethereum();
6484                    let mut parsed_request = self.is_tempo().then_some(classified_request);
6485                    if is_ethereum_request {
6486                        request.populate_blob_hashes();
6487                        let preferred_type = request.preferred_type();
6488                        request.transaction_type = Some(preferred_type as u8);
6489                        request.trim_conflicting_keys();
6490                        request.populate_blob_hashes();
6491                    }
6492                    let request_blob_gas_used = if is_ethereum_request {
6493                        u64::try_from(request.blob_versioned_hashes.as_ref().map_or(0, Vec::len))
6494                            .unwrap_or(u64::MAX)
6495                            .saturating_mul(DATA_GAS_PER_BLOB)
6496                    } else {
6497                        0
6498                    };
6499                    let max_blob_gas = blob_params.max_blob_gas_per_block();
6500                    if block_blob_gas_used.saturating_add(request_blob_gas_used) > max_blob_gas {
6501                        return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
6502                            "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
6503                        ))));
6504                    }
6505                    block_blob_gas_used = block_blob_gas_used.saturating_add(request_blob_gas_used);
6506
6507                    let inner = request.as_ref();
6508                    let remaining_regular_gas =
6509                        block_env.gas_limit.saturating_sub(block_regular_gas_used);
6510                    let remaining_state_gas =
6511                        block_env.gas_limit.saturating_sub(block_state_gas_used);
6512                    let remaining_gas = if is_amsterdam {
6513                        remaining_regular_gas.min(remaining_state_gas)
6514                    } else {
6515                        block_env.gas_limit.saturating_sub(cumulative_gas_used)
6516                    };
6517                    let requested_gas = inner.gas.unwrap_or(remaining_gas);
6518                    let exceeds_gas_limit = if is_amsterdam {
6519                        let requested_regular_gas = requested_gas.min(tx_gas_limit_cap);
6520                        requested_regular_gas > remaining_regular_gas
6521                            || requested_gas > remaining_state_gas
6522                    } else {
6523                        requested_gas > remaining_gas
6524                    };
6525                    if exceeds_gas_limit {
6526                        return Err(BlockchainError::RpcError(RpcError {
6527                            code: ErrorCode::ServerError(-38015),
6528                            message: format!(
6529                                "block gas limit exceeded: remaining {remaining_gas}, requested {requested_gas}"
6530                            )
6531                            .into(),
6532                            data: None,
6533                        }));
6534                    }
6535                    let execution_gas_limit = requested_gas.min(rpc_gas_budget);
6536                    let preserve_signed_gas = matches!(
6537                        &parsed_request,
6538                        Some(FoundryTransactionRequest::Tempo(request))
6539                            if request.fee_payer_signature.is_some()
6540                    );
6541                    request.gas = Some(execution_gas_limit);
6542                    if !preserve_signed_gas && let Some(parsed_request) = &mut parsed_request {
6543                        parsed_request.as_mut().gas = Some(execution_gas_limit);
6544                    }
6545
6546                    let caller = request.from.unwrap_or_default();
6547                    let caller_nonce = RevmDatabase::basic(&mut cache_db, caller)?
6548                        .map(|account| account.nonce)
6549                        .unwrap_or_default();
6550                    let tempo_nonce_key =
6551                        parsed_request.as_ref().and_then(|request| match request {
6552                            FoundryTransactionRequest::Tempo(request) => request.nonce_key,
6553                            _ => None,
6554                        });
6555                    if request.nonce.is_none() {
6556                        let nonce = tempo_nonce_key.map_or(Ok(caller_nonce), |nonce_key| {
6557                            tempo_nonce(&cache_db, caller, nonce_key)
6558                        })?;
6559                        request.nonce = Some(nonce);
6560                        if let Some(parsed_request) = &mut parsed_request {
6561                            parsed_request.as_mut().nonce = Some(nonce);
6562                        }
6563                    }
6564
6565                    if is_ethereum_request {
6566                        let mut canonical_request =
6567                            FoundryTransactionRequest::Ethereum(request.inner.clone());
6568                        canonical_request.prep_for_submission();
6569                        request.inner = canonical_request.as_ref().clone();
6570                        if let Some(parsed_request) = &mut parsed_request {
6571                            *parsed_request = canonical_request;
6572                        }
6573                    }
6574
6575                    let fee_details = FeeDetails::new(
6576                        request.gas_price,
6577                        request.max_fee_per_gas,
6578                        request.max_priority_fee_per_gas,
6579                        request.max_fee_per_blob_gas,
6580                    )?
6581                    .or_zero_fees();
6582
6583                    let PreparedCall { mut evm_env, mut tx_env, simulated_tempo_tx } =
6584                        if let Some(parsed_request) = parsed_request {
6585                            self.prepare_typed_call_env(
6586                                &cache_db,
6587                                parsed_request,
6588                                fee_details,
6589                                block_env.clone(),
6590                            )?
6591                        } else {
6592                            self.prepare_call_env(
6593                                &cache_db,
6594                                request.clone(),
6595                                fee_details,
6596                                block_env.clone(),
6597                            )?
6598                        };
6599                    tx_env.base_mut().gas_limit = execution_gas_limit;
6600                    apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
6601                    if !validation
6602                        && tempo_nonce_key.is_none_or(|key| key.is_zero())
6603                        && request.nonce == Some(u64::MAX)
6604                    {
6605                        tx_env.base_mut().nonce = 0;
6606                    }
6607                    let uses_protocol_call_nonce = tx_env.uses_protocol_call_nonce();
6608                    let simulated_envelope = simulated_tempo_tx.map(FoundryTxEnvelope::Tempo);
6609
6610                    if is_amsterdam {
6611                        // Ensure simulated Amsterdam calls use EIP-8037's split gas schedule.
6612                        let spec = evm_env.cfg_env.spec;
6613                        evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec);
6614                    }
6615
6616                    // Always disable EIP-3607
6617                    evm_env.cfg_env.disable_eip3607 = true;
6618
6619                    if validation {
6620                        evm_env.cfg_env.disable_nonce_check = false;
6621                        evm_env.cfg_env.disable_base_fee = false;
6622                        evm_env.cfg_env.disable_block_gas_limit = false;
6623                    }
6624
6625                    let mut inspector = self.build_inspector();
6626
6627                    // transact
6628                    inspector = inspector.with_simulation_logs(trace_transfers);
6629                    trace!(target: "backend", env=?evm_env, spec=?evm_env.spec_id(),"simulate evm env");
6630                    let execution_result = match tx_env {
6631                        CallTxEnv::Eth(tx_env)
6632                            if !validation
6633                                && tx_env.tx_type == 3
6634                                && tx_env.max_fee_per_blob_gas == 0 =>
6635                        {
6636                            self.transact_eth_simulation_with_inspector_ref(
6637                                &cache_db,
6638                                &evm_env,
6639                                &mut inspector,
6640                                tx_env,
6641                                &precompile_overrides,
6642                            )
6643                        }
6644                        tx_env if precompile_overrides.moves.is_empty() => self
6645                            .transact_call_with_inspector_ref(
6646                                &cache_db,
6647                                &evm_env,
6648                                &mut inspector,
6649                                tx_env,
6650                            ),
6651                        tx_env => self.transact_eth_with_inspector_ref_and_precompile_overrides(
6652                            &cache_db,
6653                            &evm_env,
6654                            &mut inspector,
6655                            tx_env.into_base(),
6656                            &precompile_overrides,
6657                        ),
6658                    };
6659                    let ResultAndState { result, mut state } = match execution_result {
6660                        Err(BlockchainError::InvalidTransaction(error)) => {
6661                            return Err(simulate_transaction_error(error));
6662                        }
6663                        result => result?,
6664                    };
6665                    if !validation
6666                        && caller_nonce == u64::MAX
6667                        && uses_protocol_call_nonce
6668                        && let Some(account) = state.get_mut(&caller)
6669                    {
6670                        account.info.nonce = 0;
6671                    }
6672                    trace!(target: "backend", ?result, ?request, "simulate call");
6673
6674                    let canonical_logs = result.clone().into_logs();
6675                    let (response_logs, attempted_log_count) = inspector
6676                        .take_simulation_logs(&canonical_logs, result.is_success())
6677                        .expect("simulation log collector is installed");
6678                    inspector.print_logs();
6679                    if self.print_traces {
6680                        inspector.into_print_traces(self.call_trace_decoder.clone());
6681                    }
6682
6683                    // REVM turns a previously deleted account into `Touched` when a later call
6684                    // recreates it without storage. Preserve the cleared-storage provenance so
6685                    // subsequent calls and the recursively merged state cannot reload old slots.
6686                    let previously_deleted = previously_deleted_accounts(
6687                        &cache_db.cache.accounts,
6688                        state.keys().copied(),
6689                    );
6690
6691                    // commit the transaction
6692                    cache_db.commit(state);
6693                    preserve_deleted_storage(&mut cache_db.cache.accounts, previously_deleted);
6694                    let post_state = if spec_id < SpecId::BYZANTIUM {
6695                        let accounts =
6696                            cache_db.maybe_full_db().ok_or(BlockchainError::DataUnavailable)?;
6697                        Some(state_root(&accounts))
6698                    } else {
6699                        None
6700                    };
6701                    rpc_gas_budget = rpc_gas_budget.saturating_sub(result.tx_gas_used());
6702                    cumulative_gas_used = cumulative_gas_used.saturating_add(result.tx_gas_used());
6703                    block_regular_gas_used = block_regular_gas_used
6704                        .saturating_add(result.gas().block_regular_gas_used());
6705                    block_state_gas_used =
6706                        block_state_gas_used.saturating_add(result.gas().block_state_gas_used());
6707
6708                    // create the transaction from a request
6709                    let from = caller;
6710                    request.sidecar = None;
6711                    let tx = if let Some(envelope) = simulated_envelope {
6712                        MaybeImpersonatedTransaction::impersonated(envelope, from)
6713                    } else {
6714                        if request.to.is_none() {
6715                            request.to = Some(TxKind::Create);
6716                        }
6717                        let mut request = self.parse_transaction_request(request)?;
6718                        request.prep_for_submission();
6719                        let typed_tx = request.build_unsigned().map_err(|e| {
6720                            BlockchainError::InvalidTransactionRequest(e.to_string())
6721                        })?;
6722                        MaybeImpersonatedTransaction::impersonated(
6723                            typed_tx.into_impersonated(),
6724                            from,
6725                        )
6726                    };
6727                    let tx_hash = tx.as_ref().hash();
6728                    #[cfg(feature = "optimism")]
6729                    let (deposit_nonce, deposit_receipt_version) =
6730                        if matches!(tx.as_ref(), FoundryTxEnvelope::Deposit(_)) {
6731                            let hardfork = OpHardfork::from(self.hardfork());
6732                            (
6733                                (hardfork >= OpHardfork::Regolith).then_some(caller_nonce),
6734                                (hardfork >= OpHardfork::Canyon).then_some(1),
6735                            )
6736                        } else {
6737                            (None, None)
6738                        };
6739                    #[cfg(not(feature = "optimism"))]
6740                    let (deposit_nonce, deposit_receipt_version) = (None, None);
6741                    receipts.push(FoundryReceiptBuilder::build_simulated_receipt(
6742                        tx.as_ref().tx_type(),
6743                        &result,
6744                        canonical_logs.clone(),
6745                        cumulative_gas_used,
6746                        post_state,
6747                        deposit_nonce,
6748                        deposit_receipt_version,
6749                    ));
6750                    transaction_envelopes.push(tx.as_ref().clone());
6751                    let rpc_tx =
6752                        transaction_build(Some(tx_hash), tx, None, None, Some(block_env.basefee));
6753                    transactions.push(rpc_tx);
6754
6755                    let return_data = if result.is_success() {
6756                        result.output().cloned().unwrap_or_default()
6757                    } else {
6758                        Bytes::new()
6759                    };
6760                    let sim_res = SimCallResult {
6761                        return_data,
6762                        gas_used: result.tx_gas_used(),
6763                        max_used_gas: Some(
6764                            result.gas().total_gas_spent().max(result.gas().floor_gas()),
6765                        ),
6766                        status: result.is_success(),
6767                        error: match &result {
6768                            ExecutionResult::Success { .. } => None,
6769                            ExecutionResult::Revert { output, .. } => {
6770                                let message = RevertDecoder::new()
6771                                    .maybe_decode(output, None)
6772                                    .map(|reason| format!("execution reverted: {reason}"))
6773                                    .unwrap_or_else(|| "execution reverted".to_string());
6774                                Some(SimulateError {
6775                                    code: SimulateError::EXECUTION_REVERTED_CODE,
6776                                    message,
6777                                    data: Some(output.clone()),
6778                                })
6779                            }
6780                            ExecutionResult::Halt { reason, .. } => Some(SimulateError {
6781                                code: SimulateError::VM_EXECUTION_ERROR_CODE,
6782                                message: if matches!(reason, HaltReason::OutOfGas(_)) {
6783                                    "out of gas".to_string()
6784                                } else {
6785                                    format!("vm execution error: {reason}")
6786                                },
6787                                data: None,
6788                            }),
6789                        },
6790                        logs: response_logs
6791                            .into_iter()
6792                            .map(|(idx, log)| Log {
6793                                inner: log,
6794                                block_number: Some(block_env.number.saturating_to()),
6795                                block_timestamp: Some(block_env.timestamp.saturating_to()),
6796                                transaction_index: Some(req_idx as u64),
6797                                log_index: Some(idx + log_index),
6798                                removed: false,
6799
6800                                block_hash: None,
6801                                transaction_hash: Some(tx_hash),
6802                            })
6803                            .collect(),
6804                    };
6805                    log_index += attempted_log_count;
6806                    call_res.push(sim_res);
6807                }
6808
6809                for (number, hash) in overridden_block_hashes {
6810                    if let Some(hash) = hash {
6811                        cache_db.cache.block_hashes.insert(number, hash);
6812                    } else {
6813                        cache_db.cache.block_hashes.remove(&number);
6814                    }
6815                }
6816
6817                let gas_used = if is_amsterdam {
6818                    block_regular_gas_used.max(block_state_gas_used)
6819                } else {
6820                    cumulative_gas_used
6821                };
6822                let requests = if let Some(transitions) = ethereum_transitions {
6823                    self.apply_simulation_post_execution_changes(
6824                        &mut cache_db,
6825                        &simulation_evm_env,
6826                        transitions,
6827                        &receipts,
6828                    )?
6829                } else {
6830                    Default::default()
6831                };
6832
6833                // TODO: Assess restoring fork-backed simulations by deriving a canonical
6834                // post-state root.
6835                let accounts = cache_db.maybe_full_db().ok_or(BlockchainError::DataUnavailable)?;
6836                let state_root = state_root(&accounts);
6837                let header = Header {
6838                    logs_bloom: receipts.iter().fold(Bloom::ZERO, |mut bloom, receipt| {
6839                        bloom.accrue_bloom(receipt.logs_bloom());
6840                        bloom
6841                    }),
6842                    transactions_root: calculate_transaction_root(&transaction_envelopes),
6843                    receipts_root: calculate_receipt_root(&receipts),
6844                    parent_hash,
6845                    beneficiary: block_env.beneficiary,
6846                    state_root,
6847                    difficulty: block_env.difficulty,
6848                    number: block_env.number.saturating_to(),
6849                    gas_limit: block_env.gas_limit,
6850                    gas_used,
6851                    timestamp: block_env.timestamp.saturating_to(),
6852                    extra_data: Default::default(),
6853                    mix_hash: block_env.prevrandao.unwrap_or_default(),
6854                    nonce: Default::default(),
6855                    base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(block_env.basefee),
6856                    withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
6857                    blob_gas_used: is_cancun.then_some(block_blob_gas_used),
6858                    excess_blob_gas: if is_cancun { block_env.blob_excess_gas() } else { None },
6859                    parent_beacon_block_root: ethereum_transitions.and_then(|transitions| {
6860                        (transitions.hardfork >= EthereumHardfork::Cancun)
6861                            .then_some(transitions.parent_beacon_block_root.unwrap_or_default())
6862                    }),
6863                    requests_hash: ethereum_transitions.and_then(|transitions| {
6864                        (transitions.hardfork >= EthereumHardfork::Prague)
6865                            .then(|| requests.requests_hash())
6866                    }),
6867                    ..Default::default()
6868                };
6869                let block_hash = header.hash_slow();
6870                for (transaction_index, transaction) in transactions.iter_mut().enumerate() {
6871                    transaction.block_hash = Some(block_hash);
6872                    transaction.block_number = Some(header.number);
6873                    transaction.transaction_index = Some(transaction_index as u64);
6874                    transaction.block_timestamp = Some(header.timestamp);
6875                }
6876                let mut block = alloy_rpc_types::Block {
6877                    header: AnyRpcHeader {
6878                        hash: block_hash,
6879                        inner: header.into(),
6880                        total_difficulty: None,
6881                        size: None,
6882                    },
6883                    uncles: vec![],
6884                    transactions: BlockTransactions::Full(transactions),
6885                    withdrawals: (spec_id >= SpecId::SHANGHAI).then_some(Default::default()),
6886                };
6887
6888                if !return_full_transactions {
6889                    block.transactions.convert_to_hashes();
6890                }
6891
6892                for res in &mut call_res {
6893                    res.logs.iter_mut().for_each(|log| {
6894                        log.block_hash = Some(block.header.hash);
6895                    });
6896                }
6897
6898                let simulated_block = SimulatedBlock {
6899                    inner: AnyRpcBlock::new(WithOtherFields::new(block)),
6900                    calls: call_res,
6901                };
6902
6903                parent_hash = block_hash;
6904                cache_db.cache.block_hashes.insert(block_env.number, block_hash);
6905                inherited_block_env.beneficiary = block_env.beneficiary;
6906                inherited_block_env.difficulty = block_env.difficulty;
6907                inherited_block_env.gas_limit = block_env.gas_limit;
6908                // Route through the fee manager so Tempo chains use their own base fee rules.
6909                let header = &simulated_block.inner.header;
6910                next_base_fee = self.fees.calculate_next_block_base_fee_per_gas(
6911                    header.gas_used(),
6912                    header.gas_limit(),
6913                    header.base_fee_per_gas().unwrap_or_default(),
6914                );
6915                parent_base_fee_per_gas = header.base_fee_per_gas().unwrap_or_default();
6916                parent_excess_blob_gas = header.excess_blob_gas().unwrap_or_default();
6917                parent_blob_gas_used = header.blob_gas_used().unwrap_or_default();
6918
6919                block_res.push(simulated_block);
6920            }
6921
6922            Ok(block_res)
6923        };
6924
6925        match block_request {
6926            Some(BlockRequest::Pending(pool_transactions)) => {
6927                self.with_pending_block(pool_transactions, |state, block| {
6928                    let header = &block.block.header;
6929                    let base_fee = self.fees.calculate_next_block_base_fee_per_gas(
6930                        header.gas_used(),
6931                        header.gas_limit(),
6932                        header.base_fee_per_gas().unwrap_or_default(),
6933                    );
6934                    simulate_at(
6935                        state,
6936                        block_env_from_header(header),
6937                        header.number(),
6938                        header.timestamp(),
6939                        header.hash_slow(),
6940                        base_fee,
6941                        header.base_fee_per_gas().unwrap_or_default(),
6942                        header.excess_blob_gas().unwrap_or_default(),
6943                        header.blob_gas_used().unwrap_or_default(),
6944                    )
6945                })
6946                .await
6947            }
6948            block_request => {
6949                let base_block_number = match block_request.as_ref() {
6950                    Some(BlockRequest::Number(number)) => BlockNumber::Number(*number),
6951                    Some(BlockRequest::Pending(_)) => unreachable!(),
6952                    None => BlockNumber::Latest,
6953                };
6954                let base_block = self
6955                    .block_by_number(base_block_number)
6956                    .await?
6957                    .ok_or(BlockchainError::BlockNotFound)?;
6958                let base_number = base_block.header.number();
6959                let base_timestamp = base_block.header.timestamp();
6960                let base_hash = base_block.header.hash;
6961                let base_fee = self.fees.calculate_next_block_base_fee_per_gas(
6962                    base_block.header.gas_used(),
6963                    base_block.header.gas_limit(),
6964                    base_block.header.base_fee_per_gas().unwrap_or_default(),
6965                );
6966                self.with_database_at(block_request, |state, block_env| {
6967                    simulate_at(
6968                        state,
6969                        block_env,
6970                        base_number,
6971                        base_timestamp,
6972                        base_hash,
6973                        base_fee,
6974                        base_block.header.base_fee_per_gas().unwrap_or_default(),
6975                        base_block.header.excess_blob_gas().unwrap_or_default(),
6976                        base_block.header.blob_gas_used().unwrap_or_default(),
6977                    )
6978                })
6979                .await?
6980            }
6981        }
6982    }
6983
6984    pub fn get_blob_by_tx_hash(&self, hash: B256) -> Result<Option<Vec<alloy_consensus::Blob>>> {
6985        let storage = self.blockchain.storage.read();
6986        Ok(storage.transactions.get(&hash).and_then(|mined| {
6987            storage
6988                .blocks
6989                .get(&mined.block_hash)?
6990                .body
6991                .transactions
6992                .get(mined.info.transaction_index as usize)?
6993                .as_ref()
6994                .sidecar()
6995                .map(|sidecar| sidecar.sidecar.blobs().to_vec())
6996        }))
6997    }
6998
6999    /// Sets the fee token for a user address (Tempo-only).
7000    pub async fn set_fee_token(&self, user: Address, token: Address) -> DatabaseResult<()> {
7001        self.with_tempo_storage(|| {
7002            let mut fee_manager = TipFeeManager::new();
7003            fee_manager
7004                .set_user_token(user, IFeeManager::setUserTokenCall { token })
7005                .map_err(tempo_db_err)
7006        })
7007        .await
7008    }
7009
7010    /// Sets the fee token for a validator address (Tempo-only).
7011    pub async fn set_validator_fee_token(
7012        &self,
7013        validator: Address,
7014        token: Address,
7015    ) -> DatabaseResult<()> {
7016        self.with_tempo_storage(|| {
7017            let mut fee_manager = TipFeeManager::new();
7018            // Use Address::ZERO as beneficiary so the check `sender != beneficiary` passes
7019            fee_manager
7020                .set_validator_token(
7021                    validator,
7022                    IFeeManager::setValidatorTokenCall { token },
7023                    Address::ZERO,
7024                )
7025                .map_err(tempo_db_err)
7026        })
7027        .await
7028    }
7029
7030    /// Mints FeeAMM liquidity for a token pair (Tempo-only).
7031    pub async fn set_fee_amm_liquidity(
7032        &self,
7033        user_token: Address,
7034        validator_token: Address,
7035        amount: U256,
7036    ) -> DatabaseResult<()> {
7037        // T3+ rejects minting to the zero address.
7038        let admin = Address::repeat_byte(0x11);
7039        self.with_tempo_storage(|| {
7040            // Mint the required tokens to admin so it can provide liquidity.
7041            // grant_role_internal bypasses the caller check, matching genesis seeding.
7042            for &token_address in &[user_token, validator_token] {
7043                let mut token = TIP20Token::from_address(token_address).map_err(tempo_db_err)?;
7044                token.grant_role_internal(admin, *ISSUER_ROLE).map_err(tempo_db_err)?;
7045                token.mint(admin, ITIP20::mintCall { to: admin, amount }).map_err(tempo_db_err)?;
7046            }
7047            let mut fee_manager = TipFeeManager::new();
7048            fee_manager
7049                .mint(admin, user_token, validator_token, amount, admin)
7050                .map_err(tempo_db_err)?;
7051            Ok(())
7052        })
7053        .await
7054    }
7055
7056    /// Sets an account's balance for a deployed TIP-20 token (Tempo-only).
7057    pub async fn set_tip20_balance(
7058        &self,
7059        address: Address,
7060        token_address: Address,
7061        balance: U256,
7062    ) -> DatabaseResult<()> {
7063        if self.try_set_tip20_balance(address, token_address, balance).await? {
7064            return Ok(());
7065        }
7066
7067        Err(tempo_db_err(format!("address {token_address} is not a deployed TIP-20 token")))
7068    }
7069
7070    /// Sets an account's balance if the address is a deployed TIP-20 token (Tempo-only).
7071    pub async fn try_set_tip20_balance(
7072        &self,
7073        address: Address,
7074        token_address: Address,
7075        balance: U256,
7076    ) -> DatabaseResult<bool> {
7077        self.with_tempo_storage(|| {
7078            if !TIP20Factory::new().is_tip20(token_address).map_err(tempo_db_err)? {
7079                return Ok(false);
7080            }
7081
7082            let mut token = TIP20Token::from_address(token_address).map_err(tempo_db_err)?;
7083            token.balances[address].write(balance).map_err(tempo_db_err)?;
7084            Ok(true)
7085        })
7086        .await
7087    }
7088
7089    /// Runs `f` inside a Tempo storage context initialized from the current state
7090    /// (Tempo-only).
7091    async fn with_tempo_storage<R>(&self, f: impl FnOnce() -> R) -> R {
7092        let hardfork = self.hardfork();
7093        // One consistent snapshot of the current env to build the storage context.
7094        let (chain_id, timestamp, block_number) = {
7095            let env = self.evm_env.read();
7096            (
7097                env.cfg_env.chain_id,
7098                U256::from(env.block_env.timestamp),
7099                env.block_env.number.to::<u64>(),
7100            )
7101        };
7102        let mut db = self.db.write().await;
7103        let mut storage = AnvilStorageProvider::new(
7104            &mut **db,
7105            chain_id,
7106            timestamp,
7107            block_number,
7108            hardfork.into(),
7109        );
7110        StorageCtx::enter(&mut storage, f)
7111    }
7112}
7113
7114/// Converts a Tempo error into an anvil [`DatabaseError`].
7115fn tempo_db_err<E: std::fmt::Display>(e: E) -> DatabaseError {
7116    DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))
7117}
7118
7119/// Get max nonce from transaction pool by address.
7120fn get_pool_transactions_nonce(
7121    pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
7122    address: Address,
7123) -> Option<u64> {
7124    if let Some(highest_nonce) = pool_transactions
7125        .iter()
7126        .filter(|tx| {
7127            *tx.pending_transaction.sender() == address
7128                && !tx.pending_transaction.transaction.as_ref().has_nonzero_tempo_nonce_key()
7129        })
7130        .map(|tx| tx.pending_transaction.nonce())
7131        .max()
7132    {
7133        let tx_count = highest_nonce.saturating_add(1);
7134        return Some(tx_count);
7135    }
7136    None
7137}
7138
7139#[async_trait::async_trait]
7140impl<N: Network> TransactionValidator<FoundryTxEnvelope> for Backend<N>
7141where
7142    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
7143{
7144    async fn validate_pool_transaction(
7145        &self,
7146        tx: &PendingTransaction<FoundryTxEnvelope>,
7147    ) -> Result<(), BlockchainError> {
7148        let address = *tx.sender();
7149        let account = self.get_account(address).await?;
7150        let evm_env = self.next_evm_env();
7151
7152        // Tempo AA: validate time bounds and fee token balance (async checks)
7153        if let FoundryTxEnvelope::Tempo(aa_tx) = tx.transaction.as_ref() {
7154            let tempo_tx = aa_tx.tx();
7155            let current_time = evm_env.block_env.timestamp.saturating_to::<u64>();
7156
7157            // Reject if valid_before is expired or too close to current time (< 3 seconds)
7158            const AA_VALID_BEFORE_MIN_SECS: u64 = 3;
7159            if let Some(valid_before) = tempo_tx.valid_before.map(|v| v.get()) {
7160                let min_allowed = current_time.saturating_add(AA_VALID_BEFORE_MIN_SECS);
7161                if valid_before <= min_allowed {
7162                    return Err(InvalidTransactionError::TempoValidBeforeExpired {
7163                        valid_before,
7164                        min_allowed,
7165                    }
7166                    .into());
7167                }
7168            }
7169
7170            // Reject if valid_after is too far in the future (> 1 hour)
7171            const AA_VALID_AFTER_MAX_SECS: u64 = 3600;
7172            if let Some(valid_after) = tempo_tx.valid_after.map(|v| v.get()) {
7173                let max_allowed = current_time.saturating_add(AA_VALID_AFTER_MAX_SECS);
7174                if valid_after > max_allowed {
7175                    return Err(InvalidTransactionError::TempoValidAfterTooFar {
7176                        valid_after,
7177                        max_allowed,
7178                    }
7179                    .into());
7180                }
7181            }
7182
7183            // Fee token balance check
7184            let fee_payer = tempo_tx.recover_fee_payer(address).unwrap_or(address);
7185            let fee_token =
7186                tempo_tx.fee_token.unwrap_or(foundry_evm::core::tempo::PATH_USD_ADDRESS);
7187
7188            // gas_limit * max_fee_per_gas in wei, scaled to 6-decimal token units
7189            let required_wei =
7190                U256::from(tempo_tx.gas_limit).saturating_mul(U256::from(tempo_tx.max_fee_per_gas));
7191            let required = required_wei / U256::from(10u64.pow(12));
7192
7193            let balance = self.get_fee_token_balance(fee_token, fee_payer).await?;
7194            if balance < required {
7195                return Err(InvalidTransactionError::TempoInsufficientFeeTokenBalance {
7196                    balance,
7197                    required,
7198                }
7199                .into());
7200            }
7201        }
7202
7203        Ok(self.validate_pool_transaction_for(tx, &account, &evm_env)?)
7204    }
7205
7206    fn validate_pool_transaction_for(
7207        &self,
7208        pending: &PendingTransaction<FoundryTxEnvelope>,
7209        account: &AccountInfo,
7210        evm_env: &EvmEnv,
7211    ) -> Result<(), InvalidTransactionError> {
7212        let tx = &pending.transaction;
7213
7214        if let Some(tx_chain_id) = tx.chain_id() {
7215            let chain_id = self.chain_id();
7216            if chain_id.to::<u64>() != tx_chain_id {
7217                if let FoundryTxEnvelope::Legacy(tx) = tx.as_ref() {
7218                    // <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md>
7219                    if evm_env.cfg_env.spec >= SpecId::SPURIOUS_DRAGON && tx.chain_id().is_none() {
7220                        debug!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V");
7221                        return Err(InvalidTransactionError::IncompatibleEIP155);
7222                    }
7223                } else {
7224                    debug!(target: "backend", ?chain_id, ?tx_chain_id, "invalid chain id");
7225                    return Err(InvalidTransactionError::InvalidChainId);
7226                }
7227            }
7228        }
7229
7230        // Reject native value transfers on Tempo networks
7231        if self.is_tempo() && !tx.value().is_zero() {
7232            warn!(target: "backend", "[{:?}] native value transfer not allowed in Tempo mode", tx.hash());
7233            return Err(InvalidTransactionError::TempoNativeValueTransfer);
7234        }
7235
7236        // Tempo AA T5: cap authorization list size
7237        if self.is_tempo_hardfork_active(TempoHardfork::T5)
7238            && let FoundryTxEnvelope::Tempo(aa_tx) = tx.as_ref()
7239        {
7240            const MAX_TEMPO_AUTHORIZATIONS: usize = 16;
7241            let auth_count = aa_tx.tx().tempo_authorization_list.len();
7242            if auth_count > MAX_TEMPO_AUTHORIZATIONS {
7243                warn!(target: "backend", "[{:?}] Tempo tx has too many authorizations: {}", tx.hash(), auth_count);
7244                return Err(InvalidTransactionError::TempoTooManyAuthorizations {
7245                    count: auth_count,
7246                    max: MAX_TEMPO_AUTHORIZATIONS,
7247                });
7248            }
7249        }
7250
7251        // Nonce validation — skip for deposits (L1→L2) and Tempo txs (2D nonce system)
7252        #[cfg(feature = "optimism")]
7253        let is_deposit_tx = pending.transaction.as_ref().is_deposit();
7254        #[cfg(not(feature = "optimism"))]
7255        let is_deposit_tx = false;
7256        let is_tempo_tx = pending.transaction.as_ref().is_tempo();
7257        let nonce = tx.nonce();
7258        if nonce < account.nonce && !is_deposit_tx && !is_tempo_tx {
7259            debug!(target: "backend", "[{:?}] nonce too low", tx.hash());
7260            return Err(InvalidTransactionError::NonceTooLow);
7261        }
7262
7263        // EIP-4844 structural validation
7264        if evm_env.cfg_env.spec >= SpecId::CANCUN && tx.is_eip4844() {
7265            // Heavy (blob validation) checks
7266            let blob_tx = match tx.as_ref() {
7267                FoundryTxEnvelope::Eip4844(tx) => tx.tx(),
7268                _ => unreachable!(),
7269            };
7270
7271            let blob_count = blob_tx.tx().blob_versioned_hashes.len();
7272
7273            // Ensure there are blob hashes.
7274            if blob_count == 0 {
7275                return Err(InvalidTransactionError::NoBlobHashes);
7276            }
7277
7278            // Ensure the tx does not exceed the max blobs per transaction.
7279            let max_blobs_per_tx = self.blob_params().max_blobs_per_tx as usize;
7280            if blob_count > max_blobs_per_tx {
7281                return Err(InvalidTransactionError::TooManyBlobs(blob_count, max_blobs_per_tx));
7282            }
7283
7284            // Check for any blob validation errors if not impersonating.
7285            if !self.skip_blob_validation(Some(*pending.sender()))
7286                && let Err(err) = blob_tx.validate(EnvKzgSettings::default().get())
7287            {
7288                return Err(InvalidTransactionError::BlobTransactionValidationError(err));
7289            }
7290        }
7291
7292        // EIP-3860 initcode size validation, respects --code-size-limit / --disable-code-size-limit
7293        if evm_env.cfg_env.spec >= SpecId::SHANGHAI && tx.kind() == TxKind::Create {
7294            let max_initcode_size = evm_env
7295                .cfg_env
7296                .limit_contract_code_size
7297                .map(|limit| limit.saturating_mul(2))
7298                .unwrap_or(revm::primitives::eip3860::MAX_INITCODE_SIZE);
7299            if tx.input().len() > max_initcode_size {
7300                return Err(InvalidTransactionError::MaxInitCodeSizeExceeded);
7301            }
7302        }
7303
7304        // Balance and fee related checks
7305        if !self.disable_pool_balance_checks {
7306            // Gas limit validation
7307            if tx.gas_limit() < MIN_TRANSACTION_GAS as u64 {
7308                debug!(target: "backend", "[{:?}] gas too low", tx.hash());
7309                return Err(InvalidTransactionError::GasTooLow);
7310            }
7311
7312            // Check tx gas limit against block gas limit, if block gas limit is set.
7313            if !evm_env.cfg_env.disable_block_gas_limit
7314                && tx.gas_limit() > evm_env.block_env.gas_limit
7315            {
7316                debug!(target: "backend", "[{:?}] gas too high", tx.hash());
7317                return Err(InvalidTransactionError::GasTooHigh(ErrDetail {
7318                    detail: String::from("tx.gas_limit > env.block.gas_limit"),
7319                }));
7320            }
7321
7322            // Check tx gas limit against tx gas limit cap (Osaka hard fork and later).
7323            if evm_env.cfg_env.tx_gas_limit_cap.is_none()
7324                && tx.gas_limit() > evm_env.cfg_env().tx_gas_limit_cap()
7325            {
7326                debug!(target: "backend", "[{:?}] gas too high", tx.hash());
7327                return Err(InvalidTransactionError::GasTooHigh(ErrDetail {
7328                    detail: String::from("tx.gas_limit > env.cfg.tx_gas_limit_cap"),
7329                }));
7330            }
7331
7332            // EIP-1559 fee validation (London hard fork and later).
7333            if evm_env.cfg_env.spec >= SpecId::LONDON {
7334                if tx.max_fee_per_gas() < evm_env.block_env.basefee.into() && !is_deposit_tx {
7335                    debug!(target: "backend", "max fee per gas={}, too low, block basefee={}", tx.max_fee_per_gas(), evm_env.block_env.basefee);
7336                    return Err(InvalidTransactionError::FeeCapTooLow);
7337                }
7338
7339                if !evm_env.cfg_env.disable_priority_fee_check
7340                    && let (Some(max_priority_fee_per_gas), max_fee_per_gas) =
7341                        (tx.as_ref().max_priority_fee_per_gas(), tx.as_ref().max_fee_per_gas())
7342                    && max_priority_fee_per_gas > max_fee_per_gas
7343                {
7344                    debug!(target: "backend", "max priority fee per gas={}, too high, max fee per gas={}", max_priority_fee_per_gas, max_fee_per_gas);
7345                    return Err(InvalidTransactionError::TipAboveFeeCap);
7346                }
7347            }
7348
7349            // EIP-4844 blob fee validation
7350            if evm_env.cfg_env.spec >= SpecId::CANCUN
7351                && tx.is_eip4844()
7352                && let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas()
7353                && let Some(blob_gas_and_price) = &evm_env.block_env.blob_excess_gas_and_price
7354                && max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice
7355            {
7356                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);
7357                return Err(InvalidTransactionError::BlobFeeCapTooLow(
7358                    max_fee_per_blob_gas,
7359                    blob_gas_and_price.blob_gasprice,
7360                ));
7361            }
7362
7363            let max_cost =
7364                (tx.gas_limit() as u128).saturating_mul(tx.max_fee_per_gas()).saturating_add(
7365                    tx.blob_gas_used()
7366                        .map(|g| g as u128)
7367                        .unwrap_or(0)
7368                        .mul(tx.max_fee_per_blob_gas().unwrap_or(0)),
7369                );
7370            let value = tx.value();
7371            match tx.as_ref() {
7372                #[cfg(feature = "optimism")]
7373                FoundryTxEnvelope::Deposit(deposit_tx) => {
7374                    // Deposit transactions
7375                    // https://specs.optimism.io/protocol/deposits.html#execution
7376                    // 1. no gas cost check required since already have prepaid gas from L1
7377                    // 2. increment account balance by deposited amount before checking for
7378                    //    sufficient funds `tx.value <= existing account value + deposited value`
7379                    if value > account.balance + U256::from(deposit_tx.mint) {
7380                        debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + U256::from(deposit_tx.mint), value, *pending.sender());
7381                        return Err(InvalidTransactionError::InsufficientFunds);
7382                    }
7383                }
7384                FoundryTxEnvelope::Tempo(_) => {
7385                    // Tempo AA transactions pay gas with fee tokens, not ETH.
7386                    // Fee token balance is validated in validate_pool_transaction (async).
7387                }
7388                _ => {
7389                    // check sufficient funds: `gas * price + value`
7390                    let req_funds =
7391                        max_cost.checked_add(value.saturating_to()).ok_or_else(|| {
7392                            debug!(target: "backend", "[{:?}] cost too high", tx.hash());
7393                            InvalidTransactionError::InsufficientFunds
7394                        })?;
7395                    if account.balance < U256::from(req_funds) {
7396                        debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender());
7397                        return Err(InvalidTransactionError::InsufficientFunds);
7398                    }
7399                }
7400            }
7401        }
7402        Ok(())
7403    }
7404
7405    fn validate_for(
7406        &self,
7407        tx: &PendingTransaction<FoundryTxEnvelope>,
7408        account: &AccountInfo,
7409        evm_env: &EvmEnv,
7410    ) -> Result<(), InvalidTransactionError> {
7411        self.validate_pool_transaction_for(tx, account, evm_env)?;
7412        if tx.nonce() > account.nonce {
7413            return Err(InvalidTransactionError::NonceTooHigh);
7414        }
7415        Ok(())
7416    }
7417}
7418
7419/// Replaces the cached hash of a [`Signed`] transaction, preserving the inner tx and signature.
7420fn rehash<T>(signed: Signed<T>, hash: B256) -> Signed<T>
7421where
7422    T: alloy_consensus::transaction::RlpEcdsaEncodableTx,
7423{
7424    let (t, sig, _) = signed.into_parts();
7425    Signed::new_unchecked(t, sig, hash)
7426}
7427
7428/// Creates a `AnyRpcTransaction` as it's expected for the `eth` RPC api from storage data
7429pub fn transaction_build(
7430    tx_hash: Option<B256>,
7431    eth_transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>,
7432    block: Option<&Block>,
7433    info: Option<TransactionInfo>,
7434    base_fee: Option<u64>,
7435) -> AnyRpcTransaction {
7436    #[cfg(feature = "optimism")]
7437    if let FoundryTxEnvelope::Deposit(deposit_tx) = eth_transaction.as_ref() {
7438        let dep_tx = deposit_tx;
7439
7440        let ser = serde_json::to_value(dep_tx).expect("could not serialize TxDeposit");
7441        let maybe_deposit_fields = OtherFields::try_from(ser);
7442
7443        match maybe_deposit_fields {
7444            Ok(mut fields) => {
7445                // Add zeroed signature fields for backwards compatibility
7446                // https://specs.optimism.io/protocol/deposits.html#the-deposited-transaction-type
7447                fields.insert("v".to_string(), serde_json::to_value("0x0").unwrap());
7448                fields.insert("r".to_string(), serde_json::to_value(B256::ZERO).unwrap());
7449                fields.insert(String::from("s"), serde_json::to_value(B256::ZERO).unwrap());
7450                fields.insert(String::from("nonce"), serde_json::to_value("0x0").unwrap());
7451
7452                let inner = UnknownTypedTransaction {
7453                    ty: AnyTxType(DEPOSIT_TX_TYPE_ID),
7454                    fields,
7455                    memo: Default::default(),
7456                };
7457
7458                let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
7459                    hash: tx_hash.unwrap_or_else(|| eth_transaction.hash()),
7460                    inner,
7461                });
7462
7463                let tx = Transaction {
7464                    inner: Recovered::new_unchecked(envelope, deposit_tx.from),
7465                    block_hash: block
7466                        .as_ref()
7467                        .map(|block| B256::from(keccak256(alloy_rlp::encode(&block.header)))),
7468                    block_number: block.as_ref().map(|block| block.header.number()),
7469                    transaction_index: info.as_ref().map(|info| info.transaction_index),
7470                    effective_gas_price: None,
7471                    block_timestamp: block.as_ref().map(|block| block.header.timestamp()),
7472                };
7473
7474                return AnyRpcTransaction::from(WithOtherFields::new(tx));
7475            }
7476            Err(_) => {
7477                error!(target: "backend", "failed to serialize deposit transaction");
7478            }
7479        }
7480    }
7481
7482    if let FoundryTxEnvelope::Tempo(tempo_tx) = eth_transaction.as_ref() {
7483        let from = eth_transaction.recover().unwrap_or_default();
7484        let ser = serde_json::to_value(tempo_tx).expect("could not serialize Tempo transaction");
7485        let maybe_tempo_fields = OtherFields::try_from(ser);
7486
7487        match maybe_tempo_fields {
7488            Ok(fields) => {
7489                let inner = UnknownTypedTransaction {
7490                    ty: AnyTxType(TEMPO_TX_TYPE_ID),
7491                    fields,
7492                    memo: Default::default(),
7493                };
7494
7495                let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
7496                    hash: tx_hash.unwrap_or_else(|| eth_transaction.hash()),
7497                    inner,
7498                });
7499
7500                let tx = Transaction {
7501                    inner: Recovered::new_unchecked(envelope, from),
7502                    block_hash: block.as_ref().map(|block| block.header.hash_slow()),
7503                    block_number: block.as_ref().map(|block| block.header.number()),
7504                    transaction_index: info.as_ref().map(|info| info.transaction_index),
7505                    effective_gas_price: None,
7506                    block_timestamp: block.as_ref().map(|block| block.header.timestamp()),
7507                };
7508
7509                return AnyRpcTransaction::from(WithOtherFields::new(tx));
7510            }
7511            Err(_) => {
7512                error!(target: "backend", "failed to serialize tempo transaction");
7513            }
7514        }
7515    }
7516
7517    let from = eth_transaction.recover().unwrap_or_default();
7518    let effective_gas_price = eth_transaction.effective_gas_price(base_fee);
7519
7520    // if a specific hash was provided we update the transaction's hash
7521    // This is important for impersonated transactions since they all use the
7522    // `BYPASS_SIGNATURE` which would result in different hashes
7523    // Note: for impersonated transactions this only concerns pending transactions because
7524    // there's no `info` yet.
7525    let hash = tx_hash.unwrap_or_else(|| eth_transaction.hash());
7526
7527    let eth_envelope = FoundryTxEnvelope::from(eth_transaction)
7528        .try_into_eth()
7529        .expect("non-standard transactions are handled above");
7530
7531    let envelope = match eth_envelope {
7532        TxEnvelope::Legacy(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Legacy(rehash(s, hash))),
7533        TxEnvelope::Eip1559(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip1559(rehash(s, hash))),
7534        TxEnvelope::Eip2930(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip2930(rehash(s, hash))),
7535        TxEnvelope::Eip4844(s) => {
7536            let s = if block.is_some() { s.map(TxEip4844Variant::drop_sidecar) } else { s };
7537            AnyTxEnvelope::Ethereum(TxEnvelope::Eip4844(rehash(s, hash)))
7538        }
7539        TxEnvelope::Eip7702(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(rehash(s, hash))),
7540    };
7541
7542    let tx = Transaction {
7543        inner: Recovered::new_unchecked(envelope, from),
7544        block_hash: block.as_ref().map(|block| block.header.hash_slow()),
7545        block_number: block.as_ref().map(|block| block.header.number()),
7546        transaction_index: info.as_ref().map(|info| info.transaction_index),
7547        // deprecated
7548        effective_gas_price: Some(effective_gas_price),
7549        block_timestamp: block.as_ref().map(|block| block.header.timestamp()),
7550    };
7551    AnyRpcTransaction::from(WithOtherFields::new(tx))
7552}
7553
7554/// Prove a storage key's existence or nonexistence in the account's storage trie.
7555///
7556/// `storage_key` is the hash of the desired storage key, meaning
7557/// this will only work correctly under a secure trie.
7558/// `storage_key` == keccak(key)
7559pub fn prove_storage(
7560    storage: &alloy_primitives::map::U256Map<U256>,
7561    keys: &[B256],
7562) -> (B256, Vec<Vec<Bytes>>) {
7563    let keys: Vec<_> = keys.iter().map(|key| Nibbles::unpack(keccak256(key))).collect();
7564
7565    let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(keys.clone()));
7566
7567    for (key, value) in trie_storage(storage) {
7568        builder.add_leaf(key, &value);
7569    }
7570
7571    let root = builder.root();
7572
7573    let mut proofs = Vec::new();
7574    let all_proof_nodes = builder.take_proof_nodes();
7575
7576    for proof_key in keys {
7577        // Iterate over all proof nodes and find the matching ones.
7578        // The filtered results are guaranteed to be in order.
7579        let matching_proof_nodes =
7580            all_proof_nodes.matching_nodes_sorted(&proof_key).into_iter().map(|(_, node)| node);
7581        proofs.push(matching_proof_nodes.collect());
7582    }
7583
7584    (root, proofs)
7585}
7586
7587pub fn is_arbitrum(chain_id: u64) -> bool {
7588    if let Ok(chain) = NamedChain::try_from(chain_id) {
7589        return chain.is_arbitrum();
7590    }
7591    false
7592}
7593
7594/// Commits a fully executed candidate cache to the live database.
7595fn commit_cache(db: &mut dyn Db, cache: revm::database::Cache) -> Result<(), BlockchainError> {
7596    let revm::database::Cache { accounts, contracts, .. } = cache;
7597    let mut changes = EvmState::default();
7598    for (address, db_account) in accounts {
7599        if db_account.account_state == AccountState::None {
7600            continue;
7601        }
7602
7603        let DbAccount { mut info, account_state, storage } = db_account;
7604        // `CacheDB` also records absent-account reads as `NotExisting`. They are not state changes
7605        // and must not become synthetic selfdestructs in the live database.
7606        if account_state == AccountState::NotExisting && db.basic(address)?.is_none() {
7607            continue;
7608        }
7609        if info.code.is_none() {
7610            info.code = contracts.get(&info.code_hash).cloned();
7611        }
7612        let mut account = Account::from(info);
7613        account.mark_touch();
7614        match account_state {
7615            AccountState::NotExisting => account.mark_selfdestruct(),
7616            AccountState::StorageCleared => account.mark_created(),
7617            AccountState::Touched => {}
7618            AccountState::None => unreachable!(),
7619        }
7620        for (slot, value) in storage {
7621            let original = if account_state == AccountState::StorageCleared {
7622                U256::ZERO
7623            } else {
7624                db.storage(address, slot)?
7625            };
7626            account
7627                .storage
7628                .insert(slot, EvmStorageSlot::new_changed(original, value, TransactionId::ZERO));
7629        }
7630        changes.insert(address, account);
7631    }
7632    db.commit(changes);
7633    Ok(())
7634}
7635
7636fn simulate_rpc_error(code: i64, message: impl Into<String>) -> BlockchainError {
7637    BlockchainError::RpcError(RpcError {
7638        code: ErrorCode::from(code),
7639        message: message.into().into(),
7640        data: None,
7641    })
7642}
7643
7644fn previously_deleted_accounts(
7645    accounts: &AddressMap<DbAccount>,
7646    addresses: impl IntoIterator<Item = Address>,
7647) -> Vec<Address> {
7648    addresses
7649        .into_iter()
7650        .filter(|address| {
7651            accounts
7652                .get(address)
7653                .is_some_and(|account| account.account_state == AccountState::NotExisting)
7654        })
7655        .collect()
7656}
7657
7658fn preserve_deleted_storage(
7659    accounts: &mut AddressMap<DbAccount>,
7660    previously_deleted: Vec<Address>,
7661) {
7662    for address in previously_deleted {
7663        if let Some(account) = accounts.get_mut(&address)
7664            && account.account_state != AccountState::NotExisting
7665        {
7666            account.account_state = AccountState::StorageCleared;
7667        }
7668    }
7669}
7670
7671fn simulate_transaction_error(error: InvalidTransactionError) -> BlockchainError {
7672    let code = match &error {
7673        InvalidTransactionError::NonceTooLow => -38010,
7674        InvalidTransactionError::NonceTooHigh => -38011,
7675        InvalidTransactionError::NonceMaxValue => -32603,
7676        InvalidTransactionError::FeeCapTooLow => -38012,
7677        InvalidTransactionError::GasTooLow | InvalidTransactionError::GasTooHigh(_) => -38013,
7678        InvalidTransactionError::InsufficientFunds
7679        | InvalidTransactionError::InsufficientFundsForTransfer => -38014,
7680        _ => return BlockchainError::InvalidTransaction(error),
7681    };
7682
7683    simulate_rpc_error(code, format!("err: {error}"))
7684}
7685
7686pub(in crate::eth) fn sanitize_simulation_blocks<T>(
7687    blocks: Vec<SimBlock<T>>,
7688    base_number: u64,
7689    base_timestamp: u64,
7690    block_interval: u64,
7691) -> Result<Vec<SimBlock<T>>, BlockchainError> {
7692    let block_interval = block_interval.max(1);
7693    let mut sanitized = Vec::with_capacity(blocks.len());
7694    let mut previous_number = base_number;
7695    let mut previous_timestamp = base_timestamp;
7696
7697    for mut block in blocks {
7698        let mut overrides = block.block_overrides.take().unwrap_or_default();
7699        let default_number = previous_number.checked_add(1).ok_or_else(|| {
7700            simulate_rpc_error(-38020, "block number overflow while constructing sequence")
7701        })?;
7702        let number =
7703            overrides.number.map(|number| number.saturating_to()).unwrap_or(default_number);
7704
7705        if number <= previous_number {
7706            return Err(simulate_rpc_error(
7707                -38020,
7708                format!("block numbers must be in order: {number} <= {previous_number}"),
7709            ));
7710        }
7711
7712        let gap = number - previous_number - 1;
7713        let remaining = MAX_SIMULATE_BLOCKS as usize - sanitized.len();
7714        if gap as usize >= remaining {
7715            return Err(simulate_rpc_error(-38026, "too many blocks"));
7716        }
7717
7718        for offset in 0..gap {
7719            let timestamp = previous_timestamp.checked_add(block_interval).ok_or_else(|| {
7720                simulate_rpc_error(-38021, "block timestamp overflow while filling number gap")
7721            })?;
7722            sanitized.push(SimBlock {
7723                block_overrides: Some(BlockOverrides {
7724                    number: Some(U256::from(default_number + offset)),
7725                    time: Some(timestamp),
7726                    ..Default::default()
7727                }),
7728                state_overrides: None,
7729                calls: Vec::new(),
7730            });
7731            previous_timestamp = timestamp;
7732        }
7733
7734        let timestamp = match overrides.time {
7735            Some(timestamp) => timestamp,
7736            None => previous_timestamp.checked_add(block_interval).ok_or_else(|| {
7737                simulate_rpc_error(-38021, "block timestamp overflow while constructing sequence")
7738            })?,
7739        };
7740        if timestamp <= previous_timestamp {
7741            return Err(simulate_rpc_error(
7742                -38021,
7743                format!("block timestamps must be in order: {timestamp} <= {previous_timestamp}"),
7744            ));
7745        }
7746
7747        overrides.number = Some(U256::from(number));
7748        overrides.time = Some(timestamp);
7749        block.block_overrides = Some(overrides);
7750        sanitized.push(block);
7751        previous_number = number;
7752        previous_timestamp = timestamp;
7753    }
7754
7755    Ok(sanitized)
7756}
7757
7758/// Unpacks an [`ExecutionResult`] into its exit reason, gas used, output, and logs.
7759fn unpack_execution_result<H: IntoInstructionResult>(
7760    result: ExecutionResult<H>,
7761) -> (InstructionResult, u64, Option<Output>, Vec<revm::primitives::Log>) {
7762    match result {
7763        ExecutionResult::Success { reason, gas, output, logs, .. } => {
7764            (reason.into(), gas.tx_gas_used(), Some(output), logs)
7765        }
7766        ExecutionResult::Revert { gas, output, logs, .. } => {
7767            (InstructionResult::Revert, gas.tx_gas_used(), Some(Output::Call(output)), logs)
7768        }
7769        ExecutionResult::Halt { reason, gas, logs, .. } => {
7770            (reason.into_instruction_result(), gas.tx_gas_used(), None, logs)
7771        }
7772    }
7773}
7774
7775/// Converts a halt reason into an [`InstructionResult`].
7776///
7777/// Abstracts over network-specific halt reason types (`HaltReason`, `OpHaltReason`)
7778/// so that anvil code doesn't need to match on each variant directly.
7779pub use foundry_evm::core::evm::IntoInstructionResult;
7780
7781#[cfg(test)]
7782mod tests {
7783    use crate::{NodeConfig, spawn};
7784
7785    #[tokio::test]
7786    async fn test_deterministic_block_mining() {
7787        // Test that mine_block produces deterministic block hashes with same initial conditions
7788        let genesis_timestamp = 1743944919u64;
7789
7790        // Create two identical backends
7791        let config_a = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into());
7792        let config_b = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into());
7793
7794        let (api_a, _handle_a) = spawn(config_a).await;
7795        let (api_b, _handle_b) = spawn(config_b).await;
7796
7797        // Mine empty blocks (no transactions) on both backends
7798        let outcome_a_1 = api_a.backend.mine_block(vec![]).await.unwrap();
7799        let outcome_b_1 = api_b.backend.mine_block(vec![]).await.unwrap();
7800
7801        // Both should mine the same block number
7802        assert_eq!(outcome_a_1.block_number, outcome_b_1.block_number);
7803
7804        // Get the actual blocks to compare hashes
7805        let block_a_1 =
7806            api_a.block_by_number(outcome_a_1.block_number.into()).await.unwrap().unwrap();
7807        let block_b_1 =
7808            api_b.block_by_number(outcome_b_1.block_number.into()).await.unwrap().unwrap();
7809
7810        // The block hashes should be identical
7811        assert_eq!(
7812            block_a_1.header.hash, block_b_1.header.hash,
7813            "Block hashes should be deterministic. Got {} vs {}",
7814            block_a_1.header.hash, block_b_1.header.hash
7815        );
7816
7817        // Mine another block to ensure it remains deterministic
7818        let outcome_a_2 = api_a.backend.mine_block(vec![]).await.unwrap();
7819        let outcome_b_2 = api_b.backend.mine_block(vec![]).await.unwrap();
7820
7821        let block_a_2 =
7822            api_a.block_by_number(outcome_a_2.block_number.into()).await.unwrap().unwrap();
7823        let block_b_2 =
7824            api_b.block_by_number(outcome_b_2.block_number.into()).await.unwrap().unwrap();
7825
7826        assert_eq!(
7827            block_a_2.header.hash, block_b_2.header.hash,
7828            "Second block hashes should also be deterministic. Got {} vs {}",
7829            block_a_2.header.hash, block_b_2.header.hash
7830        );
7831
7832        // Ensure the blocks are different (sanity check)
7833        assert_ne!(
7834            block_a_1.header.hash, block_a_2.header.hash,
7835            "Different blocks should have different hashes"
7836        );
7837    }
7838}