//! Support for forking off another client

use crate::eth::{backend::db::Db, error::BlockchainError};
use alloy_chains::NamedChain;
use alloy_consensus::{BlockHeader, TrieAccount};
use alloy_eips::eip2930::AccessListResult;
use alloy_network::{
    AnyNetwork, AnyRpcBlock, BlockResponse, Network, TransactionResponse,
    primitives::HeaderResponse,
};
use alloy_primitives::{
    Address, B256, Bytes, StorageValue, U256,
    map::{FbHashMap, HashMap, HashSet},
};
use alloy_provider::{
    Provider,
    ext::{DebugApi, TraceApi},
};
use alloy_rpc_types::{
    BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse,
    FeeHistory, Filter, Index, Log,
    request::TransactionRequest,
    simulate::{SimulatePayload, SimulatedBlock},
    state::StateOverride,
    trace::{
        geth::{GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, TraceResult},
        opcode::{BlockOpcodeGas, TransactionOpcodeGas},
        parity::{
            LocalizedTransactionTrace as Trace, TraceResults, TraceResultsWithTransactionHash,
            TraceType,
        },
    },
};
use alloy_rpc_types_eth::{AccountInfo, Bundle, EthCallResponse, StateContext};
use alloy_rpc_types_mev::{EthCallBundle, EthCallBundleResponse};
use alloy_serde::WithOtherFields;
use alloy_transport::TransportError;
use foundry_common::provider::{ProviderBuilder, RetryProvider};
use foundry_evm::hardfork::FoundryHardfork;
use foundry_primitives::FoundryTxReceipt;
use parking_lot::{
    RawRwLock, RwLock,
    lock_api::{RwLockReadGuard, RwLockWriteGuard},
};
use revm::context_interface::block::BlobExcessGasAndPrice;
use std::{sync::Arc, time::Duration};
use tokio::sync::RwLock as AsyncRwLock;

/// Ensures the fork network can be executed by Anvil's EVM backend.
pub(crate) fn ensure_fork_network_supported(chain_id: u64) -> Result<(), BlockchainError> {
    if matches!(NamedChain::try_from(chain_id), Ok(NamedChain::ZkSync | NamedChain::ZkSyncTestnet))
    {
        return Err(BlockchainError::UnsupportedForkNetwork {
            chain_id,
            reason: "Anvil's EVM backend cannot execute native EraVM bytecode; use `anvil-zksync` for zkSync Era forks",
        });
    }
    Ok(())
}

/// Represents a fork of a remote client
///
/// This type contains a subset of the [`EthApi`](crate::eth::EthApi) functions but will exclusively
/// fetch the requested data from the remote client, if it wasn't already fetched.
#[derive(Clone, Debug)]
pub struct ClientFork<N: Network = AnyNetwork> {
    /// Contains the cached data
    pub storage: Arc<RwLock<ForkedStorage<N>>>,
    /// contains the info how the fork is configured
    // Wrapping this in a lock, ensures we can update this on the fly via additional custom RPC
    // endpoints
    pub config: Arc<RwLock<ClientForkConfig<N>>>,
    /// This also holds a handle to the underlying database
    pub database: Arc<AsyncRwLock<Box<dyn Db>>>,
    /// The RPC URL associated with the state in the underlying database.
    database_rpc_url: Arc<RwLock<Option<String>>>,
}

impl<N: Network> ClientFork<N> {
    /// Creates a new instance of the fork
    pub fn new(config: ClientForkConfig<N>, database: Arc<AsyncRwLock<Box<dyn Db>>>) -> Self {
        let database_rpc_url = config.eth_rpc_url().map(ToOwned::to_owned);
        Self {
            storage: Default::default(),
            config: Arc::new(RwLock::new(config)),
            database,
            database_rpc_url: Arc::new(RwLock::new(database_rpc_url)),
        }
    }

    /// Removes all data cached from previous responses
    pub fn clear_cached_storage(&self) {
        self.storage.write().clear()
    }

    /// Returns true whether the block predates the fork
    pub fn predates_fork(&self, block: u64) -> bool {
        block < self.block_number()
    }

    /// Returns true whether the block predates the fork _or_ is the same block as the fork
    pub fn predates_fork_inclusive(&self, block: u64) -> bool {
        block <= self.block_number()
    }

    pub fn timestamp(&self) -> u64 {
        self.config.read().timestamp
    }

    pub fn block_number(&self) -> u64 {
        self.config.read().block_number
    }

    /// Returns the transaction hash we forked off of, if any.
    pub fn transaction_hash(&self) -> Option<B256> {
        self.config.read().transaction_hash
    }

    pub fn total_difficulty(&self) -> U256 {
        self.config.read().total_difficulty
    }

    pub fn base_fee(&self) -> Option<u128> {
        self.config.read().base_fee
    }

    pub fn block_hash(&self) -> B256 {
        self.config.read().block_hash
    }

    pub fn eth_rpc_url(&self) -> Option<String> {
        self.config.read().eth_rpc_url().map(|s| s.to_string())
    }

    pub(crate) fn database_rpc_url(&self) -> Option<String> {
        self.database_rpc_url.read().clone()
    }

    pub(crate) fn set_database_rpc_url(&self, url: Option<String>) {
        *self.database_rpc_url.write() = url;
    }

    pub fn chain_id(&self) -> u64 {
        self.config.read().chain_id
    }

    fn provider(&self) -> Arc<RetryProvider<N>> {
        self.config.read().provider.clone()
    }

    fn storage_read(&self) -> RwLockReadGuard<'_, RawRwLock, ForkedStorage<N>> {
        self.storage.read()
    }

    fn storage_write(&self) -> RwLockWriteGuard<'_, RawRwLock, ForkedStorage<N>> {
        self.storage.write()
    }

    /// Returns the fee history  `eth_feeHistory`
    pub async fn fee_history(
        &self,
        block_count: u64,
        newest_block: BlockNumber,
        reward_percentiles: &[f64],
    ) -> Result<FeeHistory, TransportError> {
        self.provider().get_fee_history(block_count, newest_block, reward_percentiles).await
    }

    /// Sends `eth_getProof`
    pub async fn get_proof(
        &self,
        address: Address,
        keys: Vec<B256>,
        block_number: Option<BlockId>,
    ) -> Result<EIP1186AccountProofResponse, TransportError> {
        self.provider().get_proof(address, keys).block_id(block_number.unwrap_or_default()).await
    }

    /// Sends `eth_getBlockAccessList`
    pub async fn block_access_list(
        &self,
        block_id: BlockId,
    ) -> Result<Option<serde_json::Value>, TransportError> {
        self.provider().raw_request("eth_getBlockAccessList".into(), (block_id,)).await
    }

    /// Sends `eth_getBlockAccessListByBlockHash`
    pub async fn block_access_list_by_hash(
        &self,
        block_hash: B256,
    ) -> Result<Option<serde_json::Value>, TransportError> {
        self.provider().raw_request("eth_getBlockAccessListByBlockHash".into(), (block_hash,)).await
    }

    /// Sends `eth_getBlockAccessListByBlockNumber`
    pub async fn block_access_list_by_number(
        &self,
        block_number: BlockNumber,
    ) -> Result<Option<serde_json::Value>, TransportError> {
        self.provider()
            .raw_request("eth_getBlockAccessListByBlockNumber".into(), (block_number,))
            .await
    }

    /// Sends `eth_getBlockAccessListRaw`.
    pub async fn block_access_list_raw(
        &self,
        block_id: BlockId,
    ) -> Result<Option<Bytes>, TransportError> {
        self.provider().raw_request("eth_getBlockAccessListRaw".into(), (block_id,)).await
    }

    pub async fn storage_at(
        &self,
        address: Address,
        index: U256,
        number: Option<BlockNumber>,
    ) -> Result<StorageValue, TransportError> {
        self.provider()
            .get_storage_at(address, index)
            .block_id(number.unwrap_or_default().into())
            .await
    }

    pub async fn logs(&self, filter: &Filter) -> Result<Vec<Log>, TransportError> {
        if let Some(logs) = self.storage_read().logs.get(filter).cloned() {
            return Ok(logs);
        }

        let logs = self.provider().get_logs(filter).await?;

        let mut storage = self.storage_write();
        storage.logs.insert(filter.clone(), logs.clone());
        Ok(logs)
    }

    pub async fn get_code(
        &self,
        address: Address,
        blocknumber: u64,
    ) -> Result<Bytes, TransportError> {
        trace!(target: "backend::fork", "get_code={:?}", address);
        if let Some(code) = self.storage_read().code_at.get(&(address, blocknumber)).cloned() {
            return Ok(code);
        }

        let block_id = BlockId::number(blocknumber);

        let code = self.provider().get_code_at(address).block_id(block_id).await?;

        let mut storage = self.storage_write();
        storage.code_at.insert((address, blocknumber), code.clone());

        Ok(code)
    }

    pub async fn get_balance(
        &self,
        address: Address,
        blocknumber: u64,
    ) -> Result<U256, TransportError> {
        trace!(target: "backend::fork", "get_balance={:?}", address);
        self.provider().get_balance(address).block_id(blocknumber.into()).await
    }

    pub async fn get_nonce(&self, address: Address, block: u64) -> Result<u64, TransportError> {
        trace!(target: "backend::fork", "get_nonce={:?}", address);
        self.provider().get_transaction_count(address).block_id(block.into()).await
    }

    pub async fn get_account(
        &self,
        address: Address,
        blocknumber: u64,
    ) -> Result<TrieAccount, TransportError> {
        trace!(target: "backend::fork", "get_account={:?}", address);
        self.provider().get_account(address).block_id(blocknumber.into()).await
    }

    pub async fn trace_transaction(&self, hash: B256) -> Result<Vec<Trace>, TransportError> {
        if let Some(traces) = self.storage_read().transaction_traces.get(&hash).cloned() {
            return Ok(traces);
        }

        let traces = self.provider().trace_transaction(hash).await?.into_iter().collect::<Vec<_>>();

        let mut storage = self.storage_write();
        storage.transaction_traces.insert(hash, traces.clone());

        Ok(traces)
    }

    pub async fn trace_transaction_opcode_gas(
        &self,
        hash: B256,
    ) -> Result<Option<TransactionOpcodeGas>, TransportError> {
        self.provider().raw_request("trace_transactionOpcodeGas".into(), (hash,)).await
    }

    /// Sends `trace_call`.
    pub async fn trace_call(
        &self,
        request: WithOtherFields<TransactionRequest>,
        trace_types: HashSet<TraceType>,
        block: BlockId,
    ) -> Result<TraceResults, TransportError> {
        self.provider().raw_request("trace_call".into(), (request, trace_types, block)).await
    }

    /// Sends `trace_get`.
    pub async fn trace_get(
        &self,
        hash: B256,
        indices: Vec<Index>,
    ) -> Result<Option<Trace>, TransportError> {
        self.provider().raw_request("trace_get".into(), (hash, indices)).await
    }

    pub async fn debug_trace_transaction(
        &self,
        hash: B256,
        opts: GethDebugTracingOptions,
    ) -> Result<GethTrace, TransportError> {
        if let Some(traces) = self.storage_read().geth_transaction_traces.get(&hash).cloned() {
            return Ok(traces);
        }

        let trace = self.provider().debug_trace_transaction(hash, opts).await?;

        let mut storage = self.storage_write();
        storage.geth_transaction_traces.insert(hash, trace.clone());

        Ok(trace)
    }

    pub async fn debug_trace_call(
        &self,
        request: WithOtherFields<TransactionRequest>,
        block_id: BlockId,
        opts: GethDebugTracingCallOptions,
    ) -> Result<GethTrace, TransportError> {
        self.provider().raw_request("debug_traceCall".into(), (request, block_id, opts)).await
    }

    pub async fn debug_code_by_hash(
        &self,
        code_hash: B256,
        block_id: Option<BlockId>,
    ) -> Result<Option<Bytes>, TransportError> {
        self.provider().debug_code_by_hash(code_hash, block_id).await
    }

    pub async fn debug_account_info_at(
        &self,
        block_id: BlockId,
        tx_index: Index,
        address: Address,
    ) -> Result<Option<AccountInfo>, TransportError> {
        self.provider()
            .raw_request("debug_accountInfoAt".into(), (block_id, tx_index, address))
            .await
    }

    pub async fn debug_trace_block_by_hash(
        &self,
        block_hash: B256,
        opts: GethDebugTracingOptions,
    ) -> Result<Vec<TraceResult>, TransportError> {
        if let Some(traces) = self.storage_read().geth_block_traces.get(&block_hash).cloned() {
            return Ok(traces);
        }

        let trace_results = self.provider().debug_trace_block_by_hash(block_hash, opts).await?;

        let mut storage = self.storage_write();
        storage.geth_block_traces.insert(block_hash, trace_results.clone());

        Ok(trace_results)
    }

    pub async fn debug_trace_block_by_number(
        &self,
        number: u64,
        opts: GethDebugTracingOptions,
    ) -> Result<Vec<TraceResult>, TransportError> {
        if let Ok(Some(block)) = self.provider().get_block_by_number(number.into()).await {
            let block_hash = block.header().hash();
            return self.debug_trace_block_by_hash(block_hash, opts).await;
        }

        self.provider().debug_trace_block_by_number(number.into(), opts).await
    }

    pub async fn trace_block(&self, number: u64) -> Result<Vec<Trace>, TransportError> {
        if let Some(traces) = self.storage_read().block_traces.get(&number).cloned() {
            return Ok(traces);
        }

        let traces =
            self.provider().trace_block(number.into()).await?.into_iter().collect::<Vec<_>>();

        let mut storage = self.storage_write();
        storage.block_traces.insert(number, traces.clone());

        Ok(traces)
    }

    pub async fn trace_replay_block_transactions(
        &self,
        number: u64,
        trace_types: HashSet<TraceType>,
    ) -> Result<Vec<TraceResultsWithTransactionHash>, TransportError> {
        // Forward to upstream provider for historical blocks. Use the typed trace API so the block
        // and trace types are serialized in the format upstream providers expect.
        self.provider()
            .trace_replay_block_transactions(BlockId::number(number))
            .trace_types(trace_types)
            .await
    }

    pub async fn trace_replay_transaction(
        &self,
        hash: B256,
        trace_types: HashSet<TraceType>,
    ) -> Result<TraceResults, TransportError> {
        self.provider().raw_request("trace_replayTransaction".into(), (hash, trace_types)).await
    }

    pub async fn trace_block_opcode_gas(
        &self,
        block_id: BlockId,
    ) -> Result<Option<BlockOpcodeGas>, TransportError> {
        self.provider().raw_request("trace_blockOpcodeGas".into(), (block_id,)).await
    }

    /// Reset the fork to a fresh forked state, and optionally update the fork config
    pub async fn reset(
        &self,
        urls: Vec<String>,
        block_number: impl Into<BlockId>,
    ) -> Result<(), BlockchainError> {
        let block_number = block_number.into();
        self.prepare_reset(urls.clone(), block_number).await?;
        {
            self.database
                .write()
                .await
                .maybe_reset(urls.clone(), block_number)
                .map_err(BlockchainError::Internal)?;
        }

        let number = self.block_number();
        let block_hash = self.block_hash();
        self.database.write().await.insert_block_hash(U256::from(number), block_hash);

        Ok(())
    }

    /// Updates the fork configuration for a reset without modifying the current database.
    pub(crate) async fn prepare_reset(
        &self,
        urls: Vec<String>,
        block_number: BlockId,
    ) -> Result<(), BlockchainError> {
        let (provider, source_chain_id) = if urls.is_empty() {
            (self.provider(), None)
        } else {
            let config = self.config.read().clone();
            let (provider, source_chain_id) = config.validated_provider_for_urls(&urls).await?;
            (provider, Some(source_chain_id))
        };
        let block =
            provider.get_block(block_number).await?.ok_or(BlockchainError::BlockNotFound)?;
        let block_hash = block.header().hash();
        let timestamp = block.header().timestamp();
        let base_fee = block.header().base_fee_per_gas();
        let total_difficulty = block.header().difficulty();

        let number = block.header().number();
        let mut config = self.config.write();
        if let Some(source_chain_id) = source_chain_id {
            if config.override_chain_id.is_none() {
                config.chain_id = source_chain_id;
            }
            config.provider = provider;
            config.fork_urls = urls;
        }
        config.update_block(
            number,
            block_hash,
            timestamp,
            base_fee.map(|g| g as u128),
            total_difficulty,
        );

        self.clear_cached_storage();

        Ok(())
    }

    /// Sends `eth_call`
    pub async fn call(
        &self,
        request: &N::TransactionRequest,
        block: Option<BlockNumber>,
    ) -> Result<Bytes, TransportError> {
        let block = block.unwrap_or(BlockNumber::Latest);
        let res = self.provider().call(request.clone()).block(block.into()).await?;

        Ok(res)
    }

    /// Sends `eth_call` with a network-specific request.
    pub async fn call_raw(
        &self,
        request: &WithOtherFields<TransactionRequest>,
        block: Option<BlockNumber>,
    ) -> Result<Bytes, TransportError> {
        self.provider()
            .raw_request("eth_call".into(), (request, block.unwrap_or(BlockNumber::Latest)))
            .await
    }

    /// Sends `eth_callMany`
    pub async fn call_many(
        &self,
        bundles: Vec<Bundle<WithOtherFields<TransactionRequest>>>,
        state_context: Option<StateContext>,
        state_override: Option<StateOverride>,
    ) -> Result<Vec<Vec<EthCallResponse>>, TransportError> {
        self.provider()
            .raw_request("eth_callMany".into(), (bundles, state_context, state_override))
            .await
    }

    /// Sends `eth_callBundle`.
    pub async fn call_bundle(
        &self,
        bundle: EthCallBundle,
    ) -> Result<EthCallBundleResponse, TransportError> {
        self.provider().raw_request("eth_callBundle".into(), (bundle,)).await
    }

    /// Sends `eth_simulateV1`
    pub async fn simulate_v1(
        &self,
        request: &SimulatePayload<WithOtherFields<TransactionRequest>>,
        block: Option<BlockId>,
    ) -> Result<Vec<SimulatedBlock<N::BlockResponse>>, TransportError> {
        self.provider().raw_request("eth_simulateV1".into(), (request, block)).await
    }

    /// Sends `eth_estimateGas`
    pub async fn estimate_gas(
        &self,
        request: &N::TransactionRequest,
        block: Option<BlockNumber>,
    ) -> Result<u128, TransportError> {
        let block = block.unwrap_or_default();
        let res = self.provider().estimate_gas(request.clone()).block(block.into()).await?;

        Ok(res as u128)
    }

    /// Sends `eth_estimateGas` with a network-specific request.
    pub async fn estimate_gas_raw(
        &self,
        request: &WithOtherFields<TransactionRequest>,
        block: Option<BlockNumber>,
    ) -> Result<u128, TransportError> {
        let gas: U256 = self
            .provider()
            .raw_request("eth_estimateGas".into(), (request, block.unwrap_or_default()))
            .await?;
        Ok(gas.saturating_to())
    }

    /// Sends `eth_createAccessList`
    pub async fn create_access_list(
        &self,
        request: &N::TransactionRequest,
        block: Option<BlockNumber>,
    ) -> Result<AccessListResult, TransportError> {
        self.provider().create_access_list(request).block_id(block.unwrap_or_default().into()).await
    }

    /// Sends `eth_createAccessList` with a network-specific request.
    pub async fn create_access_list_raw(
        &self,
        request: &WithOtherFields<TransactionRequest>,
        block: Option<BlockNumber>,
    ) -> Result<AccessListResult, TransportError> {
        self.provider()
            .raw_request("eth_createAccessList".into(), (request, block.unwrap_or_default()))
            .await
    }

    pub async fn transaction_by_block_number_and_index(
        &self,
        number: u64,
        index: usize,
    ) -> Result<Option<N::TransactionResponse>, TransportError> {
        let block = self.block_by_number(number).await?;
        self.transaction_at_block_index(block, index).await
    }

    pub async fn transaction_by_block_hash_and_index(
        &self,
        hash: B256,
        index: usize,
    ) -> Result<Option<N::TransactionResponse>, TransportError> {
        let block = self.block_by_hash(hash).await?;
        self.transaction_at_block_index(block, index).await
    }

    async fn transaction_at_block_index(
        &self,
        block: Option<N::BlockResponse>,
        index: usize,
    ) -> Result<Option<N::TransactionResponse>, TransportError> {
        if let Some(block) = block {
            match block.transactions() {
                BlockTransactions::Full(txs) => {
                    if let Some(tx) = txs.get(index) {
                        return Ok(Some(tx.clone()));
                    }
                }
                BlockTransactions::Hashes(hashes) => {
                    if let Some(tx_hash) = hashes.get(index) {
                        return self.transaction_by_hash(*tx_hash).await;
                    }
                }
                BlockTransactions::Uncle => {}
            }
        }
        Ok(None)
    }

    pub async fn transaction_by_hash(
        &self,
        hash: B256,
    ) -> Result<Option<N::TransactionResponse>, TransportError> {
        trace!(target: "backend::fork", "transaction_by_hash={:?}", hash);
        if let tx @ Some(_) = self.storage_read().transactions.get(&hash).cloned() {
            return Ok(tx);
        }

        let tx = self.provider().get_transaction_by_hash(hash).await?;
        if let Some(tx) = tx.clone() {
            let mut storage = self.storage_write();
            storage.transactions.insert(hash, tx);
        }
        Ok(tx)
    }

    pub async fn block_by_hash(
        &self,
        hash: B256,
    ) -> Result<Option<N::BlockResponse>, TransportError> {
        if let Some(mut block) = self.storage_read().blocks.get(&hash).cloned() {
            block.transactions_mut().convert_to_hashes();
            return Ok(Some(block));
        }

        Ok(self.fetch_full_block(hash).await?.map(|mut b| {
            b.transactions_mut().convert_to_hashes();
            b
        }))
    }

    pub async fn block_by_hash_full(
        &self,
        hash: B256,
    ) -> Result<Option<N::BlockResponse>, TransportError> {
        if let Some(block) = self.storage_read().blocks.get(&hash).cloned()
            && let Some(block) = self.convert_to_full_block(block)
        {
            return Ok(Some(block));
        }
        self.fetch_full_block(hash).await
    }

    pub async fn block_by_number(
        &self,
        block_number: u64,
    ) -> Result<Option<N::BlockResponse>, TransportError> {
        if let Some(mut block) = self
            .storage_read()
            .hashes
            .get(&block_number)
            .and_then(|hash| self.storage_read().blocks.get(hash).cloned())
        {
            block.transactions_mut().convert_to_hashes();
            return Ok(Some(block));
        }

        let mut block = self.fetch_full_block(block_number).await?;
        if let Some(block) = &mut block {
            block.transactions_mut().convert_to_hashes();
        }
        Ok(block)
    }

    pub async fn block_by_number_full(
        &self,
        block_number: u64,
    ) -> Result<Option<N::BlockResponse>, TransportError> {
        if let Some(block) = self
            .storage_read()
            .hashes
            .get(&block_number)
            .copied()
            .and_then(|hash| self.storage_read().blocks.get(&hash).cloned())
            && let Some(block) = self.convert_to_full_block(block)
        {
            return Ok(Some(block));
        }

        self.fetch_full_block(block_number).await
    }

    /// Fetches a block selected by its original identifier directly from the fork provider.
    pub async fn fetch_block(
        &self,
        block_id: BlockId,
    ) -> Result<Option<N::BlockResponse>, TransportError> {
        self.fetch_full_block(block_id).await
    }

    async fn fetch_full_block(
        &self,
        block_id: impl Into<BlockId>,
    ) -> Result<Option<N::BlockResponse>, TransportError> {
        if let Some(block) = self.provider().get_block(block_id.into()).full().await? {
            let hash = block.header().hash();
            let block_number = block.header().number();
            let mut storage = self.storage_write();
            // also insert all transactions
            let block_txs = match block.transactions() {
                BlockTransactions::Full(txs) => txs.to_owned(),
                _ => vec![],
            };
            storage.transactions.extend(block_txs.iter().map(|tx| (tx.tx_hash(), tx.clone())));
            storage.hashes.insert(block_number, hash);
            storage.blocks.insert(hash, block.clone());
            return Ok(Some(block));
        }

        Ok(None)
    }

    /// Converts a block of hashes into a full block
    fn convert_to_full_block(&self, mut block: N::BlockResponse) -> Option<N::BlockResponse> {
        let storage = self.storage.read();
        let transactions = block
            .transactions()
            .hashes()
            .map(|hash| storage.transactions.get(&hash).cloned())
            .collect::<Option<Vec<_>>>()?;
        *block.transactions_mut() = BlockTransactions::Full(transactions);
        Some(block)
    }
}

impl ClientFork {
    pub async fn transaction_receipt(
        &self,
        hash: B256,
    ) -> Result<Option<FoundryTxReceipt>, BlockchainError> {
        if let Some(receipt) = self.storage_read().transaction_receipts.get(&hash).cloned() {
            return Ok(Some(receipt));
        }

        if let Some(receipt) = self.provider().get_transaction_receipt(hash).await? {
            let receipt = FoundryTxReceipt::try_from(receipt)
                .map_err(|_| BlockchainError::FailedToDecodeReceipt)?;
            let mut storage = self.storage_write();
            storage.transaction_receipts.insert(hash, receipt.clone());
            return Ok(Some(receipt));
        }

        Ok(None)
    }

    pub async fn block_receipts(
        &self,
        number: u64,
    ) -> Result<Option<Vec<FoundryTxReceipt>>, BlockchainError> {
        if let receipts @ Some(_) = self.storage_read().block_receipts.get(&number).cloned() {
            return Ok(receipts);
        }

        // TODO Needs to be removed.
        // Since alloy doesn't indicate in the result whether the block exists,
        // this is being temporarily implemented in anvil.
        if self.predates_fork_inclusive(number) {
            let receipts = self.provider().get_block_receipts(BlockId::from(number)).await?;
            let receipts = receipts
                .map(|r| {
                    r.into_iter()
                        .map(|r| {
                            FoundryTxReceipt::try_from(r)
                                .map_err(|_| BlockchainError::FailedToDecodeReceipt)
                        })
                        .collect::<Result<Vec<_>, _>>()
                })
                .transpose()?;

            if let Some(receipts) = receipts.clone() {
                let mut storage = self.storage_write();
                storage.block_receipts.insert(number, receipts);
            }

            return Ok(receipts);
        }

        Ok(None)
    }

    pub async fn uncle_by_block_hash_and_index(
        &self,
        hash: B256,
        index: usize,
    ) -> Result<Option<AnyRpcBlock>, TransportError> {
        if let Some(block) = self.block_by_hash(hash).await? {
            return self.uncles_by_block_and_index(block, index).await;
        }
        Ok(None)
    }

    pub async fn uncle_by_block_number_and_index(
        &self,
        number: u64,
        index: usize,
    ) -> Result<Option<AnyRpcBlock>, TransportError> {
        if let Some(block) = self.block_by_number(number).await? {
            return self.uncles_by_block_and_index(block, index).await;
        }
        Ok(None)
    }

    async fn uncles_by_block_and_index(
        &self,
        block: AnyRpcBlock,
        index: usize,
    ) -> Result<Option<AnyRpcBlock>, TransportError> {
        let block_hash = block.header().hash();
        let block_number = block.header().number();
        if let Some(uncles) = self.storage_read().uncles.get(&block_hash) {
            return Ok(uncles.get(index).cloned());
        }

        let mut uncles = Vec::with_capacity(block.uncles.len());
        for (uncle_idx, _) in block.uncles.iter().enumerate() {
            let uncle =
                match self.provider().get_uncle(block_number.into(), uncle_idx as u64).await? {
                    Some(u) => u,
                    None => return Ok(None),
                };
            uncles.push(uncle);
        }
        self.storage_write().uncles.insert(block_hash, uncles.clone());
        Ok(uncles.get(index).cloned())
    }
}

/// Contains all fork metadata
#[derive(Clone, Debug)]
pub struct ClientForkConfig<N: Network = AnyNetwork> {
    /// All fork URLs. The first entry is the primary endpoint.
    /// When multiple URLs are present, requests are distributed using
    /// round-robin load balancing with retry-based failover.
    pub fork_urls: Vec<String>,
    /// The block number of the forked block
    pub block_number: u64,
    /// The hash of the forked block
    pub block_hash: B256,
    /// The transaction hash we forked off of, if any.
    pub transaction_hash: Option<B256>,
    pub provider: Arc<RetryProvider<N>>,
    pub chain_id: u64,
    pub override_chain_id: Option<u64>,
    /// The hardfork resolved for the forked block, if known.
    pub hardfork: Option<FoundryHardfork>,
    /// The timestamp for the forked block
    pub timestamp: u64,
    /// The basefee of the forked block
    pub base_fee: Option<u128>,
    /// Blob gas used of the forked block
    pub blob_gas_used: Option<u128>,
    /// Blob excess gas and price of the forked block
    pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
    /// request timeout
    pub timeout: Duration,
    /// request retries for spurious networks
    pub retries: u32,
    /// request retries for spurious networks
    pub backoff: Duration,
    /// available CUPS
    pub compute_units_per_second: u64,
    /// Headers to include with RPC requests
    pub headers: Vec<String>,
    /// total difficulty of the chain until this block
    pub total_difficulty: U256,
}

impl<N: Network> ClientForkConfig<N> {
    /// Returns the primary RPC URL (first entry in `fork_urls`).
    pub fn eth_rpc_url(&self) -> Option<&str> {
        self.fork_urls.first().map(|s| s.as_str())
    }

    /// Builds a provider for the given URLs without changing the active fork configuration.
    ///
    /// # Errors
    ///
    /// This will fail if no new provider could be established (erroneous URL)
    pub(crate) fn provider_for_urls(
        &self,
        urls: &[String],
    ) -> Result<Arc<RetryProvider<N>>, BlockchainError> {
        let primary = urls.first().ok_or_else(|| {
            BlockchainError::InvalidUrl("at least one fork URL required".to_string())
        })?;

        let builder = ProviderBuilder::<N>::new(primary.as_str())
            .timeout(self.timeout)
            .max_retry(self.retries)
            .initial_backoff(self.backoff.as_millis() as u64)
            .compute_units_per_second(self.compute_units_per_second)
            .headers(self.headers.clone());

        let provider = if urls.len() > 1 {
            builder
                .build_fallback(urls.to_vec())
                .map_err(|e| BlockchainError::InvalidUrl(format!("{primary}: {e}")))?
        } else {
            builder.build().map_err(|e| BlockchainError::InvalidUrl(format!("{primary}: {e}")))?
        };
        Ok(Arc::new(provider))
    }

    /// Builds a provider after validating that every URL belongs to the same supported network.
    pub(crate) async fn validated_provider_for_urls(
        &self,
        urls: &[String],
    ) -> Result<(Arc<RetryProvider<N>>, u64), BlockchainError> {
        let mut source_chain_id = None;
        for url in urls {
            let provider = self.provider_for_urls(std::slice::from_ref(url))?;
            let chain_id = provider.get_chain_id().await?;
            ensure_fork_network_supported(chain_id)?;
            if let Some(source_chain_id) = source_chain_id
                && chain_id != source_chain_id
            {
                return Err(BlockchainError::UnsupportedForkNetwork {
                    chain_id,
                    reason: "fork endpoints must use the same chain ID",
                });
            }
            source_chain_id = Some(chain_id);
        }

        let source_chain_id = source_chain_id.ok_or_else(|| {
            BlockchainError::InvalidUrl("at least one fork URL required".to_string())
        })?;
        Ok((self.provider_for_urls(urls)?, source_chain_id))
    }

    /// Updates the block forked off `(block number, block hash, timestamp)`
    pub fn update_block(
        &mut self,
        block_number: u64,
        block_hash: B256,
        timestamp: u64,
        base_fee: Option<u128>,
        total_difficulty: U256,
    ) {
        self.block_number = block_number;
        self.block_hash = block_hash;
        self.timestamp = timestamp;
        self.base_fee = base_fee;
        self.total_difficulty = total_difficulty;
        trace!(target: "fork", "Updated block number={} hash={:?}", block_number, block_hash);
    }
}

/// Contains cached state fetched to serve EthApi requests
///
/// This is used as a cache so repeated requests to the same data are not sent to the remote client
#[derive(Clone, Debug)]
pub struct ForkedStorage<N: Network = AnyNetwork> {
    pub uncles: FbHashMap<32, Vec<N::BlockResponse>>,
    pub blocks: FbHashMap<32, N::BlockResponse>,
    pub hashes: HashMap<u64, B256>,
    pub transactions: FbHashMap<32, N::TransactionResponse>,
    pub transaction_receipts: FbHashMap<32, FoundryTxReceipt>,
    pub transaction_traces: FbHashMap<32, Vec<Trace>>,
    pub logs: HashMap<Filter, Vec<Log>>,
    pub geth_transaction_traces: FbHashMap<32, GethTrace>,
    pub geth_block_traces: FbHashMap<32, Vec<TraceResult>>,
    pub block_traces: HashMap<u64, Vec<Trace>>,
    pub block_receipts: HashMap<u64, Vec<FoundryTxReceipt>>,
    pub code_at: HashMap<(Address, u64), Bytes>,
}

impl<N: Network> Default for ForkedStorage<N> {
    fn default() -> Self {
        Self {
            uncles: Default::default(),
            blocks: Default::default(),
            hashes: Default::default(),
            transactions: Default::default(),
            transaction_receipts: Default::default(),
            transaction_traces: Default::default(),
            logs: Default::default(),
            geth_transaction_traces: Default::default(),
            geth_block_traces: Default::default(),
            block_traces: Default::default(),
            block_receipts: Default::default(),
            code_at: Default::default(),
        }
    }
}

impl<N: Network> ForkedStorage<N> {
    /// Clears all data
    pub fn clear(&mut self) {
        // simply replace with a completely new, empty instance
        *self = Self::default()
    }
}
