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