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::PruneStateHistoryConfig,
7    eth::{
8        backend::{
9            cheats::{CheatEcrecover, CheatsManager},
10            db::{AnvilCacheDB, Db, MaybeFullDatabase, SerializableState, StateDb},
11            executor::{
12                AnvilBlockExecutor, ExecutedPoolTransactions, PoolTxGasConfig,
13                execute_pool_transactions,
14            },
15            fork::ClientFork,
16            genesis::GenesisConfig,
17            mem::{
18                state::{storage_root, trie_accounts},
19                storage::MinedTransactionReceipt,
20            },
21            notifications::{ChainNotification, ChainNotifications, NewBlockNotification},
22            tempo::AnvilStorageProvider,
23            time::{TimeManager, utc_from_secs},
24            validate::TransactionValidator,
25        },
26        error::{BlockchainError, ErrDetail, InvalidTransactionError},
27        fees::{FeeDetails, FeeManager, MIN_SUGGESTED_PRIORITY_FEE},
28        macros::node_info,
29        pool::transactions::PoolTransaction,
30        sign::build_impersonated,
31    },
32    mem::{
33        inspector::{AnvilInspector, InspectorTxConfig},
34        storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome},
35    },
36};
37use alloy_chains::NamedChain;
38use alloy_consensus::{
39    Blob, BlockHeader, EnvKzgSettings, Header, Signed, Transaction as TransactionTrait,
40    TrieAccount, TxEip4844Variant, TxEnvelope, TxReceipt, Typed2718,
41    constants::EMPTY_WITHDRAWALS,
42    proofs::{calculate_receipt_root, calculate_transaction_root},
43    transaction::Recovered,
44};
45use alloy_eips::{
46    BlockNumHash, Encodable2718, eip2935, eip4844::kzg_to_versioned_hash,
47    eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams, eip7910::SystemContract,
48};
49use alloy_evm::{
50    Database, EthEvmFactory, Evm, EvmEnv, EvmFactory, FromTxWithEncoded,
51    block::{BlockExecutionResult, BlockExecutor, StateDB},
52    eth::EthEvmContext,
53    overrides::{OverrideBlockHashes, apply_state_overrides},
54    precompiles::{DynPrecompile, Precompile, PrecompilesMap},
55};
56use alloy_network::{
57    AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, Network,
58    NetworkTransactionBuilder, ReceiptResponse, UnknownTxEnvelope, UnknownTypedTransaction,
59};
60#[cfg(feature = "optimism")]
61use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx};
62use alloy_primitives::{
63    Address, B256, Bloom, Bytes, TxHash, TxKind, U64, U256, hex, keccak256, logs_bloom,
64    map::{AddressMap, HashMap, HashSet},
65};
66use alloy_rlp::Decodable;
67use alloy_rpc_types::{
68    AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides,
69    BlockTransactions, EIP1186AccountProofResponse as AccountProof,
70    EIP1186StorageProof as StorageProof, Filter, Header as AlloyHeader, Index, Log, Transaction,
71    TransactionReceipt,
72    anvil::Forking,
73    request::TransactionRequest,
74    serde_helpers::JsonStorageKey,
75    simulate::{SimBlock, SimCallResult, SimulateError, SimulatePayload, SimulatedBlock},
76    state::{EvmOverrides, StateOverride},
77    trace::{
78        filter::TraceFilter,
79        geth::{
80            CallConfig, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig,
81            GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
82            NoopFrame, TraceResult,
83        },
84        opcode::{BlockOpcodeGas, TransactionOpcodeGas},
85        parity::{
86            LocalizedTransactionTrace, TraceResults, TraceResultsWithTransactionHash, TraceType,
87        },
88    },
89};
90use alloy_rpc_types_eth::{AccountInfo as RpcAccountInfo, Bundle, EthCallResponse};
91use alloy_rpc_types_mev::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult};
92use alloy_serde::{OtherFields, WithOtherFields};
93use alloy_trie::{HashBuilder, Nibbles, proof::ProofRetainer};
94use anvil_core::eth::{
95    block::{Block, BlockInfo, canonical_block, create_block},
96    transaction::{MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo},
97};
98use anvil_rpc::error::{ErrorCode, RpcError};
99use chrono::Datelike;
100use eyre::{Context, Result};
101use flate2::{Compression, read::GzDecoder, write::GzEncoder};
102use foundry_evm::{
103    backend::{DatabaseError, DatabaseResult, RevertStateSnapshotAction},
104    constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE,
105    core::{
106        evm::{EvmEnvFor, TempoEvmNetwork},
107        precompiles::EC_RECOVER,
108    },
109    decode::RevertDecoder,
110    hardfork::FoundryHardfork,
111    inspectors::AccessListInspector,
112    traces::{
113        CallTraceDecoder, FourByteInspector, GethTraceBuilder, TracingInspector,
114        TracingInspectorConfig,
115    },
116    utils::{
117        block_env_from_header, get_blob_base_fee_update_fraction,
118        get_blob_base_fee_update_fraction_by_spec_id, get_blob_params_by_spec_id,
119    },
120};
121use foundry_evm_networks::{NetworkConfigs, arbitrum};
122#[cfg(feature = "optimism")]
123use foundry_primitives::get_deposit_tx_parts;
124use foundry_primitives::{
125    FoundryHeader, FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest,
126    FoundryTxEnvelope, FoundryTxReceipt,
127};
128use futures::channel::mpsc::{UnboundedSender, unbounded};
129#[cfg(feature = "optimism")]
130use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, OpTransaction as OpTransactionTrait};
131#[cfg(feature = "optimism")]
132use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts};
133
134/// Side-channel container for OP-specific deposit info produced by
135/// [`Backend::build_call_env`] and consumed by the OP transact path.
136///
137/// When the `optimism` feature is enabled, this is an alias for
138/// `op_revm::DepositTransactionParts`. When disabled, it is a zero-sized
139/// stand-in so the eth/tempo dispatch chain still type-checks.
140#[cfg(feature = "optimism")]
141type OpCallDepositInfo = DepositTransactionParts;
142#[cfg(not(feature = "optimism"))]
143#[derive(Default, Clone, Debug)]
144struct OpCallDepositInfo;
145
146/// Maximum cumulative gas available to one `eth_simulateV1` request.
147const SIMULATE_GAS_CAP: u64 = 50_000_000;
148
149/// Marker trait that abstracts over the per-network inspector trait bounds
150/// required by the in-memory backend. The OP bound is only included when the
151/// `optimism` feature is enabled.
152#[cfg(feature = "optimism")]
153pub trait BackendInspector<DB: Database>:
154    Inspector<EthEvmContext<DB>> + Inspector<OpEvmContext<DB>> + Inspector<TempoContext<DB>>
155{
156}
157#[cfg(feature = "optimism")]
158impl<DB: Database, T> BackendInspector<DB> for T where
159    T: Inspector<EthEvmContext<DB>> + Inspector<OpEvmContext<DB>> + Inspector<TempoContext<DB>>
160{
161}
162#[cfg(not(feature = "optimism"))]
163pub trait BackendInspector<DB: Database>:
164    Inspector<EthEvmContext<DB>> + Inspector<TempoContext<DB>>
165{
166}
167#[cfg(not(feature = "optimism"))]
168impl<DB: Database, T> BackendInspector<DB> for T where
169    T: Inspector<EthEvmContext<DB>> + Inspector<TempoContext<DB>>
170{
171}
172use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
173use revm::{
174    Database as RevmDatabase, DatabaseCommit, Inspector,
175    context::{Block as RevmBlock, BlockEnv, Cfg, TxEnv},
176    context_interface::{
177        block::BlobExcessGasAndPrice,
178        result::{ExecutionResult, HaltReason, Output, ResultAndState},
179    },
180    database::{CacheDB, DbAccount, WrapDatabaseRef},
181    interpreter::InstructionResult,
182    precompile::{PrecompileSpecId, Precompiles},
183    primitives::{KECCAK_EMPTY, hardfork::SpecId},
184    state::AccountInfo,
185};
186use revm_inspectors::opcode::OpcodeGasInspector;
187use std::{
188    collections::BTreeMap,
189    fmt::{self, Debug},
190    io::{Read, Write},
191    ops::Mul,
192    path::PathBuf,
193    sync::Arc,
194    time::Duration,
195};
196use storage::{Blockchain, DEFAULT_HISTORY_LIMIT, MinedTransaction};
197use tempo_evm::evm::TempoEvmFactory;
198use tempo_hardfork::TempoHardfork;
199use tempo_precompiles::{
200    TIP_FEE_MANAGER_ADDRESS, extend_tempo_precompiles,
201    storage::{Handler, StorageActions, StorageCtx},
202    tip_fee_manager::{IFeeManager, TipFeeManager},
203    tip20::{ISSUER_ROLE, ITIP20, TIP20Token},
204    tip20_factory::TIP20Factory,
205};
206use tempo_primitives::TEMPO_TX_TYPE_ID;
207use tempo_revm::{
208    TempoBatchCallEnv, TempoBlockEnv, TempoHaltReason, TempoTxEnv, evm::TempoContext,
209    gas_params::tempo_gas_params,
210};
211use tokio::sync::RwLock as AsyncRwLock;
212
213pub mod cache;
214pub mod fork_db;
215pub mod in_memory_db;
216pub mod inspector;
217#[cfg(feature = "optimism")]
218pub mod optimism;
219pub mod state;
220pub mod storage;
221
222/// Helper trait that combines revm::DatabaseRef with Debug.
223/// This is needed because alloy-evm requires Debug on Database implementations.
224/// With trait upcasting now stable, we can now upcast from this trait to revm::DatabaseRef.
225pub trait DatabaseRef: revm::DatabaseRef<Error = DatabaseError> + Debug {}
226impl<T> DatabaseRef for T where T: revm::DatabaseRef<Error = DatabaseError> + Debug {}
227impl DatabaseRef for dyn crate::eth::backend::db::Db {}
228
229// Gas per transaction not creating a contract.
230pub const MIN_TRANSACTION_GAS: u128 = 21000;
231// Gas per transaction creating a contract.
232pub const MIN_CREATE_GAS: u128 = 53000;
233
234fn call_config_from_tracer_config(
235    tracer_config: GethDebugTracerConfig,
236) -> Result<CallConfig, serde_json::Error> {
237    let mut tracer_config = tracer_config.into_json();
238    if let Some(config) = tracer_config.as_object_mut()
239        && !config.contains_key("onlyTopCall")
240        && let Some(only_top_level_call) = config.remove("onlyTopLevelCall")
241    {
242        config.insert("onlyTopCall".to_string(), only_top_level_call);
243    }
244
245    GethDebugTracerConfig(tracer_config).into_call_config()
246}
247
248pub type State = foundry_evm::utils::StateChangeset;
249
250/// A block request, which includes the Pool Transactions if it's Pending
251pub enum BlockRequest<T> {
252    Pending(Vec<Arc<PoolTransaction<T>>>),
253    Number(u64),
254}
255
256impl<T> fmt::Debug for BlockRequest<T> {
257    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258        match self {
259            Self::Pending(txs) => f.debug_tuple("Pending").field(&txs.len()).finish(),
260            Self::Number(n) => f.debug_tuple("Number").field(n).finish(),
261        }
262    }
263}
264
265impl<T> BlockRequest<T> {
266    pub const fn block_number(&self) -> BlockNumber {
267        match *self {
268            Self::Pending(_) => BlockNumber::Pending,
269            Self::Number(n) => BlockNumber::Number(n),
270        }
271    }
272}
273
274/// Gives access to the [revm::Database]
275pub struct Backend<N: Network> {
276    /// Access to [`revm::Database`] abstraction.
277    ///
278    /// This will be used in combination with [`alloy_evm::Evm`] and is responsible for feeding
279    /// data to the evm during its execution.
280    ///
281    /// At time of writing, there are two different types of `Db`:
282    ///   - [`MemDb`](crate::mem::in_memory_db::MemDb): everything is stored in memory
283    ///   - [`ForkDb`](crate::mem::fork_db::ForkedDatabase): forks off a remote client, missing
284    ///     data is retrieved via RPC-calls
285    ///
286    /// In order to commit changes to the [`revm::Database`], the [`alloy_evm::Evm`] requires
287    /// mutable access, which requires a write-lock from this `db`. In forking mode, the time
288    /// during which the write-lock is active depends on whether the `ForkDb` can provide all
289    /// requested data from memory or whether it has to retrieve it via RPC calls first. This
290    /// means that it potentially blocks for some time, even taking into account the rate
291    /// limits of RPC endpoints. Therefore the `Db` is guarded by a `tokio::sync::RwLock` here
292    /// so calls that need to read from it, while it's currently written to, don't block. E.g.
293    /// a new block is currently mined and a new [`Self::set_storage_at()`] request is being
294    /// executed.
295    db: Arc<AsyncRwLock<Box<dyn Db>>>,
296    /// stores all block related data in memory.
297    blockchain: Blockchain<N>,
298    /// Historic states of previous blocks.
299    states: Arc<RwLock<InMemoryBlockStates>>,
300    /// EVM environment data of the chain (block env, cfg env).
301    evm_env: Arc<RwLock<EvmEnv>>,
302    /// Network configuration (optimism, custom precompiles, etc.)
303    networks: NetworkConfigs,
304    /// The active hardfork.
305    hardfork: FoundryHardfork,
306    /// This is set if this is currently forked off another client.
307    fork: Arc<RwLock<Option<ClientFork>>>,
308    /// Provides time related info, like timestamp.
309    time: TimeManager,
310    /// Contains state of custom overrides.
311    cheats: CheatsManager,
312    /// Contains fee data.
313    fees: FeeManager,
314    /// Initialised genesis.
315    genesis: GenesisConfig,
316    /// Listeners for new blocks that get notified when a new block was imported or when logs were
317    /// removed from the canonical chain due to a reorg.
318    new_block_listeners: Arc<Mutex<Vec<UnboundedSender<ChainNotification>>>>,
319    /// Keeps track of active state snapshots at a specific block.
320    active_state_snapshots: Arc<Mutex<HashMap<U256, (u64, B256)>>>,
321    enable_steps_tracing: bool,
322    print_logs: bool,
323    print_traces: bool,
324    /// Recorder used for decoding traces, used together with print_traces
325    call_trace_decoder: Arc<CallTraceDecoder>,
326    /// How to keep history state
327    prune_state_history_config: PruneStateHistoryConfig,
328    /// max number of blocks with transactions in memory
329    transaction_block_keeper: Option<usize>,
330    pub(crate) node_config: Arc<AsyncRwLock<NodeConfig>>,
331    /// Slots in an epoch
332    slots_in_an_epoch: u64,
333    /// Precompiles to inject to the EVM.
334    precompile_factory: Option<Arc<dyn PrecompileFactory>>,
335    /// Prevent race conditions during mining
336    mining: Arc<tokio::sync::Mutex<()>>,
337    /// Disable pool balance checks
338    disable_pool_balance_checks: bool,
339}
340
341impl<N: Network> Clone for Backend<N> {
342    fn clone(&self) -> Self {
343        Self {
344            db: self.db.clone(),
345            blockchain: self.blockchain.clone(),
346            states: self.states.clone(),
347            evm_env: self.evm_env.clone(),
348            networks: self.networks,
349            hardfork: self.hardfork,
350            fork: self.fork.clone(),
351            time: self.time.clone(),
352            cheats: self.cheats.clone(),
353            fees: self.fees.clone(),
354            genesis: self.genesis.clone(),
355            new_block_listeners: self.new_block_listeners.clone(),
356            active_state_snapshots: self.active_state_snapshots.clone(),
357            enable_steps_tracing: self.enable_steps_tracing,
358            print_logs: self.print_logs,
359            print_traces: self.print_traces,
360            call_trace_decoder: self.call_trace_decoder.clone(),
361            prune_state_history_config: self.prune_state_history_config,
362            transaction_block_keeper: self.transaction_block_keeper,
363            node_config: self.node_config.clone(),
364            slots_in_an_epoch: self.slots_in_an_epoch,
365            precompile_factory: self.precompile_factory.clone(),
366            mining: self.mining.clone(),
367            disable_pool_balance_checks: self.disable_pool_balance_checks,
368        }
369    }
370}
371
372impl<N: Network> fmt::Debug for Backend<N> {
373    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374        f.debug_struct("Backend").finish_non_exhaustive()
375    }
376}
377
378// Methods that are generic over any Network.
379impl<N: Network> Backend<N> {
380    /// Sets the account to impersonate
381    ///
382    /// Returns `true` if the account is already impersonated
383    pub fn impersonate(&self, addr: Address) -> bool {
384        if self.cheats.impersonated_accounts().contains(&addr) {
385            return true;
386        }
387        // Ensure EIP-3607 is disabled
388        self.evm_env.write().cfg_env.disable_eip3607 = true;
389        self.cheats.impersonate(addr)
390    }
391
392    /// Removes the account that from the impersonated set
393    ///
394    /// If the impersonated `addr` is a contract then we also reset the code here
395    pub fn stop_impersonating(&self, addr: Address) {
396        self.cheats.stop_impersonating(&addr);
397    }
398
399    /// If set to true will make every account impersonated
400    pub fn auto_impersonate_account(&self, enabled: bool) {
401        self.cheats.set_auto_impersonate_account(enabled);
402    }
403
404    /// Returns the configured fork, if any
405    pub fn get_fork(&self) -> Option<ClientFork> {
406        self.fork.read().clone()
407    }
408
409    /// Returns the database
410    pub fn get_db(&self) -> &Arc<AsyncRwLock<Box<dyn Db>>> {
411        &self.db
412    }
413
414    /// Returns the `AccountInfo` from the database
415    pub async fn get_account(&self, address: Address) -> DatabaseResult<AccountInfo> {
416        Ok(self.db.read().await.basic_ref(address)?.unwrap_or_default())
417    }
418
419    /// Whether we're forked off some remote client
420    pub fn is_fork(&self) -> bool {
421        self.fork.read().is_some()
422    }
423
424    /// Writes the CREATE2 deployer code directly to the database at the address provided.
425    pub async fn set_create2_deployer(&self, address: Address) -> DatabaseResult<()> {
426        self.set_code(address, Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE)).await?;
427        Ok(())
428    }
429
430    /// Updates memory limits that should be more strict when auto-mine is enabled
431    pub(crate) fn update_interval_mine_block_time(&self, block_time: Duration) {
432        self.states.write().update_interval_mine_block_time(block_time)
433    }
434
435    /// Returns the `TimeManager` responsible for timestamps
436    pub const fn time(&self) -> &TimeManager {
437        &self.time
438    }
439
440    /// Returns the `CheatsManager` responsible for executing cheatcodes
441    pub const fn cheats(&self) -> &CheatsManager {
442        &self.cheats
443    }
444
445    /// Whether to skip blob validation
446    pub fn skip_blob_validation(&self, impersonator: Option<Address>) -> bool {
447        self.cheats().auto_impersonate_accounts()
448            || impersonator
449                .is_some_and(|addr| self.cheats().impersonated_accounts().contains(&addr))
450    }
451
452    /// Returns the `FeeManager` that manages fee/pricings
453    pub const fn fees(&self) -> &FeeManager {
454        &self.fees
455    }
456
457    /// The EVM environment data of the blockchain
458    pub const fn evm_env(&self) -> &Arc<RwLock<EvmEnv>> {
459        &self.evm_env
460    }
461
462    /// Returns the current best hash of the chain
463    pub fn best_hash(&self) -> B256 {
464        self.blockchain.storage.read().best_hash
465    }
466
467    /// Returns the current best number of the chain
468    pub fn best_number(&self) -> u64 {
469        self.blockchain.storage.read().best_number
470    }
471
472    /// Sets the block number
473    pub fn set_block_number(&self, number: u64) {
474        self.evm_env.write().block_env.number = U256::from(number);
475    }
476
477    /// Returns the client coinbase address.
478    pub fn coinbase(&self) -> Address {
479        self.evm_env.read().block_env.beneficiary
480    }
481
482    /// Returns the client coinbase address.
483    pub fn chain_id(&self) -> U256 {
484        U256::from(self.evm_env.read().cfg_env.chain_id)
485    }
486
487    pub fn set_chain_id(&self, chain_id: u64) {
488        self.evm_env.write().cfg_env.chain_id = chain_id;
489    }
490
491    /// Returns the genesis data for the Beacon API.
492    pub const fn genesis_time(&self) -> u64 {
493        self.genesis.timestamp
494    }
495
496    /// Returns the configured genesis block number.
497    pub const fn genesis_number(&self) -> u64 {
498        self.genesis.number
499    }
500
501    /// Returns balance of the given account.
502    pub async fn current_balance(&self, address: Address) -> DatabaseResult<U256> {
503        Ok(self.get_account(address).await?.balance)
504    }
505
506    /// Returns balance of the given account.
507    pub async fn current_nonce(&self, address: Address) -> DatabaseResult<u64> {
508        Ok(self.get_account(address).await?.nonce)
509    }
510
511    /// Sets the coinbase address
512    pub fn set_coinbase(&self, address: Address) {
513        self.evm_env.write().block_env.beneficiary = address;
514    }
515
516    /// Sets the `prevrandao` value to use for the next mined block.
517    ///
518    /// This is a one-shot override that is consumed by the next block; afterwards anvil resumes its
519    /// default per-block `prevrandao` derivation.
520    pub fn set_next_block_prevrandao(&self, prevrandao: B256) {
521        self.cheats.set_next_block_prevrandao(prevrandao);
522    }
523
524    /// Sets the nonce of the given address
525    pub async fn set_nonce(&self, address: Address, nonce: U256) -> DatabaseResult<()> {
526        self.db.write().await.set_nonce(address, nonce.try_into().unwrap_or(u64::MAX))
527    }
528
529    /// Sets the balance of the given address
530    pub async fn set_balance(&self, address: Address, balance: U256) -> DatabaseResult<()> {
531        self.db.write().await.set_balance(address, balance)
532    }
533
534    /// Sets the code of the given address
535    pub async fn set_code(&self, address: Address, code: Bytes) -> DatabaseResult<()> {
536        self.db.write().await.set_code(address, code)
537    }
538
539    /// Sets the value for the given slot of the given address
540    pub async fn set_storage_at(
541        &self,
542        address: Address,
543        slot: U256,
544        val: B256,
545    ) -> DatabaseResult<()> {
546        self.db.write().await.set_storage_at(address, slot.into(), val)
547    }
548
549    /// Returns the configured specid
550    pub fn spec_id(&self) -> SpecId {
551        *self.evm_env.read().spec_id()
552    }
553
554    /// Returns true for post London
555    pub fn is_eip1559(&self) -> bool {
556        (self.spec_id() as u8) >= (SpecId::LONDON as u8)
557    }
558
559    /// Returns true for post Merge
560    pub fn is_eip3675(&self) -> bool {
561        (self.spec_id() as u8) >= (SpecId::MERGE as u8)
562    }
563
564    /// Returns true for post Berlin
565    pub fn is_eip2930(&self) -> bool {
566        (self.spec_id() as u8) >= (SpecId::BERLIN as u8)
567    }
568
569    /// Returns true for post Cancun
570    pub fn is_eip4844(&self) -> bool {
571        (self.spec_id() as u8) >= (SpecId::CANCUN as u8)
572    }
573
574    /// Returns true for post Prague
575    pub fn is_eip7702(&self) -> bool {
576        (self.spec_id() as u8) >= (SpecId::PRAGUE as u8)
577    }
578
579    /// Returns true if op-stack deposits are active
580    #[cfg(feature = "optimism")]
581    pub const fn is_optimism(&self) -> bool {
582        self.networks.is_optimism()
583    }
584
585    /// Returns true if op-stack deposits are active.
586    ///
587    /// Always `false` when built without the `optimism` feature.
588    #[cfg(not(feature = "optimism"))]
589    pub const fn is_optimism(&self) -> bool {
590        false
591    }
592
593    /// Returns true if Tempo network mode is active
594    pub const fn is_tempo(&self) -> bool {
595        self.networks.is_tempo()
596    }
597
598    /// Returns the active hardfork.
599    pub fn hardfork(&self) -> FoundryHardfork {
600        if let Some(hardfork) =
601            self.fork.read().as_ref().and_then(|fork| fork.config.read().hardfork)
602        {
603            return hardfork;
604        }
605        self.hardfork
606    }
607
608    /// Returns the active Tempo hardfork.
609    pub fn tempo_hardfork(&self) -> TempoHardfork {
610        TempoHardfork::from(self.hardfork())
611    }
612
613    /// Returns whether a Tempo hardfork is active on this backend.
614    pub fn is_tempo_hardfork_active(&self, hardfork: TempoHardfork) -> bool {
615        self.is_tempo() && self.tempo_hardfork() >= hardfork
616    }
617
618    /// Returns the precompiles for the current spec.
619    pub fn precompiles(&self) -> BTreeMap<String, Address> {
620        let spec_id = self.spec_id();
621        let mut precompiles =
622            PrecompilesMap::from_static(Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)));
623        let (chain_id, timestamp) = {
624            let evm_env = self.evm_env.read();
625            (evm_env.cfg_env.chain_id, evm_env.block_env.timestamp.saturating_to())
626        };
627        self.networks.inject_chain_precompiles(&mut precompiles, chain_id, timestamp);
628
629        let mut precompiles_map = BTreeMap::<String, Address>::default();
630        for address in precompiles.addresses() {
631            let precompile = precompiles.get(address).expect("precompile address must resolve");
632            precompiles_map.insert(precompile.precompile_id().name().to_string(), *address);
633        }
634
635        // Extend with configured network precompiles.
636        precompiles_map
637            .extend(self.networks.precompiles(self.is_tempo().then(|| self.tempo_hardfork())));
638
639        if let Some(factory) = &self.precompile_factory {
640            for (address, precompile) in factory.precompiles() {
641                precompiles_map.insert(precompile.precompile_id().to_string(), address);
642            }
643        }
644
645        precompiles_map
646    }
647
648    /// Returns the system contracts for the current spec.
649    pub fn system_contracts(&self) -> BTreeMap<SystemContract, Address> {
650        let mut system_contracts = BTreeMap::<SystemContract, Address>::default();
651
652        let spec_id = self.spec_id();
653
654        if spec_id >= SpecId::CANCUN {
655            system_contracts.extend(SystemContract::cancun());
656        }
657
658        if spec_id >= SpecId::PRAGUE {
659            system_contracts.extend(SystemContract::prague(None));
660        }
661
662        system_contracts
663    }
664
665    /// Returns [`BlobParams`] corresponding to the current spec.
666    pub fn blob_params(&self) -> BlobParams {
667        get_blob_params_by_spec_id(self.spec_id())
668    }
669
670    /// Returns an error if EIP1559 is not active (pre Berlin)
671    pub fn ensure_eip1559_active(&self) -> Result<(), BlockchainError> {
672        if self.is_eip1559() {
673            return Ok(());
674        }
675        Err(BlockchainError::EIP1559TransactionUnsupportedAtHardfork)
676    }
677
678    /// Returns an error if EIP1559 is not active (pre muirGlacier)
679    pub fn ensure_eip2930_active(&self) -> Result<(), BlockchainError> {
680        if self.is_eip2930() {
681            return Ok(());
682        }
683        Err(BlockchainError::EIP2930TransactionUnsupportedAtHardfork)
684    }
685
686    pub fn ensure_eip4844_active(&self) -> Result<(), BlockchainError> {
687        if self.is_eip4844() {
688            return Ok(());
689        }
690        Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork)
691    }
692
693    pub fn ensure_eip7702_active(&self) -> Result<(), BlockchainError> {
694        if self.is_eip7702() {
695            return Ok(());
696        }
697        Err(BlockchainError::EIP7702TransactionUnsupportedAtHardfork)
698    }
699
700    /// Returns an error if op-stack deposits are not active
701    #[cfg(feature = "optimism")]
702    pub const fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> {
703        if self.is_optimism() {
704            return Ok(());
705        }
706        Err(BlockchainError::DepositTransactionUnsupported)
707    }
708
709    /// Returns an error if Tempo transactions are not active
710    pub const fn ensure_tempo_active(&self) -> Result<(), BlockchainError> {
711        if self.is_tempo() {
712            return Ok(());
713        }
714        Err(BlockchainError::TempoTransactionUnsupported)
715    }
716
717    /// Builds the [`InspectorTxConfig`] from the backend's current settings.
718    fn inspector_tx_config(&self) -> InspectorTxConfig {
719        InspectorTxConfig {
720            print_traces: self.print_traces,
721            print_logs: self.print_logs,
722            enable_steps_tracing: self.enable_steps_tracing,
723            call_trace_decoder: self.call_trace_decoder.clone(),
724        }
725    }
726
727    /// Builds the [`PoolTxGasConfig`] from the given EVM environment.
728    fn pool_tx_gas_config(&self, evm_env: &EvmEnv) -> PoolTxGasConfig {
729        let spec_id = *evm_env.spec_id();
730        let is_cancun = spec_id >= SpecId::CANCUN;
731        let blob_params = self.blob_params();
732        PoolTxGasConfig {
733            disable_block_gas_limit: evm_env.cfg_env.disable_block_gas_limit,
734            tx_gas_limit_cap: evm_env.cfg_env.tx_gas_limit_cap,
735            tx_gas_limit_cap_resolved: evm_env.cfg_env.tx_gas_limit_cap(),
736            max_blob_gas_per_block: blob_params.max_blob_gas_per_block(),
737            is_cancun,
738        }
739    }
740
741    /// Returns the block gas limit
742    pub fn gas_limit(&self) -> u64 {
743        self.evm_env.read().block_env.gas_limit
744    }
745
746    /// Sets the block gas limit
747    pub fn set_gas_limit(&self, gas_limit: u64) {
748        self.evm_env.write().block_env.gas_limit = gas_limit;
749    }
750
751    /// Returns the current base fee
752    pub fn base_fee(&self) -> u64 {
753        self.fees.base_fee()
754    }
755
756    /// Returns whether the minimum suggested priority fee is enforced
757    pub const fn is_min_priority_fee_enforced(&self) -> bool {
758        self.fees.is_min_priority_fee_enforced()
759    }
760
761    pub fn excess_blob_gas_and_price(&self) -> Option<BlobExcessGasAndPrice> {
762        self.fees.excess_blob_gas_and_price()
763    }
764
765    /// Sets the current basefee
766    pub fn set_base_fee(&self, basefee: u64) {
767        self.fees.set_base_fee(basefee)
768    }
769
770    /// Sets the gas price
771    pub fn set_gas_price(&self, price: u128) {
772        self.fees.set_gas_price(price)
773    }
774
775    pub fn elasticity(&self) -> f64 {
776        self.fees.elasticity()
777    }
778
779    /// Returns the total difficulty of the chain until this block
780    ///
781    /// Note: this will always be `0` in memory mode
782    /// In forking mode this will always be the total difficulty of the forked block
783    pub fn total_difficulty(&self) -> U256 {
784        self.blockchain.storage.read().total_difficulty
785    }
786
787    /// Creates a new `evm_snapshot` at the current height.
788    ///
789    /// Returns the id of the snapshot created.
790    pub async fn create_state_snapshot(&self) -> U256 {
791        let num = self.best_number();
792        let hash = self.best_hash();
793        let id = self.db.write().await.snapshot_state();
794        trace!(target: "backend", "creating snapshot {} at {}", id, num);
795        self.active_state_snapshots.lock().insert(id, (num, hash));
796        id
797    }
798
799    pub fn list_state_snapshots(&self) -> BTreeMap<U256, (u64, B256)> {
800        self.active_state_snapshots.lock().clone().into_iter().collect()
801    }
802
803    /// Returns the environment for the next block
804    fn next_evm_env(&self) -> EvmEnv {
805        let mut evm_env = self.evm_env.read().clone();
806        // increase block number for this block
807        evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1));
808        evm_env.block_env.basefee = self.base_fee();
809        evm_env.block_env.blob_excess_gas_and_price = self.excess_blob_gas_and_price();
810        evm_env.block_env.timestamp = U256::from(self.time.current_call_timestamp());
811        evm_env
812    }
813
814    /// Builds [`Inspector`] with the configured options.
815    fn build_inspector(&self) -> AnvilInspector {
816        let mut inspector = AnvilInspector::default();
817
818        if self.print_logs {
819            inspector = inspector.with_log_collector();
820        }
821        if self.print_traces {
822            inspector = inspector.with_trace_printer();
823        }
824
825        inspector
826    }
827
828    /// Builds an inspector configured for block mining (tracing always enabled).
829    fn build_mining_inspector(&self) -> AnvilInspector {
830        let mut inspector = AnvilInspector::default().with_tracing();
831        if self.enable_steps_tracing {
832            inspector = inspector.with_steps_tracing();
833        }
834        if self.print_logs {
835            inspector = inspector.with_log_collector();
836        }
837        if self.print_traces {
838            inspector = inspector.with_trace_printer();
839        }
840        inspector
841    }
842
843    /// Returns a new block event stream that yields Notifications when a new block was added or
844    /// when logs were removed from the canonical chain due to a reorg
845    pub fn new_block_notifications(&self) -> ChainNotifications {
846        let (tx, rx) = unbounded();
847        self.new_block_listeners.lock().push(tx);
848        trace!(target: "backed", "added new block listener");
849        rx
850    }
851
852    /// Returns the number of new-block listeners. Closed listeners are pruned lazily on the next
853    /// new block notification.
854    pub fn new_block_listeners_count(&self) -> usize {
855        self.new_block_listeners.lock().len()
856    }
857
858    /// Notifies all `new_block_listeners` about the new block
859    fn notify_on_new_block(&self, header: Header, hash: B256) {
860        // cleanup closed notification streams first, if the channel is closed we can remove the
861        // sender half for the set
862        self.new_block_listeners.lock().retain(|tx| !tx.is_closed());
863
864        let notification =
865            ChainNotification::Block(NewBlockNotification { hash, header: Arc::new(header) });
866
867        self.new_block_listeners
868            .lock()
869            .retain(|tx| tx.unbounded_send(notification.clone()).is_ok());
870    }
871
872    /// Notifies all `new_block_listeners` about the logs that were removed from the canonical
873    /// chain due to a reorg.
874    fn notify_on_removed_logs(&self, logs: Vec<Log>) {
875        // cleanup closed notification streams first, if the channel is closed we can remove the
876        // sender half for the set
877        self.new_block_listeners.lock().retain(|tx| !tx.is_closed());
878
879        let notification = ChainNotification::RemovedLogs(Arc::new(logs));
880
881        self.new_block_listeners
882            .lock()
883            .retain(|tx| tx.unbounded_send(notification.clone()).is_ok());
884    }
885
886    /// Returns the block number for the given block id
887    pub fn convert_block_number(&self, block: Option<BlockNumber>) -> u64 {
888        let current = self.best_number();
889        match block.unwrap_or(BlockNumber::Latest) {
890            BlockNumber::Latest | BlockNumber::Pending => current,
891            BlockNumber::Earliest => 0,
892            BlockNumber::Number(num) => num,
893            BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch),
894            BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2),
895        }
896    }
897
898    /// Returns the block and its hash for the given id
899    fn get_block_with_hash(&self, id: impl Into<BlockId>) -> Option<(Block, B256)> {
900        let hash = self.blockchain.hash(id.into(), self.slots_in_an_epoch)?;
901        let block = self.get_block_by_hash(hash)?;
902        Some((block, hash))
903    }
904
905    pub fn get_block(&self, id: impl Into<BlockId>) -> Option<Block> {
906        self.get_block_with_hash(id).map(|(block, _)| block)
907    }
908
909    pub fn get_block_by_hash(&self, hash: B256) -> Option<Block> {
910        self.blockchain.get_block_by_hash(&hash)
911    }
912
913    /// Returns the traces for the given transaction
914    pub(crate) fn mined_parity_trace_transaction(
915        &self,
916        hash: B256,
917    ) -> Option<Vec<LocalizedTransactionTrace>> {
918        self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.parity_traces())
919    }
920
921    /// Returns the traces for the given block
922    pub(crate) fn mined_parity_trace_block(
923        &self,
924        block: u64,
925    ) -> Option<Vec<LocalizedTransactionTrace>> {
926        let block = self.get_block(block)?;
927        let mut traces = vec![];
928        let storage = self.blockchain.storage.read();
929        for tx in block.body.transactions {
930            if let Some(mined_tx) = storage.transactions.get(&tx.hash()) {
931                traces.extend(mined_tx.parity_traces());
932            }
933        }
934        Some(traces)
935    }
936
937    /// Returns the mined transaction for the given hash
938    pub(crate) fn mined_transaction(&self, hash: B256) -> Option<MinedTransaction<N>> {
939        self.blockchain.storage.read().transactions.get(&hash).cloned()
940    }
941
942    /// Overrides the given signature to impersonate the specified address during ecrecover.
943    pub async fn impersonate_signature(
944        &self,
945        signature: Bytes,
946        address: Address,
947    ) -> Result<(), BlockchainError> {
948        self.cheats.add_recover_override(signature, address);
949        Ok(())
950    }
951
952    /// Returns code by its hash
953    pub async fn debug_code_by_hash(
954        &self,
955        code_hash: B256,
956        block_id: Option<BlockId>,
957    ) -> Result<Option<Bytes>, BlockchainError> {
958        if let Ok(code) = self.db.read().await.code_by_hash_ref(code_hash) {
959            return Ok(Some(code.original_bytes()));
960        }
961        if let Some(fork) = self.get_fork() {
962            return Ok(fork.debug_code_by_hash(code_hash, block_id).await?);
963        }
964
965        Ok(None)
966    }
967
968    /// Returns the value associated with a key from the database
969    /// Currently only supports bytecode lookups.
970    ///
971    /// Based on Reth implementation: <https://github.com/paradigmxyz/reth/blob/66cfa9ed1a8c4bc2424aacf6fb2c1e67a78ee9a2/crates/rpc/rpc/src/debug.rs#L1146-L1178>
972    ///
973    /// Key should be: 0x63 (1-byte prefix) + 32 bytes (code_hash)
974    /// Total key length must be 33 bytes.
975    pub async fn debug_db_get(&self, key: String) -> Result<Option<Bytes>, BlockchainError> {
976        let key_bytes = if key.starts_with("0x") {
977            hex::decode(&key)
978                .map_err(|_| BlockchainError::Message("Invalid hex key".to_string()))?
979        } else {
980            key.into_bytes()
981        };
982
983        // Validate key length: must be 33 bytes (1 byte prefix + 32 bytes code hash)
984        if key_bytes.len() != 33 {
985            return Err(BlockchainError::Message(format!(
986                "Invalid key length: expected 33 bytes, got {}",
987                key_bytes.len()
988            )));
989        }
990
991        // Check for bytecode prefix (0x63 = 'c' in ASCII)
992        if key_bytes[0] != 0x63 {
993            return Err(BlockchainError::Message(
994                "Key prefix must be 0x63 for code hash lookups".to_string(),
995            ));
996        }
997
998        let code_hash = B256::from_slice(&key_bytes[1..33]);
999
1000        // Use the existing debug_code_by_hash method to retrieve the bytecode
1001        self.debug_code_by_hash(code_hash, None).await
1002    }
1003
1004    fn mined_block_by_hash(&self, hash: B256) -> Option<AnyRpcBlock> {
1005        let block = self.blockchain.get_block_by_hash(&hash)?;
1006        Some(self.convert_block_with_hash(block, Some(hash)))
1007    }
1008
1009    pub(crate) async fn mined_transactions_by_block_number(
1010        &self,
1011        number: BlockNumber,
1012    ) -> Option<Vec<AnyRpcTransaction>> {
1013        if let Some(block) = self.get_block(number) {
1014            return self.mined_transactions_in_block(&block);
1015        }
1016        None
1017    }
1018
1019    /// Returns all transactions given a block
1020    pub(crate) fn mined_transactions_in_block(
1021        &self,
1022        block: &Block,
1023    ) -> Option<Vec<AnyRpcTransaction>> {
1024        let mut transactions = Vec::with_capacity(block.body.transactions.len());
1025        let base_fee = block.header.base_fee_per_gas();
1026        let storage = self.blockchain.storage.read();
1027        for hash in block.body.transactions.iter().map(|tx| tx.hash()) {
1028            let info = storage.transactions.get(&hash)?.info.clone();
1029            let tx = block.body.transactions.get(info.transaction_index as usize)?.clone();
1030
1031            let tx = transaction_build(Some(hash), tx, Some(block), Some(info), base_fee);
1032            transactions.push(tx);
1033        }
1034        Some(transactions)
1035    }
1036
1037    pub fn mined_block_by_number(&self, number: BlockNumber) -> Option<AnyRpcBlock> {
1038        let (block, hash) = self.get_block_with_hash(number)?;
1039        let mut block = self.convert_block_with_hash(block, Some(hash));
1040        block.transactions.convert_to_hashes();
1041        Some(block)
1042    }
1043
1044    pub fn get_full_block(&self, id: impl Into<BlockId>) -> Option<AnyRpcBlock> {
1045        let (block, hash) = self.get_block_with_hash(id)?;
1046        let transactions = self.mined_transactions_in_block(&block)?;
1047        let mut block = self.convert_block_with_hash(block, Some(hash));
1048        block.inner.transactions = BlockTransactions::Full(transactions);
1049        Some(block)
1050    }
1051
1052    /// Takes a block as it's stored internally and returns the eth api conform block format.
1053    pub fn convert_block(&self, block: Block) -> AnyRpcBlock {
1054        self.convert_block_with_hash(block, None)
1055    }
1056
1057    /// Takes a block as it's stored internally and returns the eth api conform block format.
1058    /// If `known_hash` is provided, it will be used instead of computing `hash_slow()`.
1059    pub fn convert_block_with_hash(&self, block: Block, known_hash: Option<B256>) -> AnyRpcBlock {
1060        let size = U256::from(alloy_rlp::encode(canonical_block(block.clone())).len() as u32);
1061
1062        let header = block.header.clone();
1063        let transactions = block.body.transactions;
1064
1065        let hash = known_hash.unwrap_or_else(|| header.hash_slow());
1066        let number = header.number();
1067        let withdrawals_root = header.withdrawals_root();
1068        let tempo_fields = header
1069            .as_tempo()
1070            .map(|header| {
1071                (
1072                    header.timestamp_millis(),
1073                    header.general_gas_limit,
1074                    header.shared_gas_limit,
1075                    header.timestamp_millis_part,
1076                )
1077            })
1078            .or_else(|| {
1079                self.is_tempo()
1080                    .then(|| (header.timestamp().saturating_mul(1000), header.gas_limit(), 0, 0))
1081            });
1082
1083        let block = AlloyBlock {
1084            header: AlloyHeader {
1085                inner: AnyHeader::from(header.into_inner()),
1086                hash,
1087                total_difficulty: Some(self.total_difficulty()),
1088                size: Some(size),
1089            },
1090            transactions: alloy_rpc_types::BlockTransactions::Hashes(
1091                transactions.into_iter().map(|tx| tx.hash()).collect(),
1092            ),
1093            uncles: vec![],
1094            withdrawals: withdrawals_root.map(|_| Default::default()),
1095        };
1096
1097        let mut block = WithOtherFields::new(block);
1098
1099        // If Arbitrum, apply chain specifics to converted block.
1100        if is_arbitrum(self.chain_id().to::<u64>()) {
1101            // Set `l1BlockNumber` field.
1102            block.other.insert("l1BlockNumber".to_string(), number.into());
1103        }
1104
1105        if let Some((
1106            timestamp_millis,
1107            general_gas_limit,
1108            shared_gas_limit,
1109            timestamp_millis_part,
1110        )) = tempo_fields
1111        {
1112            block.other.insert(
1113                "timestampMillis".to_string(),
1114                serde_json::Value::String(format!("0x{timestamp_millis:x}")),
1115            );
1116            block.other.insert(
1117                "mainBlockGeneralGasLimit".to_string(),
1118                serde_json::Value::String(format!("0x{general_gas_limit:x}")),
1119            );
1120            block.other.insert(
1121                "sharedGasLimit".to_string(),
1122                serde_json::Value::String(format!("0x{shared_gas_limit:x}")),
1123            );
1124            block.other.insert(
1125                "timestampMillisPart".to_string(),
1126                serde_json::Value::String(format!("0x{timestamp_millis_part:x}")),
1127            );
1128        }
1129
1130        AnyRpcBlock::from(block)
1131    }
1132
1133    pub async fn block_by_hash(&self, hash: B256) -> Result<Option<AnyRpcBlock>, BlockchainError> {
1134        trace!(target: "backend", "get block by hash {:?}", hash);
1135        if let tx @ Some(_) = self.mined_block_by_hash(hash) {
1136            return Ok(tx);
1137        }
1138
1139        if let Some(fork) = self.get_fork() {
1140            return Ok(fork.block_by_hash(hash).await?);
1141        }
1142
1143        Ok(None)
1144    }
1145
1146    pub async fn block_by_hash_full(
1147        &self,
1148        hash: B256,
1149    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
1150        trace!(target: "backend", "get block by hash {:?}", hash);
1151        if let tx @ Some(_) = self.get_full_block(hash) {
1152            return Ok(tx);
1153        }
1154
1155        if let Some(fork) = self.get_fork() {
1156            return Ok(fork.block_by_hash_full(hash).await?);
1157        }
1158
1159        Ok(None)
1160    }
1161
1162    pub async fn block_by_number(
1163        &self,
1164        number: BlockNumber,
1165    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
1166        trace!(target: "backend", "get block by number {:?}", number);
1167        if let tx @ Some(_) = self.mined_block_by_number(number) {
1168            return Ok(tx);
1169        }
1170
1171        if let Some(fork) = self.get_fork() {
1172            let number = self.convert_block_number(Some(number));
1173            if fork.predates_fork_inclusive(number) {
1174                return Ok(fork.block_by_number(number).await?);
1175            }
1176        }
1177
1178        Ok(None)
1179    }
1180
1181    pub async fn block_by_number_full(
1182        &self,
1183        number: BlockNumber,
1184    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
1185        trace!(target: "backend", "get block by number {:?}", number);
1186        if let tx @ Some(_) = self.get_full_block(number) {
1187            return Ok(tx);
1188        }
1189
1190        if let Some(fork) = self.get_fork() {
1191            let number = self.convert_block_number(Some(number));
1192            if fork.predates_fork_inclusive(number) {
1193                return Ok(fork.block_by_number_full(number).await?);
1194            }
1195        }
1196
1197        Ok(None)
1198    }
1199
1200    /// Converts the `BlockNumber` into a numeric value
1201    ///
1202    /// # Errors
1203    ///
1204    /// returns an error if the requested number is larger than the current height
1205    pub async fn ensure_block_number<T: Into<BlockId>>(
1206        &self,
1207        block_id: Option<T>,
1208    ) -> Result<u64, BlockchainError> {
1209        let current = self.best_number();
1210        let requested =
1211            match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) {
1212                BlockId::Hash(hash) => {
1213                    self.block_by_hash(hash.block_hash)
1214                        .await?
1215                        .ok_or(BlockchainError::BlockNotFound)?
1216                        .header
1217                        .number
1218                }
1219                BlockId::Number(num) => match num {
1220                    BlockNumber::Latest | BlockNumber::Pending => current,
1221                    BlockNumber::Earliest => U64::ZERO.to::<u64>(),
1222                    BlockNumber::Number(num) => num,
1223                    BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch),
1224                    BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2),
1225                },
1226            };
1227
1228        if requested > current {
1229            Err(BlockchainError::BlockOutOfRange(current, requested))
1230        } else {
1231            Ok(requested)
1232        }
1233    }
1234
1235    /// Injects all configured precompiles into the given precompile map.
1236    ///
1237    /// This applies four layers:
1238    /// 1. Network-specific precompiles (e.g. Tempo, OP)
1239    /// 2. Chain- and timestamp-specific precompiles
1240    /// 3. User-provided precompiles via [`PrecompileFactory`]
1241    /// 4. Cheatcode ecrecover overrides (if active)
1242    fn inject_precompiles(&self, precompiles: &mut PrecompilesMap, evm_env: &EvmEnv) {
1243        self.networks.inject_precompiles(precompiles);
1244        self.networks.inject_chain_precompiles(
1245            precompiles,
1246            evm_env.cfg_env.chain_id,
1247            evm_env.block_env.timestamp.saturating_to(),
1248        );
1249
1250        if let Some(factory) = &self.precompile_factory {
1251            factory.install(precompiles);
1252        }
1253
1254        let cheats = Arc::new(self.cheats.clone());
1255        if cheats.has_recover_overrides() {
1256            let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats));
1257            precompiles.apply_precompile(&EC_RECOVER, move |_| {
1258                Some(DynPrecompile::new_stateful(
1259                    cheat_ecrecover.precompile_id().clone(),
1260                    move |input| cheat_ecrecover.call(input),
1261                ))
1262            });
1263        }
1264    }
1265
1266    fn inject_arbitrum_precompile(&self, precompiles: &mut PrecompilesMap, evm_env: &EvmEnv) {
1267        let Some(block_number) = self.arbitrum_block_number(evm_env) else { return };
1268        precompiles.apply_precompile(&arbitrum::ARB_SYS_ADDRESS, move |_| {
1269            Some(arbitrum::arb_sys_precompile(block_number))
1270        });
1271    }
1272
1273    fn inject_tempo_precompiles<DB, I>(
1274        &self,
1275        evm: &mut tempo_evm::evm::TempoEvm<DB, I>,
1276        evm_env: &EvmEnv,
1277    ) where
1278        DB: Database,
1279        I: Inspector<TempoContext<DB>>,
1280    {
1281        self.inject_precompiles(evm.precompiles_mut(), evm_env);
1282        // Re-extend Tempo precompiles, preserving shared non-creditable slots.
1283        let cfg = evm.ctx().cfg.clone();
1284        let non_creditable_slots = evm.non_creditable_slots();
1285        extend_tempo_precompiles(
1286            evm.precompiles_mut(),
1287            &cfg,
1288            StorageActions::disabled(),
1289            non_creditable_slots,
1290        );
1291    }
1292
1293    /// Creates a concrete EVM, injects precompiles, transacts, and returns the result mapped
1294    /// to [`HaltReason`] so all call sites share a single halt-reason type.
1295    fn transact_with_inspector_ref<'db, I, DB>(
1296        &self,
1297        db: &'db DB,
1298        evm_env: &EvmEnv,
1299        inspector: &mut I,
1300        tx_env: TxEnv,
1301        op_deposit: OpCallDepositInfo,
1302    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
1303    where
1304        DB: DatabaseRef + ?Sized,
1305        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
1306        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
1307    {
1308        #[cfg(feature = "optimism")]
1309        if self.is_optimism() {
1310            let op_tx = OpTransaction { base: tx_env, deposit: op_deposit, ..Default::default() };
1311            return self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx);
1312        }
1313        // `op_deposit` only matters on the OP path; eth/tempo ignore it.
1314        let _ = op_deposit;
1315        if self.is_tempo() {
1316            self.transact_tempo_with_inspector_ref(db, evm_env, inspector, TempoTxEnv::from(tx_env))
1317        } else {
1318            self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)
1319        }
1320    }
1321
1322    /// Eth path of [`Backend::transact_with_inspector_ref`].
1323    ///
1324    /// Creates an Ethereum EVM, injects precompiles, and transacts with a
1325    /// plain [`TxEnv`].
1326    fn transact_eth_with_inspector_ref<'db, I, DB>(
1327        &self,
1328        db: &'db DB,
1329        evm_env: &EvmEnv,
1330        inspector: &mut I,
1331        tx_env: TxEnv,
1332    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
1333    where
1334        DB: DatabaseRef + ?Sized,
1335        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
1336        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
1337    {
1338        let mut evm = EthEvmFactory::default().create_evm_with_inspector(
1339            WrapDatabaseRef(db),
1340            evm_env.clone(),
1341            inspector,
1342        );
1343        self.inject_precompiles(evm.precompiles_mut(), evm_env);
1344        self.inject_arbitrum_precompile(evm.precompiles_mut(), evm_env);
1345        Ok(evm.transact(tx_env)?)
1346    }
1347
1348    /// Builds the appropriate tx env from a [`FoundryTxEnvelope`], executes via the correct
1349    /// EVM backend (Op/Tempo/Eth), and returns both the result and the base [`TxEnv`].
1350    fn transact_envelope_with_inspector_ref<'db, I, DB>(
1351        &self,
1352        db: &'db DB,
1353        evm_env: &EvmEnv,
1354        inspector: &mut I,
1355        tx: &FoundryTxEnvelope,
1356        sender: Address,
1357    ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
1358    where
1359        DB: DatabaseRef + ?Sized,
1360        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
1361        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
1362    {
1363        if tx.is_tempo() {
1364            let tx_env: TempoTxEnv =
1365                FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
1366            let base = tx_env.inner.clone();
1367            let result = self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)?;
1368            return Ok((result, base));
1369        }
1370        #[cfg(feature = "optimism")]
1371        if self.is_optimism() {
1372            let op_tx: OpTransaction<TxEnv> =
1373                FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
1374            let base = op_tx.base.clone();
1375            let result = self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx)?;
1376            return Ok((result, base));
1377        }
1378        let tx_env: TxEnv =
1379            FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
1380        let base = tx_env.clone();
1381        let result = self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?;
1382        Ok((result, base))
1383    }
1384
1385    /// Builds the Tempo [`EvmEnv`] (spec, gas params, [`TempoBlockEnv`]) from a base
1386    /// env.
1387    fn build_tempo_evm_env(&self, evm_env: &EvmEnv) -> EvmEnvFor<TempoEvmNetwork> {
1388        let hardfork = self.tempo_hardfork();
1389        EvmEnv::new(
1390            evm_env.cfg_env.clone().with_spec_and_gas_params(hardfork, tempo_gas_params(hardfork)),
1391            TempoBlockEnv {
1392                inner: evm_env.block_env.clone(),
1393                timestamp_millis_part: 0,
1394                ..Default::default()
1395            },
1396        )
1397    }
1398
1399    /// Creates a Tempo EVM, injects precompiles, and transacts with a native [`TempoTxEnv`].
1400    fn transact_tempo_with_inspector_ref<'db, I, DB>(
1401        &self,
1402        db: &'db DB,
1403        evm_env: &EvmEnv,
1404        inspector: &mut I,
1405        tx_env: TempoTxEnv,
1406    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
1407    where
1408        DB: DatabaseRef + ?Sized,
1409        I: Inspector<TempoContext<WrapDatabaseRef<&'db DB>>>,
1410        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
1411    {
1412        let tempo_env = self.build_tempo_evm_env(evm_env);
1413        let mut evm = TempoEvmFactory::default().create_evm_with_inspector(
1414            WrapDatabaseRef(db),
1415            tempo_env,
1416            inspector,
1417        );
1418        self.inject_tempo_precompiles(&mut evm, evm_env);
1419        let result = evm.transact(tx_env)?;
1420        Ok(ResultAndState {
1421            result: result.result.map_haltreason(|h| match h {
1422                TempoHaltReason::Ethereum(eth) => eth,
1423                _ => HaltReason::PrecompileError,
1424            }),
1425            state: result.state,
1426        })
1427    }
1428
1429    /// Creates a concrete EVM + [`AnvilBlockExecutor`], runs pre-execution changes, and
1430    /// executes pool transactions. Returns the execution results and drops the EVM.
1431    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
1432    fn execute_with_block_executor<DB>(
1433        &self,
1434        db: DB,
1435        evm_env: &EvmEnv,
1436        parent_hash: B256,
1437        spec_id: SpecId,
1438        pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
1439        gas_config: &PoolTxGasConfig,
1440        inspector_tx_config: &InspectorTxConfig,
1441        validator: &dyn Fn(
1442            &PendingTransaction<FoundryTxEnvelope>,
1443            &AccountInfo,
1444        ) -> Result<(), InvalidTransactionError>,
1445    ) -> (ExecutedPoolTransactions<FoundryTxEnvelope>, BlockExecutionResult<FoundryReceiptEnvelope>)
1446    where
1447        DB: StateDB<Error = DatabaseError>,
1448    {
1449        let inspector = self.build_mining_inspector();
1450
1451        macro_rules! run {
1452            ($evm:expr) => {{
1453                self.inject_precompiles($evm.precompiles_mut(), evm_env);
1454                self.inject_arbitrum_precompile($evm.precompiles_mut(), evm_env);
1455                let mut executor = AnvilBlockExecutor::new($evm, parent_hash, spec_id);
1456                executor.apply_pre_execution_changes().expect("pre-execution changes failed");
1457                let pool_result = execute_pool_transactions(
1458                    &mut executor,
1459                    pool_transactions,
1460                    gas_config,
1461                    inspector_tx_config,
1462                    self.cheats(),
1463                    validator,
1464                );
1465                let (evm, block_result) = executor.finish().expect("executor finish failed");
1466                drop(evm);
1467                (pool_result, block_result)
1468            }};
1469        }
1470
1471        #[cfg(feature = "optimism")]
1472        if self.is_optimism() {
1473            let op_env = EvmEnv::new(
1474                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(self.hardfork.into()),
1475                evm_env.block_env.clone(),
1476            );
1477            let mut evm =
1478                OpEvmFactory::<OpTx>::default().create_evm_with_inspector(db, op_env, inspector);
1479            return run!(evm);
1480        }
1481
1482        if self.is_tempo() {
1483            let tempo_env = self.build_tempo_evm_env(evm_env);
1484            let mut evm =
1485                TempoEvmFactory::default().create_evm_with_inspector(db, tempo_env, inspector);
1486            run!(evm)
1487        } else {
1488            let mut evm =
1489                EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
1490            run!(evm)
1491        }
1492    }
1493
1494    /// ## EVM settings
1495    ///
1496    /// 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>:
1497    ///
1498    ///  - `disable_eip3607` is set to `true`
1499    ///  - `disable_base_fee` is set to `true`
1500    ///  - `tx_gas_limit_cap` is set to `Some(u64::MAX)` indicating no gas limit cap
1501    ///  - `nonce` check is skipped
1502    fn build_call_env(
1503        &self,
1504        request: WithOtherFields<TransactionRequest>,
1505        fee_details: FeeDetails,
1506        block_env: BlockEnv,
1507    ) -> (EvmEnv, TxEnv, OpCallDepositInfo) {
1508        let tx_type = request.minimal_tx_type() as u8;
1509
1510        let WithOtherFields::<TransactionRequest> {
1511            inner:
1512                TransactionRequest {
1513                    from,
1514                    to,
1515                    gas,
1516                    value,
1517                    input,
1518                    access_list,
1519                    blob_versioned_hashes,
1520                    authorization_list,
1521                    nonce,
1522                    sidecar: _,
1523                    chain_id,
1524                    .. // Rest of the gas fees related fields are taken from `fee_details`
1525                },
1526            other,
1527        } = request;
1528
1529        let FeeDetails {
1530            gas_price,
1531            max_fee_per_gas,
1532            max_priority_fee_per_gas,
1533            max_fee_per_blob_gas,
1534        } = fee_details;
1535
1536        let gas_limit = gas.unwrap_or(block_env.gas_limit);
1537        let mut evm_env = self.evm_env.read().clone();
1538        evm_env.block_env = block_env;
1539        // we want to disable this in eth_call, since this is common practice used by other node
1540        // impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
1541        evm_env.cfg_env.disable_block_gas_limit = true;
1542        evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX);
1543
1544        // The basefee should be ignored for calls against state for
1545        // - eth_call
1546        // - eth_estimateGas
1547        // - eth_createAccessList
1548        // - tracing
1549        evm_env.cfg_env.disable_base_fee = true;
1550
1551        // Disable nonce check in revm
1552        evm_env.cfg_env.disable_nonce_check = true;
1553
1554        let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| {
1555            self.fees().raw_gas_price().saturating_add(MIN_SUGGESTED_PRIORITY_FEE)
1556        });
1557        let caller = from.unwrap_or_default();
1558        let to = to.as_ref().and_then(TxKind::to);
1559        let blob_hashes = blob_versioned_hashes.unwrap_or_default();
1560        let mut tx_env = TxEnv {
1561            caller,
1562            gas_limit,
1563            gas_price,
1564            gas_priority_fee: max_priority_fee_per_gas,
1565            max_fee_per_blob_gas: max_fee_per_blob_gas
1566                .or_else(|| {
1567                    if blob_hashes.is_empty() { Some(0) } else { evm_env.block_env.blob_gasprice() }
1568                })
1569                .unwrap_or_default(),
1570            kind: match to {
1571                Some(addr) => TxKind::Call(*addr),
1572                None => TxKind::Create,
1573            },
1574            tx_type,
1575            value: value.unwrap_or_default(),
1576            data: input.into_input().unwrap_or_default(),
1577            chain_id: Some(chain_id.unwrap_or(self.chain_id().to::<u64>())),
1578            access_list: access_list.unwrap_or_default(),
1579            blob_hashes,
1580            ..Default::default()
1581        };
1582        tx_env.set_signed_authorization(authorization_list.unwrap_or_default());
1583
1584        if let Some(nonce) = nonce {
1585            tx_env.nonce = nonce;
1586        }
1587
1588        if evm_env.block_env.basefee == 0 {
1589            // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee`
1590            // 0 is only possible if it's manually set
1591            evm_env.cfg_env.disable_base_fee = true;
1592        }
1593
1594        // Deposit transaction? (only valid when op-stack deposits are active)
1595        #[cfg(feature = "optimism")]
1596        let op_deposit = if self.ensure_op_deposits_active().is_ok()
1597            && let Ok(deposit) = get_deposit_tx_parts(&other)
1598        {
1599            deposit
1600        } else {
1601            OpCallDepositInfo::default()
1602        };
1603        #[cfg(not(feature = "optimism"))]
1604        let op_deposit = {
1605            // `other` carries OP-only deposit fields; consumed only when feature is enabled.
1606            let _ = &other;
1607            OpCallDepositInfo
1608        };
1609
1610        (evm_env, tx_env, op_deposit)
1611    }
1612
1613    pub fn call_with_state(
1614        &self,
1615        state: &dyn DatabaseRef,
1616        request: WithOtherFields<TransactionRequest>,
1617        fee_details: FeeDetails,
1618        block_env: BlockEnv,
1619    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
1620        let mut inspector = self.build_inspector();
1621
1622        // Extract Tempo-specific fields before `build_call_env` consumes `other`.
1623        let tempo_overrides = self.is_tempo().then(|| {
1624            let fee_token =
1625                request.other.get_deserialized::<Address>("feeToken").and_then(|r| r.ok());
1626            let nonce_key = request
1627                .other
1628                .get_deserialized::<U256>("nonceKey")
1629                .and_then(|r| r.ok())
1630                .unwrap_or_default();
1631            let valid_before = request
1632                .other
1633                .get_deserialized::<U256>("validBefore")
1634                .and_then(|r| r.ok())
1635                .map(|v| v.saturating_to::<u64>());
1636            let valid_after = request
1637                .other
1638                .get_deserialized::<U256>("validAfter")
1639                .and_then(|r| r.ok())
1640                .map(|v| v.saturating_to::<u64>());
1641            (fee_token, nonce_key, valid_before, valid_after)
1642        });
1643
1644        let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block_env);
1645
1646        let ResultAndState { result, state } =
1647            if let Some((fee_token, nonce_key, valid_before, valid_after)) = tempo_overrides {
1648                use tempo_primitives::transaction::Call;
1649
1650                let base = tx_env;
1651                let mut tempo_tx = TempoTxEnv::from(base.clone());
1652                tempo_tx.fee_token = fee_token;
1653
1654                if !nonce_key.is_zero() || valid_before.is_some() || valid_after.is_some() {
1655                    // For gas estimation we don't have a signed tx, so generate a
1656                    // unique hash for expiring-nonce replay protection.  The nonce
1657                    // manager needs a non-zero hash; the actual value doesn't matter
1658                    // because the state is discarded after estimation.
1659                    let estimation_hash = keccak256(base.data.as_ref());
1660                    // T1B+ uses `TempoTxEnv::unique_tx_identifier` (sender-scoped) as
1661                    // the expiring-nonce replay hash; pre-T1B uses `tx_hash`.
1662                    // Set both so the synthetic env works across hardforks.
1663                    tempo_tx.unique_tx_identifier = Some(estimation_hash);
1664                    tempo_tx.tempo_tx_env = Some(Box::new(TempoBatchCallEnv {
1665                        nonce_key,
1666                        valid_before,
1667                        valid_after,
1668                        aa_calls: vec![Call { to: base.kind, value: base.value, input: base.data }],
1669                        tx_hash: estimation_hash,
1670                        expiring_nonce_idx: Some(0),
1671                        ..Default::default()
1672                    }));
1673                }
1674                self.transact_tempo_with_inspector_ref(state, &evm_env, &mut inspector, tempo_tx)?
1675            } else {
1676                self.transact_with_inspector_ref(
1677                    state,
1678                    &evm_env,
1679                    &mut inspector,
1680                    tx_env,
1681                    op_deposit,
1682                )?
1683            };
1684
1685        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
1686        inspector.print_logs();
1687
1688        if self.print_traces {
1689            inspector.into_print_traces(self.call_trace_decoder.clone());
1690        }
1691
1692        Ok((exit_reason, out, gas_used as u128, state))
1693    }
1694
1695    pub fn build_access_list_with_state(
1696        &self,
1697        state: &dyn DatabaseRef,
1698        request: WithOtherFields<TransactionRequest>,
1699        fee_details: FeeDetails,
1700        block_env: BlockEnv,
1701    ) -> Result<(InstructionResult, Option<Output>, u64, AccessList), BlockchainError> {
1702        let mut inspector =
1703            AccessListInspector::new(request.access_list.clone().unwrap_or_default());
1704
1705        let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block_env);
1706        let ResultAndState { result, state: _ } =
1707            self.transact_with_inspector_ref(state, &evm_env, &mut inspector, tx_env, op_deposit)?;
1708        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
1709        let access_list = inspector.access_list();
1710        Ok((exit_reason, out, gas_used, access_list))
1711    }
1712
1713    fn arbitrum_block_number(&self, evm_env: &EvmEnv) -> Option<u64> {
1714        if !arbitrum::is_arbitrum_chain(evm_env.cfg_env.chain_id) {
1715            return None;
1716        }
1717
1718        let env_block = evm_env.block_env.number.saturating_to();
1719        Some(self.get_fork().map_or(env_block, |fork| fork.block_number().max(env_block)))
1720    }
1721
1722    pub fn get_code_with_state(
1723        &self,
1724        state: &dyn DatabaseRef,
1725        address: Address,
1726    ) -> Result<Bytes, BlockchainError> {
1727        trace!(target: "backend", "get code for {:?}", address);
1728        let account = state.basic_ref(address)?.unwrap_or_default();
1729        if account.code_hash == KECCAK_EMPTY {
1730            // if the code hash is `KECCAK_EMPTY`, we check no further
1731            return Ok(Default::default());
1732        }
1733        let code = if let Some(code) = account.code {
1734            code
1735        } else {
1736            state.code_by_hash_ref(account.code_hash)?
1737        };
1738        Ok(code.bytes()[..code.len()].to_vec().into())
1739    }
1740
1741    pub fn get_balance_with_state<D>(
1742        &self,
1743        state: D,
1744        address: Address,
1745    ) -> Result<U256, BlockchainError>
1746    where
1747        D: DatabaseRef,
1748    {
1749        trace!(target: "backend", "get balance for {:?}", address);
1750        Ok(state.basic_ref(address)?.unwrap_or_default().balance)
1751    }
1752
1753    pub async fn transaction_by_block_number_and_index(
1754        &self,
1755        number: BlockNumber,
1756        index: Index,
1757    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
1758        if let Some(block) = self.mined_block_by_number(number) {
1759            return Ok(self.mined_transaction_by_block_hash_and_index(block.header.hash, index));
1760        }
1761
1762        if let Some(fork) = self.get_fork() {
1763            let number = self.convert_block_number(Some(number));
1764            if fork.predates_fork(number) {
1765                return Ok(fork
1766                    .transaction_by_block_number_and_index(number, index.into())
1767                    .await?);
1768            }
1769        }
1770
1771        Ok(None)
1772    }
1773
1774    pub async fn transaction_by_block_hash_and_index(
1775        &self,
1776        hash: B256,
1777        index: Index,
1778    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
1779        if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) {
1780            return Ok(tx);
1781        }
1782
1783        if let Some(fork) = self.get_fork() {
1784            return Ok(fork.transaction_by_block_hash_and_index(hash, index.into()).await?);
1785        }
1786
1787        Ok(None)
1788    }
1789
1790    pub fn mined_transaction_by_block_hash_and_index(
1791        &self,
1792        block_hash: B256,
1793        index: Index,
1794    ) -> Option<AnyRpcTransaction> {
1795        let (info, block, tx) = {
1796            let storage = self.blockchain.storage.read();
1797            let block = storage.blocks.get(&block_hash).cloned()?;
1798            let index: usize = index.into();
1799            let tx = block.body.transactions.get(index)?.clone();
1800            let info = storage.transactions.get(&tx.hash())?.info.clone();
1801            (info, block, tx)
1802        };
1803
1804        Some(transaction_build(
1805            Some(info.transaction_hash),
1806            tx,
1807            Some(&block),
1808            Some(info),
1809            block.header.base_fee_per_gas(),
1810        ))
1811    }
1812
1813    pub async fn transaction_by_hash(
1814        &self,
1815        hash: B256,
1816    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
1817        trace!(target: "backend", "transaction_by_hash={:?}", hash);
1818        if let tx @ Some(_) = self.mined_transaction_by_hash(hash) {
1819            return Ok(tx);
1820        }
1821
1822        if let Some(fork) = self.get_fork() {
1823            return fork
1824                .transaction_by_hash(hash)
1825                .await
1826                .map_err(BlockchainError::AlloyForkProvider);
1827        }
1828
1829        Ok(None)
1830    }
1831
1832    pub fn mined_transaction_by_hash(&self, hash: B256) -> Option<AnyRpcTransaction> {
1833        let (info, block) = {
1834            let storage = self.blockchain.storage.read();
1835            let MinedTransaction { info, block_hash, .. } =
1836                storage.transactions.get(&hash)?.clone();
1837            let block = storage.blocks.get(&block_hash).cloned()?;
1838            (info, block)
1839        };
1840        let tx = block.body.transactions.get(info.transaction_index as usize)?.clone();
1841
1842        Some(transaction_build(
1843            Some(info.transaction_hash),
1844            tx,
1845            Some(&block),
1846            Some(info),
1847            block.header.base_fee_per_gas(),
1848        ))
1849    }
1850
1851    /// Returns the traces for the given transaction
1852    pub async fn trace_transaction(
1853        &self,
1854        hash: B256,
1855    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
1856        if let Some(traces) = self.mined_parity_trace_transaction(hash) {
1857            return Ok(traces);
1858        }
1859
1860        if let Some(fork) = self.get_fork() {
1861            return Ok(fork.trace_transaction(hash).await?);
1862        }
1863
1864        Ok(vec![])
1865    }
1866
1867    /// Returns a transaction trace at a given index.
1868    pub async fn trace_get(
1869        &self,
1870        hash: B256,
1871        indices: Vec<Index>,
1872    ) -> Result<Option<LocalizedTransactionTrace>, BlockchainError> {
1873        if indices.len() != 1 {
1874            return Ok(None);
1875        }
1876
1877        let index: usize = indices[0].into();
1878        if let Some(traces) = self.mined_parity_trace_transaction(hash) {
1879            return Ok(traces.into_iter().nth(index));
1880        }
1881
1882        if let Some(fork) = self.get_fork() {
1883            return Ok(fork.trace_get(hash, indices).await?);
1884        }
1885
1886        Ok(None)
1887    }
1888
1889    /// Returns the traces for the given block
1890    pub async fn trace_block(
1891        &self,
1892        block: BlockNumber,
1893    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
1894        let number = self.convert_block_number(Some(block));
1895        if let Some(traces) = self.mined_parity_trace_block(number) {
1896            return Ok(traces);
1897        }
1898
1899        if let Some(fork) = self.get_fork()
1900            && fork.predates_fork(number)
1901        {
1902            return Ok(fork.trace_block(number).await?);
1903        }
1904
1905        Ok(vec![])
1906    }
1907
1908    /// Executes a transaction call and returns requested parity trace results.
1909    pub async fn trace_call(
1910        &self,
1911        request: WithOtherFields<TransactionRequest>,
1912        fee_details: FeeDetails,
1913        trace_types: HashSet<TraceType>,
1914        block_request: BlockRequest<FoundryTxEnvelope>,
1915        block_id: BlockId,
1916    ) -> Result<TraceResults, BlockchainError>
1917    where
1918        Self: TransactionValidator<FoundryTxEnvelope>,
1919        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
1920    {
1921        if let BlockRequest::Number(number) = &block_request
1922            && let Some(fork) = self.get_fork()
1923            && fork.predates_fork(*number)
1924        {
1925            return Ok(fork.trace_call(request, trace_types, block_id).await?);
1926        }
1927
1928        self.with_database_at(Some(block_request), |state, block| {
1929            let cache_db = CacheDB::new(state);
1930            let mut inspector =
1931                TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
1932            let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block);
1933            let result = self.transact_with_inspector_ref(
1934                &cache_db,
1935                &evm_env,
1936                &mut inspector,
1937                tx_env,
1938                op_deposit,
1939            )?;
1940
1941            inspector
1942                .into_parity_builder()
1943                .into_trace_results_with_state(&result, &trace_types, &cache_db)
1944                .map_err(Into::into)
1945        })
1946        .await?
1947    }
1948
1949    /// Replays all transactions in a block and returns the requested traces for each transaction
1950    pub async fn trace_replay_block_transactions(
1951        &self,
1952        block: BlockNumber,
1953        trace_types: HashSet<TraceType>,
1954    ) -> Result<Vec<TraceResultsWithTransactionHash>, BlockchainError> {
1955        let block_number = self.convert_block_number(Some(block));
1956
1957        // Try mined blocks first
1958        if let Some(results) =
1959            self.mined_parity_trace_replay_block_transactions(block_number, &trace_types)
1960        {
1961            return Ok(results);
1962        }
1963
1964        // Fallback to fork if block predates fork
1965        if let Some(fork) = self.get_fork()
1966            && fork.predates_fork(block_number)
1967        {
1968            return Ok(fork.trace_replay_block_transactions(block_number, trace_types).await?);
1969        }
1970
1971        Ok(vec![])
1972    }
1973
1974    /// Replays a mined transaction and returns the requested traces.
1975    pub async fn trace_replay_transaction(
1976        &self,
1977        hash: B256,
1978        trace_types: HashSet<TraceType>,
1979    ) -> Result<TraceResults, BlockchainError> {
1980        let block_number =
1981            self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.block_number);
1982
1983        // If the transaction was mined locally, replay it locally. Do not fall
1984        // through to the fork when the local replay fails; that would misreport
1985        // a local data problem as an upstream transaction lookup.
1986        if let Some(block_number) = block_number {
1987            let results = self
1988                .mined_parity_trace_replay_block_transactions(block_number, &trace_types)
1989                .ok_or(BlockchainError::BlockNotFound)?;
1990
1991            return results
1992                .into_iter()
1993                .find(|result| result.transaction_hash == hash)
1994                .map(|result| result.full_trace)
1995                .ok_or_else(|| {
1996                    BlockchainError::Internal(format!(
1997                        "replayed block {block_number} for local transaction {hash:?}, \
1998                         but its trace was missing"
1999                    ))
2000                });
2001        }
2002
2003        // Not known locally: forward to the fork if present.
2004        if let Some(fork) = self.get_fork() {
2005            return Ok(fork.trace_replay_transaction(hash, trace_types).await?);
2006        }
2007
2008        Err(BlockchainError::TransactionNotFound)
2009    }
2010
2011    /// Traces a raw transaction without committing it to the chain state or mempool.
2012    pub async fn trace_raw_transaction(
2013        &self,
2014        pending_transaction: PendingTransaction<FoundryTxEnvelope>,
2015        trace_types: HashSet<TraceType>,
2016        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
2017    ) -> Result<TraceResults, BlockchainError>
2018    where
2019        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
2020    {
2021        let trace_config = TracingInspectorConfig::from_parity_config(&trace_types);
2022
2023        self.with_database_at(block_request, |state, block_env| {
2024            let cache_db = CacheDB::new(state);
2025            let mut evm_env = self.evm_env.read().clone();
2026            evm_env.block_env = block_env;
2027
2028            let mut inspector = TracingInspector::new(trace_config);
2029            let (result, _) = self.transact_envelope_with_inspector_ref(
2030                &cache_db,
2031                &evm_env,
2032                &mut inspector,
2033                pending_transaction.transaction.as_ref(),
2034                *pending_transaction.sender(),
2035            )?;
2036
2037            inspector
2038                .into_parity_builder()
2039                .into_trace_results_with_state(&result, &trace_types, &cache_db)
2040                .map_err(BlockchainError::from)
2041        })
2042        .await?
2043    }
2044
2045    /// Traces calls sequentially against a shared in-memory state.
2046    pub async fn trace_call_many(
2047        &self,
2048        calls: Vec<(WithOtherFields<TransactionRequest>, HashSet<TraceType>)>,
2049        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
2050    ) -> Result<Vec<TraceResults>, BlockchainError>
2051    where
2052        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
2053    {
2054        self.with_database_at(block_request, |state, block_env| {
2055            let mut cache_db = CacheDB::new(state);
2056            let mut results = Vec::with_capacity(calls.len());
2057            let mut calls = calls.into_iter().peekable();
2058
2059            while let Some((request, trace_types)) = calls.next() {
2060                let fee_details = FeeDetails::new(
2061                    request.gas_price,
2062                    request.max_fee_per_gas,
2063                    request.max_priority_fee_per_gas,
2064                    request.max_fee_per_blob_gas,
2065                )?
2066                .or_zero_fees();
2067                let (evm_env, tx_env, op_deposit) =
2068                    self.build_call_env(request, fee_details, block_env.clone());
2069
2070                let trace_config = TracingInspectorConfig::from_parity_config(&trace_types);
2071                let mut inspector = TracingInspector::new(trace_config);
2072                let result = self.transact_with_inspector_ref(
2073                    &cache_db,
2074                    &evm_env,
2075                    &mut inspector,
2076                    tx_env,
2077                    op_deposit,
2078                )?;
2079
2080                let trace_result = inspector
2081                    .into_parity_builder()
2082                    .into_trace_results_with_state(&result, &trace_types, &cache_db)
2083                    .map_err(BlockchainError::from)?;
2084                results.push(trace_result);
2085
2086                if calls.peek().is_some() {
2087                    cache_db.commit(result.state);
2088                }
2089            }
2090
2091            Ok(results)
2092        })
2093        .await?
2094    }
2095
2096    /// Returns the trace results for all transactions in a mined block by replaying them
2097    fn mined_parity_trace_replay_block_transactions(
2098        &self,
2099        block_number: u64,
2100        trace_types: &HashSet<TraceType>,
2101    ) -> Option<Vec<TraceResultsWithTransactionHash>> {
2102        let block = self.get_block(block_number)?;
2103
2104        // Execute this in the context of the parent state
2105        let parent_hash = block.header.parent_hash;
2106        let trace_config = TracingInspectorConfig::from_parity_config(trace_types);
2107
2108        let read_guard = self.states.upgradable_read();
2109        if let Some(state) = read_guard.get_state(&parent_hash) {
2110            self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types)
2111        } else {
2112            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
2113            let state = write_guard.get_on_disk_state(&parent_hash)?;
2114            self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types)
2115        }
2116    }
2117
2118    /// Replays all transactions in a block with the tracing inspector to generate TraceResults
2119    fn replay_block_transactions_with_inspector(
2120        &self,
2121        block: &Block,
2122        parent_state: &StateDb,
2123        trace_config: TracingInspectorConfig,
2124        trace_types: &HashSet<TraceType>,
2125    ) -> Option<Vec<TraceResultsWithTransactionHash>> {
2126        let mut cache_db = CacheDB::new(Box::new(parent_state));
2127        let mut results = Vec::new();
2128
2129        // Configure the block environment
2130        let mut evm_env = self.evm_env.read().clone();
2131        evm_env.block_env = block_env_from_header(&block.header);
2132
2133        // Execute each transaction in the block with tracing
2134        for tx_envelope in &block.body.transactions {
2135            let tx_hash = tx_envelope.hash();
2136
2137            // Create a fresh inspector for this transaction
2138            let mut inspector = TracingInspector::new(trace_config);
2139
2140            // Prepare transaction environment and execute
2141            let pending_tx =
2142                PendingTransaction::from_maybe_impersonated(tx_envelope.clone()).ok()?;
2143            let (result, _) = self
2144                .transact_envelope_with_inspector_ref(
2145                    &cache_db,
2146                    &evm_env,
2147                    &mut inspector,
2148                    pending_tx.transaction.as_ref(),
2149                    *pending_tx.sender(),
2150                )
2151                .ok()?;
2152
2153            // Build TraceResults from the inspector and execution result
2154            let full_trace = inspector
2155                .into_parity_builder()
2156                .into_trace_results_with_state(&result, trace_types, &cache_db)
2157                .ok()?;
2158
2159            results.push(TraceResultsWithTransactionHash { transaction_hash: tx_hash, full_trace });
2160
2161            // Commit the state changes for the next transaction
2162            cache_db.commit(result.state);
2163        }
2164
2165        Some(results)
2166    }
2167
2168    // Returns the traces matching a given filter
2169    pub async fn trace_filter(
2170        &self,
2171        filter: TraceFilter,
2172    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
2173        let matcher = filter.matcher();
2174        let start = filter.from_block.unwrap_or(0);
2175        let end = filter.to_block.unwrap_or_else(|| self.best_number());
2176
2177        if start > end {
2178            return Err(BlockchainError::RpcError(RpcError::invalid_params(
2179                "invalid block range, ensure that to block is greater than from block".to_string(),
2180            )));
2181        }
2182
2183        let dist = end - start;
2184        if dist > 300 {
2185            return Err(BlockchainError::RpcError(RpcError::invalid_params(
2186                "block range too large, currently limited to 300".to_string(),
2187            )));
2188        }
2189
2190        // Accumulate tasks for block range
2191        let mut trace_tasks = vec![];
2192        for num in start..=end {
2193            trace_tasks.push(self.trace_block(num.into()));
2194        }
2195
2196        // Execute tasks and filter traces
2197        let traces = futures::future::try_join_all(trace_tasks).await?;
2198        let filtered_traces =
2199            traces.into_iter().flatten().filter(|trace| matcher.matches(&trace.trace));
2200
2201        // Apply after and count
2202        let filtered_traces: Vec<_> = if let Some(after) = filter.after {
2203            filtered_traces.skip(after as usize).collect()
2204        } else {
2205            filtered_traces.collect()
2206        };
2207
2208        let filtered_traces: Vec<_> = if let Some(count) = filter.count {
2209            filtered_traces.into_iter().take(count as usize).collect()
2210        } else {
2211            filtered_traces
2212        };
2213
2214        Ok(filtered_traces)
2215    }
2216
2217    pub fn get_blobs_by_block_id(
2218        &self,
2219        id: impl Into<BlockId>,
2220        versioned_hashes: Vec<B256>,
2221    ) -> Result<Option<Vec<alloy_consensus::Blob>>> {
2222        Ok(self.get_block(id).map(|block| {
2223            block
2224                .body
2225                .transactions
2226                .iter()
2227                .filter_map(|tx| tx.as_ref().sidecar())
2228                .flat_map(|sidecar| {
2229                    sidecar.sidecar.blobs().iter().zip(sidecar.sidecar.commitments().iter())
2230                })
2231                .filter(|(_, commitment)| {
2232                    // Filter blobs by versioned_hashes if provided
2233                    versioned_hashes.is_empty()
2234                        || versioned_hashes.contains(&kzg_to_versioned_hash(commitment.as_slice()))
2235                })
2236                .map(|(blob, _)| *blob)
2237                .collect()
2238        }))
2239    }
2240
2241    #[allow(clippy::large_stack_frames)]
2242    pub fn get_blob_by_versioned_hash(&self, hash: B256) -> Result<Option<Blob>> {
2243        let storage = self.blockchain.storage.read();
2244        for block in storage.blocks.values() {
2245            for tx in &block.body.transactions {
2246                let typed_tx = tx.as_ref();
2247                if let Some(sidecar) = typed_tx.sidecar() {
2248                    for versioned_hash in sidecar.sidecar.versioned_hashes() {
2249                        if versioned_hash == hash
2250                            && let Some(index) =
2251                                sidecar.sidecar.commitments().iter().position(|commitment| {
2252                                    kzg_to_versioned_hash(commitment.as_slice()) == *hash
2253                                })
2254                            && let Some(blob) = sidecar.sidecar.blobs().get(index)
2255                        {
2256                            return Ok(Some(*blob));
2257                        }
2258                    }
2259                }
2260            }
2261        }
2262        Ok(None)
2263    }
2264
2265    /// Initialises the balance of the given accounts
2266    #[expect(clippy::too_many_arguments)]
2267    pub async fn with_genesis(
2268        db: Arc<AsyncRwLock<Box<dyn Db>>>,
2269        env: Arc<RwLock<EvmEnv>>,
2270        networks: NetworkConfigs,
2271        genesis: GenesisConfig,
2272        fees: FeeManager,
2273        fork: Arc<RwLock<Option<ClientFork>>>,
2274        enable_steps_tracing: bool,
2275        print_logs: bool,
2276        print_traces: bool,
2277        call_trace_decoder: Arc<CallTraceDecoder>,
2278        prune_state_history_config: PruneStateHistoryConfig,
2279        max_persisted_states: Option<usize>,
2280        transaction_block_keeper: Option<usize>,
2281        automine_block_time: Option<Duration>,
2282        cache_path: Option<PathBuf>,
2283        node_config: Arc<AsyncRwLock<NodeConfig>>,
2284    ) -> Result<Self> {
2285        // if this is a fork then adjust the blockchain storage
2286        let blockchain = if let Some(fork) = fork.read().as_ref() {
2287            trace!(target: "backend", "using forked blockchain at {}", fork.block_number());
2288            Blockchain::forked(fork.block_number(), fork.block_hash(), fork.total_difficulty())
2289        } else {
2290            Blockchain::new(
2291                &env.read(),
2292                fees.is_eip1559().then(|| fees.base_fee()),
2293                genesis.timestamp,
2294                genesis.number,
2295                networks.is_tempo(),
2296            )
2297        };
2298
2299        // Sync EVM block.number with genesis for non-fork mode.
2300        // Fork mode syncs in setup_fork_db_config() instead.
2301        if fork.read().is_none() {
2302            env.write().block_env.number = U256::from(genesis.number);
2303
2304            // The genesis block keeps its base fee, but the next block must already follow Tempo's
2305            // rules (e.g. T7 clamps the seed down to the cap). Fork mode seeds this from the fork
2306            // block instead.
2307            if fees.tempo_hardfork().is_some() {
2308                let env = env.read();
2309                let next_base_fee = fees.get_next_block_base_fee_per_gas(
2310                    0,
2311                    env.block_env.gas_limit,
2312                    env.block_env.basefee,
2313                );
2314                drop(env);
2315                fees.set_base_fee(next_base_fee);
2316            }
2317        }
2318
2319        let start_timestamp = if let Some(fork) = fork.read().as_ref() {
2320            fork.timestamp()
2321        } else {
2322            genesis.timestamp
2323        };
2324
2325        let mut states = if prune_state_history_config.is_config_enabled() {
2326            // if prune state history is enabled, configure the state cache only for memory
2327            prune_state_history_config
2328                .max_memory_history
2329                .map(|limit| InMemoryBlockStates::new(limit, 0))
2330                .unwrap_or_default()
2331                .memory_only()
2332        } else if max_persisted_states.is_some() {
2333            max_persisted_states
2334                .map(|limit| InMemoryBlockStates::new(DEFAULT_HISTORY_LIMIT, limit))
2335                .unwrap_or_default()
2336        } else {
2337            Default::default()
2338        };
2339
2340        if let Some(cache_path) = cache_path {
2341            states = states.disk_path(cache_path);
2342        }
2343
2344        let (slots_in_an_epoch, precompile_factory, disable_pool_balance_checks, hardfork) = {
2345            let cfg = node_config.read().await;
2346            (
2347                cfg.slots_in_an_epoch,
2348                cfg.precompile_factory.clone(),
2349                cfg.disable_pool_balance_checks,
2350                cfg.get_hardfork(),
2351            )
2352        };
2353
2354        let backend = Self {
2355            db,
2356            blockchain,
2357            states: Arc::new(RwLock::new(states)),
2358            evm_env: env,
2359            networks,
2360            hardfork,
2361            fork,
2362            time: TimeManager::new(start_timestamp),
2363            cheats: Default::default(),
2364            new_block_listeners: Default::default(),
2365            fees,
2366            genesis,
2367            active_state_snapshots: Arc::new(Mutex::new(Default::default())),
2368            enable_steps_tracing,
2369            print_logs,
2370            print_traces,
2371            call_trace_decoder,
2372            prune_state_history_config,
2373            transaction_block_keeper,
2374            node_config,
2375            slots_in_an_epoch,
2376            precompile_factory,
2377            mining: Arc::new(tokio::sync::Mutex::new(())),
2378            disable_pool_balance_checks,
2379        };
2380
2381        if let Some(interval_block_time) = automine_block_time {
2382            backend.update_interval_mine_block_time(interval_block_time);
2383        }
2384
2385        // Note: this can only fail in forking mode, in which case we can't recover
2386        backend.apply_genesis().await.wrap_err("failed to create genesis")?;
2387        Ok(backend)
2388    }
2389
2390    /// Applies the configured genesis settings
2391    ///
2392    /// This will fund, create the genesis accounts
2393    async fn apply_genesis(&self) -> Result<(), DatabaseError> {
2394        trace!(target: "backend", "setting genesis balances");
2395
2396        if self.fork.read().is_some() {
2397            // fetch all account first
2398            let mut genesis_accounts_futures = Vec::with_capacity(self.genesis.accounts.len());
2399            for address in self.genesis.accounts.iter().copied() {
2400                let db = Arc::clone(&self.db);
2401
2402                // The forking Database backend can handle concurrent requests, we can fetch all dev
2403                // accounts concurrently by spawning the job to a new task
2404                genesis_accounts_futures.push(tokio::task::spawn(async move {
2405                    let db = db.read().await;
2406                    let info = db.basic_ref(address)?.unwrap_or_default();
2407                    Ok::<_, DatabaseError>((address, info))
2408                }));
2409            }
2410
2411            let genesis_accounts = futures::future::join_all(genesis_accounts_futures).await;
2412
2413            let mut db = self.db.write().await;
2414
2415            for res in genesis_accounts {
2416                let (address, mut info) = res.unwrap()?;
2417                info.balance = self.genesis.balance;
2418                db.insert_account(address, info.clone());
2419            }
2420        } else {
2421            let mut db = self.db.write().await;
2422            for (account, info) in self.genesis.account_infos() {
2423                db.insert_account(account, info);
2424            }
2425
2426            // insert the new genesis hash to the database so it's available for the next block in
2427            // the evm
2428            db.insert_block_hash(U256::from(self.best_number()), self.best_hash());
2429
2430            // Deploy EIP-2935 blockhash history storage contract if Prague is active.
2431            if self.spec_id() >= SpecId::PRAGUE {
2432                db.set_code(
2433                    eip2935::HISTORY_STORAGE_ADDRESS,
2434                    eip2935::HISTORY_STORAGE_CODE.clone(),
2435                )?;
2436            }
2437        }
2438
2439        let db = self.db.write().await;
2440        // apply the genesis.json alloc
2441        self.genesis.apply_genesis_json_alloc(db)?;
2442
2443        // Initialize Tempo precompiles and fee tokens when in Tempo mode (not in fork mode).
2444        // In fork mode, precompiles are inherited from the forked origin.
2445        if self.networks.is_tempo() && !self.is_fork() {
2446            let chain_id = self.evm_env.read().cfg_env.chain_id;
2447            let timestamp = self.genesis.timestamp;
2448            let test_accounts: Vec<Address> = self.genesis.accounts.clone();
2449            let hardfork = self.tempo_hardfork();
2450            let mut db = self.db.write().await;
2451            crate::eth::backend::tempo::initialize_tempo_precompiles(
2452                &mut **db,
2453                chain_id,
2454                timestamp,
2455                &test_accounts,
2456                hardfork,
2457            )
2458            .map_err(|e| {
2459                tracing::error!(target: "backend", "failed to initialize Tempo precompiles: {e}");
2460                DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))
2461            })?;
2462            trace!(target: "backend", "initialized Tempo precompiles and fee tokens for {} accounts", test_accounts.len());
2463        }
2464
2465        trace!(target: "backend", "set genesis balances");
2466
2467        Ok(())
2468    }
2469
2470    /// Resets the fork to a fresh state
2471    pub async fn reset_fork(&self, forking: Forking) -> Result<(), BlockchainError> {
2472        if !self.is_fork() {
2473            if let Some(eth_rpc_url) = forking.json_rpc_url.clone() {
2474                let mut evm_env = self.evm_env.read().clone();
2475
2476                let (db, config) = {
2477                    let mut node_config = self.node_config.write().await;
2478
2479                    // we want to force the correct base fee for the next block during
2480                    // `setup_fork_db_config`
2481                    node_config.base_fee.take();
2482                    node_config.fork_urls = vec![eth_rpc_url.clone()];
2483                    node_config.apply_tempo_fork_beneficiary_default(&mut evm_env);
2484
2485                    node_config.setup_fork_db_config(eth_rpc_url, &mut evm_env, &self.fees).await?
2486                };
2487
2488                *self.db.write().await = Box::new(db);
2489
2490                let fork = ClientFork::new(config, Arc::clone(&self.db));
2491
2492                *self.evm_env.write() = evm_env;
2493                *self.fork.write() = Some(fork);
2494            } else {
2495                return Err(RpcError::invalid_params(
2496                    "Forking not enabled and RPC URL not provided to start forking",
2497                )
2498                .into());
2499            }
2500        }
2501
2502        if let Some(fork) = self.get_fork() {
2503            let block_number =
2504                forking.block_number.map(BlockNumber::from).unwrap_or(BlockNumber::Latest);
2505            // reset the fork entirely and reapply the genesis config
2506            let reset_urls =
2507                forking.json_rpc_url.as_ref().map(|url| vec![url.clone()]).unwrap_or_default();
2508            let target_rpc_url = forking.json_rpc_url.clone().or_else(|| fork.eth_rpc_url());
2509            let rpc_url_changed = target_rpc_url != fork.database_rpc_url();
2510            fork.prepare_reset(reset_urls, block_number.into()).await?;
2511            if rpc_url_changed {
2512                // Clear state fetched from the previous RPC URL before persisting the cache.
2513                fork.database.write().await.clear_into_state_snapshot();
2514            }
2515            // Persist fetched remote state before rebuilding the fork database so the new
2516            // block-specific database can load it from disk.
2517            fork.database.read().await.maybe_flush_cache().map_err(BlockchainError::Internal)?;
2518            let fork_block_number = fork.block_number();
2519            if rpc_url_changed {
2520                let cache_dir = {
2521                    let config = self.node_config.read().await;
2522                    if config.no_storage_caching || config.fork_urls.is_empty() {
2523                        None
2524                    } else {
2525                        foundry_config::Config::foundry_chain_cache_dir(config.get_chain_id())
2526                    }
2527                };
2528                if let Some(cache_dir) = cache_dir
2529                    && let Err(err) = std::fs::remove_dir_all(&cache_dir)
2530                    && err.kind() != std::io::ErrorKind::NotFound
2531                {
2532                    return Err(BlockchainError::Internal(format!(
2533                        "failed to invalidate fork cache at {}: {err}",
2534                        cache_dir.display()
2535                    )));
2536                }
2537            }
2538            let fork_block = fork
2539                .block_by_number(fork_block_number)
2540                .await?
2541                .ok_or(BlockchainError::BlockNotFound)?;
2542            // update all settings related to the forked block
2543            {
2544                if let Some(fork_url) = forking.json_rpc_url {
2545                    self.reset_block_number(fork_url, fork_block_number).await?;
2546                } else {
2547                    // If rpc url is unspecified, then update the fork with the new block number and
2548                    // existing rpc url, this updates the cache path
2549                    if let Some(fork_url) = target_rpc_url.clone() {
2550                        self.reset_block_number(fork_url, fork_block_number).await?;
2551                    }
2552
2553                    let gas_limit = self.node_config.read().await.fork_gas_limit(&fork_block);
2554                    let mut env = self.evm_env.write();
2555
2556                    env.cfg_env.chain_id = fork.chain_id();
2557                    env.block_env = BlockEnv {
2558                        number: U256::from(fork_block_number),
2559                        timestamp: U256::from(fork_block.header.timestamp()),
2560                        gas_limit,
2561                        difficulty: fork_block.header.difficulty(),
2562                        prevrandao: Some(fork_block.header.mix_hash().unwrap_or_default()),
2563                        // Keep previous `beneficiary` and `basefee` value
2564                        beneficiary: env.block_env.beneficiary,
2565                        basefee: env.block_env.basefee,
2566                        ..env.block_env.clone()
2567                    };
2568
2569                    // this is the base fee of the current block, but we need the base fee of
2570                    // the next block
2571                    let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas(
2572                        fork_block.header.gas_used(),
2573                        gas_limit,
2574                        fork_block.header.base_fee_per_gas().unwrap_or_default(),
2575                    );
2576
2577                    self.fees.set_base_fee(next_block_base_fee);
2578                }
2579
2580                // reset the time to the timestamp of the forked block
2581                self.time.reset(fork_block.header.timestamp());
2582                // drop any pending next-block prevrandao override so it does not leak into a block
2583                self.cheats.clear_next_block_prevrandao();
2584
2585                // also reset the total difficulty
2586                self.blockchain.storage.write().total_difficulty = fork.total_difficulty();
2587            }
2588            // reset storage
2589            *self.blockchain.storage.write() = BlockchainStorage::forked(
2590                fork.block_number(),
2591                fork.block_hash(),
2592                fork.total_difficulty(),
2593            );
2594            self.states.write().clear();
2595            self.apply_genesis().await?;
2596            fork.set_database_rpc_url(target_rpc_url);
2597
2598            trace!(target: "backend", "reset fork");
2599
2600            Ok(())
2601        } else {
2602            Err(RpcError::invalid_params("Forking not enabled").into())
2603        }
2604    }
2605
2606    /// Resets the backend to a fresh in-memory state, clearing all existing data
2607    pub async fn reset_to_in_mem(&self) -> Result<(), BlockchainError> {
2608        // Clear the fork if any exists
2609        *self.fork.write() = None;
2610
2611        let genesis_timestamp = self.genesis.timestamp;
2612        let genesis_number = self.genesis.number;
2613
2614        // Tempo chains seed the hardfork's own fixed base fee on reset; other chains keep the
2615        // pre-reset base fee for the genesis block, as before. Computed up front so the env,
2616        // storage, and fee manager all agree.
2617        let reset_base_fee = self.fees.tempo_hardfork().map(crate::config::tempo_default_base_fee);
2618        let genesis_base_fee = reset_base_fee.unwrap_or_else(|| self.fees.base_fee());
2619
2620        // Reset environment to genesis state
2621        {
2622            let mut env = self.evm_env.write();
2623            env.block_env.number = U256::from(genesis_number);
2624            env.block_env.timestamp = U256::from(genesis_timestamp);
2625            // Reset other block env fields to their defaults
2626            env.block_env.basefee = genesis_base_fee;
2627            env.block_env.prevrandao = Some(B256::ZERO);
2628        }
2629
2630        // Clear all storage and reinitialize with genesis
2631        let base_fee = self.fees.is_eip1559().then_some(genesis_base_fee);
2632        *self.blockchain.storage.write() = BlockchainStorage::new(
2633            &self.evm_env.read(),
2634            base_fee,
2635            genesis_timestamp,
2636            genesis_number,
2637            self.is_tempo(),
2638        );
2639        self.states.write().clear();
2640
2641        // Clear the database
2642        self.db.write().await.clear();
2643
2644        // Reset time manager
2645        self.time.reset(genesis_timestamp);
2646        // drop any pending next-block prevrandao override so it does not leak into a block
2647        self.cheats.clear_next_block_prevrandao();
2648
2649        // Seed the next block's base fee. On Tempo the genesis keeps its fixed default while the
2650        // next block already follows the hardfork's rule (e.g. T7 clamps to the cap); other chains
2651        // use Anvil's Ethereum default.
2652        if self.fees.is_eip1559() {
2653            let next_base_fee = match reset_base_fee {
2654                Some(genesis_base_fee) => {
2655                    // Seed the fixed genesis fee first so the next-block computation is not
2656                    // affected by any manually zeroed base fee, then advance one block per Tempo.
2657                    self.fees.set_base_fee(genesis_base_fee);
2658                    let gas_limit = self.evm_env.read().block_env.gas_limit;
2659                    self.fees.get_next_block_base_fee_per_gas(0, gas_limit, genesis_base_fee)
2660                }
2661                None => crate::eth::fees::INITIAL_BASE_FEE,
2662            };
2663            self.fees.set_base_fee(next_base_fee);
2664        }
2665
2666        self.fees.set_gas_price(crate::eth::fees::INITIAL_GAS_PRICE);
2667
2668        // Reapply genesis configuration
2669        self.apply_genesis().await?;
2670
2671        trace!(target: "backend", "reset to fresh in-memory state");
2672
2673        Ok(())
2674    }
2675
2676    async fn reset_block_number(
2677        &self,
2678        fork_url: String,
2679        fork_block_number: u64,
2680    ) -> Result<(), BlockchainError> {
2681        let mut node_config = self.node_config.write().await;
2682        node_config.fork_choice = Some(ForkChoice::Block(fork_block_number as i128));
2683        // Update fork_urls so setup_fork_db_config uses the correct URL set
2684        node_config.fork_urls = vec![fork_url.clone()];
2685
2686        let mut evm_env = self.evm_env.read().clone();
2687        let (forked_db, client_fork_config) =
2688            node_config.setup_fork_db_config(fork_url, &mut evm_env, &self.fees).await?;
2689
2690        *self.db.write().await = Box::new(forked_db);
2691        let fork = ClientFork::new(client_fork_config, Arc::clone(&self.db));
2692        *self.fork.write() = Some(fork);
2693        *self.evm_env.write() = evm_env;
2694
2695        Ok(())
2696    }
2697
2698    /// Reverts the state to the state snapshot identified by the given `id`.
2699    pub async fn revert_state_snapshot(&self, id: U256) -> Result<bool, BlockchainError> {
2700        let block = { self.active_state_snapshots.lock().remove(&id) };
2701        if let Some((num, hash)) = block {
2702            let best_block_hash = {
2703                // revert the storage that's newer than the snapshot
2704                let current_height = self.best_number();
2705                let mut storage = self.blockchain.storage.write();
2706
2707                for n in ((num + 1)..=current_height).rev() {
2708                    trace!(target: "backend", "reverting block {}", n);
2709                    if let Some(hash) = storage.hashes.remove(&n)
2710                        && let Some(block) = storage.blocks.remove(&hash)
2711                    {
2712                        for tx in block.body.transactions {
2713                            let _ = storage.transactions.remove(&tx.hash());
2714                        }
2715                    }
2716                }
2717
2718                storage.best_number = num;
2719                storage.best_hash = hash;
2720                hash
2721            };
2722            let block =
2723                self.block_by_hash(best_block_hash).await?.ok_or(BlockchainError::BlockNotFound)?;
2724
2725            let reset_time = block.header.timestamp();
2726            self.time.reset(reset_time);
2727            // drop any pending next-block prevrandao override so it does not leak into a block
2728            self.cheats.clear_next_block_prevrandao();
2729
2730            let mut env = self.evm_env.write();
2731            env.block_env = BlockEnv {
2732                number: U256::from(num),
2733                timestamp: U256::from(block.header.timestamp()),
2734                difficulty: block.header.difficulty(),
2735                // ensures prevrandao is set
2736                prevrandao: Some(block.header.mix_hash().unwrap_or_default()),
2737                gas_limit: block.header.gas_limit(),
2738                // Keep previous `beneficiary` and `basefee` value
2739                beneficiary: env.block_env.beneficiary,
2740                basefee: env.block_env.basefee,
2741                ..Default::default()
2742            }
2743        }
2744        Ok(self.db.write().await.revert_state(id, RevertStateSnapshotAction::RevertRemove))
2745    }
2746
2747    /// executes the transactions without writing to the underlying database
2748    pub async fn inspect_tx(
2749        &self,
2750        tx: Arc<PoolTransaction<FoundryTxEnvelope>>,
2751    ) -> Result<
2752        (InstructionResult, Option<Output>, u64, State, Vec<revm::primitives::Log>),
2753        BlockchainError,
2754    > {
2755        let evm_env = self.next_evm_env();
2756        let db = self.db.read().await;
2757        let mut inspector = self.build_inspector();
2758        let (ResultAndState { result, state }, _) = self.transact_envelope_with_inspector_ref(
2759            &**db,
2760            &evm_env,
2761            &mut inspector,
2762            tx.pending_transaction.transaction.as_ref(),
2763            *tx.pending_transaction.sender(),
2764        )?;
2765        let (exit_reason, gas_used, out, logs) = unpack_execution_result(result);
2766
2767        inspector.print_logs();
2768
2769        if self.print_traces {
2770            inspector.print_traces(self.call_trace_decoder.clone());
2771        }
2772
2773        Ok((exit_reason, out, gas_used, state, logs))
2774    }
2775}
2776
2777impl<N: Network> Backend<N>
2778where
2779    N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
2780{
2781    /// Returns all `Log`s mined by the node that were emitted in the `block` and match the `Filter`
2782    fn mined_logs_for_block(&self, filter: Filter, block: Block, block_hash: B256) -> Vec<Log> {
2783        let mut all_logs = Vec::new();
2784        let mut block_log_index = 0u32;
2785
2786        let storage = self.blockchain.storage.read();
2787
2788        for tx in block.body.transactions {
2789            let Some(tx) = storage.transactions.get(&tx.hash()) else {
2790                continue;
2791            };
2792
2793            let logs = tx.receipt.logs();
2794            let transaction_hash = tx.info.transaction_hash;
2795
2796            for log in logs {
2797                if filter.matches(log) {
2798                    all_logs.push(Log {
2799                        inner: log.clone(),
2800                        block_hash: Some(block_hash),
2801                        block_number: Some(block.header.number()),
2802                        block_timestamp: Some(block.header.timestamp()),
2803                        transaction_hash: Some(transaction_hash),
2804                        transaction_index: Some(tx.info.transaction_index),
2805                        log_index: Some(block_log_index as u64),
2806                        removed: false,
2807                    });
2808                }
2809                block_log_index += 1;
2810            }
2811        }
2812        all_logs
2813    }
2814
2815    /// Returns all logs of the blocks with a number greater than `block_number`, marked as
2816    /// removed.
2817    ///
2818    /// This is used during a reorg to capture the logs of the blocks that are about to be
2819    /// unwound before their transactions and receipts are cleared from storage, so they can be
2820    /// re-delivered to log subscriptions and filters with `removed: true`.
2821    fn removed_logs_since(&self, block_number: u64) -> Vec<Log> {
2822        let storage = self.blockchain.storage.read();
2823        let mut all_logs = Vec::new();
2824
2825        for num in (block_number + 1)..=storage.best_number {
2826            if let Some(hash) = storage.hashes.get(&num)
2827                && let Some(block) = storage.blocks.get(hash)
2828            {
2829                let mut block_log_index = 0u64;
2830                for tx in &block.body.transactions {
2831                    if let Some(tx) = storage.transactions.get(&tx.hash()) {
2832                        for log in tx.receipt.logs() {
2833                            all_logs.push(Log {
2834                                inner: log.clone(),
2835                                block_hash: Some(*hash),
2836                                block_number: Some(num),
2837                                block_timestamp: Some(block.header.timestamp()),
2838                                transaction_hash: Some(tx.info.transaction_hash),
2839                                transaction_index: Some(tx.info.transaction_index),
2840                                log_index: Some(block_log_index),
2841                                removed: true,
2842                            });
2843                            block_log_index += 1;
2844                        }
2845                    }
2846                }
2847            }
2848        }
2849
2850        all_logs
2851    }
2852
2853    /// Returns the logs of the block that match the filter
2854    async fn logs_for_block(
2855        &self,
2856        filter: Filter,
2857        hash: B256,
2858    ) -> Result<Vec<Log>, BlockchainError> {
2859        if let Some(block) = self.blockchain.get_block_by_hash(&hash) {
2860            return Ok(self.mined_logs_for_block(filter, block, hash));
2861        }
2862
2863        if let Some(fork) = self.get_fork() {
2864            return Ok(fork.logs(&filter).await?);
2865        }
2866
2867        Err(BlockchainError::UnknownBlock)
2868    }
2869
2870    /// Returns the logs that match the filter in the given range of blocks
2871    async fn logs_for_range(
2872        &self,
2873        filter: &Filter,
2874        mut from: u64,
2875        to: u64,
2876    ) -> Result<Vec<Log>, BlockchainError> {
2877        let mut all_logs = Vec::new();
2878
2879        // get the range that predates the fork if any
2880        if let Some(fork) = self.get_fork() {
2881            let to_on_fork = if fork.predates_fork(to) {
2882                to
2883            } else {
2884                // adjust the ranges
2885                fork.block_number()
2886            };
2887
2888            if fork.predates_fork_inclusive(from) {
2889                // this data is only available on the forked client
2890                let filter = filter.clone().from_block(from).to_block(to_on_fork);
2891                all_logs = fork.logs(&filter).await?;
2892
2893                // update the range
2894                from = fork.block_number() + 1;
2895            }
2896        }
2897
2898        for number in from..=to {
2899            if let Some((block, hash)) = self.get_block_with_hash(number) {
2900                all_logs.extend(self.mined_logs_for_block(filter.clone(), block, hash));
2901            }
2902        }
2903
2904        Ok(all_logs)
2905    }
2906
2907    /// Returns the logs according to the filter
2908    pub async fn logs(&self, filter: Filter) -> Result<Vec<Log>, BlockchainError> {
2909        trace!(target: "backend", "get logs [{:?}]", filter);
2910        if let Some(hash) = filter.get_block_hash() {
2911            self.logs_for_block(filter, hash).await
2912        } else {
2913            let best = self.best_number();
2914            let to_block =
2915                self.convert_block_number(filter.block_option.get_to_block().copied()).min(best);
2916            let from_block =
2917                self.convert_block_number(filter.block_option.get_from_block().copied());
2918            if from_block > best {
2919                return Err(BlockchainError::BlockOutOfRange(best, from_block));
2920            }
2921
2922            self.logs_for_range(&filter, from_block, to_block).await
2923        }
2924    }
2925
2926    /// Returns all receipts of the block
2927    pub fn mined_receipts(&self, hash: B256) -> Option<Vec<N::ReceiptEnvelope>> {
2928        let block = self.mined_block_by_hash(hash)?;
2929        let mut receipts = Vec::new();
2930        let storage = self.blockchain.storage.read();
2931        for tx in block.transactions.hashes() {
2932            let receipt = storage.transactions.get(&tx)?.receipt.clone();
2933            receipts.push(receipt);
2934        }
2935        Some(receipts)
2936    }
2937}
2938
2939// Mining methods — generic over N: Network, with Foundry-associated-type bounds for now.
2940impl<N: Network> Backend<N>
2941where
2942    Self: TransactionValidator<FoundryTxEnvelope>,
2943    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
2944{
2945    /// Mines a new block and stores it.
2946    ///
2947    /// this will execute all transaction in the order they come in and return all the markers they
2948    /// provide.
2949    pub async fn mine_block(
2950        &self,
2951        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
2952    ) -> MinedBlockOutcome<FoundryTxEnvelope> {
2953        self.do_mine_block(pool_transactions).await
2954    }
2955
2956    /// Builds a [`BlockInfo`] from the EVM environment, execution results, and transactions.
2957    #[allow(clippy::too_many_arguments)]
2958    fn build_block_info(
2959        &self,
2960        evm_env: &EvmEnv,
2961        parent_hash: B256,
2962        number: u64,
2963        state_root: B256,
2964        block_result: BlockExecutionResult<FoundryReceiptEnvelope>,
2965        transactions: Vec<MaybeImpersonatedTransaction<FoundryTxEnvelope>>,
2966        transaction_infos: Vec<TransactionInfo>,
2967    ) -> BlockInfo<N> {
2968        let spec_id = *evm_env.spec_id();
2969        let is_shanghai = spec_id >= SpecId::SHANGHAI;
2970        let is_cancun = spec_id >= SpecId::CANCUN;
2971        let is_prague = spec_id >= SpecId::PRAGUE;
2972
2973        let receipts_root = calculate_receipt_root(&block_result.receipts);
2974        let cumulative_blob_gas_used = is_cancun.then_some(block_result.blob_gas_used);
2975        let bloom = block_result.receipts.iter().fold(Bloom::default(), |mut b, r| {
2976            b.accrue_bloom(r.logs_bloom());
2977            b
2978        });
2979
2980        let header = Header {
2981            parent_hash,
2982            ommers_hash: Default::default(),
2983            beneficiary: evm_env.block_env.beneficiary,
2984            state_root,
2985            transactions_root: Default::default(),
2986            receipts_root,
2987            logs_bloom: bloom,
2988            difficulty: evm_env.block_env.difficulty,
2989            number,
2990            gas_limit: evm_env.block_env.gas_limit,
2991            gas_used: block_result.gas_used,
2992            timestamp: evm_env.block_env.timestamp.saturating_to(),
2993            extra_data: Default::default(),
2994            mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(),
2995            nonce: Default::default(),
2996            base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(evm_env.block_env.basefee),
2997            parent_beacon_block_root: is_cancun.then_some(Default::default()),
2998            blob_gas_used: cumulative_blob_gas_used,
2999            excess_blob_gas: if is_cancun { evm_env.block_env.blob_excess_gas() } else { None },
3000            withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
3001            requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH),
3002            block_access_list_hash: None,
3003            slot_number: None,
3004        };
3005
3006        let block = create_block(FoundryHeader::new(header, self.is_tempo()), transactions);
3007        BlockInfo { block, transactions: transaction_infos, receipts: block_result.receipts }
3008    }
3009
3010    async fn do_mine_block(
3011        &self,
3012        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
3013    ) -> MinedBlockOutcome<FoundryTxEnvelope> {
3014        let _mining_guard = self.mining.lock().await;
3015        trace!(target: "backend", "creating new block with {} transactions", pool_transactions.len());
3016
3017        let (outcome, header, block_hash) = {
3018            let current_base_fee = self.base_fee();
3019            let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price();
3020
3021            let mut evm_env = self.evm_env.read().clone();
3022
3023            if evm_env.block_env.basefee == 0 {
3024                // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee`
3025                // 0 is only possible if it's manually set
3026                evm_env.cfg_env.disable_base_fee = true;
3027            }
3028
3029            let block_number = self.blockchain.storage.read().best_number.saturating_add(1);
3030
3031            // increase block number for this block
3032            if is_arbitrum(evm_env.cfg_env.chain_id) {
3033                // Temporary set `env.block.number` to `block_number` for Arbitrum chains.
3034                evm_env.block_env.number = U256::from(block_number);
3035            } else {
3036                evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1));
3037            }
3038
3039            evm_env.block_env.basefee = current_base_fee;
3040            evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price;
3041
3042            let best_hash = self.blockchain.storage.read().best_hash;
3043
3044            let mut input = [0u8; 40];
3045            input[..32].copy_from_slice(best_hash.as_slice());
3046            input[32..].copy_from_slice(&block_number.to_le_bytes());
3047            // Use the `prevrandao` value set via `anvil_setNextBlockPrevRandao` for this block if
3048            // one was provided, otherwise derive it from the parent hash and block number. The
3049            // manual override is consumed here so it only applies to this single block.
3050            evm_env.block_env.prevrandao =
3051                Some(self.cheats.take_next_block_prevrandao().unwrap_or_else(|| keccak256(input)));
3052
3053            if self.prune_state_history_config.is_state_history_supported() {
3054                let db = self.db.read().await.current_state();
3055                // store current state before executing all transactions
3056                self.states.write().insert(best_hash, db);
3057            }
3058
3059            let (block_info, included, invalid, not_yet_valid, block_hash) = {
3060                let mut db = self.db.write().await;
3061
3062                // finally set the next block timestamp, this is done just before execution, because
3063                // there can be concurrent requests that can delay acquiring the db lock and we want
3064                // to ensure the timestamp is as close as possible to the actual execution.
3065                evm_env.block_env.timestamp = U256::from(self.time.next_timestamp());
3066
3067                let spec_id = *evm_env.spec_id();
3068
3069                let inspector_tx_config = self.inspector_tx_config();
3070                let gas_config = self.pool_tx_gas_config(&evm_env);
3071
3072                let (pool_result, block_result) = self.execute_with_block_executor(
3073                    &mut **db,
3074                    &evm_env,
3075                    best_hash,
3076                    spec_id,
3077                    &pool_transactions,
3078                    &gas_config,
3079                    &inspector_tx_config,
3080                    &|pending, account| {
3081                        self.validate_pool_transaction_for(pending, account, &evm_env)
3082                    },
3083                );
3084
3085                let included = pool_result.included;
3086                let invalid = pool_result.invalid;
3087                let not_yet_valid = pool_result.not_yet_valid;
3088
3089                let state_root = db.maybe_state_root().unwrap_or_default();
3090                let block_info = self.build_block_info(
3091                    &evm_env,
3092                    best_hash,
3093                    block_number,
3094                    state_root,
3095                    block_result,
3096                    pool_result.txs,
3097                    pool_result.tx_info,
3098                );
3099
3100                // update the new blockhash in the db itself
3101                let block_hash = block_info.block.header.hash_slow();
3102                db.insert_block_hash(U256::from(block_info.block.header.number()), block_hash);
3103
3104                (block_info, included, invalid, not_yet_valid, block_hash)
3105            };
3106
3107            // create the new block with the current timestamp
3108            let BlockInfo { block, transactions, receipts } = block_info;
3109
3110            let header = block.header.clone();
3111
3112            trace!(
3113                target: "backend",
3114                "Mined block {} with {} tx {:?}",
3115                block_number,
3116                transactions.len(),
3117                transactions.iter().map(|tx| tx.transaction_hash).collect::<Vec<_>>()
3118            );
3119            let mut storage = self.blockchain.storage.write();
3120            // update block metadata
3121            storage.best_number = block_number;
3122            storage.best_hash = block_hash;
3123            // Difficulty is removed and not used after Paris (aka TheMerge). Value is replaced with
3124            // prevrandao. https://github.com/bluealloy/revm/blob/1839b3fce8eaeebb85025576f2519b80615aca1e/crates/interpreter/src/instructions/host_env.rs#L27
3125            if !self.is_eip3675() {
3126                storage.total_difficulty =
3127                    storage.total_difficulty.saturating_add(header.difficulty);
3128            }
3129
3130            storage.blocks.insert(block_hash, block);
3131            storage.hashes.insert(block_number, block_hash);
3132
3133            node_info!("");
3134            // insert all transactions
3135            for (info, receipt) in transactions.into_iter().zip(receipts) {
3136                // log some tx info
3137                node_info!("    Transaction: {:?}", info.transaction_hash);
3138                if let Some(contract) = &info.contract_address {
3139                    node_info!("    Contract created: {contract}");
3140                }
3141                node_info!("    Gas used: {}", receipt.cumulative_gas_used());
3142                if !info.exit.is_ok() {
3143                    let r = RevertDecoder::new().decode(
3144                        info.out.as_ref().map(|b| &b[..]).unwrap_or_default(),
3145                        Some(info.exit),
3146                    );
3147                    node_info!("    Error: reverted with: {r}");
3148                }
3149                node_info!("");
3150
3151                let mined_tx = MinedTransaction { info, receipt, block_hash, block_number };
3152                storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx);
3153            }
3154
3155            // remove old transactions that exceed the transaction block keeper
3156            if let Some(transaction_block_keeper) = self.transaction_block_keeper
3157                && storage.blocks.len() > transaction_block_keeper
3158            {
3159                let to_clear = block_number
3160                    .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX));
3161                storage.remove_block_transactions_by_number(to_clear)
3162            }
3163
3164            // we intentionally set the difficulty to `0` for newer blocks
3165            evm_env.block_env.difficulty = U256::from(0);
3166
3167            // update env with new values
3168            *self.evm_env.write() = evm_env;
3169
3170            let timestamp = utc_from_secs(header.timestamp);
3171
3172            node_info!("    Block Number: {}", block_number);
3173            node_info!("    Block Hash: {:?}", block_hash);
3174            if timestamp.year() > 9999 {
3175                // rf2822 panics with more than 4 digits
3176                node_info!("    Block Time: {:?}\n", timestamp.to_rfc3339());
3177            } else {
3178                node_info!("    Block Time: {:?}\n", timestamp.to_rfc2822());
3179            }
3180
3181            let outcome = MinedBlockOutcome { block_number, included, invalid, not_yet_valid };
3182
3183            (outcome, header, block_hash)
3184        };
3185        let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas(
3186            header.gas_used,
3187            header.gas_limit,
3188            header.base_fee_per_gas.unwrap_or_default(),
3189        );
3190        let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas(
3191            header.excess_blob_gas.unwrap_or_default(),
3192            header.blob_gas_used.unwrap_or_default(),
3193        );
3194
3195        // update next base fee
3196        self.fees.set_base_fee(next_block_base_fee);
3197
3198        self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
3199            next_block_excess_blob_gas,
3200            get_blob_base_fee_update_fraction_by_spec_id(*self.evm_env.read().spec_id()),
3201        ));
3202
3203        // notify all listeners
3204        self.notify_on_new_block(header.into_inner(), block_hash);
3205
3206        outcome
3207    }
3208
3209    /// Reorg the chain to a common height and execute blocks to build new chain.
3210    ///
3211    /// The state of the chain is rewound using `rewind` to the common block, including the db,
3212    /// storage, and env.
3213    ///
3214    /// Finally, `do_mine_block` is called to create the new chain.
3215    pub async fn reorg(
3216        &self,
3217        depth: u64,
3218        tx_pairs: HashMap<u64, Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>>,
3219        common_block: Block,
3220    ) -> Result<(), BlockchainError> {
3221        self.rollback(common_block).await?;
3222        // Create the new reorged chain, filling the blocks with transactions if supplied
3223        for i in 0..depth {
3224            let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new);
3225            let outcome = self.do_mine_block(to_be_mined).await;
3226            node_info!(
3227                "    Mined reorg block number {}. With {} valid txs and with invalid {} txs",
3228                outcome.block_number,
3229                outcome.included.len(),
3230                outcome.invalid.len()
3231            );
3232        }
3233
3234        Ok(())
3235    }
3236
3237    /// Creates the pending block
3238    ///
3239    /// This will execute all transaction in the order they come but will not mine the block
3240    pub async fn pending_block(
3241        &self,
3242        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
3243    ) -> BlockInfo<N> {
3244        self.with_pending_block(pool_transactions, |_, block| block).await
3245    }
3246
3247    /// Creates the pending block
3248    ///
3249    /// This will execute all transaction in the order they come but will not mine the block
3250    pub async fn with_pending_block<F, T>(
3251        &self,
3252        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
3253        f: F,
3254    ) -> T
3255    where
3256        F: FnOnce(Box<dyn MaybeFullDatabase + '_>, BlockInfo<N>) -> T,
3257    {
3258        let db = self.db.read().await;
3259        let evm_env = self.next_evm_env();
3260
3261        let mut cache_db = AnvilCacheDB::new(&*db);
3262
3263        let parent_hash = self.blockchain.storage.read().best_hash;
3264
3265        let spec_id = *evm_env.spec_id();
3266
3267        let inspector_tx_config = self.inspector_tx_config();
3268        let gas_config = self.pool_tx_gas_config(&evm_env);
3269
3270        let (pool_result, block_result) = self.execute_with_block_executor(
3271            &mut cache_db,
3272            &evm_env,
3273            parent_hash,
3274            spec_id,
3275            &pool_transactions,
3276            &gas_config,
3277            &inspector_tx_config,
3278            &|pending, account| self.validate_pool_transaction_for(pending, account, &evm_env),
3279        );
3280
3281        // Extract inner CacheDB (which implements MaybeFullDatabase)
3282        let cache_db = cache_db.0;
3283
3284        let state_root = cache_db.maybe_state_root().unwrap_or_default();
3285        let block_number = evm_env.block_env.number.saturating_to();
3286        let block_info = self.build_block_info(
3287            &evm_env,
3288            parent_hash,
3289            block_number,
3290            state_root,
3291            block_result,
3292            pool_result.txs,
3293            pool_result.tx_info,
3294        );
3295
3296        f(Box::new(cache_db), block_info)
3297    }
3298
3299    /// Returns the ERC20/TIP20 token balance for an account.
3300    ///
3301    /// Calls `balanceOf(address)` on the token contract. Returns `U256::ZERO` if
3302    /// the call fails (e.g. the token contract doesn't exist).
3303    pub async fn get_fee_token_balance(
3304        &self,
3305        token: Address,
3306        account: Address,
3307    ) -> Result<U256, BlockchainError> {
3308        // balanceOf(address) selector: 0x70a08231
3309        let mut calldata = vec![0x70, 0xa0, 0x82, 0x31];
3310        // ABI-encode the address (left-padded to 32 bytes)
3311        calldata.extend_from_slice(&[0u8; 12]);
3312        calldata.extend_from_slice(account.as_slice());
3313
3314        let request = WithOtherFields::new(TransactionRequest {
3315            from: Some(Address::ZERO),
3316            to: Some(TxKind::Call(token)),
3317            input: calldata.into(),
3318            ..Default::default()
3319        });
3320
3321        let fee_details = FeeDetails::zero();
3322        let (exit, out, _, _) = self.call(request, fee_details, None, Default::default()).await?;
3323
3324        // Check if call succeeded
3325        if exit != InstructionResult::Return && exit != InstructionResult::Stop {
3326            // Return zero balance if call failed (token might not exist)
3327            return Ok(U256::ZERO);
3328        }
3329
3330        // Decode U256 from output
3331        match out {
3332            Some(Output::Call(data)) if data.len() >= 32 => Ok(U256::from_be_slice(&data[..32])),
3333            _ => Ok(U256::ZERO),
3334        }
3335    }
3336
3337    /// Executes the [TransactionRequest] without writing to the DB
3338    ///
3339    /// # Errors
3340    ///
3341    /// Returns an error if the `block_number` is greater than the current height
3342    pub async fn call(
3343        &self,
3344        request: WithOtherFields<TransactionRequest>,
3345        fee_details: FeeDetails,
3346        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3347        overrides: EvmOverrides,
3348    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3349        self.with_database_at(block_request, |state, mut block| {
3350            let block_number = block.number;
3351            let (exit, out, gas, state) = {
3352                let mut cache_db = CacheDB::new(state);
3353                if let Some(state_overrides) = overrides.state {
3354                    apply_state_overrides(state_overrides.into_iter().collect(), &mut cache_db)?;
3355                }
3356                if let Some(block_overrides) = overrides.block {
3357                    cache_db.apply_block_overrides(*block_overrides, &mut block);
3358                }
3359                self.call_with_state(&cache_db, request, fee_details, block)
3360            }?;
3361            trace!(target: "backend", "call return {:?} out: {:?} gas {} on block {}", exit, out, gas, block_number);
3362            Ok((exit, out, gas, state))
3363        }).await?
3364    }
3365
3366    pub async fn call_with_tracing(
3367        &self,
3368        request: WithOtherFields<TransactionRequest>,
3369        fee_details: FeeDetails,
3370        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3371        opts: GethDebugTracingCallOptions,
3372    ) -> Result<GethTrace, BlockchainError> {
3373        let GethDebugTracingCallOptions {
3374            tracing_options,
3375            block_overrides,
3376            state_overrides,
3377            tx_index,
3378        } = opts;
3379
3380        if let Some(tx_index) = tx_index {
3381            return self
3382                .call_with_tracing_at_tx_index(
3383                    request,
3384                    fee_details,
3385                    block_request,
3386                    tx_index,
3387                    tracing_options,
3388                    state_overrides,
3389                    block_overrides,
3390                )
3391                .await;
3392        }
3393
3394        self.with_database_at(block_request, |state, block| {
3395            let cache_db = CacheDB::new(state);
3396            self.trace_call_with_state(
3397                request,
3398                fee_details,
3399                block,
3400                cache_db,
3401                tracing_options,
3402                state_overrides,
3403                block_overrides,
3404            )
3405        })
3406        .await?
3407    }
3408
3409    #[allow(clippy::too_many_arguments)]
3410    async fn call_with_tracing_at_tx_index(
3411        &self,
3412        request: WithOtherFields<TransactionRequest>,
3413        fee_details: FeeDetails,
3414        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3415        tx_index: u64,
3416        tracing_options: GethDebugTracingOptions,
3417        state_overrides: Option<StateOverride>,
3418        block_overrides: Option<BlockOverrides>,
3419    ) -> Result<GethTrace, BlockchainError> {
3420        let tx_index = usize::try_from(tx_index).map_err(|_| {
3421            BlockchainError::RpcError(RpcError::invalid_params(format!(
3422                "tx_index {tx_index} does not fit in usize"
3423            )))
3424        })?;
3425        let block_number = match block_request {
3426            Some(BlockRequest::Pending(_)) => {
3427                return Err(BlockchainError::RpcError(RpcError::invalid_params(
3428                    "tx_index is not supported for pending blocks".to_string(),
3429                )));
3430            }
3431            Some(BlockRequest::Number(number)) => number,
3432            None => self.best_number(),
3433        };
3434        let block_id = BlockId::Number(BlockNumber::Number(block_number));
3435
3436        if let Some(block) = self.get_block(block_id) {
3437            return self.mined_trace_call_at_tx_index(
3438                request,
3439                fee_details,
3440                &block,
3441                tx_index,
3442                tracing_options,
3443                state_overrides,
3444                block_overrides,
3445            );
3446        }
3447
3448        if let Some(fork) = self.get_fork()
3449            && fork.predates_fork_inclusive(block_number)
3450        {
3451            let opts = GethDebugTracingCallOptions {
3452                tracing_options,
3453                state_overrides,
3454                block_overrides,
3455                tx_index: Some(tx_index as u64),
3456            };
3457            return Ok(fork.debug_trace_call(request, block_id, opts).await?);
3458        }
3459
3460        Err(BlockchainError::BlockNotFound)
3461    }
3462
3463    #[allow(clippy::too_many_arguments)]
3464    fn mined_trace_call_at_tx_index(
3465        &self,
3466        request: WithOtherFields<TransactionRequest>,
3467        fee_details: FeeDetails,
3468        block: &Block,
3469        tx_index: usize,
3470        tracing_options: GethDebugTracingOptions,
3471        state_overrides: Option<StateOverride>,
3472        block_overrides: Option<BlockOverrides>,
3473    ) -> Result<GethTrace, BlockchainError> {
3474        let transaction_count = block.body.transactions.len();
3475        if tx_index >= transaction_count {
3476            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
3477                "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
3478            ))));
3479        }
3480
3481        let pool_txs: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>> = block.body.transactions
3482            [..tx_index]
3483            .iter()
3484            .map(|tx| {
3485                let pending_tx =
3486                    PendingTransaction::from_maybe_impersonated(tx.clone()).expect("is valid");
3487                Arc::new(PoolTransaction {
3488                    pending_transaction: pending_tx,
3489                    requires: vec![],
3490                    provides: vec![],
3491                    priority: crate::eth::pool::transactions::TransactionPriority(0),
3492                })
3493            })
3494            .collect();
3495
3496        let trace = |parent_state: &StateDb| -> Result<GethTrace, BlockchainError> {
3497            let mut cache_db =
3498                AnvilCacheDB::new(Box::new(parent_state) as Box<dyn MaybeFullDatabase + '_>);
3499
3500            let mut evm_env = self.evm_env.read().clone();
3501            evm_env.block_env = block_env_from_header(&block.header);
3502
3503            let spec_id = *evm_env.spec_id();
3504            let inspector_tx_config = self.inspector_tx_config();
3505            let gas_config = self.pool_tx_gas_config(&evm_env);
3506
3507            self.execute_with_block_executor(
3508                &mut cache_db,
3509                &evm_env,
3510                block.header.parent_hash,
3511                spec_id,
3512                &pool_txs,
3513                &gas_config,
3514                &inspector_tx_config,
3515                &|pending, account| self.validate_pool_transaction_for(pending, account, &evm_env),
3516            );
3517
3518            let cache_db = cache_db.0;
3519            self.trace_call_with_state(
3520                request,
3521                fee_details,
3522                evm_env.block_env,
3523                cache_db,
3524                tracing_options,
3525                state_overrides,
3526                block_overrides,
3527            )
3528        };
3529
3530        let read_guard = self.states.upgradable_read();
3531        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
3532            trace(state)
3533        } else {
3534            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
3535            let state = write_guard
3536                .get_on_disk_state(&block.header.parent_hash)
3537                .ok_or(BlockchainError::BlockNotFound)?;
3538            trace(state)
3539        }
3540    }
3541
3542    #[allow(clippy::too_many_arguments)]
3543    fn trace_call_with_state(
3544        &self,
3545        request: WithOtherFields<TransactionRequest>,
3546        fee_details: FeeDetails,
3547        mut block: BlockEnv,
3548        mut cache_db: CacheDB<Box<dyn MaybeFullDatabase + '_>>,
3549        tracing_options: GethDebugTracingOptions,
3550        state_overrides: Option<StateOverride>,
3551        block_overrides: Option<BlockOverrides>,
3552    ) -> Result<GethTrace, BlockchainError> {
3553        let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options;
3554        let block_number = block.number;
3555
3556        if let Some(state_overrides) = state_overrides {
3557            apply_state_overrides(state_overrides, &mut cache_db)?;
3558        }
3559        if let Some(block_overrides) = block_overrides {
3560            cache_db.apply_block_overrides(block_overrides, &mut block);
3561        }
3562
3563        if let Some(tracer) = tracer {
3564            return match tracer {
3565                GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
3566                    GethDebugBuiltInTracerType::CallTracer => {
3567                        let call_config = call_config_from_tracer_config(tracer_config)
3568                            .map_err(|e| RpcError::invalid_params(e.to_string()))?;
3569
3570                        let mut inspector = self.build_inspector().with_tracing_config(
3571                            TracingInspectorConfig::from_geth_call_config(&call_config),
3572                        );
3573
3574                        let (evm_env, tx_env, op_deposit) =
3575                            self.build_call_env(request, fee_details, block);
3576                        let ResultAndState { result, state: _ } = self
3577                            .transact_with_inspector_ref(
3578                                &cache_db,
3579                                &evm_env,
3580                                &mut inspector,
3581                                tx_env,
3582                                op_deposit,
3583                            )?;
3584
3585                        inspector.print_logs();
3586                        if self.print_traces {
3587                            inspector.print_traces(self.call_trace_decoder.clone());
3588                        }
3589
3590                        let tracing_inspector = inspector.tracer.expect("tracer disappeared");
3591
3592                        Ok(tracing_inspector
3593                            .into_geth_builder()
3594                            .geth_call_traces(call_config, result.tx_gas_used())
3595                            .into())
3596                    }
3597                    GethDebugBuiltInTracerType::PreStateTracer => {
3598                        let pre_state_config = tracer_config
3599                            .into_pre_state_config()
3600                            .map_err(|e| RpcError::invalid_params(e.to_string()))?;
3601
3602                        let mut inspector = TracingInspector::new(
3603                            TracingInspectorConfig::from_geth_prestate_config(&pre_state_config),
3604                        );
3605
3606                        let (evm_env, tx_env, op_deposit) =
3607                            self.build_call_env(request, fee_details, block);
3608                        let result = self.transact_with_inspector_ref(
3609                            &cache_db,
3610                            &evm_env,
3611                            &mut inspector,
3612                            tx_env,
3613                            op_deposit,
3614                        )?;
3615
3616                        Ok(inspector
3617                            .into_geth_builder()
3618                            .geth_prestate_traces(&result, &pre_state_config, cache_db)?
3619                            .into())
3620                    }
3621                    GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()),
3622                    GethDebugBuiltInTracerType::FourByteTracer
3623                    | GethDebugBuiltInTracerType::MuxTracer
3624                    | GethDebugBuiltInTracerType::FlatCallTracer
3625                    | GethDebugBuiltInTracerType::Erc7562Tracer => {
3626                        Err(RpcError::invalid_params("unsupported tracer type").into())
3627                    }
3628                },
3629                #[cfg(not(feature = "js-tracer"))]
3630                GethDebugTracerType::JsTracer(_) => {
3631                    Err(RpcError::invalid_params("unsupported tracer type").into())
3632                }
3633                #[cfg(feature = "js-tracer")]
3634                GethDebugTracerType::JsTracer(code) => {
3635                    let config = tracer_config.into_json();
3636                    let mut inspector =
3637                        revm_inspectors::tracing::js::JsInspector::new(code, config)
3638                            .map_err(|err| BlockchainError::Message(err.to_string()))?;
3639
3640                    let (evm_env, tx_env, op_deposit) =
3641                        self.build_call_env(request, fee_details, block.clone());
3642                    let result = self.transact_with_inspector_ref(
3643                        &cache_db,
3644                        &evm_env,
3645                        &mut inspector,
3646                        tx_env.clone(),
3647                        op_deposit,
3648                    )?;
3649                    let res = inspector
3650                        .json_result(result, &tx_env, &block, &cache_db)
3651                        .map_err(|err| BlockchainError::Message(err.to_string()))?;
3652
3653                    Ok(GethTrace::JS(res))
3654                }
3655            };
3656        }
3657
3658        // defaults to StructLog tracer used since no tracer is specified
3659        let mut inspector = self
3660            .build_inspector()
3661            .with_tracing_config(TracingInspectorConfig::from_geth_config(&config));
3662
3663        let (evm_env, tx_env, op_deposit) = self.build_call_env(request, fee_details, block);
3664        let ResultAndState { result, state: _ } = self.transact_with_inspector_ref(
3665            &cache_db,
3666            &evm_env,
3667            &mut inspector,
3668            tx_env,
3669            op_deposit,
3670        )?;
3671
3672        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3673
3674        let tracing_inspector = inspector.tracer.expect("tracer disappeared");
3675        let return_value = out.as_ref().map(|o| o.data()).cloned().unwrap_or_default();
3676
3677        trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call");
3678
3679        let res = tracing_inspector
3680            .into_geth_builder()
3681            .geth_traces(gas_used, return_value, config)
3682            .into();
3683
3684        Ok(res)
3685    }
3686
3687    /// Helper function to execute a closure with the database at a specific block
3688    pub async fn with_database_at<F, T>(
3689        &self,
3690        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3691        f: F,
3692    ) -> Result<T, BlockchainError>
3693    where
3694        F: FnOnce(Box<dyn MaybeFullDatabase + '_>, BlockEnv) -> T,
3695    {
3696        let block_number = match block_request {
3697            Some(BlockRequest::Pending(pool_transactions)) => {
3698                let result = self
3699                    .with_pending_block(pool_transactions, |state, block| {
3700                        let block = block.block;
3701                        f(state, block_env_from_header(&block.header))
3702                    })
3703                    .await;
3704                return Ok(result);
3705            }
3706            Some(BlockRequest::Number(bn)) => Some(BlockNumber::Number(bn)),
3707            None => None,
3708        };
3709        let block_number = self.convert_block_number(block_number);
3710        let current_number = self.best_number();
3711
3712        // Reject requests for future blocks that don't exist yet
3713        if block_number > current_number {
3714            return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
3715        }
3716
3717        if block_number < current_number {
3718            if let Some((block_hash, block)) = self
3719                .block_by_number(BlockNumber::Number(block_number))
3720                .await?
3721                .map(|block| (block.header.hash, block))
3722            {
3723                let read_guard = self.states.upgradable_read();
3724                if let Some(state_db) = read_guard.get_state(&block_hash) {
3725                    return Ok(f(Box::new(state_db), block_env_from_header(&block.header)));
3726                }
3727
3728                let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
3729                if let Some(state) = write_guard.get_on_disk_state(&block_hash) {
3730                    return Ok(f(Box::new(state), block_env_from_header(&block.header)));
3731                }
3732            }
3733
3734            warn!(target: "backend", "Not historic state found for block={}", block_number);
3735            return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
3736        }
3737
3738        let db = self.db.read().await;
3739        let block = self.evm_env.read().block_env.clone();
3740        Ok(f(Box::new(&**db), block))
3741    }
3742
3743    pub async fn storage_at(
3744        &self,
3745        address: Address,
3746        index: U256,
3747        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3748    ) -> Result<B256, BlockchainError> {
3749        self.with_database_at(block_request, |db, _| {
3750            trace!(target: "backend", "get storage for {:?} at {:?}", address, index);
3751            let val = db.storage_ref(address, index)?;
3752            Ok(val.into())
3753        })
3754        .await?
3755    }
3756
3757    /// Returns storage values for multiple accounts and slots in a single call.
3758    pub async fn storage_values(
3759        &self,
3760        requests: HashMap<Address, Vec<B256>>,
3761        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3762    ) -> Result<HashMap<Address, Vec<B256>>, BlockchainError> {
3763        self.with_database_at(block_request, |db, _| {
3764            trace!(target: "backend", "get storage values for {} addresses", requests.len());
3765            let mut result: HashMap<Address, Vec<B256>> = HashMap::default();
3766            for (address, slots) in &requests {
3767                let mut values = Vec::with_capacity(slots.len());
3768                for slot in slots {
3769                    let val = db.storage_ref(*address, (*slot).into())?;
3770                    values.push(val.into());
3771                }
3772                result.insert(*address, values);
3773            }
3774            Ok(result)
3775        })
3776        .await?
3777    }
3778
3779    /// Returns the code of the address
3780    ///
3781    /// If the code is not present and fork mode is enabled then this will try to fetch it from the
3782    /// forked client
3783    pub async fn get_code(
3784        &self,
3785        address: Address,
3786        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3787    ) -> Result<Bytes, BlockchainError> {
3788        self.with_database_at(block_request, |db, _| self.get_code_with_state(&db, address)).await?
3789    }
3790
3791    /// Returns the balance of the address
3792    ///
3793    /// If the requested number predates the fork then this will fetch it from the endpoint
3794    pub async fn get_balance(
3795        &self,
3796        address: Address,
3797        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3798    ) -> Result<U256, BlockchainError> {
3799        self.with_database_at(block_request, |db, _| self.get_balance_with_state(db, address))
3800            .await?
3801    }
3802
3803    pub async fn get_account_at_block(
3804        &self,
3805        address: Address,
3806        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3807    ) -> Result<TrieAccount, BlockchainError> {
3808        self.with_database_at(block_request, |block_db, _| {
3809            let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
3810            let account = db.get(&address).cloned().unwrap_or_default();
3811            let storage_root = storage_root(&account.storage);
3812            let code_hash = account.info.code_hash;
3813            let balance = account.info.balance;
3814            let nonce = account.info.nonce;
3815            Ok(TrieAccount { balance, nonce, code_hash, storage_root })
3816        })
3817        .await?
3818    }
3819
3820    /// Returns the nonce of the address
3821    ///
3822    /// If the requested number predates the fork then this will fetch it from the endpoint
3823    pub async fn get_nonce(
3824        &self,
3825        address: Address,
3826        block_request: BlockRequest<FoundryTxEnvelope>,
3827    ) -> Result<u64, BlockchainError> {
3828        if let BlockRequest::Pending(pool_transactions) = &block_request
3829            && let Some(value) = get_pool_transactions_nonce(pool_transactions, address)
3830        {
3831            return Ok(value);
3832        }
3833        let final_block_request = match block_request {
3834            BlockRequest::Pending(_) => BlockRequest::Number(self.best_number()),
3835            BlockRequest::Number(bn) => BlockRequest::Number(bn),
3836        };
3837
3838        self.with_database_at(Some(final_block_request), |db, _| {
3839            trace!(target: "backend", "get nonce for {:?}", address);
3840            Ok(db.basic_ref(address)?.unwrap_or_default().nonce)
3841        })
3842        .await?
3843    }
3844
3845    fn replay_tx_with_inspector<I, F, T>(
3846        &self,
3847        hash: B256,
3848        mut inspector: I,
3849        f: F,
3850    ) -> Result<T, BlockchainError>
3851    where
3852        for<'a> I: BackendInspector<WrapDatabaseRef<&'a CacheDB<Box<&'a StateDb>>>> + 'a,
3853        for<'a> F:
3854            FnOnce(ResultAndState<HaltReason>, CacheDB<Box<&'a StateDb>>, I, TxEnv, EvmEnv) -> T,
3855    {
3856        let block = {
3857            let storage = self.blockchain.storage.read();
3858            let MinedTransaction { block_hash, .. } = storage
3859                .transactions
3860                .get(&hash)
3861                .cloned()
3862                .ok_or(BlockchainError::TransactionNotFound)?;
3863
3864            storage.blocks.get(&block_hash).cloned().ok_or(BlockchainError::BlockNotFound)?
3865        };
3866
3867        let index = block
3868            .body
3869            .transactions
3870            .iter()
3871            .position(|tx| tx.hash() == hash)
3872            .expect("transaction not found in block");
3873
3874        let pool_txs: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>> = block.body.transactions
3875            [..index]
3876            .iter()
3877            .map(|tx| {
3878                let pending_tx =
3879                    PendingTransaction::from_maybe_impersonated(tx.clone()).expect("is valid");
3880                Arc::new(PoolTransaction {
3881                    pending_transaction: pending_tx,
3882                    requires: vec![],
3883                    provides: vec![],
3884                    priority: crate::eth::pool::transactions::TransactionPriority(0),
3885                })
3886            })
3887            .collect();
3888
3889        let trace = |parent_state: &StateDb| -> Result<T, BlockchainError> {
3890            let mut cache_db = AnvilCacheDB::new(Box::new(parent_state));
3891
3892            // configure the blockenv for the block of the transaction
3893            let mut evm_env = self.evm_env.read().clone();
3894
3895            evm_env.block_env = block_env_from_header(&block.header);
3896
3897            let spec_id = *evm_env.spec_id();
3898
3899            let inspector_tx_config = self.inspector_tx_config();
3900            let gas_config = self.pool_tx_gas_config(&evm_env);
3901
3902            self.execute_with_block_executor(
3903                &mut cache_db,
3904                &evm_env,
3905                block.header.parent_hash,
3906                spec_id,
3907                &pool_txs,
3908                &gas_config,
3909                &inspector_tx_config,
3910                &|pending, account| self.validate_pool_transaction_for(pending, account, &evm_env),
3911            );
3912
3913            // Extract inner CacheDB to match the expected types for the target tx execution
3914            let cache_db = cache_db.0;
3915
3916            let target_tx = block.body.transactions[index].clone();
3917            let target_tx = PendingTransaction::from_maybe_impersonated(target_tx)?;
3918            let (result, base_tx_env) = self.transact_envelope_with_inspector_ref(
3919                &cache_db,
3920                &evm_env,
3921                &mut inspector,
3922                target_tx.transaction.as_ref(),
3923                *target_tx.sender(),
3924            )?;
3925
3926            Ok(f(result, cache_db, inspector, base_tx_env, evm_env))
3927        };
3928
3929        let read_guard = self.states.upgradable_read();
3930        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
3931            trace(state)
3932        } else {
3933            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
3934            let state = write_guard
3935                .get_on_disk_state(&block.header.parent_hash)
3936                .ok_or(BlockchainError::BlockNotFound)?;
3937            trace(state)
3938        }
3939    }
3940
3941    /// Traces the transaction with the js tracer
3942    #[cfg(feature = "js-tracer")]
3943    pub async fn trace_tx_with_js_tracer(
3944        &self,
3945        hash: B256,
3946        code: String,
3947        opts: GethDebugTracingOptions,
3948    ) -> Result<GethTrace, BlockchainError> {
3949        let GethDebugTracingOptions { tracer_config, .. } = opts;
3950        let config = tracer_config.into_json();
3951        let inspector = revm_inspectors::tracing::js::JsInspector::new(code, config)
3952            .map_err(|err| BlockchainError::Message(err.to_string()))?;
3953        let trace = self.replay_tx_with_inspector(
3954            hash,
3955            inspector,
3956            |result, cache_db, mut inspector, tx_env, evm_env| {
3957                inspector
3958                    .json_result(
3959                        result,
3960                        &alloy_evm::IntoTxEnv::into_tx_env(tx_env),
3961                        &evm_env.block_env,
3962                        &cache_db,
3963                    )
3964                    .map_err(|e| BlockchainError::Message(e.to_string()))
3965            },
3966        )??;
3967        Ok(GethTrace::JS(trace))
3968    }
3969
3970    /// Prove an account's existence or nonexistence in the state trie.
3971    ///
3972    /// Returns a merkle proof of the account's trie node, `account_key` == keccak(address)
3973    pub async fn prove_account_at(
3974        &self,
3975        address: Address,
3976        keys: Vec<B256>,
3977        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3978    ) -> Result<AccountProof, BlockchainError> {
3979        let block_number = block_request.as_ref().map(|r| r.block_number());
3980
3981        self.with_database_at(block_request, |block_db, _| {
3982            trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number);
3983            let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
3984            let account = db.get(&address).cloned().unwrap_or_default();
3985
3986            let mut builder = HashBuilder::default()
3987                .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))]));
3988
3989            for (key, account) in trie_accounts(db) {
3990                builder.add_leaf(key, &account);
3991            }
3992
3993            let _ = builder.root();
3994
3995            let proof = builder
3996                .take_proof_nodes()
3997                .into_nodes_sorted()
3998                .into_iter()
3999                .map(|(_, v)| v)
4000                .collect();
4001            let (storage_hash, storage_proofs) = prove_storage(&account.storage, &keys);
4002
4003            let account_proof = AccountProof {
4004                address,
4005                balance: account.info.balance,
4006                nonce: account.info.nonce,
4007                code_hash: account.info.code_hash,
4008                storage_hash,
4009                account_proof: proof,
4010                storage_proof: keys
4011                    .into_iter()
4012                    .zip(storage_proofs)
4013                    .map(|(key, proof)| {
4014                        let storage_key: U256 = key.into();
4015                        let value = account.storage.get(&storage_key).copied().unwrap_or_default();
4016                        StorageProof { key: JsonStorageKey::Hash(key), value, proof }
4017                    })
4018                    .collect(),
4019            };
4020
4021            Ok(account_proof)
4022        })
4023        .await?
4024    }
4025}
4026
4027impl<N: Network> Backend<N>
4028where
4029    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
4030{
4031    /// Returns opcode gas usage for the given transaction.
4032    pub async fn trace_transaction_opcode_gas(
4033        &self,
4034        hash: B256,
4035    ) -> Result<Option<TransactionOpcodeGas>, BlockchainError> {
4036        match self.replay_tx_with_inspector(
4037            hash,
4038            OpcodeGasInspector::default(),
4039            move |_, _, inspector, _, _| TransactionOpcodeGas {
4040                transaction_hash: hash,
4041                opcode_gas: inspector.opcode_gas_iter().collect(),
4042            },
4043        ) {
4044            Ok(trace) => Ok(Some(trace)),
4045            Err(BlockchainError::TransactionNotFound) => {
4046                if let Some(fork) = self.get_fork() {
4047                    return Ok(fork.trace_transaction_opcode_gas(hash).await?);
4048                }
4049
4050                Ok(None)
4051            }
4052            Err(err) => Err(err),
4053        }
4054    }
4055
4056    /// Returns opcode gas usage for all transactions in the given block.
4057    pub async fn trace_block_opcode_gas(
4058        &self,
4059        block_id: BlockId,
4060    ) -> Result<Option<BlockOpcodeGas>, BlockchainError> {
4061        if let Some((block, block_hash)) = self.get_block_with_hash(block_id) {
4062            return self.mined_block_opcode_gas(&block, block_hash).map(Some);
4063        }
4064
4065        if let Some(fork) = self.get_fork() {
4066            let number = self.ensure_block_number(Some(block_id)).await?;
4067            if fork.predates_fork_inclusive(number) {
4068                return Ok(fork.trace_block_opcode_gas(block_id).await?);
4069            }
4070        }
4071
4072        Err(BlockchainError::BlockNotFound)
4073    }
4074
4075    fn mined_block_opcode_gas(
4076        &self,
4077        block: &Block,
4078        block_hash: B256,
4079    ) -> Result<BlockOpcodeGas, BlockchainError> {
4080        if block.body.transactions.is_empty() {
4081            return Ok(BlockOpcodeGas {
4082                block_hash,
4083                block_number: block.header.number(),
4084                transactions: Vec::new(),
4085            });
4086        }
4087
4088        let parent_hash = block.header.parent_hash;
4089
4090        let trace = |parent_state: &StateDb| -> Result<Vec<TransactionOpcodeGas>, BlockchainError> {
4091            let mut cache_db = CacheDB::new(Box::new(parent_state));
4092            let mut transactions = Vec::with_capacity(block.body.transactions.len());
4093
4094            let mut evm_env = self.evm_env.read().clone();
4095            evm_env.block_env = block_env_from_header(&block.header);
4096
4097            for tx_envelope in &block.body.transactions {
4098                let mut inspector = OpcodeGasInspector::default();
4099                let pending_tx = PendingTransaction::from_maybe_impersonated(tx_envelope.clone())?;
4100                let (result, _) = self.transact_envelope_with_inspector_ref(
4101                    &cache_db,
4102                    &evm_env,
4103                    &mut inspector,
4104                    pending_tx.transaction.as_ref(),
4105                    *pending_tx.sender(),
4106                )?;
4107
4108                transactions.push(TransactionOpcodeGas {
4109                    transaction_hash: tx_envelope.hash(),
4110                    opcode_gas: inspector.opcode_gas_iter().collect(),
4111                });
4112
4113                cache_db.commit(result.state);
4114            }
4115
4116            Ok(transactions)
4117        };
4118
4119        let read_guard = self.states.upgradable_read();
4120        let transactions = if let Some(state) = read_guard.get_state(&parent_hash) {
4121            trace(state)?
4122        } else {
4123            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
4124            let state = write_guard
4125                .get_on_disk_state(&parent_hash)
4126                .ok_or(BlockchainError::BlockNotFound)?;
4127            trace(state)?
4128        };
4129
4130        Ok(BlockOpcodeGas { block_hash, block_number: block.header.number(), transactions })
4131    }
4132
4133    /// Returns account information after replaying a block through the transaction at `tx_index`.
4134    pub async fn debug_account_info_at(
4135        &self,
4136        block_id: BlockId,
4137        tx_index: Index,
4138        address: Address,
4139    ) -> Result<Option<RpcAccountInfo>, BlockchainError> {
4140        if let Some((block, _)) = self.get_block_with_hash(block_id) {
4141            return self.mined_debug_account_info_at(&block, tx_index, address).map(Some);
4142        }
4143
4144        if let Some(fork) = self.get_fork() {
4145            let number = self.ensure_block_number(Some(block_id)).await?;
4146            if fork.predates_fork_inclusive(number) {
4147                // Delegate the resolved block number so tags (`latest`/`pending`/`safe`/
4148                // `finalized`) are resolved against the fork's head instead of drifting with
4149                // the upstream chain. Hashes are forwarded unchanged.
4150                let resolved = match block_id {
4151                    BlockId::Hash(_) => block_id,
4152                    _ => BlockId::number(number),
4153                };
4154                return Ok(fork.debug_account_info_at(resolved, tx_index, address).await?);
4155            }
4156        }
4157
4158        Err(BlockchainError::BlockNotFound)
4159    }
4160
4161    fn mined_debug_account_info_at(
4162        &self,
4163        block: &Block,
4164        tx_index: Index,
4165        address: Address,
4166    ) -> Result<RpcAccountInfo, BlockchainError> {
4167        let tx_index = tx_index.0;
4168        let transaction_count = block.body.transactions.len();
4169        if tx_index >= transaction_count {
4170            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
4171                "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
4172            ))));
4173        }
4174
4175        let pool_txs: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>> = block.body.transactions
4176            [..=tx_index]
4177            .iter()
4178            .map(|tx| {
4179                let pending_tx =
4180                    PendingTransaction::from_maybe_impersonated(tx.clone()).expect("is valid");
4181                Arc::new(PoolTransaction {
4182                    pending_transaction: pending_tx,
4183                    requires: vec![],
4184                    provides: vec![],
4185                    priority: crate::eth::pool::transactions::TransactionPriority(0),
4186                })
4187            })
4188            .collect();
4189
4190        let trace = |parent_state: &StateDb| -> Result<RpcAccountInfo, BlockchainError> {
4191            let mut cache_db = AnvilCacheDB::new(Box::new(parent_state));
4192            let mut evm_env = self.evm_env.read().clone();
4193            evm_env.block_env = block_env_from_header(&block.header);
4194
4195            let spec_id = *evm_env.spec_id();
4196            let inspector_tx_config = self.inspector_tx_config();
4197            let gas_config = self.pool_tx_gas_config(&evm_env);
4198
4199            self.execute_with_block_executor(
4200                &mut cache_db,
4201                &evm_env,
4202                block.header.parent_hash,
4203                spec_id,
4204                &pool_txs,
4205                &gas_config,
4206                &inspector_tx_config,
4207                &|pending, account| self.validate_pool_transaction_for(pending, account, &evm_env),
4208            );
4209
4210            let cache_db = cache_db.0;
4211            let account = revm::DatabaseRef::basic_ref(&cache_db, address)?.unwrap_or_default();
4212            let code = self.get_code_with_state(&cache_db, address)?;
4213            Ok(RpcAccountInfo { balance: account.balance, nonce: account.nonce, code })
4214        };
4215
4216        let read_guard = self.states.upgradable_read();
4217        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
4218            trace(state)
4219        } else {
4220            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
4221            let state = write_guard
4222                .get_on_disk_state(&block.header.parent_hash)
4223                .ok_or(BlockchainError::BlockNotFound)?;
4224            trace(state)
4225        }
4226    }
4227
4228    /// Rollback the chain to a common height.
4229    ///
4230    /// The state of the chain is rewound using `rewind` to the common block, including the db,
4231    /// storage, and env.
4232    pub async fn rollback(&self, common_block: Block) -> Result<(), BlockchainError> {
4233        let hash = common_block.header.hash_slow();
4234
4235        // Get the database at the common block
4236        let common_state = {
4237            let return_state_or_throw_err =
4238                |db: Option<&StateDb>| -> Result<AddressMap<DbAccount>, BlockchainError> {
4239                    let state_db = db.ok_or(BlockchainError::DataUnavailable)?;
4240                    let db_full =
4241                        state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
4242                    Ok(db_full.clone())
4243                };
4244
4245            let read_guard = self.states.upgradable_read();
4246            if let Some(db) = read_guard.get_state(&hash) {
4247                return_state_or_throw_err(Some(db))?
4248            } else {
4249                let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
4250                return_state_or_throw_err(write_guard.get_on_disk_state(&hash))?
4251            }
4252        };
4253
4254        {
4255            // Collect the logs of the blocks that are about to be removed from the canonical
4256            // chain, while their transactions and receipts are still in storage
4257            let removed_logs = self.removed_logs_since(common_block.header.number());
4258
4259            // Unwind the storage back to the common ancestor first
4260            let removed_blocks =
4261                self.blockchain.storage.write().unwind_to(common_block.header.number(), hash);
4262
4263            // Clean up in-memory and on-disk states for removed blocks
4264            let removed_hashes: Vec<_> =
4265                removed_blocks.iter().map(|b| b.header.hash_slow()).collect();
4266            self.states.write().remove_block_states(&removed_hashes);
4267
4268            // Notify all log subscriptions and filters about the removed logs, so they receive
4269            // them again marked as removed, before any new chain notifications are emitted
4270            if !removed_logs.is_empty() {
4271                self.notify_on_removed_logs(removed_logs);
4272            }
4273
4274            // Set environment back to common block
4275            let mut env = self.evm_env.write();
4276            env.block_env.number = U256::from(common_block.header.number());
4277            env.block_env.timestamp = U256::from(common_block.header.timestamp());
4278            env.block_env.gas_limit = common_block.header.gas_limit();
4279            env.block_env.difficulty = common_block.header.difficulty();
4280            env.block_env.prevrandao = common_block.header.mix_hash();
4281
4282            self.time.reset(env.block_env.timestamp.saturating_to());
4283            // drop any pending next-block prevrandao override so it does not leak into a block
4284            self.cheats.clear_next_block_prevrandao();
4285        }
4286
4287        {
4288            // Collect block hashes before acquiring db lock to avoid holding blockchain storage
4289            // lock across await. Only collect the last 256 blocks since that's all BLOCKHASH can
4290            // access.
4291            let block_hashes: Vec<_> = {
4292                let storage = self.blockchain.storage.read();
4293                let min_block = common_block.header.number().saturating_sub(256);
4294                storage
4295                    .hashes
4296                    .iter()
4297                    .filter(|(num, _)| **num >= min_block)
4298                    .map(|(&num, &hash)| (num, hash))
4299                    .collect()
4300            };
4301
4302            // Acquire db lock once for the entire restore operation to reduce lock churn.
4303            let mut db = self.db.write().await;
4304            db.clear();
4305
4306            // Insert account info before storage to prevent fork-mode RPC fetches after clear.
4307            for (address, acc) in common_state {
4308                db.insert_account(address, acc.info);
4309                for (key, value) in acc.storage {
4310                    db.set_storage_at(address, key.into(), value.into())?;
4311                }
4312            }
4313
4314            // Restore block hashes from blockchain storage (now unwound, contains only valid
4315            // blocks).
4316            for (block_num, hash) in block_hashes {
4317                db.insert_block_hash(U256::from(block_num), hash);
4318            }
4319        }
4320
4321        Ok(())
4322    }
4323
4324    /// Returns the traces for the given transaction
4325    pub async fn debug_trace_transaction(
4326        &self,
4327        hash: B256,
4328        opts: GethDebugTracingOptions,
4329    ) -> Result<GethTrace, BlockchainError> {
4330        #[cfg(feature = "js-tracer")]
4331        if let Some(tracer_type) = opts.tracer.as_ref()
4332            && tracer_type.is_js()
4333        {
4334            return self
4335                .trace_tx_with_js_tracer(hash, tracer_type.as_str().to_string(), opts.clone())
4336                .await;
4337        }
4338
4339        if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()).await {
4340            return trace;
4341        }
4342
4343        if let Some(fork) = self.get_fork() {
4344            return Ok(fork.debug_trace_transaction(hash, opts).await?);
4345        }
4346
4347        Ok(GethTrace::Default(Default::default()))
4348    }
4349
4350    /// Returns geth-style traces for all transactions in an RLP-encoded block.
4351    pub async fn debug_trace_block(
4352        &self,
4353        rlp_block: Bytes,
4354        opts: GethDebugTracingOptions,
4355    ) -> Result<Vec<TraceResult>, BlockchainError> {
4356        let mut rlp = rlp_block.as_ref();
4357        let block = Block::<FoundryTxEnvelope>::decode(&mut rlp).map_err(|err| {
4358            BlockchainError::RpcError(RpcError::invalid_params(format!(
4359                "failed to decode block: {err}"
4360            )))
4361        })?;
4362        if !rlp.is_empty() {
4363            return Err(BlockchainError::RpcError(RpcError::invalid_params(
4364                "failed to decode block: trailing bytes".to_string(),
4365            )));
4366        }
4367
4368        self.debug_trace_block_by_hash(block.header.hash_slow(), opts).await
4369    }
4370
4371    /// Returns geth-style traces for all transactions in a block by hash.
4372    pub async fn debug_trace_block_by_hash(
4373        &self,
4374        block_hash: B256,
4375        opts: GethDebugTracingOptions,
4376    ) -> Result<Vec<TraceResult>, BlockchainError> {
4377        if let Some(block) = self.blockchain.get_block_by_hash(&block_hash) {
4378            let mut traces = Vec::new();
4379            for tx in &block.body.transactions {
4380                let tx_hash = tx.hash();
4381                match self.debug_trace_transaction(tx_hash, opts.clone()).await {
4382                    Ok(trace) => {
4383                        traces.push(TraceResult::Success { result: trace, tx_hash: Some(tx_hash) });
4384                    }
4385                    Err(error) => {
4386                        traces.push(TraceResult::Error {
4387                            error: error.to_string(),
4388                            tx_hash: Some(tx_hash),
4389                        });
4390                    }
4391                }
4392            }
4393            return Ok(traces);
4394        }
4395
4396        if let Some(fork) = self.get_fork() {
4397            return Ok(fork.debug_trace_block_by_hash(block_hash, opts).await?);
4398        }
4399
4400        Err(BlockchainError::BlockNotFound)
4401    }
4402
4403    /// Returns geth-style traces for all transactions in a block by number.
4404    pub async fn debug_trace_block_by_number(
4405        &self,
4406        block_number: BlockNumber,
4407        opts: GethDebugTracingOptions,
4408    ) -> Result<Vec<TraceResult>, BlockchainError> {
4409        let number = self.convert_block_number(Some(block_number));
4410
4411        if let Some(block) = self.get_block(BlockId::Number(BlockNumber::Number(number))) {
4412            let mut traces = Vec::new();
4413            for tx in &block.body.transactions {
4414                let tx_hash = tx.hash();
4415                match self.debug_trace_transaction(tx_hash, opts.clone()).await {
4416                    Ok(trace) => {
4417                        traces.push(TraceResult::Success { result: trace, tx_hash: Some(tx_hash) });
4418                    }
4419                    Err(error) => {
4420                        traces.push(TraceResult::Error {
4421                            error: error.to_string(),
4422                            tx_hash: Some(tx_hash),
4423                        });
4424                    }
4425                }
4426            }
4427            return Ok(traces);
4428        }
4429
4430        if let Some(fork) = self.get_fork() {
4431            return Ok(fork.debug_trace_block_by_number(number, opts).await?);
4432        }
4433
4434        Err(BlockchainError::BlockNotFound)
4435    }
4436
4437    fn geth_trace(
4438        &self,
4439        tx: &MinedTransaction<N>,
4440        opts: GethDebugTracingOptions,
4441    ) -> Result<GethTrace, BlockchainError> {
4442        let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts;
4443
4444        if let Some(tracer) = tracer {
4445            match tracer {
4446                GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
4447                    GethDebugBuiltInTracerType::FourByteTracer => {
4448                        let inspector = FourByteInspector::default();
4449                        let res = self.replay_tx_with_inspector(
4450                            tx.info.transaction_hash,
4451                            inspector,
4452                            |_, _, inspector, _, _| FourByteFrame::from(inspector).into(),
4453                        )?;
4454                        return Ok(res);
4455                    }
4456                    GethDebugBuiltInTracerType::CallTracer => {
4457                        return match call_config_from_tracer_config(tracer_config) {
4458                            Ok(call_config) => {
4459                                let inspector = TracingInspector::new(
4460                                    TracingInspectorConfig::from_geth_call_config(&call_config),
4461                                );
4462                                let frame = self.replay_tx_with_inspector(
4463                                    tx.info.transaction_hash,
4464                                    inspector,
4465                                    |_, _, inspector, _, _| {
4466                                        inspector
4467                                            .geth_builder()
4468                                            .geth_call_traces(
4469                                                call_config,
4470                                                tx.receipt.cumulative_gas_used(),
4471                                            )
4472                                            .into()
4473                                    },
4474                                )?;
4475                                Ok(frame)
4476                            }
4477                            Err(e) => Err(RpcError::invalid_params(e.to_string()).into()),
4478                        };
4479                    }
4480                    GethDebugBuiltInTracerType::PreStateTracer => {
4481                        return match tracer_config.into_pre_state_config() {
4482                            Ok(pre_state_config) => {
4483                                let inspector = TracingInspector::new(
4484                                    TracingInspectorConfig::from_geth_prestate_config(
4485                                        &pre_state_config,
4486                                    ),
4487                                );
4488                                let frame = self.replay_tx_with_inspector(
4489                                    tx.info.transaction_hash,
4490                                    inspector,
4491                                    |state, db, inspector, _, _| {
4492                                        inspector.geth_builder().geth_prestate_traces(
4493                                            &state,
4494                                            &pre_state_config,
4495                                            db,
4496                                        )
4497                                    },
4498                                )??;
4499                                Ok(frame.into())
4500                            }
4501                            Err(e) => Err(RpcError::invalid_params(e.to_string()).into()),
4502                        };
4503                    }
4504                    GethDebugBuiltInTracerType::NoopTracer
4505                    | GethDebugBuiltInTracerType::MuxTracer
4506                    | GethDebugBuiltInTracerType::Erc7562Tracer
4507                    | GethDebugBuiltInTracerType::FlatCallTracer => {}
4508                },
4509                GethDebugTracerType::JsTracer(_code) => {}
4510            }
4511
4512            return Ok(NoopFrame::default().into());
4513        }
4514
4515        // default structlog tracer
4516        Ok(GethTraceBuilder::new(tx.info.traces.clone())
4517            .geth_traces(
4518                tx.receipt.cumulative_gas_used(),
4519                tx.info.out.clone().unwrap_or_default(),
4520                config,
4521            )
4522            .into())
4523    }
4524
4525    async fn mined_geth_trace_transaction(
4526        &self,
4527        hash: B256,
4528        opts: GethDebugTracingOptions,
4529    ) -> Option<Result<GethTrace, BlockchainError>> {
4530        self.blockchain.storage.read().transactions.get(&hash).map(|tx| self.geth_trace(tx, opts))
4531    }
4532
4533    /// returns all receipts for the given transactions
4534    fn get_receipts(
4535        &self,
4536        tx_hashes: impl IntoIterator<Item = TxHash>,
4537    ) -> Vec<FoundryReceiptEnvelope> {
4538        let storage = self.blockchain.storage.read();
4539        let mut receipts = vec![];
4540
4541        for hash in tx_hashes {
4542            if let Some(tx) = storage.transactions.get(&hash) {
4543                receipts.push(tx.receipt.clone());
4544            }
4545        }
4546
4547        receipts
4548    }
4549
4550    pub async fn transaction_receipt(
4551        &self,
4552        hash: B256,
4553    ) -> Result<Option<FoundryTxReceipt>, BlockchainError> {
4554        if let Some(receipt) = self.mined_transaction_receipt(hash) {
4555            return Ok(Some(receipt.inner));
4556        }
4557
4558        if let Some(fork) = self.get_fork() {
4559            let receipt = fork.transaction_receipt(hash).await?;
4560            let number = self.convert_block_number(
4561                receipt.clone().and_then(|r| r.block_number()).map(BlockNumber::from),
4562            );
4563
4564            if fork.predates_fork_inclusive(number) {
4565                return Ok(receipt);
4566            }
4567        }
4568
4569        Ok(None)
4570    }
4571
4572    /// Returns all transaction receipts of the block
4573    pub fn mined_block_receipts(&self, id: impl Into<BlockId>) -> Option<Vec<FoundryTxReceipt>> {
4574        let mut receipts = Vec::new();
4575        let block = self.get_block(id)?;
4576
4577        for transaction in block.body.transactions {
4578            let receipt = self.mined_transaction_receipt(transaction.hash())?;
4579            receipts.push(receipt.inner);
4580        }
4581
4582        Some(receipts)
4583    }
4584
4585    /// Returns the transaction receipt for the given hash
4586    pub(crate) fn mined_transaction_receipt(
4587        &self,
4588        hash: B256,
4589    ) -> Option<MinedTransactionReceipt<FoundryNetwork>> {
4590        let MinedTransaction { info, receipt: tx_receipt, block_hash, .. } =
4591            self.blockchain.get_transaction_by_hash(&hash)?;
4592
4593        let index = info.transaction_index as usize;
4594        let block = self.blockchain.get_block_by_hash(&block_hash)?;
4595        let transaction = block.body.transactions[index].clone();
4596
4597        // Cancun specific
4598        let excess_blob_gas = block.header.excess_blob_gas();
4599        let blob_gas_price =
4600            alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas.unwrap_or_default());
4601        let blob_gas_used = transaction.blob_gas_used();
4602
4603        let effective_gas_price = transaction.effective_gas_price(block.header.base_fee_per_gas());
4604
4605        let receipts = self.get_receipts(block.body.transactions.iter().map(|tx| tx.hash()));
4606        let next_log_index = receipts[..index].iter().map(|r| r.logs().len()).sum::<usize>();
4607
4608        let tx_receipt = tx_receipt.convert_logs_rpc(
4609            BlockNumHash::new(block.header.number(), block_hash),
4610            block.header.timestamp(),
4611            info.transaction_hash,
4612            info.transaction_index,
4613            next_log_index,
4614        );
4615
4616        let receipt = TransactionReceipt {
4617            inner: tx_receipt,
4618            transaction_hash: info.transaction_hash,
4619            transaction_index: Some(info.transaction_index),
4620            block_number: Some(block.header.number()),
4621            gas_used: info.gas_used,
4622            contract_address: info.contract_address,
4623            effective_gas_price,
4624            block_hash: Some(block_hash),
4625            from: info.from,
4626            to: info.to,
4627            blob_gas_price: Some(blob_gas_price),
4628            blob_gas_used,
4629        };
4630
4631        // Include timestamp in receipt to avoid extra block lookups (e.g., in Otterscan API)
4632        let mut inner = FoundryTxReceipt::with_timestamp(receipt, block.header.timestamp());
4633        if self.is_tempo() {
4634            inner = inner.with_fee_payer(info.from);
4635        }
4636        Some(MinedTransactionReceipt { inner, out: info.out })
4637    }
4638
4639    /// Returns the blocks receipts for the given number
4640    pub async fn block_receipts(
4641        &self,
4642        number: BlockId,
4643    ) -> Result<Option<Vec<FoundryTxReceipt>>, BlockchainError> {
4644        if let Some(receipts) = self.mined_block_receipts(number) {
4645            return Ok(Some(receipts));
4646        }
4647
4648        if let Some(fork) = self.get_fork() {
4649            let number = match self.ensure_block_number(Some(number)).await {
4650                Err(_) => return Ok(None),
4651                Ok(n) => n,
4652            };
4653
4654            if fork.predates_fork_inclusive(number) {
4655                let receipts = fork.block_receipts(number).await?;
4656
4657                return Ok(receipts);
4658            }
4659        }
4660
4661        Ok(None)
4662    }
4663}
4664
4665impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> Backend<N> {
4666    /// Get the current state.
4667    pub async fn serialized_state(
4668        &self,
4669        preserve_historical_states: bool,
4670    ) -> Result<SerializableState, BlockchainError> {
4671        let at = self.evm_env.read().block_env.clone();
4672        let best_number = self.blockchain.storage.read().best_number;
4673        let blocks = self.blockchain.storage.read().serialized_blocks();
4674        let transactions = self.blockchain.storage.read().serialized_transactions();
4675        let historical_states =
4676            preserve_historical_states.then(|| self.states.write().serialized_states());
4677
4678        let state = self.db.read().await.dump_state(
4679            at,
4680            best_number,
4681            blocks,
4682            transactions,
4683            historical_states,
4684        )?;
4685        state.ok_or_else(|| {
4686            RpcError::invalid_params("Dumping state not supported with the current configuration")
4687                .into()
4688        })
4689    }
4690
4691    /// Write all chain data to serialized bytes buffer
4692    pub async fn dump_state(
4693        &self,
4694        preserve_historical_states: bool,
4695    ) -> Result<Bytes, BlockchainError> {
4696        let state = self.serialized_state(preserve_historical_states).await?;
4697        let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
4698        encoder
4699            .write_all(&serde_json::to_vec(&state).unwrap_or_default())
4700            .map_err(|_| BlockchainError::DataUnavailable)?;
4701        Ok(encoder.finish().unwrap_or_default().into())
4702    }
4703
4704    /// Apply [SerializableState] data to the backend storage.
4705    pub async fn load_state(&self, state: SerializableState) -> Result<bool, BlockchainError> {
4706        // load the blocks and transactions into the storage atomically so concurrent readers
4707        // never observe blocks without their transactions
4708        {
4709            let mut storage = self.blockchain.storage.write();
4710            storage.load_blocks(state.blocks.clone());
4711            storage.load_transactions(state.transactions.clone());
4712        }
4713        // reset the block env
4714        if let Some(block) = state.block.clone() {
4715            {
4716                let mut env = self.evm_env.write();
4717                env.block_env = block.clone();
4718                if self.is_tempo() && self.is_fork() && env.block_env.beneficiary.is_zero() {
4719                    env.block_env.beneficiary = TIP_FEE_MANAGER_ADDRESS;
4720                }
4721            }
4722
4723            // Set the current best block number.
4724            // Defaults to block number for compatibility with existing state files.
4725            let fork_num_and_hash = self.get_fork().map(|f| (f.block_number(), f.block_hash()));
4726
4727            let best_number = state.best_block_number.unwrap_or(block.number.saturating_to());
4728            let selected_best_number = if let Some((number, hash)) = fork_num_and_hash {
4729                trace!(target: "backend", state_block_number=?best_number, fork_block_number=?number);
4730                // If the state.block_number is greater than the fork block number, set best number
4731                // to the state block number.
4732                // Ref: https://github.com/foundry-rs/foundry/issues/9539
4733                if best_number > number {
4734                    self.blockchain.storage.write().best_number = best_number;
4735                    let best_hash = self
4736                        .blockchain
4737                        .storage
4738                        .read()
4739                        .hash(best_number.into(), self.slots_in_an_epoch)
4740                        .ok_or_else(|| {
4741                            BlockchainError::RpcError(RpcError::internal_error_with(format!(
4742                                "Best hash not found for best number {best_number}",
4743                            )))
4744                        })?;
4745                    self.blockchain.storage.write().best_hash = best_hash;
4746                    best_number
4747                } else {
4748                    // If loading state file on a fork, set best number to the fork block number.
4749                    // Ref: https://github.com/foundry-rs/foundry/pull/9215#issue-2618681838
4750                    self.blockchain.storage.write().best_number = number;
4751                    self.blockchain.storage.write().best_hash = hash;
4752                    number
4753                }
4754            } else {
4755                self.blockchain.storage.write().best_number = best_number;
4756
4757                // Set the current best block hash;
4758                let best_hash = self
4759                    .blockchain
4760                    .storage
4761                    .read()
4762                    .hash(best_number.into(), self.slots_in_an_epoch)
4763                    .ok_or_else(|| {
4764                        BlockchainError::RpcError(RpcError::internal_error_with(format!(
4765                            "Best hash not found for best number {best_number}",
4766                        )))
4767                    })?;
4768
4769                self.blockchain.storage.write().best_hash = best_hash;
4770                best_number
4771            };
4772
4773            // Keep NUMBER aligned with the canonical local head chosen above. Arbitrum state dumps
4774            // can intentionally keep BlockEnv.number distinct from the best L2 block number.
4775            if !is_arbitrum(self.chain_id().to()) {
4776                self.set_block_number(selected_best_number);
4777            }
4778        }
4779
4780        if let Some(latest) = state.blocks.iter().max_by_key(|b| b.header.number()) {
4781            let header = &latest.header;
4782            let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas(
4783                header.gas_used(),
4784                header.gas_limit(),
4785                header.base_fee_per_gas().unwrap_or_default(),
4786            );
4787            let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas(
4788                header.excess_blob_gas().unwrap_or_default(),
4789                header.blob_gas_used().unwrap_or_default(),
4790            );
4791
4792            // update next base fee
4793            self.fees.set_base_fee(next_block_base_fee);
4794
4795            self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
4796                next_block_excess_blob_gas,
4797                get_blob_base_fee_update_fraction(
4798                    self.evm_env.read().cfg_env.chain_id,
4799                    header.timestamp,
4800                ),
4801            ));
4802        }
4803
4804        if !self.db.write().await.load_state(state.clone())? {
4805            return Err(RpcError::invalid_params(
4806                "Loading state not supported with the current configuration",
4807            )
4808            .into());
4809        }
4810
4811        // Backfill the EVM-level block hash cache from the freshly loaded blocks so that the
4812        // BLOCKHASH opcode stays consistent after loading state. Reuses the hashes already
4813        // computed by `load_blocks` above. Only collect the last 256 blocks since that's all
4814        // BLOCKHASH can access.
4815        let block_hashes = {
4816            let storage = self.blockchain.storage.read();
4817            let min_block = storage.best_number.saturating_sub(256);
4818            storage
4819                .hashes
4820                .iter()
4821                .filter(|(num, _)| (min_block..=storage.best_number).contains(*num))
4822                .map(|(&num, &hash)| (U256::from(num), hash))
4823                .collect()
4824        };
4825        self.db.write().await.set_block_hashes(block_hashes);
4826
4827        if let Some(historical_states) = state.historical_states {
4828            self.states.write().load_states(historical_states);
4829        }
4830
4831        Ok(true)
4832    }
4833
4834    /// Deserialize and add all chain data to the backend storage
4835    pub async fn load_state_bytes(&self, buf: Bytes) -> Result<bool, BlockchainError> {
4836        let orig_buf = &buf.0[..];
4837        let mut decoder = GzDecoder::new(orig_buf);
4838        let mut decoded_data = Vec::new();
4839
4840        let state: SerializableState = serde_json::from_slice(if decoder.header().is_some() {
4841            decoder
4842                .read_to_end(decoded_data.as_mut())
4843                .map_err(|_| BlockchainError::FailedToDecodeStateDump)?;
4844            &decoded_data
4845        } else {
4846            &buf.0
4847        })
4848        .map_err(|_| BlockchainError::FailedToDecodeStateDump)?;
4849
4850        self.load_state(state).await
4851    }
4852}
4853
4854impl Backend<FoundryNetwork> {
4855    /// Simulates a bundle of signed transactions and returns Flashbots-compatible results.
4856    pub async fn call_bundle(
4857        &self,
4858        bundle: EthCallBundle,
4859        transactions: Vec<PendingTransaction<FoundryTxEnvelope>>,
4860        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4861    ) -> Result<EthCallBundleResponse, BlockchainError> {
4862        let EthCallBundle {
4863            block_number,
4864            coinbase,
4865            timestamp,
4866            gas_limit,
4867            difficulty,
4868            base_fee,
4869            ..
4870        } = bundle;
4871
4872        let blob_gas_used = transactions
4873            .iter()
4874            .filter_map(|transaction| transaction.transaction.blob_gas_used())
4875            .sum::<u64>();
4876        let max_blob_gas = self.blob_params().max_blob_gas_per_block();
4877        if blob_gas_used > max_blob_gas {
4878            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
4879                "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
4880            ))));
4881        }
4882
4883        self.with_database_at(block_request, |state, mut block_env| {
4884            let state_block_number = block_env.number.to::<u64>();
4885            block_env.number = U256::from(block_number);
4886            block_env.timestamp = timestamp
4887                .map(U256::from)
4888                .unwrap_or_else(|| block_env.timestamp.saturating_add(U256::from(12)));
4889            if let Some(coinbase) = coinbase {
4890                block_env.beneficiary = coinbase;
4891            }
4892            if let Some(gas_limit) = gas_limit {
4893                block_env.gas_limit = gas_limit;
4894            }
4895            if let Some(difficulty) = difficulty {
4896                block_env.difficulty = difficulty;
4897            }
4898            if let Some(base_fee) = base_fee {
4899                block_env.basefee = base_fee.try_into().unwrap_or(u64::MAX);
4900            }
4901
4902            let mut evm_env = self.evm_env.read().clone();
4903            evm_env.block_env = block_env;
4904            let coinbase = evm_env.block_env.beneficiary;
4905            let base_fee = evm_env.block_env.basefee;
4906            let mut cache_db = CacheDB::new(state);
4907            let initial_coinbase = revm::DatabaseRef::basic_ref(&cache_db, coinbase)?
4908                .map(|account| account.balance)
4909                .unwrap_or_default();
4910            let mut coinbase_balance_before_tx = initial_coinbase;
4911            let mut coinbase_balance_after_tx = initial_coinbase;
4912            let mut total_gas_used = 0u64;
4913            let mut total_gas_fees = U256::ZERO;
4914            let mut bundle_hash = alloy_primitives::Keccak256::new();
4915            let mut results = Vec::with_capacity(transactions.len());
4916
4917            for transaction in transactions {
4918                let sender = *transaction.sender();
4919                let tx = transaction.transaction.into_inner();
4920                let tx_hash = tx.hash();
4921                bundle_hash.update(tx_hash);
4922
4923                let mut inspector = self.build_inspector();
4924                let (ResultAndState { result, state }, _) = self
4925                    .transact_envelope_with_inspector_ref(
4926                        &cache_db,
4927                        &evm_env,
4928                        &mut inspector,
4929                        &tx,
4930                        sender,
4931                    )?;
4932
4933                let gas_price = tx.effective_tip_per_gas(base_fee).unwrap_or_default();
4934                let gas_used = result.tx_gas_used();
4935                let gas_fees = U256::from(gas_used) * U256::from(gas_price);
4936                total_gas_used += gas_used;
4937                total_gas_fees += gas_fees;
4938
4939                coinbase_balance_after_tx = state
4940                    .get(&coinbase)
4941                    .map(|account| account.info.balance)
4942                    .unwrap_or(coinbase_balance_before_tx);
4943                let coinbase_diff =
4944                    coinbase_balance_after_tx.saturating_sub(coinbase_balance_before_tx);
4945                let eth_sent_to_coinbase = coinbase_diff.saturating_sub(gas_fees);
4946                coinbase_balance_before_tx = coinbase_balance_after_tx;
4947
4948                let output = result.output().cloned().unwrap_or_default();
4949                let (value, revert) =
4950                    if result.is_success() { (Some(output), None) } else { (None, Some(output)) };
4951
4952                results.push(EthCallBundleTransactionResult {
4953                    coinbase_diff,
4954                    eth_sent_to_coinbase,
4955                    from_address: sender,
4956                    gas_fees,
4957                    gas_price: U256::from(gas_price),
4958                    gas_used,
4959                    to_address: tx.to(),
4960                    tx_hash,
4961                    value,
4962                    revert,
4963                });
4964                cache_db.commit(state);
4965            }
4966
4967            let coinbase_diff = coinbase_balance_after_tx.saturating_sub(initial_coinbase);
4968            let eth_sent_to_coinbase = coinbase_diff.saturating_sub(total_gas_fees);
4969            let bundle_gas_price =
4970                coinbase_diff.checked_div(U256::from(total_gas_used)).unwrap_or_default();
4971
4972            Ok(EthCallBundleResponse {
4973                bundle_hash: bundle_hash.finalize(),
4974                bundle_gas_price,
4975                coinbase_diff,
4976                eth_sent_to_coinbase,
4977                gas_fees: total_gas_fees,
4978                results,
4979                state_block_number,
4980                total_gas_used,
4981            })
4982        })
4983        .await?
4984    }
4985
4986    /// Executes bundles of call requests and returns each call output.
4987    pub async fn call_many(
4988        &self,
4989        bundles: Vec<Bundle<WithOtherFields<TransactionRequest>>>,
4990        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4991        state_override: Option<alloy_rpc_types::state::StateOverride>,
4992    ) -> Result<Vec<Vec<EthCallResponse>>, BlockchainError> {
4993        if bundles.is_empty() {
4994            return Err(BlockchainError::RpcError(RpcError::invalid_params(
4995                "bundles are empty.".to_string(),
4996            )));
4997        }
4998
4999        self.with_database_at(block_request, |state, block_env| {
5000            let mut cache_db = CacheDB::new(state);
5001            if let Some(state_override) = state_override {
5002                apply_state_overrides(state_override, &mut cache_db)?;
5003            }
5004
5005            let mut results = Vec::with_capacity(bundles.len());
5006            for bundle in bundles {
5007                let Bundle { transactions, block_override } = bundle;
5008                let mut bundle_block_env = block_env.clone();
5009                if let Some(block_override) = block_override {
5010                    cache_db.apply_block_overrides(block_override, &mut bundle_block_env);
5011                }
5012
5013                let mut bundle_results = Vec::with_capacity(transactions.len());
5014                for request in transactions {
5015                    let fee_details = FeeDetails::new(
5016                        request.gas_price,
5017                        request.max_fee_per_gas,
5018                        request.max_priority_fee_per_gas,
5019                        request.max_fee_per_blob_gas,
5020                    )?
5021                    .or_zero_fees();
5022                    let (evm_env, tx_env, op_deposit) =
5023                        self.build_call_env(request, fee_details, bundle_block_env.clone());
5024
5025                    let mut inspector = self.build_inspector();
5026                    let ResultAndState { result, state } = self.transact_with_inspector_ref(
5027                        &cache_db,
5028                        &evm_env,
5029                        &mut inspector,
5030                        tx_env,
5031                        op_deposit,
5032                    )?;
5033
5034                    let output = result.output().cloned().unwrap_or_default();
5035                    let response = if result.is_success() {
5036                        EthCallResponse { value: Some(output), error: None }
5037                    } else {
5038                        let error = RevertDecoder::new()
5039                            .maybe_decode(&output, None)
5040                            .unwrap_or_else(|| "execution failed".to_string());
5041                        EthCallResponse { value: None, error: Some(error) }
5042                    };
5043
5044                    cache_db.commit(state);
5045                    bundle_results.push(response);
5046                }
5047
5048                results.push(bundle_results);
5049            }
5050
5051            Ok(results)
5052        })
5053        .await?
5054    }
5055
5056    /// Simulates the payload by executing the calls in request.
5057    pub async fn simulate(
5058        &self,
5059        request: SimulatePayload,
5060        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
5061    ) -> Result<Vec<SimulatedBlock<AnyRpcBlock>>, BlockchainError> {
5062        self.with_database_at(block_request, |state, mut block_env| {
5063            let SimulatePayload {
5064                block_state_calls,
5065                trace_transfers,
5066                validation,
5067                return_full_transactions,
5068            } = request;
5069            let mut cache_db = CacheDB::new(state);
5070            let mut block_res = Vec::with_capacity(block_state_calls.len());
5071            let (is_amsterdam, tx_gas_limit_cap) = {
5072                let cfg_env = &self.evm_env.read().cfg_env;
5073                (cfg_env.spec >= SpecId::AMSTERDAM, cfg_env.tx_gas_limit_cap())
5074            };
5075            let mut rpc_gas_budget = SIMULATE_GAS_CAP;
5076
5077            // execute the blocks
5078            for block in block_state_calls {
5079                let SimBlock { block_overrides, state_overrides, calls } = block;
5080                let mut call_res = Vec::with_capacity(calls.len());
5081                let mut log_index = 0;
5082                let mut cumulative_gas_used = 0;
5083                let mut block_regular_gas_used = 0;
5084                let mut block_state_gas_used = 0;
5085                let mut transactions = Vec::with_capacity(calls.len());
5086                let mut logs= Vec::new();
5087
5088                // apply state overrides before executing the transactions
5089                if let Some(state_overrides) = state_overrides {
5090                    apply_state_overrides(state_overrides, &mut cache_db)?;
5091                }
5092                if !validation {
5093                    block_env.basefee = 0;
5094                }
5095                if let Some(block_overrides) = block_overrides {
5096                    cache_db.apply_block_overrides(block_overrides, &mut block_env);
5097                }
5098
5099                // execute all calls in that block
5100                for (req_idx, mut request) in calls.into_iter().enumerate() {
5101                    let remaining_regular_gas =
5102                        block_env.gas_limit.saturating_sub(block_regular_gas_used);
5103                    let remaining_state_gas =
5104                        block_env.gas_limit.saturating_sub(block_state_gas_used);
5105                    let remaining_gas = if is_amsterdam {
5106                        remaining_regular_gas.min(remaining_state_gas)
5107                    } else {
5108                        block_env.gas_limit.saturating_sub(cumulative_gas_used)
5109                    };
5110                    let requested_gas = request.gas.unwrap_or(remaining_gas);
5111                    let exceeds_gas_limit = if is_amsterdam {
5112                        let requested_regular_gas = requested_gas.min(tx_gas_limit_cap);
5113                        requested_regular_gas > remaining_regular_gas
5114                            || requested_gas > remaining_state_gas
5115                    } else {
5116                        requested_gas > remaining_gas
5117                    };
5118                    if exceeds_gas_limit {
5119                        return Err(BlockchainError::RpcError(RpcError {
5120                            code: ErrorCode::ServerError(-38015),
5121                            message: format!(
5122                                "block gas limit exceeded: remaining {remaining_gas}, requested {requested_gas}"
5123                            )
5124                            .into(),
5125                            data: None,
5126                        }));
5127                    }
5128                    request.gas = Some(requested_gas.min(rpc_gas_budget));
5129
5130                    let caller = request.from.unwrap_or_default();
5131                    let caller_nonce = RevmDatabase::basic(&mut cache_db, caller)?
5132                        .map(|account| account.nonce)
5133                        .unwrap_or_default();
5134                    if request.nonce.is_none() {
5135                        request.nonce = Some(caller_nonce);
5136                    }
5137
5138                    let fee_details = FeeDetails::new(
5139                        request.gas_price,
5140                        request.max_fee_per_gas,
5141                        request.max_priority_fee_per_gas,
5142                        request.max_fee_per_blob_gas,
5143                    )?
5144                    .or_zero_fees();
5145
5146                    let mut execution_request = request.clone();
5147                    if !validation {
5148                        execution_request.nonce = None;
5149                    }
5150                    let (mut evm_env, tx_env, op_deposit) = self.build_call_env(
5151                        WithOtherFields::new(execution_request),
5152                        fee_details,
5153                        block_env.clone(),
5154                    );
5155
5156                    if is_amsterdam {
5157                        // Ensure simulated Amsterdam calls use EIP-8037's split gas schedule.
5158                        let spec = evm_env.cfg_env.spec;
5159                        evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec);
5160                    }
5161
5162                    // Always disable EIP-3607
5163                    evm_env.cfg_env.disable_eip3607 = true;
5164
5165                    if validation {
5166                        evm_env.cfg_env.disable_nonce_check = false;
5167                        evm_env.cfg_env.disable_base_fee = false;
5168                        evm_env.cfg_env.disable_block_gas_limit = false;
5169                    }
5170
5171                    let mut inspector = self.build_inspector();
5172
5173                    // transact
5174                    if trace_transfers {
5175                        inspector = inspector.with_transfers();
5176                    }
5177                    trace!(target: "backend", env=?evm_env, spec=?evm_env.spec_id(),"simulate evm env");
5178                    let execution_result = self.transact_with_inspector_ref(
5179                        &cache_db,
5180                        &evm_env,
5181                        &mut inspector,
5182                        tx_env,
5183                        op_deposit,
5184                    );
5185                    let ResultAndState { result, mut state } = match execution_result {
5186                        Err(BlockchainError::InvalidTransaction(error)) => {
5187                            return Err(simulate_transaction_error(error));
5188                        }
5189                        result => result?,
5190                    };
5191                    if !validation && caller_nonce == u64::MAX &&
5192                        matches!(request.to.as_ref(), Some(TxKind::Call(_))) &&
5193                        let Some(account) = state.get_mut(&caller)
5194                    {
5195                        account.info.nonce = 0;
5196                    }
5197                    trace!(target: "backend", ?result, ?request, "simulate call");
5198
5199                    inspector.print_logs();
5200                    if self.print_traces {
5201                        inspector.into_print_traces(self.call_trace_decoder.clone());
5202                    }
5203
5204                    // commit the transaction
5205                    cache_db.commit(state);
5206                    rpc_gas_budget = rpc_gas_budget.saturating_sub(result.tx_gas_used());
5207                    cumulative_gas_used =
5208                        cumulative_gas_used.saturating_add(result.tx_gas_used());
5209                    block_regular_gas_used = block_regular_gas_used
5210                        .saturating_add(result.gas().block_regular_gas_used());
5211                    block_state_gas_used = block_state_gas_used
5212                        .saturating_add(result.gas().block_state_gas_used());
5213
5214                    // create the transaction from a request
5215                    let from = caller;
5216
5217                    let mut request =
5218                        Into::<FoundryTransactionRequest>::into(WithOtherFields::new(request));
5219                    if request.as_ref().to.is_none() {
5220                        request.as_mut().to = Some(TxKind::Create);
5221                    }
5222                    request.prep_for_submission();
5223
5224                    let typed_tx = request.build_unsigned().map_err(|e| {
5225                        BlockchainError::InvalidTransactionRequest(e.to_string())
5226                    })?;
5227
5228                    let tx = build_impersonated(typed_tx);
5229                    let tx_hash = tx.hash();
5230                    let rpc_tx = transaction_build(
5231                        None,
5232                        MaybeImpersonatedTransaction::impersonated(tx, from),
5233                        None,
5234                        None,
5235                        Some(block_env.basefee),
5236                    );
5237                    transactions.push(rpc_tx);
5238
5239                    let return_data = if result.is_success() {
5240                        result.output().cloned().unwrap_or_default()
5241                    } else {
5242                        Bytes::new()
5243                    };
5244                    let sim_res = SimCallResult {
5245                        return_data,
5246                        gas_used: result.tx_gas_used(),
5247                        max_used_gas: None,
5248                        status: result.is_success(),
5249                        error: match &result {
5250                            ExecutionResult::Success { .. } => None,
5251                            ExecutionResult::Revert { output, .. } => {
5252                                let message = RevertDecoder::new()
5253                                    .maybe_decode(output, None)
5254                                    .map(|reason| format!("execution reverted: {reason}"))
5255                                    .unwrap_or_else(|| "execution reverted".to_string());
5256                                Some(SimulateError {
5257                                    code: SimulateError::EXECUTION_REVERTED_CODE,
5258                                    message,
5259                                    data: Some(output.clone()),
5260                                })
5261                            }
5262                            ExecutionResult::Halt {
5263                                reason: HaltReason::OutOfGas(_), ..
5264                            } => Some(SimulateError {
5265                                code: SimulateError::VM_EXECUTION_ERROR_CODE,
5266                                message: "out of gas".to_string(),
5267                                data: None,
5268                            }),
5269                            _ => Some(SimulateError {
5270                                code: -3200,
5271                                message: "execution failed".to_string(),
5272                                data: None,
5273                            }),
5274                        },
5275                        logs: result.clone()
5276                            .into_logs()
5277                            .into_iter()
5278                            .enumerate()
5279                            .map(|(idx, log)| Log {
5280                                inner: log,
5281                                block_number: Some(block_env.number.saturating_to()),
5282                                block_timestamp: Some(block_env.timestamp.saturating_to()),
5283                                transaction_index: Some(req_idx as u64),
5284                                log_index: Some((idx + log_index) as u64),
5285                                removed: false,
5286
5287                                block_hash: None,
5288                                transaction_hash: Some(tx_hash),
5289                            })
5290                            .collect(),
5291                    };
5292                    logs.extend(sim_res.logs.iter().map(|log| log.inner.clone()));
5293                    log_index += sim_res.logs.len();
5294                    call_res.push(sim_res);
5295                }
5296
5297                let gas_used = if is_amsterdam {
5298                    block_regular_gas_used.max(block_state_gas_used)
5299                } else {
5300                    cumulative_gas_used
5301                };
5302
5303                let transactions_envelopes: Vec<AnyTxEnvelope> = transactions
5304                .iter()
5305                .map(|tx| AnyTxEnvelope::from(tx.clone()))
5306                .collect();
5307                let header = Header {
5308                    logs_bloom: logs_bloom(logs.iter()),
5309                    transactions_root: calculate_transaction_root(&transactions_envelopes),
5310                    receipts_root: calculate_receipt_root(&transactions_envelopes),
5311                    parent_hash: Default::default(),
5312                    beneficiary: block_env.beneficiary,
5313                    state_root: Default::default(),
5314                    difficulty: Default::default(),
5315                    number: block_env.number.saturating_to(),
5316                    gas_limit: block_env.gas_limit,
5317                    gas_used,
5318                    timestamp: block_env.timestamp.saturating_to(),
5319                    extra_data: Default::default(),
5320                    mix_hash: Default::default(),
5321                    nonce: Default::default(),
5322                    base_fee_per_gas: Some(block_env.basefee),
5323                    withdrawals_root: None,
5324                    blob_gas_used: None,
5325                    excess_blob_gas: None,
5326                    parent_beacon_block_root: None,
5327                    requests_hash: None,
5328                    ..Default::default()
5329                };
5330                let mut block = alloy_rpc_types::Block {
5331                    header: AnyRpcHeader {
5332                        hash: header.hash_slow(),
5333                        inner: header.into(),
5334                        total_difficulty: None,
5335                        size: None,
5336                    },
5337                    uncles: vec![],
5338                    transactions: BlockTransactions::Full(transactions),
5339                    withdrawals: None,
5340                };
5341
5342                if !return_full_transactions {
5343                    block.transactions.convert_to_hashes();
5344                }
5345
5346                for res in &mut call_res {
5347                    res.logs.iter_mut().for_each(|log| {
5348                        log.block_hash = Some(block.header.hash);
5349                    });
5350                }
5351
5352                let simulated_block = SimulatedBlock {
5353                    inner: AnyRpcBlock::new(WithOtherFields::new(block)),
5354                    calls: call_res,
5355                };
5356
5357                // update block env
5358                block_env.number += U256::from(1);
5359                block_env.timestamp += U256::from(12);
5360                // Route through the fee manager so Tempo chains use their own base fee rules.
5361                let header = &simulated_block.inner.header;
5362                block_env.basefee = self.fees.get_next_block_base_fee_per_gas(
5363                    header.gas_used(),
5364                    header.gas_limit(),
5365                    header.base_fee_per_gas().unwrap_or_default(),
5366                );
5367
5368                block_res.push(simulated_block);
5369            }
5370
5371            Ok(block_res)
5372        })
5373        .await?
5374    }
5375
5376    pub fn get_blob_by_tx_hash(&self, hash: B256) -> Result<Option<Vec<alloy_consensus::Blob>>> {
5377        let storage = self.blockchain.storage.read();
5378        Ok(storage.transactions.get(&hash).and_then(|mined| {
5379            storage
5380                .blocks
5381                .get(&mined.block_hash)?
5382                .body
5383                .transactions
5384                .get(mined.info.transaction_index as usize)?
5385                .as_ref()
5386                .sidecar()
5387                .map(|sidecar| sidecar.sidecar.blobs().to_vec())
5388        }))
5389    }
5390
5391    /// Sets the fee token for a user address (Tempo-only).
5392    pub async fn set_fee_token(&self, user: Address, token: Address) -> DatabaseResult<()> {
5393        self.with_tempo_storage(|| {
5394            let mut fee_manager = TipFeeManager::new();
5395            fee_manager
5396                .set_user_token(user, IFeeManager::setUserTokenCall { token })
5397                .map_err(tempo_db_err)
5398        })
5399        .await
5400    }
5401
5402    /// Sets the fee token for a validator address (Tempo-only).
5403    pub async fn set_validator_fee_token(
5404        &self,
5405        validator: Address,
5406        token: Address,
5407    ) -> DatabaseResult<()> {
5408        self.with_tempo_storage(|| {
5409            let mut fee_manager = TipFeeManager::new();
5410            // Use Address::ZERO as beneficiary so the check `sender != beneficiary` passes
5411            fee_manager
5412                .set_validator_token(
5413                    validator,
5414                    IFeeManager::setValidatorTokenCall { token },
5415                    Address::ZERO,
5416                )
5417                .map_err(tempo_db_err)
5418        })
5419        .await
5420    }
5421
5422    /// Mints FeeAMM liquidity for a token pair (Tempo-only).
5423    pub async fn set_fee_amm_liquidity(
5424        &self,
5425        user_token: Address,
5426        validator_token: Address,
5427        amount: U256,
5428    ) -> DatabaseResult<()> {
5429        // T3+ rejects minting to the zero address.
5430        let admin = Address::repeat_byte(0x11);
5431        self.with_tempo_storage(|| {
5432            // Mint the required tokens to admin so it can provide liquidity.
5433            // grant_role_internal bypasses the caller check, matching genesis seeding.
5434            for &token_address in &[user_token, validator_token] {
5435                let mut token = TIP20Token::from_address(token_address).map_err(tempo_db_err)?;
5436                token.grant_role_internal(admin, *ISSUER_ROLE).map_err(tempo_db_err)?;
5437                token.mint(admin, ITIP20::mintCall { to: admin, amount }).map_err(tempo_db_err)?;
5438            }
5439            let mut fee_manager = TipFeeManager::new();
5440            fee_manager
5441                .mint(admin, user_token, validator_token, amount, admin)
5442                .map_err(tempo_db_err)?;
5443            Ok(())
5444        })
5445        .await
5446    }
5447
5448    /// Sets an account's balance for a deployed TIP-20 token (Tempo-only).
5449    pub async fn set_tip20_balance(
5450        &self,
5451        address: Address,
5452        token_address: Address,
5453        balance: U256,
5454    ) -> DatabaseResult<()> {
5455        if self.try_set_tip20_balance(address, token_address, balance).await? {
5456            return Ok(());
5457        }
5458
5459        Err(tempo_db_err(format!("address {token_address} is not a deployed TIP-20 token")))
5460    }
5461
5462    /// Sets an account's balance if the address is a deployed TIP-20 token (Tempo-only).
5463    pub async fn try_set_tip20_balance(
5464        &self,
5465        address: Address,
5466        token_address: Address,
5467        balance: U256,
5468    ) -> DatabaseResult<bool> {
5469        self.with_tempo_storage(|| {
5470            if !TIP20Factory::new().is_tip20(token_address).map_err(tempo_db_err)? {
5471                return Ok(false);
5472            }
5473
5474            let mut token = TIP20Token::from_address(token_address).map_err(tempo_db_err)?;
5475            token.balances[address].write(balance).map_err(tempo_db_err)?;
5476            Ok(true)
5477        })
5478        .await
5479    }
5480
5481    /// Runs `f` inside a Tempo storage context initialized from the current state
5482    /// (Tempo-only).
5483    async fn with_tempo_storage<R>(&self, f: impl FnOnce() -> R) -> R {
5484        let hardfork = self.hardfork();
5485        // One consistent snapshot of the current env to build the storage context.
5486        let (chain_id, timestamp, block_number) = {
5487            let env = self.evm_env.read();
5488            (
5489                env.cfg_env.chain_id,
5490                U256::from(env.block_env.timestamp),
5491                env.block_env.number.to::<u64>(),
5492            )
5493        };
5494        let mut db = self.db.write().await;
5495        let mut storage = AnvilStorageProvider::new(
5496            &mut **db,
5497            chain_id,
5498            timestamp,
5499            block_number,
5500            hardfork.into(),
5501        );
5502        StorageCtx::enter(&mut storage, f)
5503    }
5504}
5505
5506/// Converts a Tempo error into an anvil [`DatabaseError`].
5507fn tempo_db_err<E: std::fmt::Display>(e: E) -> DatabaseError {
5508    DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))
5509}
5510
5511/// Get max nonce from transaction pool by address.
5512fn get_pool_transactions_nonce(
5513    pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
5514    address: Address,
5515) -> Option<u64> {
5516    if let Some(highest_nonce) = pool_transactions
5517        .iter()
5518        .filter(|tx| {
5519            *tx.pending_transaction.sender() == address
5520                && !tx.pending_transaction.transaction.as_ref().has_nonzero_tempo_nonce_key()
5521        })
5522        .map(|tx| tx.pending_transaction.nonce())
5523        .max()
5524    {
5525        let tx_count = highest_nonce.saturating_add(1);
5526        return Some(tx_count);
5527    }
5528    None
5529}
5530
5531#[async_trait::async_trait]
5532impl<N: Network> TransactionValidator<FoundryTxEnvelope> for Backend<N>
5533where
5534    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
5535{
5536    async fn validate_pool_transaction(
5537        &self,
5538        tx: &PendingTransaction<FoundryTxEnvelope>,
5539    ) -> Result<(), BlockchainError> {
5540        let address = *tx.sender();
5541        let account = self.get_account(address).await?;
5542        let evm_env = self.next_evm_env();
5543
5544        // Tempo AA: validate time bounds and fee token balance (async checks)
5545        if let FoundryTxEnvelope::Tempo(aa_tx) = tx.transaction.as_ref() {
5546            let tempo_tx = aa_tx.tx();
5547            let current_time = evm_env.block_env.timestamp.saturating_to::<u64>();
5548
5549            // Reject if valid_before is expired or too close to current time (< 3 seconds)
5550            const AA_VALID_BEFORE_MIN_SECS: u64 = 3;
5551            if let Some(valid_before) = tempo_tx.valid_before.map(|v| v.get()) {
5552                let min_allowed = current_time.saturating_add(AA_VALID_BEFORE_MIN_SECS);
5553                if valid_before <= min_allowed {
5554                    return Err(InvalidTransactionError::TempoValidBeforeExpired {
5555                        valid_before,
5556                        min_allowed,
5557                    }
5558                    .into());
5559                }
5560            }
5561
5562            // Reject if valid_after is too far in the future (> 1 hour)
5563            const AA_VALID_AFTER_MAX_SECS: u64 = 3600;
5564            if let Some(valid_after) = tempo_tx.valid_after.map(|v| v.get()) {
5565                let max_allowed = current_time.saturating_add(AA_VALID_AFTER_MAX_SECS);
5566                if valid_after > max_allowed {
5567                    return Err(InvalidTransactionError::TempoValidAfterTooFar {
5568                        valid_after,
5569                        max_allowed,
5570                    }
5571                    .into());
5572                }
5573            }
5574
5575            // Fee token balance check
5576            let fee_payer = tempo_tx.recover_fee_payer(address).unwrap_or(address);
5577            let fee_token =
5578                tempo_tx.fee_token.unwrap_or(foundry_evm::core::tempo::PATH_USD_ADDRESS);
5579
5580            // gas_limit * max_fee_per_gas in wei, scaled to 6-decimal token units
5581            let required_wei =
5582                U256::from(tempo_tx.gas_limit).saturating_mul(U256::from(tempo_tx.max_fee_per_gas));
5583            let required = required_wei / U256::from(10u64.pow(12));
5584
5585            let balance = self.get_fee_token_balance(fee_token, fee_payer).await?;
5586            if balance < required {
5587                return Err(InvalidTransactionError::TempoInsufficientFeeTokenBalance {
5588                    balance,
5589                    required,
5590                }
5591                .into());
5592            }
5593        }
5594
5595        Ok(self.validate_pool_transaction_for(tx, &account, &evm_env)?)
5596    }
5597
5598    fn validate_pool_transaction_for(
5599        &self,
5600        pending: &PendingTransaction<FoundryTxEnvelope>,
5601        account: &AccountInfo,
5602        evm_env: &EvmEnv,
5603    ) -> Result<(), InvalidTransactionError> {
5604        let tx = &pending.transaction;
5605
5606        if let Some(tx_chain_id) = tx.chain_id() {
5607            let chain_id = self.chain_id();
5608            if chain_id.to::<u64>() != tx_chain_id {
5609                if let FoundryTxEnvelope::Legacy(tx) = tx.as_ref() {
5610                    // <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md>
5611                    if evm_env.cfg_env.spec >= SpecId::SPURIOUS_DRAGON && tx.chain_id().is_none() {
5612                        debug!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V");
5613                        return Err(InvalidTransactionError::IncompatibleEIP155);
5614                    }
5615                } else {
5616                    debug!(target: "backend", ?chain_id, ?tx_chain_id, "invalid chain id");
5617                    return Err(InvalidTransactionError::InvalidChainId);
5618                }
5619            }
5620        }
5621
5622        // Reject native value transfers on Tempo networks
5623        if self.is_tempo() && !tx.value().is_zero() {
5624            warn!(target: "backend", "[{:?}] native value transfer not allowed in Tempo mode", tx.hash());
5625            return Err(InvalidTransactionError::TempoNativeValueTransfer);
5626        }
5627
5628        // Tempo AA T5: cap authorization list size
5629        if self.is_tempo_hardfork_active(TempoHardfork::T5)
5630            && let FoundryTxEnvelope::Tempo(aa_tx) = tx.as_ref()
5631        {
5632            const MAX_TEMPO_AUTHORIZATIONS: usize = 16;
5633            let auth_count = aa_tx.tx().tempo_authorization_list.len();
5634            if auth_count > MAX_TEMPO_AUTHORIZATIONS {
5635                warn!(target: "backend", "[{:?}] Tempo tx has too many authorizations: {}", tx.hash(), auth_count);
5636                return Err(InvalidTransactionError::TempoTooManyAuthorizations {
5637                    count: auth_count,
5638                    max: MAX_TEMPO_AUTHORIZATIONS,
5639                });
5640            }
5641        }
5642
5643        // Nonce validation — skip for deposits (L1→L2) and Tempo txs (2D nonce system)
5644        #[cfg(feature = "optimism")]
5645        let is_deposit_tx = pending.transaction.as_ref().is_deposit();
5646        #[cfg(not(feature = "optimism"))]
5647        let is_deposit_tx = false;
5648        let is_tempo_tx = pending.transaction.as_ref().is_tempo();
5649        let nonce = tx.nonce();
5650        if nonce < account.nonce && !is_deposit_tx && !is_tempo_tx {
5651            debug!(target: "backend", "[{:?}] nonce too low", tx.hash());
5652            return Err(InvalidTransactionError::NonceTooLow);
5653        }
5654
5655        // EIP-4844 structural validation
5656        if evm_env.cfg_env.spec >= SpecId::CANCUN && tx.is_eip4844() {
5657            // Heavy (blob validation) checks
5658            let blob_tx = match tx.as_ref() {
5659                FoundryTxEnvelope::Eip4844(tx) => tx.tx(),
5660                _ => unreachable!(),
5661            };
5662
5663            let blob_count = blob_tx.tx().blob_versioned_hashes.len();
5664
5665            // Ensure there are blob hashes.
5666            if blob_count == 0 {
5667                return Err(InvalidTransactionError::NoBlobHashes);
5668            }
5669
5670            // Ensure the tx does not exceed the max blobs per transaction.
5671            let max_blobs_per_tx = self.blob_params().max_blobs_per_tx as usize;
5672            if blob_count > max_blobs_per_tx {
5673                return Err(InvalidTransactionError::TooManyBlobs(blob_count, max_blobs_per_tx));
5674            }
5675
5676            // Check for any blob validation errors if not impersonating.
5677            if !self.skip_blob_validation(Some(*pending.sender()))
5678                && let Err(err) = blob_tx.validate(EnvKzgSettings::default().get())
5679            {
5680                return Err(InvalidTransactionError::BlobTransactionValidationError(err));
5681            }
5682        }
5683
5684        // EIP-3860 initcode size validation, respects --code-size-limit / --disable-code-size-limit
5685        if evm_env.cfg_env.spec >= SpecId::SHANGHAI && tx.kind() == TxKind::Create {
5686            let max_initcode_size = evm_env
5687                .cfg_env
5688                .limit_contract_code_size
5689                .map(|limit| limit.saturating_mul(2))
5690                .unwrap_or(revm::primitives::eip3860::MAX_INITCODE_SIZE);
5691            if tx.input().len() > max_initcode_size {
5692                return Err(InvalidTransactionError::MaxInitCodeSizeExceeded);
5693            }
5694        }
5695
5696        // Balance and fee related checks
5697        if !self.disable_pool_balance_checks {
5698            // Gas limit validation
5699            if tx.gas_limit() < MIN_TRANSACTION_GAS as u64 {
5700                debug!(target: "backend", "[{:?}] gas too low", tx.hash());
5701                return Err(InvalidTransactionError::GasTooLow);
5702            }
5703
5704            // Check tx gas limit against block gas limit, if block gas limit is set.
5705            if !evm_env.cfg_env.disable_block_gas_limit
5706                && tx.gas_limit() > evm_env.block_env.gas_limit
5707            {
5708                debug!(target: "backend", "[{:?}] gas too high", tx.hash());
5709                return Err(InvalidTransactionError::GasTooHigh(ErrDetail {
5710                    detail: String::from("tx.gas_limit > env.block.gas_limit"),
5711                }));
5712            }
5713
5714            // Check tx gas limit against tx gas limit cap (Osaka hard fork and later).
5715            if evm_env.cfg_env.tx_gas_limit_cap.is_none()
5716                && tx.gas_limit() > evm_env.cfg_env().tx_gas_limit_cap()
5717            {
5718                debug!(target: "backend", "[{:?}] gas too high", tx.hash());
5719                return Err(InvalidTransactionError::GasTooHigh(ErrDetail {
5720                    detail: String::from("tx.gas_limit > env.cfg.tx_gas_limit_cap"),
5721                }));
5722            }
5723
5724            // EIP-1559 fee validation (London hard fork and later).
5725            if evm_env.cfg_env.spec >= SpecId::LONDON {
5726                if tx.max_fee_per_gas() < evm_env.block_env.basefee.into() && !is_deposit_tx {
5727                    debug!(target: "backend", "max fee per gas={}, too low, block basefee={}", tx.max_fee_per_gas(), evm_env.block_env.basefee);
5728                    return Err(InvalidTransactionError::FeeCapTooLow);
5729                }
5730
5731                if let (Some(max_priority_fee_per_gas), max_fee_per_gas) =
5732                    (tx.as_ref().max_priority_fee_per_gas(), tx.as_ref().max_fee_per_gas())
5733                    && max_priority_fee_per_gas > max_fee_per_gas
5734                {
5735                    debug!(target: "backend", "max priority fee per gas={}, too high, max fee per gas={}", max_priority_fee_per_gas, max_fee_per_gas);
5736                    return Err(InvalidTransactionError::TipAboveFeeCap);
5737                }
5738            }
5739
5740            // EIP-4844 blob fee validation
5741            if evm_env.cfg_env.spec >= SpecId::CANCUN
5742                && tx.is_eip4844()
5743                && let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas()
5744                && let Some(blob_gas_and_price) = &evm_env.block_env.blob_excess_gas_and_price
5745                && max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice
5746            {
5747                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);
5748                return Err(InvalidTransactionError::BlobFeeCapTooLow(
5749                    max_fee_per_blob_gas,
5750                    blob_gas_and_price.blob_gasprice,
5751                ));
5752            }
5753
5754            let max_cost =
5755                (tx.gas_limit() as u128).saturating_mul(tx.max_fee_per_gas()).saturating_add(
5756                    tx.blob_gas_used()
5757                        .map(|g| g as u128)
5758                        .unwrap_or(0)
5759                        .mul(tx.max_fee_per_blob_gas().unwrap_or(0)),
5760                );
5761            let value = tx.value();
5762            match tx.as_ref() {
5763                #[cfg(feature = "optimism")]
5764                FoundryTxEnvelope::Deposit(deposit_tx) => {
5765                    // Deposit transactions
5766                    // https://specs.optimism.io/protocol/deposits.html#execution
5767                    // 1. no gas cost check required since already have prepaid gas from L1
5768                    // 2. increment account balance by deposited amount before checking for
5769                    //    sufficient funds `tx.value <= existing account value + deposited value`
5770                    if value > account.balance + U256::from(deposit_tx.mint) {
5771                        debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + U256::from(deposit_tx.mint), value, *pending.sender());
5772                        return Err(InvalidTransactionError::InsufficientFunds);
5773                    }
5774                }
5775                FoundryTxEnvelope::Tempo(_) => {
5776                    // Tempo AA transactions pay gas with fee tokens, not ETH.
5777                    // Fee token balance is validated in validate_pool_transaction (async).
5778                }
5779                _ => {
5780                    // check sufficient funds: `gas * price + value`
5781                    let req_funds =
5782                        max_cost.checked_add(value.saturating_to()).ok_or_else(|| {
5783                            debug!(target: "backend", "[{:?}] cost too high", tx.hash());
5784                            InvalidTransactionError::InsufficientFunds
5785                        })?;
5786                    if account.balance < U256::from(req_funds) {
5787                        debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender());
5788                        return Err(InvalidTransactionError::InsufficientFunds);
5789                    }
5790                }
5791            }
5792        }
5793        Ok(())
5794    }
5795
5796    fn validate_for(
5797        &self,
5798        tx: &PendingTransaction<FoundryTxEnvelope>,
5799        account: &AccountInfo,
5800        evm_env: &EvmEnv,
5801    ) -> Result<(), InvalidTransactionError> {
5802        self.validate_pool_transaction_for(tx, account, evm_env)?;
5803        if tx.nonce() > account.nonce {
5804            return Err(InvalidTransactionError::NonceTooHigh);
5805        }
5806        Ok(())
5807    }
5808}
5809
5810/// Replaces the cached hash of a [`Signed`] transaction, preserving the inner tx and signature.
5811fn rehash<T>(signed: Signed<T>, hash: B256) -> Signed<T>
5812where
5813    T: alloy_consensus::transaction::RlpEcdsaEncodableTx,
5814{
5815    let (t, sig, _) = signed.into_parts();
5816    Signed::new_unchecked(t, sig, hash)
5817}
5818
5819/// Creates a `AnyRpcTransaction` as it's expected for the `eth` RPC api from storage data
5820pub fn transaction_build(
5821    tx_hash: Option<B256>,
5822    eth_transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>,
5823    block: Option<&Block>,
5824    info: Option<TransactionInfo>,
5825    base_fee: Option<u64>,
5826) -> AnyRpcTransaction {
5827    #[cfg(feature = "optimism")]
5828    if let FoundryTxEnvelope::Deposit(deposit_tx) = eth_transaction.as_ref() {
5829        let dep_tx = deposit_tx;
5830
5831        let ser = serde_json::to_value(dep_tx).expect("could not serialize TxDeposit");
5832        let maybe_deposit_fields = OtherFields::try_from(ser);
5833
5834        match maybe_deposit_fields {
5835            Ok(mut fields) => {
5836                // Add zeroed signature fields for backwards compatibility
5837                // https://specs.optimism.io/protocol/deposits.html#the-deposited-transaction-type
5838                fields.insert("v".to_string(), serde_json::to_value("0x0").unwrap());
5839                fields.insert("r".to_string(), serde_json::to_value(B256::ZERO).unwrap());
5840                fields.insert(String::from("s"), serde_json::to_value(B256::ZERO).unwrap());
5841                fields.insert(String::from("nonce"), serde_json::to_value("0x0").unwrap());
5842
5843                let inner = UnknownTypedTransaction {
5844                    ty: AnyTxType(DEPOSIT_TX_TYPE_ID),
5845                    fields,
5846                    memo: Default::default(),
5847                };
5848
5849                let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
5850                    hash: eth_transaction.hash(),
5851                    inner,
5852                });
5853
5854                let tx = Transaction {
5855                    inner: Recovered::new_unchecked(envelope, deposit_tx.from),
5856                    block_hash: block
5857                        .as_ref()
5858                        .map(|block| B256::from(keccak256(alloy_rlp::encode(&block.header)))),
5859                    block_number: block.as_ref().map(|block| block.header.number()),
5860                    transaction_index: info.as_ref().map(|info| info.transaction_index),
5861                    effective_gas_price: None,
5862                    block_timestamp: block.as_ref().map(|block| block.header.timestamp()),
5863                };
5864
5865                return AnyRpcTransaction::from(WithOtherFields::new(tx));
5866            }
5867            Err(_) => {
5868                error!(target: "backend", "failed to serialize deposit transaction");
5869            }
5870        }
5871    }
5872
5873    if let FoundryTxEnvelope::Tempo(tempo_tx) = eth_transaction.as_ref() {
5874        let from = eth_transaction.recover().unwrap_or_default();
5875        let ser = serde_json::to_value(tempo_tx).expect("could not serialize Tempo transaction");
5876        let maybe_tempo_fields = OtherFields::try_from(ser);
5877
5878        match maybe_tempo_fields {
5879            Ok(fields) => {
5880                let inner = UnknownTypedTransaction {
5881                    ty: AnyTxType(TEMPO_TX_TYPE_ID),
5882                    fields,
5883                    memo: Default::default(),
5884                };
5885
5886                let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
5887                    hash: eth_transaction.hash(),
5888                    inner,
5889                });
5890
5891                let tx = Transaction {
5892                    inner: Recovered::new_unchecked(envelope, from),
5893                    block_hash: block.as_ref().map(|block| block.header.hash_slow()),
5894                    block_number: block.as_ref().map(|block| block.header.number()),
5895                    transaction_index: info.as_ref().map(|info| info.transaction_index),
5896                    effective_gas_price: None,
5897                    block_timestamp: block.as_ref().map(|block| block.header.timestamp()),
5898                };
5899
5900                return AnyRpcTransaction::from(WithOtherFields::new(tx));
5901            }
5902            Err(_) => {
5903                error!(target: "backend", "failed to serialize tempo transaction");
5904            }
5905        }
5906    }
5907
5908    let from = eth_transaction.recover().unwrap_or_default();
5909    let effective_gas_price = eth_transaction.effective_gas_price(base_fee);
5910
5911    // if a specific hash was provided we update the transaction's hash
5912    // This is important for impersonated transactions since they all use the
5913    // `BYPASS_SIGNATURE` which would result in different hashes
5914    // Note: for impersonated transactions this only concerns pending transactions because
5915    // there's no `info` yet.
5916    let hash = tx_hash.unwrap_or_else(|| eth_transaction.hash());
5917
5918    let eth_envelope = FoundryTxEnvelope::from(eth_transaction)
5919        .try_into_eth()
5920        .expect("non-standard transactions are handled above");
5921
5922    let envelope = match eth_envelope {
5923        TxEnvelope::Legacy(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Legacy(rehash(s, hash))),
5924        TxEnvelope::Eip1559(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip1559(rehash(s, hash))),
5925        TxEnvelope::Eip2930(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip2930(rehash(s, hash))),
5926        TxEnvelope::Eip4844(s) => {
5927            let s = if block.is_some() { s.map(TxEip4844Variant::drop_sidecar) } else { s };
5928            AnyTxEnvelope::Ethereum(TxEnvelope::Eip4844(rehash(s, hash)))
5929        }
5930        TxEnvelope::Eip7702(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(rehash(s, hash))),
5931    };
5932
5933    let tx = Transaction {
5934        inner: Recovered::new_unchecked(envelope, from),
5935        block_hash: block.as_ref().map(|block| block.header.hash_slow()),
5936        block_number: block.as_ref().map(|block| block.header.number()),
5937        transaction_index: info.as_ref().map(|info| info.transaction_index),
5938        // deprecated
5939        effective_gas_price: Some(effective_gas_price),
5940        block_timestamp: block.as_ref().map(|block| block.header.timestamp()),
5941    };
5942    AnyRpcTransaction::from(WithOtherFields::new(tx))
5943}
5944
5945/// Prove a storage key's existence or nonexistence in the account's storage trie.
5946///
5947/// `storage_key` is the hash of the desired storage key, meaning
5948/// this will only work correctly under a secure trie.
5949/// `storage_key` == keccak(key)
5950pub fn prove_storage(
5951    storage: &alloy_primitives::map::U256Map<U256>,
5952    keys: &[B256],
5953) -> (B256, Vec<Vec<Bytes>>) {
5954    let keys: Vec<_> = keys.iter().map(|key| Nibbles::unpack(keccak256(key))).collect();
5955
5956    let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(keys.clone()));
5957
5958    for (key, value) in trie_storage(storage) {
5959        builder.add_leaf(key, &value);
5960    }
5961
5962    let root = builder.root();
5963
5964    let mut proofs = Vec::new();
5965    let all_proof_nodes = builder.take_proof_nodes();
5966
5967    for proof_key in keys {
5968        // Iterate over all proof nodes and find the matching ones.
5969        // The filtered results are guaranteed to be in order.
5970        let matching_proof_nodes =
5971            all_proof_nodes.matching_nodes_sorted(&proof_key).into_iter().map(|(_, node)| node);
5972        proofs.push(matching_proof_nodes.collect());
5973    }
5974
5975    (root, proofs)
5976}
5977
5978pub fn is_arbitrum(chain_id: u64) -> bool {
5979    if let Ok(chain) = NamedChain::try_from(chain_id) {
5980        return chain.is_arbitrum();
5981    }
5982    false
5983}
5984
5985fn simulate_transaction_error(error: InvalidTransactionError) -> BlockchainError {
5986    let code = match &error {
5987        InvalidTransactionError::NonceTooLow => -38010,
5988        InvalidTransactionError::NonceTooHigh => -38011,
5989        InvalidTransactionError::NonceMaxValue => -32603,
5990        InvalidTransactionError::FeeCapTooLow => -38012,
5991        InvalidTransactionError::GasTooLow | InvalidTransactionError::GasTooHigh(_) => -38013,
5992        InvalidTransactionError::InsufficientFunds
5993        | InvalidTransactionError::InsufficientFundsForTransfer => -38014,
5994        _ => return BlockchainError::InvalidTransaction(error),
5995    };
5996
5997    BlockchainError::RpcError(RpcError {
5998        code: ErrorCode::from(code),
5999        message: format!("err: {error}").into(),
6000        data: None,
6001    })
6002}
6003
6004/// Unpacks an [`ExecutionResult`] into its exit reason, gas used, output, and logs.
6005fn unpack_execution_result<H: IntoInstructionResult>(
6006    result: ExecutionResult<H>,
6007) -> (InstructionResult, u64, Option<Output>, Vec<revm::primitives::Log>) {
6008    match result {
6009        ExecutionResult::Success { reason, gas, output, logs, .. } => {
6010            (reason.into(), gas.tx_gas_used(), Some(output), logs)
6011        }
6012        ExecutionResult::Revert { gas, output, logs, .. } => {
6013            (InstructionResult::Revert, gas.tx_gas_used(), Some(Output::Call(output)), logs)
6014        }
6015        ExecutionResult::Halt { reason, gas, logs, .. } => {
6016            (reason.into_instruction_result(), gas.tx_gas_used(), None, logs)
6017        }
6018    }
6019}
6020
6021/// Converts a halt reason into an [`InstructionResult`].
6022///
6023/// Abstracts over network-specific halt reason types (`HaltReason`, `OpHaltReason`)
6024/// so that anvil code doesn't need to match on each variant directly.
6025pub use foundry_evm::core::evm::IntoInstructionResult;
6026
6027#[cfg(test)]
6028mod tests {
6029    use crate::{NodeConfig, spawn};
6030
6031    #[tokio::test]
6032    async fn test_deterministic_block_mining() {
6033        // Test that mine_block produces deterministic block hashes with same initial conditions
6034        let genesis_timestamp = 1743944919u64;
6035
6036        // Create two identical backends
6037        let config_a = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into());
6038        let config_b = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into());
6039
6040        let (api_a, _handle_a) = spawn(config_a).await;
6041        let (api_b, _handle_b) = spawn(config_b).await;
6042
6043        // Mine empty blocks (no transactions) on both backends
6044        let outcome_a_1 = api_a.backend.mine_block(vec![]).await;
6045        let outcome_b_1 = api_b.backend.mine_block(vec![]).await;
6046
6047        // Both should mine the same block number
6048        assert_eq!(outcome_a_1.block_number, outcome_b_1.block_number);
6049
6050        // Get the actual blocks to compare hashes
6051        let block_a_1 =
6052            api_a.block_by_number(outcome_a_1.block_number.into()).await.unwrap().unwrap();
6053        let block_b_1 =
6054            api_b.block_by_number(outcome_b_1.block_number.into()).await.unwrap().unwrap();
6055
6056        // The block hashes should be identical
6057        assert_eq!(
6058            block_a_1.header.hash, block_b_1.header.hash,
6059            "Block hashes should be deterministic. Got {} vs {}",
6060            block_a_1.header.hash, block_b_1.header.hash
6061        );
6062
6063        // Mine another block to ensure it remains deterministic
6064        let outcome_a_2 = api_a.backend.mine_block(vec![]).await;
6065        let outcome_b_2 = api_b.backend.mine_block(vec![]).await;
6066
6067        let block_a_2 =
6068            api_a.block_by_number(outcome_a_2.block_number.into()).await.unwrap().unwrap();
6069        let block_b_2 =
6070            api_b.block_by_number(outcome_b_2.block_number.into()).await.unwrap().unwrap();
6071
6072        assert_eq!(
6073            block_a_2.header.hash, block_b_2.header.hash,
6074            "Second block hashes should also be deterministic. Got {} vs {}",
6075            block_a_2.header.hash, block_b_2.header.hash
6076        );
6077
6078        // Ensure the blocks are different (sanity check)
6079        assert_ne!(
6080            block_a_1.header.hash, block_a_2.header.hash,
6081            "Different blocks should have different hashes"
6082        );
6083    }
6084}