Skip to main content

anvil/eth/backend/mem/
mod.rs

1//! In-memory blockchain backend.
2use self::{in_memory_db::StateRootDb, state::trie_storage};
3use crate::{
4    ForkChoice, NodeConfig, PrecompileFactory,
5    config::{ForkTransactionReplay, PruneStateHistoryConfig, source_hardfork},
6    eth::{
7        backend::{
8            cheats::{CheatEcrecover, CheatsManager, NextBlockOverrides},
9            db::{
10                AnvilCacheDB, BLOCKHASH_HISTORY, Db, EmptyAsAbsentDb, MaybeFullDatabase,
11                SerializableState, StateDb,
12            },
13            executor::{
14                AnvilBlockExecutor, BlockExecutionKind, EthereumBlockTransitions,
15                ExecutedPoolTransactions, FoundryReceiptBuilder, PoolTxGasConfig,
16                apply_ethereum_post_execution_changes, apply_ethereum_pre_execution_changes,
17                block_blob_gas_limit, build_tx_env_for_pending, execute_pool_transaction,
18                execute_pool_transactions,
19            },
20            fork::{ClientFork, ForkEndpointIdentity},
21            genesis::GenesisConfig,
22            mem::{
23                state::{
24                    StateRootCache, state_root, state_trie_witness, storage_root, trie_accounts,
25                },
26                storage::MinedTransactionReceipt,
27            },
28            notifications::{ChainNotification, ChainNotifications, NewBlockNotification},
29            replay::{
30                ExecutedHistoricalReplay, HistoricalReplayTransaction,
31                PreparedForkTransactionReplay, execute_historical_replay,
32                prepare_fork_transaction_replay,
33            },
34            tempo::AnvilStorageProvider,
35            time::{PendingBlockTimestamp, TimeManager, TimeSnapshot, utc_from_secs},
36            validate::TransactionValidator,
37        },
38        error::{BlockchainError, ErrDetail, InvalidTransactionError},
39        fees::{FeeDetails, FeeManager, FeeSnapshot, MIN_SUGGESTED_PRIORITY_FEE},
40        macros::node_info,
41        pool::transactions::PoolTransaction,
42        preserve_simulation_request_fields,
43    },
44    mem::{
45        inspector::{AnvilInspector, InspectorTxConfig},
46        storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome},
47    },
48};
49use alloy_chains::NamedChain;
50use alloy_consensus::{
51    Blob, BlockBody, BlockHeader, EnvKzgSettings, Header, Signed, Transaction as TransactionTrait,
52    TransactionEnvelope, TrieAccount, TxEip4844Variant, TxEnvelope, TxReceipt, Typed2718,
53    constants::EMPTY_WITHDRAWALS,
54    proofs::{calculate_receipt_root, calculate_transaction_root},
55    transaction::Recovered,
56};
57use alloy_eips::{
58    BlockNumHash, Encodable2718, eip2935, eip4788,
59    eip4844::{DATA_GAS_PER_BLOB, kzg_to_versioned_hash},
60    eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS,
61    eip7002, eip7251,
62    eip7685::EMPTY_REQUESTS_HASH,
63    eip7840::BlobParams,
64    eip7910::SystemContract,
65    eip7928::{BlockAccessList, EMPTY_BLOCK_ACCESS_LIST_HASH, compute_block_access_list_hash},
66};
67use alloy_evm::{
68    Database, EthEvmFactory, Evm, EvmEnv, EvmFactory, FromTxWithEncoded,
69    block::{BalIndexedDatabase, BlockExecutionResult, BlockExecutor, StateDB},
70    eth::{EthEvm, EthEvmContext},
71    overrides::{OverrideBlockHashes, apply_state_overrides},
72    precompiles::{DynPrecompile, MovePrecompileError, Precompile, PrecompilesMap},
73};
74use alloy_genesis::Genesis;
75use alloy_network::{
76    AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType,
77    BlockResponse, Network, NetworkTransactionBuilder, ReceiptResponse, UnknownTxEnvelope,
78    UnknownTypedTransaction,
79};
80use alloy_primitives::{
81    Address, B256, Bloom, Bytes, Signature, TxKind, U256, address, hex, keccak256,
82    map::{AddressMap, B256Set, HashMap, HashSet},
83};
84use alloy_rlp::{Decodable, Encodable};
85use alloy_rpc_types::{
86    AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides,
87    BlockTransactions, EIP1186AccountProofResponse as AccountProof,
88    EIP1186StorageProof as StorageProof, Filter, Header as AlloyHeader, Index, Log, Transaction,
89    TransactionReceipt,
90    anvil::Forking,
91    debug::ExecutionWitness,
92    request::TransactionRequest,
93    serde_helpers::JsonStorageKey,
94    simulate::{
95        MAX_SIMULATE_BLOCKS, SimBlock, SimCallResult, SimulateError, SimulatePayload,
96        SimulatedBlock,
97    },
98    state::{EvmOverrides, StateOverride},
99    trace::{
100        filter::TraceFilter,
101        geth::{
102            CallConfig, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig,
103            GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
104            NoopFrame, TraceResult,
105        },
106        opcode::{BlockOpcodeGas, TransactionOpcodeGas},
107        parity::{
108            LocalizedTransactionTrace, TraceResults, TraceResultsWithTransactionHash, TraceType,
109        },
110    },
111};
112use alloy_rpc_types_eth::{AccountInfo as RpcAccountInfo, Bundle, EthCallResponse};
113use alloy_rpc_types_mev::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult};
114use alloy_serde::{OtherFields, WithOtherFields};
115use alloy_sol_types::SolCall;
116use alloy_trie::{HashBuilder, Nibbles, proof::ProofRetainer, root::state_root_ref_unhashed};
117use anvil_core::eth::{
118    block::{Block, BlockInfo, canonical_block, create_block},
119    transaction::{MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo},
120};
121use anvil_rpc::error::{ErrorCode, RpcError};
122use chrono::Datelike;
123use eyre::{Context, Result};
124use flate2::{Compression, read::GzDecoder, write::GzEncoder};
125use foundry_evm::{
126    backend::{BlockchainDb, DatabaseError, DatabaseResult, RevertStateSnapshotAction},
127    constants::{DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE},
128    core::precompiles::EC_RECOVER,
129    decode::RevertDecoder,
130    hardfork::{EthereumHardfork, FoundryHardfork},
131    inspectors::AccessListInspector,
132    traces::{
133        CallTraceDecoder, FourByteInspector, GethTraceBuilder, TracingInspector,
134        TracingInspectorConfig,
135    },
136    utils::{
137        apply_chain_specific_tx_replay_env_changes_for_chain, block_env_from_header,
138        get_blob_base_fee_update_fraction, get_blob_base_fee_update_fraction_by_spec_id,
139        get_blob_params_by_hardfork,
140    },
141};
142use foundry_evm_networks::{NetworkConfigs, apply_bsc_p256_precompile, arbitrum};
143use foundry_primitives::{
144    FoundryHeader, FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest,
145    FoundryTxEnvelope, FoundryTxReceipt, TempoTransactionRequest,
146};
147use futures::channel::mpsc::{UnboundedSender, unbounded};
148use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
149use revm::{
150    Database as RevmDatabase, DatabaseCommit, Inspector,
151    context::{Block as RevmBlock, BlockEnv, Cfg, CfgEnv, ContextSetters, ContextTr, TxEnv},
152    context_interface::{
153        JournalTr, Transaction as _,
154        block::BlobExcessGasAndPrice,
155        result::{
156            EVMError, ExecutionResult, HaltReason, InvalidTransaction, Output, ResultAndState,
157        },
158        transaction::TransactionType,
159    },
160    database::{
161        AccountState, CacheDB, DbAccount, WrapDatabaseRef,
162        bal::{BalDatabase, BalState},
163    },
164    handler::{
165        EthFrame, EvmTr, EvmTrError, FrameResult, FrameTr, Handler as EvmHandler, validation,
166    },
167    inspector::{InspectorEvmTr, InspectorHandler},
168    interpreter::{InstructionResult, interpreter::EthInterpreter, interpreter_action::FrameInit},
169    precompile::{PrecompileSpecId, Precompiles},
170    primitives::{eip2780, hardfork::SpecId},
171    state::{Account, AccountInfo, EvmState, EvmStorageSlot, TransactionId},
172};
173use revm_inspectors::opcode::OpcodeGasInspector;
174use std::{
175    collections::BTreeMap,
176    fmt::{self, Debug},
177    io::{Read, Write},
178    marker::PhantomData,
179    ops::Mul,
180    path::{Path, PathBuf},
181    sync::{
182        Arc,
183        atomic::{AtomicBool, Ordering},
184    },
185    time::Duration,
186};
187use storage::{Blockchain, DEFAULT_HISTORY_LIMIT, MinedTransaction};
188use tempo_evm::{TempoPoolValidationEvm, evm::TempoEvmFactory};
189use tempo_hardfork::TempoHardfork;
190use tempo_precompiles::{
191    NONCE_PRECOMPILE_ADDRESS, TIP_FEE_MANAGER_ADDRESS, extend_tempo_precompiles,
192    nonce::NonceManager,
193    storage::{Handler, StorageActions, StorageCtx},
194    tip_fee_manager::{IFeeManager, TipFeeManager},
195    tip20::{ITIP20, TIP20Token},
196    tip20_factory::TIP20Factory,
197};
198use tempo_primitives::{
199    AASigned, SignatureType, TEMPO_TX_TYPE_ID, TempoSignature,
200    transaction::{
201        Call, KeychainSignature, PrimitiveSignature, RecoveredTempoAuthorization,
202        tt_signature::{P256SignatureWithPreHash, WebAuthnSignature},
203    },
204};
205use tempo_revm::{
206    ExecutionContext, TempoBatchCallEnv, TempoBlockEnv, TempoInvalidTransaction, TempoTxEnv,
207    evm::TempoContext, gas_params::tempo_gas_params,
208};
209#[cfg(test)]
210use tokio::sync::Notify;
211use tokio::{sync::RwLock as AsyncRwLock, task::JoinSet};
212
213#[cfg(any(feature = "base", feature = "optimism"))]
214use foundry_primitives::get_deposit_tx_parts;
215#[cfg(any(feature = "base", feature = "optimism"))]
216use op_alloy_consensus::DEPOSIT_TX_TYPE_ID;
217#[cfg(any(feature = "base", feature = "optimism"))]
218use op_revm::transaction::deposit::DepositTransactionParts;
219
220#[cfg(feature = "base")]
221use alloy_hardforks::ForkCondition;
222#[cfg(feature = "base")]
223use base_common_chains::{ChainConfig, ChainUpgrades};
224#[cfg(feature = "base")]
225use base_common_consensus::{
226    BaseTransactionInfo, BaseTxEnvelope, EIP8130_REJECTION_MSG, Eip8130Constants, Predeploys,
227};
228#[cfg(feature = "base")]
229use base_common_evm::{
230    BaseContext, BaseEvmFactory, BaseSpecId, BaseTime, BaseTransaction, DEPOSIT_TRANSACTION_TYPE,
231    DepositTransactionParts as BaseDepositTransactionParts, EIP8130_TRANSACTION_TYPE,
232    Eip8130PhaseStatuses, L1BlockInfo, ensure_create2_deployer, ensure_eip8130_system_accounts,
233};
234#[cfg(feature = "base")]
235use base_common_genesis::RollupConfig;
236#[cfg(feature = "base")]
237use base_common_precompiles::NonceManagerStorage;
238#[cfg(feature = "base")]
239use base_common_rpc_types::{
240    EIP8130_PRE_ZENITH_RPC_ERROR, Eip8130Nonce, Eip8130ReceiptFields,
241    Transaction as BaseRpcTransaction,
242};
243#[cfg(feature = "base")]
244use base_execution_eip8130::{FeeCheck, IntrinsicGas, IntrinsicGasInput};
245#[cfg(feature = "base")]
246use foundry_evm::{
247    core::{constants::SYSTEM_PRECOMPILE_STUB, evm::base_code_sentinel_addresses},
248    hardfork::BaseUpgrade,
249};
250#[cfg(feature = "base")]
251use revm::inspector::NoOpInspector;
252
253#[cfg(feature = "optimism")]
254use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx};
255#[cfg(feature = "optimism")]
256use foundry_evm::hardfork::OpHardfork;
257#[cfg(feature = "optimism")]
258use foundry_evm_networks::NetworkVariant;
259#[cfg(feature = "optimism")]
260use op_alloy_consensus::POST_EXEC_TX_TYPE_ID;
261#[cfg(feature = "optimism")]
262use op_revm::OpTransaction;
263
264/// Network-specific transaction data produced by [`Backend::build_call_env_with_base`].
265#[derive(Default, Clone, Debug)]
266struct CallTransactionInfo {
267    /// OP-compatible deposit fields shared by Base and Optimism RPC requests.
268    #[cfg(any(feature = "base", feature = "optimism"))]
269    deposit: DepositTransactionParts,
270}
271
272/// Fully prepared fork replacement awaiting an atomic backend commit.
273pub(crate) struct StagedForkReset {
274    node_config: NodeConfig,
275    db: Box<dyn Db>,
276    fees: FeeManager,
277    evm_env: EvmEnv,
278    fork: ClientFork,
279    timestamp: u64,
280    discard_old_cached_state: bool,
281    invalidated_cache_namespaces: Vec<ForkCacheNamespace>,
282    flush_old_cache: bool,
283    cache_lease: StagedForkCacheLease,
284}
285
286/// Fully prepared in-memory replacement awaiting an atomic backend commit.
287pub(crate) struct StagedMemoryReset<N: Network> {
288    node_config: NodeConfig,
289    db: Box<dyn Db>,
290    fees: FeeManager,
291    evm_env: EvmEnv,
292    hardfork: FoundryHardfork,
293    storage: BlockchainStorage<N>,
294    timestamp: u64,
295    flush_old_cache: bool,
296}
297
298/// Identifies the endpoint that supplied the most recently committed fork.
299#[derive(Clone, Debug, PartialEq, Eq)]
300struct ForkCacheSource {
301    rpc_url: String,
302    endpoint_identity: ForkEndpointIdentity,
303}
304
305impl ForkCacheSource {
306    fn from_fork(fork: &ClientFork) -> Option<Self> {
307        let config = fork.config.read();
308        Some(Self {
309            rpc_url: config.eth_rpc_url()?.to_string(),
310            endpoint_identity: config.endpoint_identity,
311        })
312    }
313
314    fn authoritative_identity_changed_at_same_url(
315        &self,
316        rpc_url: &str,
317        endpoint_identity: ForkEndpointIdentity,
318    ) -> bool {
319        // `hardfork` is populated only when `anvil_nodeInfo` succeeds, which makes the complete
320        // endpoint identity authoritative. Anonymous RPC endpoints intentionally retain Foundry's
321        // existing cache behavior when reused through the same URL.
322        let authoritative =
323            self.endpoint_identity.is_authoritative() || endpoint_identity.is_authoritative();
324        self.rpc_url == rpc_url && authoritative && self.endpoint_identity != endpoint_identity
325    }
326}
327
328/// Identifies one endpoint's persisted cache files across all blocks of a source chain.
329#[derive(Clone, Debug, PartialEq, Eq)]
330struct ForkCacheNamespace {
331    chain_cache_dir: PathBuf,
332    file_name: String,
333}
334
335impl ForkCacheNamespace {
336    fn new(source_chain_id: u64, rpc_url: &str) -> Option<Self> {
337        Some(Self {
338            chain_cache_dir: foundry_config::Config::foundry_chain_cache_dir(source_chain_id)?,
339            file_name: format!("storage-{:x}.json", keccak256(rpc_url)),
340        })
341    }
342
343    fn invalidate(&self) -> Result<(), BlockchainError> {
344        let entries = match std::fs::read_dir(&self.chain_cache_dir) {
345            Ok(entries) => entries,
346            Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()),
347            Err(err) => {
348                return Err(BlockchainError::Internal(format!(
349                    "failed to inspect fork cache at {}: {err}",
350                    self.chain_cache_dir.display()
351                )));
352            }
353        };
354
355        for entry in entries {
356            let entry = entry.map_err(|err| {
357                BlockchainError::Internal(format!(
358                    "failed to inspect fork cache at {}: {err}",
359                    self.chain_cache_dir.display()
360                ))
361            })?;
362            let file_type = entry.file_type().map_err(|err| {
363                BlockchainError::Internal(format!(
364                    "failed to inspect fork cache entry at {}: {err}",
365                    entry.path().display()
366                ))
367            })?;
368            if !file_type.is_dir() {
369                continue;
370            }
371            let cache_path = entry.path().join(&self.file_name);
372            if let Err(err) = std::fs::remove_file(&cache_path)
373                && err.kind() != std::io::ErrorKind::NotFound
374            {
375                return Err(BlockchainError::Internal(format!(
376                    "failed to invalidate fork cache at {}: {err}",
377                    cache_path.display()
378                )));
379            }
380        }
381        Ok(())
382    }
383}
384
385/// Keeps a staged fork from persisting remote state unless it is committed.
386#[derive(Clone, Debug, Default)]
387struct StagedForkCacheLease(Option<Arc<StagedForkCacheLeaseInner>>);
388
389#[derive(Debug)]
390struct StagedForkCacheLeaseInner {
391    db: BlockchainDb,
392    cache_path: PathBuf,
393    armed: AtomicBool,
394}
395
396impl StagedForkCacheLease {
397    fn new(db: BlockchainDb, cache_path: Option<PathBuf>) -> Self {
398        Self(cache_path.map(|cache_path| {
399            Arc::new(StagedForkCacheLeaseInner { db, cache_path, armed: AtomicBool::new(true) })
400        }))
401    }
402
403    fn for_db(db: &BlockchainDb) -> Self {
404        Self::new(db.clone(), db.cache().cache_path().map(Path::to_path_buf))
405    }
406
407    fn disarm(&self) {
408        if let Some(inner) = &self.0 {
409            inner.armed.store(false, Ordering::Release);
410        }
411    }
412
413    fn rollback(&self) -> Result<(), BlockchainError> {
414        let Some(inner) = &self.0 else { return Ok(()) };
415        // A clone owned by an in-flight database user must outlive that user's SharedBackend.
416        // Leave cleanup armed for the final owner instead of racing its eventual cache flush.
417        if Arc::strong_count(inner) == 1 && inner.armed.load(Ordering::Acquire) {
418            inner.invalidate()?;
419            inner.armed.store(false, Ordering::Release);
420        }
421        Ok(())
422    }
423}
424
425impl StagedForkCacheLeaseInner {
426    fn invalidate(&self) -> Result<(), BlockchainError> {
427        self.db.db().clear();
428        self.db.cache().flush();
429        if let Err(err) = std::fs::remove_file(&self.cache_path)
430            && err.kind() != std::io::ErrorKind::NotFound
431        {
432            return Err(BlockchainError::Internal(format!(
433                "failed to invalidate fork cache at {}: {err}",
434                self.cache_path.display()
435            )));
436        }
437        Ok(())
438    }
439}
440
441impl Drop for StagedForkCacheLeaseInner {
442    fn drop(&mut self) {
443        if self.armed.swap(false, Ordering::AcqRel)
444            && let Err(err) = self.invalidate()
445        {
446            warn!(target: "backend", %err, "failed to roll back staged fork cache");
447        }
448    }
449}
450
451/// Couples an asynchronous staged-database user to its rollback lease.
452///
453/// Fields drop in declaration order, so the database handle (and its SharedBackend) is released
454/// before the lease can perform final cache cleanup.
455struct StagedForkDbUser<D> {
456    db: Option<Arc<AsyncRwLock<D>>>,
457    cache_lease: StagedForkCacheLease,
458}
459
460impl<D> Clone for StagedForkDbUser<D> {
461    fn clone(&self) -> Self {
462        Self { db: self.db.clone(), cache_lease: self.cache_lease.clone() }
463    }
464}
465
466impl<D> StagedForkDbUser<D> {
467    const fn db(&self) -> &Arc<AsyncRwLock<D>> {
468        self.db.as_ref().expect("staged fork database must be present until drop")
469    }
470}
471
472impl<D> Drop for StagedForkDbUser<D> {
473    fn drop(&mut self) {
474        // Explicitly release the SharedBackend before `cache_lease` can invalidate its cache.
475        drop(self.db.take());
476    }
477}
478
479#[cfg(feature = "monad")]
480pub(crate) type MonadReplayContext = monad_revm::MonadChainContext;
481// Opaque stand-in that keeps feature-independent replay context plumbing type-stable.
482#[cfg(not(feature = "monad"))]
483#[derive(Clone)]
484pub(crate) struct MonadReplayContext;
485
486#[cfg(feature = "monad")]
487enum MonadExecutionContext<'a> {
488    Exact(Box<MonadReplayContext>),
489    Next(&'a mut MonadReplayContext),
490}
491
492#[cfg(not(feature = "monad"))]
493struct MonadExecutionContext<'a> {
494    _marker: std::marker::PhantomData<&'a mut MonadReplayContext>,
495}
496
497#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
498enum EnvelopeExecutionKind {
499    #[default]
500    Transaction,
501    Replay,
502}
503
504#[cfg_attr(not(feature = "monad"), allow(dead_code))]
505struct EnvelopeExecution<'a> {
506    monad_context: Option<MonadExecutionContext<'a>>,
507    kind: EnvelopeExecutionKind,
508    hardfork: FoundryHardfork,
509}
510
511impl<'a> EnvelopeExecution<'a> {
512    const fn transaction(
513        monad_context: Option<MonadExecutionContext<'a>>,
514        hardfork: FoundryHardfork,
515    ) -> Self {
516        Self { monad_context, kind: EnvelopeExecutionKind::Transaction, hardfork }
517    }
518
519    const fn replay(
520        monad_context: Option<MonadExecutionContext<'a>>,
521        hardfork: FoundryHardfork,
522    ) -> Self {
523        Self { monad_context, kind: EnvelopeExecutionKind::Replay, hardfork }
524    }
525}
526
527#[cfg(feature = "monad")]
528fn monad_execution_context_at(
529    context: Option<&MonadReplayContext>,
530    current_tx_index: usize,
531) -> Option<MonadExecutionContext<'static>> {
532    context.map(|context| {
533        let mut context = context.clone();
534        context.current_tx_index = current_tx_index;
535        MonadExecutionContext::Exact(Box::new(context))
536    })
537}
538
539#[cfg(not(feature = "monad"))]
540const fn monad_execution_context_at(
541    _context: Option<&MonadReplayContext>,
542    _current_tx_index: usize,
543) -> Option<MonadExecutionContext<'static>> {
544    None
545}
546
547#[cfg(feature = "monad")]
548const fn next_monad_context(context: &mut MonadReplayContext) -> MonadExecutionContext<'_> {
549    MonadExecutionContext::Next(context)
550}
551
552#[cfg(not(feature = "monad"))]
553const fn next_monad_context(_context: &mut MonadReplayContext) -> MonadExecutionContext<'_> {
554    MonadExecutionContext { _marker: std::marker::PhantomData }
555}
556
557/// Maximum cumulative gas available to one `eth_simulateV1` request.
558const SIMULATE_GAS_CAP: u64 = 50_000_000;
559const SEPOLIA_DEPOSIT_CONTRACT_ADDRESS: Address =
560    address!("7f02c3e3c98b133055b8b348b2ac625669ed295d");
561const HOLESKY_DEPOSIT_CONTRACT_ADDRESS: Address = Address::repeat_byte(0x42);
562
563/// Fixed transaction context for direct Tempo RPC simulations.
564const TEMPO_RPC_SIMULATION_CONTEXT: B256 = B256::new(*b"TEMPO_RPC_SIMULATION_MPP_CONTEXT");
565
566/// Ethereum handler that skips blob fee cap validation for non-validating calls with a zero cap.
567struct SimulationHandler<EVM, ERROR, FRAME> {
568    _phantom: PhantomData<(EVM, ERROR, FRAME)>,
569}
570
571impl<EVM, ERROR, FRAME> Default for SimulationHandler<EVM, ERROR, FRAME> {
572    fn default() -> Self {
573        Self { _phantom: PhantomData }
574    }
575}
576
577impl<EVM, ERROR, FRAME> EvmHandler for SimulationHandler<EVM, ERROR, FRAME>
578where
579    EVM: EvmTr<
580            Context: ContextTr<
581                Block = BlockEnv,
582                Tx = TxEnv,
583                Journal: JournalTr<State = EvmState>,
584            > + ContextSetters,
585            Frame = FRAME,
586        >,
587    ERROR: EvmTrError<EVM>,
588    FRAME: FrameTr<FrameResult = FrameResult, FrameInit = FrameInit>,
589{
590    type Evm = EVM;
591    type Error = ERROR;
592    type HaltReason = HaltReason;
593
594    fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
595        let skip_blob_fee_check = evm.ctx_ref().cfg().is_base_fee_check_disabled()
596            && evm.ctx_ref().tx().tx_type == 3
597            && evm.ctx_ref().tx().max_fee_per_blob_gas == 0;
598        if !skip_blob_fee_check {
599            return validation::validate_env(evm.ctx());
600        }
601
602        let block = evm.ctx_ref().block().clone();
603        let mut validation_block = block.clone();
604        if let Some(blob_gas_and_price) = &mut validation_block.blob_excess_gas_and_price {
605            blob_gas_and_price.blob_gasprice = 0;
606        }
607        evm.ctx().set_block(validation_block);
608        let result = validation::validate_env(evm.ctx());
609        evm.ctx().set_block(block);
610        result
611    }
612}
613
614impl<EVM, ERROR> InspectorHandler for SimulationHandler<EVM, ERROR, EthFrame<EthInterpreter>>
615where
616    EVM: InspectorEvmTr<
617            Context: ContextTr<
618                Block = BlockEnv,
619                Tx = TxEnv,
620                Journal: JournalTr<State = EvmState>,
621            > + ContextSetters,
622            Frame = EthFrame<EthInterpreter>,
623            Inspector: Inspector<<EVM as EvmTr>::Context, EthInterpreter>,
624        >,
625    ERROR: EvmTrError<EVM>,
626{
627    type IT = EthInterpreter;
628}
629
630#[derive(Clone)]
631enum CallTxEnv {
632    Eth(TxEnv),
633    #[cfg(feature = "base")]
634    Base(Box<BaseTransaction<TxEnv>>),
635    #[cfg(feature = "monad")]
636    Monad(TxEnv),
637    #[cfg(feature = "optimism")]
638    Op(OpTransaction<TxEnv>),
639    Tempo(TempoTxEnv),
640}
641
642impl CallTxEnv {
643    #[cfg_attr(not(feature = "js-tracer"), allow(dead_code))]
644    const fn base(&self) -> &TxEnv {
645        match self {
646            Self::Eth(tx) => tx,
647            #[cfg(feature = "base")]
648            Self::Base(tx) => &tx.base,
649            #[cfg(feature = "monad")]
650            Self::Monad(tx) => tx,
651            #[cfg(feature = "optimism")]
652            Self::Op(tx) => &tx.base,
653            Self::Tempo(tx) => &tx.inner,
654        }
655    }
656
657    const fn base_mut(&mut self) -> &mut TxEnv {
658        match self {
659            Self::Eth(tx) => tx,
660            #[cfg(feature = "base")]
661            Self::Base(tx) => &mut tx.base,
662            #[cfg(feature = "monad")]
663            Self::Monad(tx) => tx,
664            #[cfg(feature = "optimism")]
665            Self::Op(tx) => &mut tx.base,
666            Self::Tempo(tx) => &mut tx.inner,
667        }
668    }
669
670    fn into_base(self) -> TxEnv {
671        match self {
672            Self::Eth(tx) => tx,
673            #[cfg(feature = "base")]
674            Self::Base(tx) => tx.base,
675            #[cfg(feature = "monad")]
676            Self::Monad(tx) => tx,
677            #[cfg(feature = "optimism")]
678            Self::Op(tx) => tx.base,
679            Self::Tempo(tx) => tx.inner,
680        }
681    }
682
683    fn uses_protocol_call_nonce(&self) -> bool {
684        match self {
685            Self::Eth(tx) => tx.kind.is_call(),
686            #[cfg(feature = "base")]
687            Self::Base(tx) => {
688                tx.eip8130.is_none()
689                    && tx.base.tx_type != DEPOSIT_TRANSACTION_TYPE
690                    && tx.base.kind.is_call()
691            }
692            #[cfg(feature = "monad")]
693            Self::Monad(tx) => tx.kind.is_call(),
694            #[cfg(feature = "optimism")]
695            Self::Op(tx) => tx.base.kind.is_call(),
696            Self::Tempo(tx) => tx.tempo_tx_env.as_ref().map_or_else(
697                || tx.inner.kind.is_call(),
698                |aa| {
699                    aa.nonce_key.is_zero()
700                        && aa.aa_calls.first().is_some_and(|call| call.to.is_call())
701                },
702            ),
703        }
704    }
705}
706
707fn apply_tempo_envelope_identity(tx_env: &mut CallTxEnv, simulated_tx: Option<&AASigned>) {
708    if let (CallTxEnv::Tempo(tx_env), Some(simulated_tx)) = (tx_env, simulated_tx) {
709        tx_env.unique_tx_identifier = Some(simulated_tx.expiring_nonce_hash(tx_env.inner.caller));
710        if let Some(batch) = &mut tx_env.tempo_tx_env {
711            batch.tx_hash = *simulated_tx.hash();
712        }
713    }
714}
715
716struct PreparedCall {
717    evm_env: EvmEnv,
718    tx_env: CallTxEnv,
719    simulated_tempo_tx: Option<AASigned>,
720    #[cfg(feature = "base")]
721    simulated_envelope: Option<FoundryTxEnvelope>,
722}
723
724#[derive(Default)]
725struct TypedCallOverrides {
726    gas_limit: Option<u64>,
727    access_list: Option<AccessList>,
728    disable_fee_charge: bool,
729}
730
731pub(crate) struct GasEstimateCallOptions {
732    gas_limit: u64,
733    disable_fee_charge: bool,
734    monad_context: Option<MonadReplayContext>,
735}
736
737impl GasEstimateCallOptions {
738    pub(crate) const fn new(
739        gas_limit: u64,
740        disable_fee_charge: bool,
741        monad_context: Option<MonadReplayContext>,
742    ) -> Self {
743        Self { gas_limit, disable_fee_charge, monad_context }
744    }
745}
746
747/// Marker trait that abstracts over the per-network inspector trait bounds
748/// required by the in-memory backend. The OP bound is only included when the
749/// `optimism` feature is enabled.
750#[cfg(all(feature = "base", feature = "optimism", feature = "monad"))]
751pub trait BackendInspector<DB: Database>:
752    Inspector<EthEvmContext<DB>>
753    + Inspector<BaseContext<DB>>
754    + Inspector<OpEvmContext<DB>>
755    + Inspector<TempoContext<DB>>
756    + Inspector<alloy_monad_evm::MonadContext<DB>>
757{
758}
759#[cfg(all(feature = "base", feature = "optimism", feature = "monad"))]
760impl<DB: Database, T> BackendInspector<DB> for T where
761    T: Inspector<EthEvmContext<DB>>
762        + Inspector<BaseContext<DB>>
763        + Inspector<OpEvmContext<DB>>
764        + Inspector<TempoContext<DB>>
765        + Inspector<alloy_monad_evm::MonadContext<DB>>
766{
767}
768#[cfg(all(feature = "base", feature = "optimism", not(feature = "monad")))]
769pub trait BackendInspector<DB: Database>:
770    Inspector<EthEvmContext<DB>>
771    + Inspector<BaseContext<DB>>
772    + Inspector<OpEvmContext<DB>>
773    + Inspector<TempoContext<DB>>
774{
775}
776#[cfg(all(feature = "base", feature = "optimism", not(feature = "monad")))]
777impl<DB: Database, T> BackendInspector<DB> for T where
778    T: Inspector<EthEvmContext<DB>>
779        + Inspector<BaseContext<DB>>
780        + Inspector<OpEvmContext<DB>>
781        + Inspector<TempoContext<DB>>
782{
783}
784#[cfg(all(feature = "base", not(feature = "optimism"), feature = "monad"))]
785pub trait BackendInspector<DB: Database>:
786    Inspector<EthEvmContext<DB>>
787    + Inspector<BaseContext<DB>>
788    + Inspector<TempoContext<DB>>
789    + Inspector<alloy_monad_evm::MonadContext<DB>>
790{
791}
792#[cfg(all(feature = "base", not(feature = "optimism"), feature = "monad"))]
793impl<DB: Database, T> BackendInspector<DB> for T where
794    T: Inspector<EthEvmContext<DB>>
795        + Inspector<BaseContext<DB>>
796        + Inspector<TempoContext<DB>>
797        + Inspector<alloy_monad_evm::MonadContext<DB>>
798{
799}
800#[cfg(all(feature = "base", not(feature = "optimism"), not(feature = "monad")))]
801pub trait BackendInspector<DB: Database>:
802    Inspector<EthEvmContext<DB>> + Inspector<BaseContext<DB>> + Inspector<TempoContext<DB>>
803{
804}
805#[cfg(all(feature = "base", not(feature = "optimism"), not(feature = "monad")))]
806impl<DB: Database, T> BackendInspector<DB> for T where
807    T: Inspector<EthEvmContext<DB>> + Inspector<BaseContext<DB>> + Inspector<TempoContext<DB>>
808{
809}
810#[cfg(all(not(feature = "base"), feature = "optimism", feature = "monad"))]
811pub trait BackendInspector<DB: Database>:
812    Inspector<EthEvmContext<DB>>
813    + Inspector<OpEvmContext<DB>>
814    + Inspector<TempoContext<DB>>
815    + Inspector<alloy_monad_evm::MonadContext<DB>>
816{
817}
818#[cfg(all(not(feature = "base"), feature = "optimism", feature = "monad"))]
819impl<DB: Database, T> BackendInspector<DB> for T where
820    T: Inspector<EthEvmContext<DB>>
821        + Inspector<OpEvmContext<DB>>
822        + Inspector<TempoContext<DB>>
823        + Inspector<alloy_monad_evm::MonadContext<DB>>
824{
825}
826#[cfg(all(not(feature = "base"), feature = "optimism", not(feature = "monad")))]
827pub trait BackendInspector<DB: Database>:
828    Inspector<EthEvmContext<DB>> + Inspector<OpEvmContext<DB>> + Inspector<TempoContext<DB>>
829{
830}
831#[cfg(all(not(feature = "base"), feature = "optimism", not(feature = "monad")))]
832impl<DB: Database, T> BackendInspector<DB> for T where
833    T: Inspector<EthEvmContext<DB>> + Inspector<OpEvmContext<DB>> + Inspector<TempoContext<DB>>
834{
835}
836#[cfg(all(not(feature = "base"), not(feature = "optimism"), feature = "monad"))]
837pub trait BackendInspector<DB: Database>:
838    Inspector<EthEvmContext<DB>>
839    + Inspector<TempoContext<DB>>
840    + Inspector<alloy_monad_evm::MonadContext<DB>>
841{
842}
843#[cfg(all(not(feature = "base"), not(feature = "optimism"), feature = "monad"))]
844impl<DB: Database, T> BackendInspector<DB> for T where
845    T: Inspector<EthEvmContext<DB>>
846        + Inspector<TempoContext<DB>>
847        + Inspector<alloy_monad_evm::MonadContext<DB>>
848{
849}
850#[cfg(all(not(feature = "base"), not(feature = "optimism"), not(feature = "monad")))]
851pub trait BackendInspector<DB: Database>:
852    Inspector<EthEvmContext<DB>> + Inspector<TempoContext<DB>>
853{
854}
855#[cfg(all(not(feature = "base"), not(feature = "optimism"), not(feature = "monad")))]
856impl<DB: Database, T> BackendInspector<DB> for T where
857    T: Inspector<EthEvmContext<DB>> + Inspector<TempoContext<DB>>
858{
859}
860#[cfg(feature = "base")]
861mod base;
862pub mod cache;
863pub mod fork_db;
864pub mod in_memory_db;
865pub mod inspector;
866#[cfg(feature = "monad")]
867mod monad;
868#[cfg(feature = "optimism")]
869pub mod optimism;
870pub mod state;
871pub mod storage;
872
873/// Helper trait that combines revm::DatabaseRef with Debug.
874/// This is needed because alloy-evm requires Debug on Database implementations.
875/// With trait upcasting now stable, we can now upcast from this trait to revm::DatabaseRef.
876pub trait DatabaseRef: revm::DatabaseRef<Error = DatabaseError> + Debug {}
877impl<T> DatabaseRef for T where T: revm::DatabaseRef<Error = DatabaseError> + Debug {}
878impl DatabaseRef for dyn crate::eth::backend::db::Db {}
879
880// Gas per transaction not creating a contract.
881pub const MIN_TRANSACTION_GAS: u128 = 21000;
882// Gas per transaction creating a contract.
883pub const MIN_CREATE_GAS: u128 = 53000;
884/// Standalone Base L1 base fee, in wei.
885#[cfg(feature = "base")]
886const DEFAULT_BASE_L1_BASE_FEE: u64 = 1_000_000_000;
887/// Standalone Base L1 base-fee scalar, with six decimal places.
888#[cfg(feature = "base")]
889const DEFAULT_BASE_L1_FEE_SCALAR: u32 = 1_000_000;
890
891/// Installs the BaseTime proxy and links its Denim implementation.
892///
893/// Standalone genesis owns the proxy admin and always resets it. A fork must not rewrite an
894/// inherited deployment, so the proxy code and admin are only seeded when the account has no code.
895#[cfg(feature = "base")]
896fn ensure_base_time_predeploy(
897    mut db: &mut dyn crate::eth::backend::db::Db,
898    reset_admin: bool,
899) -> Result<(), DatabaseError> {
900    base::ensure_l1_block_predeploy(db)?;
901    let mut proxy = db.basic(Predeploys::BASE_TIME)?.unwrap_or_default();
902    let missing_code = proxy.is_empty_code_hash();
903    if missing_code {
904        let code = revm::state::Bytecode::new_raw(BaseTime::proxy_bytecode());
905        proxy.code_hash = code.hash_slow();
906        proxy.code = Some(code);
907        db.insert_account(Predeploys::BASE_TIME, proxy);
908    }
909    if reset_admin || missing_code {
910        db.set_storage_at(
911            Predeploys::BASE_TIME,
912            B256::from(BaseTime::ADMIN_SLOT.to_be_bytes::<32>()),
913            B256::from(U256::from_be_slice(Predeploys::PROXY_ADMIN.as_slice()).to_be_bytes::<32>()),
914        )?;
915    }
916    let upgrades = ChainUpgrades::new([(BaseUpgrade::Denim, ForkCondition::Timestamp(1))]);
917    BaseTime::ensure_predeploy(upgrades, 1, &mut db)
918        .map_err(|err| DatabaseError::AnyRequest(Arc::new(eyre::eyre!(err))))
919}
920
921fn tempo_nonce(
922    state: &dyn DatabaseRef,
923    caller: Address,
924    nonce_key: U256,
925) -> Result<u64, BlockchainError> {
926    if nonce_key.is_zero() {
927        return Ok(state.basic_ref(caller)?.map(|account| account.nonce).unwrap_or_default());
928    }
929    if nonce_key == U256::MAX {
930        return Ok(0);
931    }
932    let slot = NonceManager::new().nonces[caller][nonce_key].slot();
933    Ok(state.storage_ref(NONCE_PRECOMPILE_ADDRESS, slot)?.saturating_to())
934}
935
936fn mock_tempo_signature(
937    key_type: SignatureType,
938    key_data: Option<Bytes>,
939    key_id: Option<Address>,
940    caller: Address,
941    is_t1c: bool,
942) -> TempoSignature {
943    let signature = match key_type {
944        SignatureType::Secp256k1 => {
945            PrimitiveSignature::Secp256k1(Signature::new(U256::ZERO, U256::ZERO, false))
946        }
947        SignatureType::P256 => PrimitiveSignature::P256(P256SignatureWithPreHash {
948            r: B256::ZERO,
949            s: B256::ZERO,
950            pub_key_x: B256::ZERO,
951            pub_key_y: B256::ZERO,
952            pre_hash: false,
953        }),
954        SignatureType::WebAuthn => {
955            const CLIENT_JSON: &str = r#"{"type":"webauthn.get","challenge":"","origin":""}"#;
956            const AUTH_DATA_SIZE: usize = 37;
957            const MIN_SIZE: usize = AUTH_DATA_SIZE + CLIENT_JSON.len();
958            const DEFAULT_SIZE: usize = 800;
959            const MAX_SIZE: usize = 8192;
960
961            let size = key_data
962                .as_deref()
963                .and_then(|data| match data.len() {
964                    1 => Some(data[0] as usize),
965                    2 => Some(u16::from_be_bytes([data[0], data[1]]) as usize),
966                    4 => Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize),
967                    _ => None,
968                })
969                .unwrap_or(DEFAULT_SIZE)
970                .clamp(MIN_SIZE, MAX_SIZE);
971            let mut webauthn_data = vec![0u8; AUTH_DATA_SIZE];
972            webauthn_data[32] = 0x01;
973            let padding = "x".repeat(size - MIN_SIZE);
974            webauthn_data.extend_from_slice(
975                format!(r#"{{"type":"webauthn.get","challenge":"","origin":"{padding}"}}"#)
976                    .as_bytes(),
977            );
978            PrimitiveSignature::WebAuthn(WebAuthnSignature {
979                webauthn_data: webauthn_data.into(),
980                r: B256::ZERO,
981                s: B256::ZERO,
982                pub_key_x: B256::ZERO,
983                pub_key_y: B256::ZERO,
984            })
985        }
986    };
987
988    if key_id.is_some() {
989        let signature = if is_t1c {
990            KeychainSignature::new(caller, signature)
991        } else {
992            KeychainSignature::new_v1(caller, signature)
993        };
994        TempoSignature::Keychain(signature)
995    } else {
996        TempoSignature::Primitive(signature)
997    }
998}
999
1000fn call_config_from_tracer_config(
1001    tracer_config: GethDebugTracerConfig,
1002) -> Result<CallConfig, serde_json::Error> {
1003    let mut tracer_config = tracer_config.into_json();
1004    if let Some(config) = tracer_config.as_object_mut()
1005        && !config.contains_key("onlyTopCall")
1006        && let Some(only_top_level_call) = config.remove("onlyTopLevelCall")
1007    {
1008        config.insert("onlyTopCall".to_string(), only_top_level_call);
1009    }
1010
1011    GethDebugTracerConfig(tracer_config).into_call_config()
1012}
1013
1014/// Builds Parity trace results for `result`, with `db` as the state before the transaction.
1015///
1016/// The state diff treats empty accounts as absent, so created and newly funded accounts are
1017/// marked as added.
1018fn parity_trace_results(
1019    inspector: TracingInspector,
1020    result: &ResultAndState<HaltReason>,
1021    trace_types: &HashSet<TraceType>,
1022    db: impl revm::DatabaseRef<Error = DatabaseError>,
1023) -> Result<TraceResults, BlockchainError> {
1024    inspector
1025        .into_parity_builder()
1026        .into_trace_results_with_state(result, trace_types, EmptyAsAbsentDb(db))
1027        .map_err(Into::into)
1028}
1029
1030/// Rejects the `pending` tag for block trace methods, which only trace mined blocks.
1031///
1032/// Resolving it to the latest block would present mined transactions as the pending block.
1033fn ensure_mined_trace_block(block: BlockNumber) -> Result<(), BlockchainError> {
1034    if block.is_pending() {
1035        return Err(BlockchainError::RpcError(RpcError::invalid_params(
1036            "pending block traces are not supported",
1037        )));
1038    }
1039    Ok(())
1040}
1041
1042pub type State = foundry_evm::utils::StateChangeset;
1043
1044#[derive(Clone, Debug, Default)]
1045struct SimulationPrecompileOverrides {
1046    moves: Vec<(Address, Address)>,
1047}
1048
1049/// A block request, which includes the Pool Transactions if it's Pending
1050pub enum BlockRequest<T> {
1051    Pending(Vec<Arc<PoolTransaction<T>>>),
1052    Number(u64),
1053}
1054
1055impl<T> fmt::Debug for BlockRequest<T> {
1056    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1057        match self {
1058            Self::Pending(txs) => f.debug_tuple("Pending").field(&txs.len()).finish(),
1059            Self::Number(n) => f.debug_tuple("Number").field(n).finish(),
1060        }
1061    }
1062}
1063
1064impl<T> BlockRequest<T> {
1065    pub const fn block_number(&self) -> BlockNumber {
1066        match *self {
1067            Self::Pending(_) => BlockNumber::Pending,
1068            Self::Number(n) => BlockNumber::Number(n),
1069        }
1070    }
1071}
1072
1073struct StateSnapshot {
1074    block_number: u64,
1075    block_hash: B256,
1076    fees: FeeSnapshot,
1077    time: TimeSnapshot,
1078    next_block: NextBlockOverrides,
1079}
1080
1081#[cfg(test)]
1082#[derive(Clone)]
1083struct MiningCommitHook {
1084    reached: Arc<Notify>,
1085    resume: Arc<Notify>,
1086}
1087
1088/// Gives access to the [revm::Database]
1089pub struct Backend<N: Network> {
1090    /// Access to [`revm::Database`] abstraction.
1091    ///
1092    /// This will be used in combination with [`alloy_evm::Evm`] and is responsible for feeding
1093    /// data to the evm during its execution.
1094    ///
1095    /// At time of writing, there are two different types of `Db`:
1096    ///   - [`MemDb`](crate::mem::in_memory_db::MemDb): everything is stored in memory
1097    ///   - [`ForkDb`](crate::mem::fork_db::ForkedDatabase): forks off a remote client, missing
1098    ///     data is retrieved via RPC-calls
1099    ///
1100    /// In order to commit changes to the [`revm::Database`], the [`alloy_evm::Evm`] requires
1101    /// mutable access, which requires a write-lock from this `db`. In forking mode, the time
1102    /// during which the write-lock is active depends on whether the `ForkDb` can provide all
1103    /// requested data from memory or whether it has to retrieve it via RPC calls first. This
1104    /// means that it potentially blocks for some time, even taking into account the rate
1105    /// limits of RPC endpoints. Therefore the `Db` is guarded by a `tokio::sync::RwLock` here
1106    /// so calls that need to read from it, while it's currently written to, don't block. E.g.
1107    /// a new block is currently mined and a new [`Self::set_storage_at()`] request is being
1108    /// executed.
1109    db: Arc<AsyncRwLock<Box<dyn Db>>>,
1110    /// stores all block related data in memory.
1111    blockchain: Blockchain<N>,
1112    /// Historic states of previous blocks.
1113    states: Arc<RwLock<InMemoryBlockStates>>,
1114    /// EVM environment data of the chain (block env, cfg env).
1115    evm_env: Arc<RwLock<EvmEnv>>,
1116    /// Network configuration (optimism, custom precompiles, etc.)
1117    networks: NetworkConfigs,
1118    /// Base activation-registry administrator override.
1119    #[cfg(feature = "base")]
1120    base_activation_admin: Option<Address>,
1121    /// The active hardfork.
1122    hardfork: Arc<RwLock<FoundryHardfork>>,
1123    /// This is set if this is currently forked off another client.
1124    fork: Arc<RwLock<Option<ClientFork>>>,
1125    /// The last source that supplied the live fork backend, retained across memory resets.
1126    last_fork_cache_source: Arc<RwLock<Option<ForkCacheSource>>>,
1127    /// Provides time related info, like timestamp.
1128    time: TimeManager,
1129    /// Contains state of custom overrides.
1130    cheats: CheatsManager,
1131    /// Contains fee data.
1132    fees: FeeManager,
1133    /// Initialised genesis.
1134    genesis: GenesisConfig,
1135    /// Listeners for new blocks that get notified when a new block was imported or when logs were
1136    /// removed from the canonical chain due to a reorg.
1137    new_block_listeners: Arc<Mutex<Vec<UnboundedSender<ChainNotification>>>>,
1138    /// Keeps track of active state snapshots at a specific block.
1139    active_state_snapshots: Arc<Mutex<HashMap<U256, StateSnapshot>>>,
1140    enable_steps_tracing: bool,
1141    print_logs: bool,
1142    print_traces: bool,
1143    /// Recorder used for decoding traces, used together with print_traces.
1144    call_trace_decoder: Arc<RwLock<Arc<CallTraceDecoder>>>,
1145    /// How to keep history state
1146    prune_state_history_config: PruneStateHistoryConfig,
1147    /// max number of blocks with transactions in memory
1148    transaction_block_keeper: Option<usize>,
1149    pub(crate) node_config: Arc<AsyncRwLock<NodeConfig>>,
1150    /// Slots in an epoch
1151    slots_in_an_epoch: u64,
1152    /// Precompiles to inject to the EVM.
1153    precompile_factory: Option<Arc<dyn PrecompileFactory>>,
1154    /// Prevent race conditions during mining
1155    mining: Arc<tokio::sync::Mutex<()>>,
1156    /// Test-only synchronization point after database commit and before canonical publication.
1157    #[cfg(test)]
1158    mining_commit_hook: Arc<Mutex<Option<MiningCommitHook>>>,
1159    /// Disable pool balance checks
1160    disable_pool_balance_checks: bool,
1161    /// Keeps startup fork-cache rollback armed until startup initialization completes.
1162    ///
1163    /// This must remain the final field so all other backend-held database references are released
1164    /// before a rejected startup invalidates its cache.
1165    startup_fork_cache_user: StagedForkDbUser<Box<dyn Db>>,
1166}
1167
1168impl<N: Network> Clone for Backend<N> {
1169    fn clone(&self) -> Self {
1170        Self {
1171            db: self.db.clone(),
1172            blockchain: self.blockchain.clone(),
1173            states: self.states.clone(),
1174            evm_env: self.evm_env.clone(),
1175            networks: self.networks,
1176            #[cfg(feature = "base")]
1177            base_activation_admin: self.base_activation_admin,
1178            hardfork: self.hardfork.clone(),
1179            fork: self.fork.clone(),
1180            last_fork_cache_source: self.last_fork_cache_source.clone(),
1181            time: self.time.clone(),
1182            cheats: self.cheats.clone(),
1183            fees: self.fees.clone(),
1184            genesis: self.genesis.clone(),
1185            new_block_listeners: self.new_block_listeners.clone(),
1186            active_state_snapshots: self.active_state_snapshots.clone(),
1187            enable_steps_tracing: self.enable_steps_tracing,
1188            print_logs: self.print_logs,
1189            print_traces: self.print_traces,
1190            call_trace_decoder: self.call_trace_decoder.clone(),
1191            prune_state_history_config: self.prune_state_history_config,
1192            transaction_block_keeper: self.transaction_block_keeper,
1193            node_config: self.node_config.clone(),
1194            slots_in_an_epoch: self.slots_in_an_epoch,
1195            precompile_factory: self.precompile_factory.clone(),
1196            mining: self.mining.clone(),
1197            #[cfg(test)]
1198            mining_commit_hook: self.mining_commit_hook.clone(),
1199            disable_pool_balance_checks: self.disable_pool_balance_checks,
1200            startup_fork_cache_user: self.startup_fork_cache_user.clone(),
1201        }
1202    }
1203}
1204
1205impl<N: Network> fmt::Debug for Backend<N> {
1206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1207        f.debug_struct("Backend").finish_non_exhaustive()
1208    }
1209}
1210
1211// Methods that are generic over any Network.
1212impl<N: Network> Backend<N> {
1213    /// Sets the account to impersonate
1214    ///
1215    /// Returns `true` if the account is already impersonated
1216    pub fn impersonate(&self, addr: Address) -> bool {
1217        if self.cheats.impersonated_accounts().contains(&addr) {
1218            return true;
1219        }
1220        // Ensure EIP-3607 is disabled
1221        self.evm_env.write().cfg_env.disable_eip3607 = true;
1222        self.cheats.impersonate(addr)
1223    }
1224
1225    /// Removes the account that from the impersonated set
1226    ///
1227    /// If the impersonated `addr` is a contract then we also reset the code here
1228    pub fn stop_impersonating(&self, addr: Address) {
1229        self.cheats.stop_impersonating(&addr);
1230    }
1231
1232    /// If set to true will make every account impersonated
1233    pub fn auto_impersonate_account(&self, enabled: bool) {
1234        self.cheats.set_auto_impersonate_account(enabled);
1235    }
1236
1237    /// Returns the configured fork, if any
1238    pub fn get_fork(&self) -> Option<ClientFork> {
1239        self.fork.read().clone()
1240    }
1241
1242    /// Marks startup fork-cache writes as belonging to the validated live backend.
1243    pub(crate) fn commit_startup_fork_cache(&self) {
1244        self.startup_fork_cache_user.cache_lease.disarm();
1245    }
1246
1247    /// Returns the database
1248    pub fn get_db(&self) -> &Arc<AsyncRwLock<Box<dyn Db>>> {
1249        &self.db
1250    }
1251
1252    /// Locks block production while a backend-wide lifecycle transition is committed.
1253    pub(crate) async fn lock_mining(&self) -> tokio::sync::MutexGuard<'_, ()> {
1254        self.mining.lock().await
1255    }
1256
1257    /// Locks block production with a guard that can be moved into a mining task.
1258    pub(crate) async fn lock_mining_owned(self: &Arc<Self>) -> tokio::sync::OwnedMutexGuard<()> {
1259        self.mining.clone().lock_owned().await
1260    }
1261
1262    /// Returns the `AccountInfo` from the database
1263    pub async fn get_account(&self, address: Address) -> DatabaseResult<AccountInfo> {
1264        Ok(self.db.read().await.basic_ref(address)?.unwrap_or_default())
1265    }
1266
1267    /// Whether we're forked off some remote client
1268    pub fn is_fork(&self) -> bool {
1269        self.fork.read().is_some()
1270    }
1271
1272    /// Writes the CREATE2 deployer code directly to the database at the address provided.
1273    pub async fn set_create2_deployer(&self, address: Address) -> DatabaseResult<()> {
1274        self.set_code(address, Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE)).await?;
1275        Ok(())
1276    }
1277
1278    /// Updates memory limits that should be more strict when auto-mine is enabled
1279    pub(crate) fn update_interval_mine_block_time(&self, block_time: Duration) {
1280        self.states.write().update_interval_mine_block_time(block_time)
1281    }
1282
1283    /// Returns the `TimeManager` responsible for timestamps
1284    pub const fn time(&self) -> &TimeManager {
1285        &self.time
1286    }
1287
1288    /// Returns the `CheatsManager` responsible for executing cheatcodes
1289    pub const fn cheats(&self) -> &CheatsManager {
1290        &self.cheats
1291    }
1292
1293    /// Whether to skip blob validation
1294    pub fn skip_blob_validation(&self, impersonator: Option<Address>) -> bool {
1295        self.cheats().auto_impersonate_accounts()
1296            || impersonator
1297                .is_some_and(|addr| self.cheats().impersonated_accounts().contains(&addr))
1298    }
1299
1300    /// Returns the `FeeManager` that manages fee/pricings
1301    pub const fn fees(&self) -> &FeeManager {
1302        &self.fees
1303    }
1304
1305    /// The EVM environment data of the blockchain
1306    pub const fn evm_env(&self) -> &Arc<RwLock<EvmEnv>> {
1307        &self.evm_env
1308    }
1309
1310    /// Returns the current best hash of the chain
1311    pub fn best_hash(&self) -> B256 {
1312        self.blockchain.storage.read().best_hash
1313    }
1314
1315    /// Returns the current best number of the chain
1316    pub fn best_number(&self) -> u64 {
1317        self.blockchain.storage.read().best_number
1318    }
1319
1320    /// Sets the block number
1321    pub fn set_block_number(&self, number: u64) {
1322        self.evm_env.write().block_env.number = U256::from(number);
1323    }
1324
1325    /// Returns the client coinbase address.
1326    pub fn coinbase(&self) -> Address {
1327        self.evm_env.read().block_env.beneficiary
1328    }
1329
1330    /// Returns the client coinbase address.
1331    pub fn chain_id(&self) -> U256 {
1332        U256::from(self.evm_env.read().cfg_env.chain_id)
1333    }
1334
1335    /// Returns the chain ID that defines protocol behavior.
1336    fn protocol_chain_id(&self) -> u64 {
1337        self.get_fork().map_or_else(|| self.evm_env.read().cfg_env.chain_id, |fork| fork.chain_id())
1338    }
1339
1340    pub fn set_chain_id(&self, chain_id: u64) {
1341        self.evm_env.write().cfg_env.chain_id = chain_id;
1342    }
1343
1344    /// Returns the genesis data for the Beacon API.
1345    pub const fn genesis_time(&self) -> u64 {
1346        self.genesis.timestamp
1347    }
1348
1349    /// Returns the configured genesis block number.
1350    pub const fn genesis_number(&self) -> u64 {
1351        self.genesis.number
1352    }
1353
1354    /// Returns balance of the given account.
1355    pub async fn current_balance(&self, address: Address) -> DatabaseResult<U256> {
1356        Ok(self.get_account(address).await?.balance)
1357    }
1358
1359    /// Returns balance of the given account.
1360    pub async fn current_nonce(&self, address: Address) -> DatabaseResult<u64> {
1361        Ok(self.get_account(address).await?.nonce)
1362    }
1363
1364    /// Sets the coinbase address
1365    pub fn set_coinbase(&self, address: Address) {
1366        self.evm_env.write().block_env.beneficiary = address;
1367    }
1368
1369    /// Sets the `prevrandao` value to use for the next mined block.
1370    ///
1371    /// This is a one-shot override that is consumed by the next block; afterwards anvil resumes its
1372    /// default per-block `prevrandao` derivation.
1373    pub fn set_next_block_prevrandao(&self, prevrandao: B256) {
1374        self.cheats.set_next_block_prevrandao(prevrandao);
1375    }
1376
1377    /// Sets the parent beacon block root to use for the next mined block.
1378    ///
1379    /// This is a one-shot override that is consumed by the next block; afterwards anvil resumes
1380    /// using the zero root.
1381    pub fn set_next_block_parent_beacon_block_root(&self, root: B256) {
1382        self.cheats.set_next_block_parent_beacon_block_root(root);
1383    }
1384
1385    /// Sets the nonce of the given address
1386    pub async fn set_nonce(&self, address: Address, nonce: U256) -> DatabaseResult<()> {
1387        self.db.write().await.set_nonce(address, nonce.try_into().unwrap_or(u64::MAX))
1388    }
1389
1390    /// Sets the balance of the given address
1391    pub async fn set_balance(&self, address: Address, balance: U256) -> DatabaseResult<()> {
1392        self.db.write().await.set_balance(address, balance)
1393    }
1394
1395    /// Increases the balance of the given address, saturating at `U256::MAX`.
1396    pub(crate) async fn add_balance(&self, address: Address, balance: U256) -> DatabaseResult<()> {
1397        let mut db = self.db.write().await;
1398        let current_balance = db.basic(address)?.unwrap_or_default().balance;
1399        db.set_balance(address, current_balance.saturating_add(balance))
1400    }
1401
1402    /// Sets the code of the given address
1403    pub async fn set_code(&self, address: Address, code: Bytes) -> DatabaseResult<()> {
1404        self.db.write().await.set_code(address, code)
1405    }
1406
1407    /// Sets the value for the given slot of the given address
1408    pub async fn set_storage_at(
1409        &self,
1410        address: Address,
1411        slot: U256,
1412        val: B256,
1413    ) -> DatabaseResult<()> {
1414        self.db.write().await.set_storage_at(address, slot.into(), val)
1415    }
1416
1417    /// Returns the configured specid
1418    pub fn spec_id(&self) -> SpecId {
1419        *self.evm_env.read().spec_id()
1420    }
1421
1422    /// Returns true for post London
1423    pub fn is_eip1559(&self) -> bool {
1424        (self.spec_id() as u8) >= (SpecId::LONDON as u8)
1425    }
1426
1427    /// Returns true for post Merge
1428    pub fn is_eip3675(&self) -> bool {
1429        (self.spec_id() as u8) >= (SpecId::MERGE as u8)
1430    }
1431
1432    /// Returns true for post Berlin
1433    pub fn is_eip2930(&self) -> bool {
1434        (self.spec_id() as u8) >= (SpecId::BERLIN as u8)
1435    }
1436
1437    /// Returns true for post Cancun
1438    pub fn is_eip4844(&self) -> bool {
1439        (self.spec_id() as u8) >= (SpecId::CANCUN as u8)
1440    }
1441
1442    /// Returns true for post Prague
1443    pub fn is_eip7702(&self) -> bool {
1444        (self.spec_id() as u8) >= (SpecId::PRAGUE as u8)
1445    }
1446
1447    /// Returns true if op-stack deposits are active.
1448    ///
1449    /// Always `false` when built without the `optimism` feature.
1450    pub const fn is_optimism(&self) -> bool {
1451        self.networks.is_optimism()
1452    }
1453
1454    /// Returns true if Tempo network mode is active
1455    pub const fn is_tempo(&self) -> bool {
1456        self.networks.is_tempo()
1457    }
1458
1459    /// Returns whether Celo compatibility is enabled.
1460    pub const fn is_celo(&self) -> bool {
1461        self.networks.is_celo()
1462    }
1463
1464    /// Returns true if Monad network mode is active
1465    pub const fn is_monad(&self) -> bool {
1466        self.networks.is_monad()
1467    }
1468
1469    /// Returns the active execution profile name.
1470    pub const fn execution_profile_name(&self) -> &'static str {
1471        self.networks.execution_profile_name()
1472    }
1473
1474    /// Reconstructs a locally mined transaction using its authoritative stored sender.
1475    fn pending_mined_transaction(
1476        &self,
1477        transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>,
1478    ) -> Result<PendingTransaction<FoundryTxEnvelope>, BlockchainError> {
1479        #[cfg(feature = "monad")]
1480        if self.is_monad() {
1481            return Self::monad_pending_mined_transaction_from_storage(
1482                &self.blockchain.storage.read(),
1483                transaction,
1484            );
1485        }
1486        Ok(PendingTransaction::from_maybe_impersonated(transaction)?)
1487    }
1488
1489    #[cfg(not(feature = "monad"))]
1490    fn active_monad_context_for_mined_block(
1491        &self,
1492        _block: &Block,
1493    ) -> Result<Option<MonadReplayContext>, BlockchainError> {
1494        Ok(None)
1495    }
1496
1497    #[cfg(not(feature = "monad"))]
1498    fn active_monad_context_before_mined_transaction(
1499        &self,
1500        _block: &Block,
1501        _current_tx_index: usize,
1502    ) -> Result<Option<MonadReplayContext>, BlockchainError> {
1503        Ok(None)
1504    }
1505
1506    /// Returns true if Base network mode is active.
1507    #[cfg(feature = "base")]
1508    pub const fn is_base(&self) -> bool {
1509        self.networks.is_base()
1510    }
1511
1512    /// Returns the active hardfork.
1513    pub fn hardfork(&self) -> FoundryHardfork {
1514        if let Some(hardfork) =
1515            self.fork.read().as_ref().and_then(|fork| fork.config.read().hardfork)
1516        {
1517            return hardfork;
1518        }
1519        *self.hardfork.read()
1520    }
1521
1522    /// Returns canonical Ethereum transition configuration only for an Ethereum network.
1523    fn ethereum_block_transitions(
1524        &self,
1525        hardfork: FoundryHardfork,
1526        parent_beacon_block_root: Option<B256>,
1527        execution_kind: BlockExecutionKind,
1528    ) -> Option<EthereumBlockTransitions> {
1529        if self.is_optimism() || self.is_tempo() {
1530            return None;
1531        }
1532        let FoundryHardfork::Ethereum(hardfork) = hardfork else { return None };
1533        Some(EthereumBlockTransitions {
1534            hardfork,
1535            deposit_contract_address: self.ethereum_deposit_contract_address(),
1536            parent_beacon_block_root,
1537            execution_kind,
1538        })
1539    }
1540
1541    /// Returns the configured deposit contract, then the canonical address for known chains.
1542    fn ethereum_deposit_contract_address(&self) -> Address {
1543        if let Some(address) = self
1544            .genesis
1545            .genesis_init
1546            .as_ref()
1547            .and_then(|genesis| genesis.config.deposit_contract_address)
1548        {
1549            return address;
1550        }
1551
1552        match NamedChain::try_from(self.evm_env.read().cfg_env.chain_id) {
1553            Ok(NamedChain::Sepolia) => SEPOLIA_DEPOSIT_CONTRACT_ADDRESS,
1554            Ok(NamedChain::Holesky) => HOLESKY_DEPOSIT_CONTRACT_ADDRESS,
1555            // Hoodi shares the mainnet address; other chains use Alloy's mainnet fallback.
1556            _ => MAINNET_DEPOSIT_CONTRACT_ADDRESS,
1557        }
1558    }
1559
1560    /// Returns the active Tempo hardfork.
1561    pub fn tempo_hardfork(&self) -> TempoHardfork {
1562        TempoHardfork::from(self.hardfork())
1563    }
1564
1565    /// Returns the active Monad hardfork.
1566    #[cfg(feature = "monad")]
1567    pub fn monad_hardfork(&self) -> monad_revm::MonadHardfork {
1568        monad_revm::MonadHardfork::from(self.hardfork())
1569    }
1570
1571    /// Returns the active Base upgrade.
1572    #[cfg(feature = "base")]
1573    pub fn base_upgrade(&self) -> BaseUpgrade {
1574        match self.hardfork() {
1575            FoundryHardfork::Base(upgrade) => upgrade,
1576            _ => BaseUpgrade::Azul,
1577        }
1578    }
1579
1580    /// Returns the configured Base activation-registry administrator override.
1581    #[cfg(feature = "base")]
1582    pub const fn base_activation_admin(&self) -> Option<Address> {
1583        self.base_activation_admin
1584    }
1585
1586    /// Returns the Base upgrade active at `timestamp`.
1587    #[cfg(feature = "base")]
1588    pub fn base_upgrade_at_timestamp(&self, timestamp: u64) -> BaseUpgrade {
1589        if self.is_fork() {
1590            BaseUpgrade::from_chain_and_timestamp(self.chain_id().to(), timestamp)
1591                .unwrap_or_else(|| self.base_upgrade())
1592        } else {
1593            self.base_upgrade()
1594        }
1595    }
1596
1597    /// Prepares the clock for a candidate block without consuming its time controls.
1598    fn prepare_block_timestamp(
1599        &self,
1600        db: &dyn revm::DatabaseRef<Error = DatabaseError>,
1601        parent_timestamp: u64,
1602    ) -> Result<PendingBlockTimestamp, DatabaseError> {
1603        #[cfg(feature = "base")]
1604        if self.is_base() && self.base_upgrade() >= BaseUpgrade::Denim {
1605            return base::prepare_block_timestamp(db, &self.time, parent_timestamp);
1606        }
1607        let _ = (db, parent_timestamp);
1608        Ok(self.time.prepare_next_timestamp())
1609    }
1610
1611    /// Builds the required deposits from the same parent state used to prepare the clock.
1612    #[cfg(feature = "base")]
1613    fn base_system_transactions(
1614        &self,
1615        db: &dyn revm::DatabaseRef<Error = DatabaseError>,
1616        block_number: u64,
1617        timestamp: u64,
1618        parent_hash: B256,
1619    ) -> Result<Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>, DatabaseError> {
1620        if self.is_base() && self.base_upgrade() >= BaseUpgrade::Denim {
1621            base::system_transactions(db, block_number, timestamp, parent_hash, self.is_fork())
1622        } else {
1623            Ok(Vec::new())
1624        }
1625    }
1626
1627    /// Returns the head timestamp snapshot used for EIP-8130 pool admission.
1628    #[cfg(feature = "base")]
1629    pub fn eip8130_pool_timestamp(&self) -> u64 {
1630        self.evm_env.read().block_env.timestamp.saturating_to()
1631    }
1632
1633    /// Returns the EIP-8130 pool admission snapshot in Unix milliseconds.
1634    ///
1635    /// EIP-8130 validity windows are millisecond timestamps evaluated against
1636    /// `block.timestamp * 1000`, so protocol validation must use this rather than the
1637    /// second-valued block timestamp.
1638    #[cfg(feature = "base")]
1639    pub fn eip8130_pool_timestamp_ms(&self) -> u64 {
1640        self.eip8130_pool_timestamp().saturating_mul(1_000)
1641    }
1642
1643    /// Returns whether a Tempo hardfork is active on this backend.
1644    pub fn is_tempo_hardfork_active(&self, hardfork: TempoHardfork) -> bool {
1645        self.is_tempo() && self.tempo_hardfork() >= hardfork
1646    }
1647
1648    /// Returns the precompiles for the current spec.
1649    pub fn precompiles(&self) -> BTreeMap<String, Address> {
1650        let spec_id = self.spec_id();
1651        let mut precompiles =
1652            PrecompilesMap::from_static(Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)));
1653        let chain_id = self.protocol_chain_id();
1654        let timestamp = self.evm_env.read().block_env.timestamp.saturating_to();
1655        apply_bsc_p256_precompile(&mut precompiles, chain_id, timestamp);
1656
1657        let mut precompiles_map = BTreeMap::<String, Address>::default();
1658        for address in precompiles.addresses() {
1659            let precompile = precompiles.get(address).expect("precompile address must resolve");
1660            precompiles_map.insert(precompile.precompile_id().name().to_string(), *address);
1661        }
1662
1663        // Extend with configured network precompiles.
1664        precompiles_map.extend(self.networks.precompiles(Some(self.hardfork())));
1665
1666        if let Some(factory) = &self.precompile_factory {
1667            for (address, precompile) in factory.precompiles() {
1668                precompiles_map.insert(precompile.precompile_id().to_string(), address);
1669            }
1670        }
1671
1672        precompiles_map
1673    }
1674
1675    /// Returns whether the address is an active precompile in the given block environment.
1676    pub fn is_precompile(&self, address: &Address, block_env: &BlockEnv) -> bool {
1677        let mut evm_env = self.evm_env.read().clone();
1678        evm_env.block_env = block_env.clone();
1679        let mut precompiles = PrecompilesMap::from_static(Precompiles::new(
1680            PrecompileSpecId::from_spec_id(self.spec_id()),
1681        ));
1682        self.inject_precompiles(&mut precompiles, &evm_env);
1683        precompiles.get(address).is_some()
1684    }
1685
1686    /// Returns the system contracts for the current spec.
1687    pub fn system_contracts(&self) -> BTreeMap<SystemContract, Address> {
1688        let mut system_contracts = BTreeMap::<SystemContract, Address>::default();
1689
1690        let spec_id = self.spec_id();
1691
1692        if spec_id >= SpecId::CANCUN {
1693            system_contracts.extend(SystemContract::cancun());
1694        }
1695
1696        if spec_id >= SpecId::PRAGUE {
1697            system_contracts.extend(SystemContract::prague(None));
1698        }
1699
1700        system_contracts
1701    }
1702
1703    /// Returns the active [`BlobParams`].
1704    pub fn blob_params(&self) -> BlobParams {
1705        self.fees.blob_params()
1706    }
1707
1708    fn simulation_blob_params_at_timestamp(&self, timestamp: u64) -> BlobParams {
1709        let configured_hardfork = self.hardfork();
1710        if let FoundryHardfork::Ethereum(
1711            configured
1712            @ (EthereumHardfork::Osaka | EthereumHardfork::Bpo1 | EthereumHardfork::Bpo2),
1713        ) = configured_hardfork
1714            && let Some(hardfork) = FoundryHardfork::from_chain_and_timestamp(
1715                self.evm_env.read().cfg_env.chain_id,
1716                timestamp,
1717            )
1718            && let FoundryHardfork::Ethereum(
1719                scheduled @ (EthereumHardfork::Osaka
1720                | EthereumHardfork::Bpo1
1721                | EthereumHardfork::Bpo2),
1722            ) = hardfork
1723        {
1724            let hardfork = match (configured, scheduled) {
1725                (EthereumHardfork::Bpo2, _) | (_, EthereumHardfork::Bpo2) => EthereumHardfork::Bpo2,
1726                (EthereumHardfork::Bpo1, _) | (_, EthereumHardfork::Bpo1) => EthereumHardfork::Bpo1,
1727                _ => EthereumHardfork::Osaka,
1728            };
1729            return get_blob_params_by_hardfork(hardfork.into());
1730        }
1731        get_blob_params_by_hardfork(configured_hardfork)
1732    }
1733
1734    #[cfg(feature = "optimism")]
1735    fn is_optimism_jovian_at_header<H: BlockHeader>(
1736        &self,
1737        header: &H,
1738        decoded: Option<bool>,
1739    ) -> bool {
1740        if !self.is_optimism() {
1741            return false;
1742        }
1743        if let Some(jovian) = decoded {
1744            return jovian;
1745        }
1746        if !header.extra_data().is_empty() {
1747            return false;
1748        }
1749        let hardfork = if self.get_fork().is_some() {
1750            NetworkVariant::Optimism
1751                .historical_hardfork(self.protocol_chain_id(), header.timestamp())
1752                .unwrap_or_else(|| self.hardfork())
1753        } else {
1754            self.hardfork()
1755        };
1756        OpHardfork::from(hardfork) >= OpHardfork::Jovian
1757    }
1758
1759    #[cfg(not(feature = "optimism"))]
1760    const fn is_optimism_jovian_at_header<H: BlockHeader>(
1761        &self,
1762        _header: &H,
1763        _decoded: Option<bool>,
1764    ) -> bool {
1765        false
1766    }
1767
1768    /// Returns an error if EIP1559 is not active (pre Berlin)
1769    pub fn ensure_eip1559_active(&self) -> Result<(), BlockchainError> {
1770        if self.is_eip1559() {
1771            return Ok(());
1772        }
1773        Err(BlockchainError::EIP1559TransactionUnsupportedAtHardfork)
1774    }
1775
1776    /// Returns an error if EIP1559 is not active (pre muirGlacier)
1777    pub fn ensure_eip2930_active(&self) -> Result<(), BlockchainError> {
1778        if self.is_eip2930() {
1779            return Ok(());
1780        }
1781        Err(BlockchainError::EIP2930TransactionUnsupportedAtHardfork)
1782    }
1783
1784    pub fn ensure_eip4844_active(&self) -> Result<(), BlockchainError> {
1785        if self.is_eip4844() {
1786            return Ok(());
1787        }
1788        Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork)
1789    }
1790
1791    pub fn ensure_eip7702_active(&self) -> Result<(), BlockchainError> {
1792        if self.is_eip7702() {
1793            return Ok(());
1794        }
1795        Err(BlockchainError::EIP7702TransactionUnsupportedAtHardfork)
1796    }
1797
1798    /// Returns an error if deposit transactions are not active.
1799    #[cfg(any(feature = "base", feature = "optimism"))]
1800    pub const fn ensure_deposits_active(&self) -> Result<(), BlockchainError> {
1801        #[cfg(feature = "base")]
1802        if self.is_base() {
1803            return Ok(());
1804        }
1805        #[cfg(feature = "optimism")]
1806        if self.is_optimism() {
1807            return Ok(());
1808        }
1809        Err(BlockchainError::DepositTransactionUnsupported)
1810    }
1811
1812    /// Returns an error if Base EIP-8130 transactions are not active.
1813    #[cfg(feature = "base")]
1814    pub fn ensure_base_eip8130_active(&self) -> Result<(), BlockchainError> {
1815        let timestamp = self.evm_env.read().block_env.timestamp.saturating_to();
1816        self.ensure_base_eip8130_active_at(timestamp)
1817    }
1818
1819    /// Returns an error if Base EIP-8130 RPC features are not active at `timestamp`.
1820    #[cfg(feature = "base")]
1821    pub fn ensure_base_eip8130_active_at(&self, timestamp: u64) -> Result<(), BlockchainError> {
1822        let upgrade = self.base_upgrade_at_timestamp(timestamp);
1823        if self.is_base() && upgrade >= BaseUpgrade::Zenith {
1824            return Ok(());
1825        }
1826        Err(BlockchainError::InvalidTransactionRequest(EIP8130_PRE_ZENITH_RPC_ERROR.to_string()))
1827    }
1828
1829    /// Returns an admission error if Base EIP-8130 submissions are not active.
1830    #[cfg(feature = "base")]
1831    pub fn ensure_base_eip8130_submission_active(&self) -> Result<(), BlockchainError> {
1832        if self.is_base() && self.base_upgrade() >= BaseUpgrade::Zenith {
1833            return Ok(());
1834        }
1835        Err(BlockchainError::Eip8130TransactionRejected(EIP8130_REJECTION_MSG.to_string()))
1836    }
1837
1838    /// Returns an error if Tempo transactions are not active
1839    pub const fn ensure_tempo_active(&self) -> Result<(), BlockchainError> {
1840        if self.is_tempo() {
1841            return Ok(());
1842        }
1843        Err(BlockchainError::TempoTransactionUnsupported)
1844    }
1845
1846    /// Returns an error unless CIP-64 native-fee compatibility is active.
1847    pub fn ensure_cip64_active(&self) -> Result<(), BlockchainError> {
1848        if !self.is_celo() {
1849            return Err(BlockchainError::InvalidTransactionRequest(
1850                "CIP-64 transactions require Celo mode (--celo)".into(),
1851            ));
1852        }
1853        self.ensure_eip1559_active()
1854    }
1855
1856    /// Builds the [`InspectorTxConfig`] from the backend's current settings.
1857    fn inspector_tx_config(&self) -> InspectorTxConfig {
1858        InspectorTxConfig {
1859            print_traces: self.print_traces,
1860            print_logs: self.print_logs,
1861            call_trace_decoder: self.call_trace_decoder(),
1862        }
1863    }
1864
1865    /// Returns a trace decoder configured for the currently resolved hardfork.
1866    fn call_trace_decoder(&self) -> Arc<CallTraceDecoder> {
1867        let hardfork = Some(self.networks.executed_hardfork(self.hardfork()));
1868        let decoder = self.call_trace_decoder.read();
1869        if decoder.hardfork() == hardfork {
1870            return Arc::clone(&decoder);
1871        }
1872        drop(decoder);
1873
1874        let mut decoder = self.call_trace_decoder.write();
1875        let mut updated = decoder.as_ref().clone();
1876        updated.set_hardfork(hardfork);
1877        *decoder = Arc::new(updated);
1878        Arc::clone(&decoder)
1879    }
1880
1881    /// Builds the [`PoolTxGasConfig`] from the given EVM environment.
1882    fn pool_tx_gas_config(&self, evm_env: &EvmEnv) -> PoolTxGasConfig {
1883        let spec_id = *evm_env.spec_id();
1884        let is_cancun = spec_id >= SpecId::CANCUN;
1885        let blob_params = self.blob_params();
1886        PoolTxGasConfig {
1887            disable_block_gas_limit: evm_env.cfg_env.disable_block_gas_limit,
1888            enforced_tx_gas_limit_cap: evm_env
1889                .cfg_env
1890                .tx_gas_limit_cap
1891                .is_none()
1892                .then(|| self.tx_gas_limit_cap(evm_env)),
1893            max_blob_gas_per_block: blob_params.max_blob_gas_per_block(),
1894            is_cancun,
1895        }
1896    }
1897
1898    #[cfg(feature = "monad")]
1899    fn monad_cfg_env(&self, evm_env: &EvmEnv) -> Option<monad_revm::MonadCfgEnv> {
1900        if !self.is_monad() {
1901            return None;
1902        }
1903
1904        let hardfork = monad_revm::MonadHardfork::from(self.hardfork());
1905        Some(monad_revm::MonadCfgEnv::from(evm_env.cfg_env.clone().with_spec_and_gas_params(
1906            hardfork,
1907            monad_revm::instructions::monad_gas_params(hardfork),
1908        )))
1909    }
1910
1911    fn tx_gas_limit_cap(&self, evm_env: &EvmEnv) -> u64 {
1912        #[cfg(feature = "monad")]
1913        if let Some(cfg) = self.monad_cfg_env(evm_env) {
1914            return cfg.tx_gas_limit_cap();
1915        }
1916        evm_env.cfg_env.tx_gas_limit_cap()
1917    }
1918
1919    pub(crate) fn fallback_tx_gas_limit(&self, evm_env: &EvmEnv) -> u64 {
1920        let block_gas_limit = evm_env.block_env.gas_limit;
1921        if evm_env.cfg_env.tx_gas_limit_cap.is_none() {
1922            block_gas_limit.min(self.tx_gas_limit_cap(evm_env))
1923        } else {
1924            block_gas_limit
1925        }
1926    }
1927
1928    fn max_initcode_size(&self, evm_env: &EvmEnv) -> usize {
1929        #[cfg(feature = "monad")]
1930        if let Some(cfg) = self.monad_cfg_env(evm_env) {
1931            return cfg.max_initcode_size();
1932        }
1933        evm_env.cfg_env.max_initcode_size()
1934    }
1935
1936    /// Returns the block gas limit
1937    pub fn gas_limit(&self) -> u64 {
1938        self.evm_env.read().block_env.gas_limit
1939    }
1940
1941    /// Sets the block gas limit
1942    pub fn set_gas_limit(&self, gas_limit: u64) {
1943        self.evm_env.write().block_env.gas_limit = gas_limit;
1944    }
1945
1946    /// Returns the current base fee
1947    pub fn base_fee(&self) -> u64 {
1948        self.fees.base_fee()
1949    }
1950
1951    /// Returns whether the minimum suggested priority fee is enforced
1952    pub const fn is_min_priority_fee_enforced(&self) -> bool {
1953        self.fees.is_min_priority_fee_enforced()
1954    }
1955
1956    pub fn excess_blob_gas_and_price(&self) -> Option<BlobExcessGasAndPrice> {
1957        self.fees.excess_blob_gas_and_price()
1958    }
1959
1960    /// Sets the current basefee
1961    pub fn set_base_fee(&self, basefee: u64) {
1962        self.fees.set_base_fee(basefee)
1963    }
1964
1965    /// Sets the gas price
1966    pub fn set_gas_price(&self, price: u128) {
1967        self.fees.set_gas_price(price)
1968    }
1969
1970    pub fn elasticity(&self) -> f64 {
1971        self.fees.elasticity()
1972    }
1973
1974    /// Returns the total difficulty of the chain until this block
1975    ///
1976    /// Note: this will always be `0` in memory mode
1977    /// In forking mode this will always be the total difficulty of the forked block
1978    pub fn total_difficulty(&self) -> U256 {
1979        self.blockchain.storage.read().total_difficulty
1980    }
1981
1982    /// Creates a new `evm_snapshot` at the current height.
1983    ///
1984    /// Returns the id of the snapshot created.
1985    pub async fn create_state_snapshot(&self) -> U256 {
1986        let num = self.best_number();
1987        let hash = self.best_hash();
1988        let id = self.db.write().await.snapshot_state();
1989        trace!(target: "backend", "creating snapshot {} at {}", id, num);
1990        self.active_state_snapshots.lock().insert(
1991            id,
1992            StateSnapshot {
1993                block_number: num,
1994                block_hash: hash,
1995                fees: self.fees.snapshot(),
1996                time: self.time.snapshot(),
1997                next_block: self.cheats.next_block_overrides(),
1998            },
1999        );
2000        id
2001    }
2002
2003    pub fn list_state_snapshots(&self) -> BTreeMap<U256, (u64, B256)> {
2004        self.active_state_snapshots
2005            .lock()
2006            .iter()
2007            .map(|(&id, snapshot)| (id, (snapshot.block_number, snapshot.block_hash)))
2008            .collect()
2009    }
2010
2011    /// Returns the EVM-visible number for a locally mined RPC block.
2012    fn evm_block_number(&self, rpc_number: u64) -> U256 {
2013        self.get_fork()
2014            .map_or_else(|| U256::from(rpc_number), |fork| fork.evm_block_number(rpc_number))
2015    }
2016
2017    /// Restores the execution number separately from the RPC header number.
2018    fn block_env_from_header(&self, header: &impl BlockHeader) -> BlockEnv {
2019        let mut block = block_env_from_header::<BlockEnv>(header);
2020        block.number = self.evm_block_number(header.number());
2021        if let Some(excess_blob_gas) = header.excess_blob_gas() {
2022            block.set_blob_excess_gas_and_price(
2023                excess_blob_gas,
2024                self.blob_params().update_fraction as u64,
2025            );
2026        }
2027        block
2028    }
2029
2030    /// Returns the environment for the next block
2031    fn next_evm_env(
2032        &self,
2033        db: &dyn revm::DatabaseRef<Error = DatabaseError>,
2034    ) -> Result<EvmEnv, DatabaseError> {
2035        let mut evm_env = self.evm_env.read().clone();
2036        // increase block number for this block
2037        evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::ONE);
2038        evm_env.block_env.basefee = self.base_fee();
2039        evm_env.block_env.blob_excess_gas_and_price = self.excess_blob_gas_and_price();
2040        let pending =
2041            self.prepare_block_timestamp(db, evm_env.block_env.timestamp.saturating_to())?;
2042        evm_env.block_env.timestamp = U256::from(pending.timestamp);
2043        Ok(evm_env)
2044    }
2045
2046    /// Returns the environment for replaying transactions from a historical block.
2047    fn tx_replay_evm_env(&self, block: &Block) -> (EvmEnv, FoundryHardfork) {
2048        let mut evm_env = self.evm_env.read().clone();
2049        evm_env.block_env = self.block_env_from_header(&block.header);
2050        let hardfork = self.hardfork();
2051        #[cfg(feature = "monad")]
2052        let hardfork = if self.is_monad() {
2053            let block_hash = block.header.hash_slow();
2054            let fallback = crate::eth::backend::db::MonadBlockReplayProfile {
2055                execution_chain_id: evm_env.cfg_env.chain_id,
2056                hardfork: self.monad_hardfork(),
2057            };
2058            let profile = self
2059                .blockchain
2060                .storage
2061                .read()
2062                .monad_block_replay_profiles
2063                .get(&block_hash)
2064                .copied()
2065                .unwrap_or(fallback);
2066            evm_env.cfg_env.chain_id = profile.execution_chain_id;
2067            evm_env.cfg_env.spec = profile.hardfork.into();
2068            profile.hardfork.into()
2069        } else {
2070            hardfork
2071        };
2072        apply_chain_specific_tx_replay_env_changes_for_chain(
2073            &mut evm_env,
2074            self.protocol_chain_id(),
2075        );
2076        (evm_env, hardfork)
2077    }
2078
2079    /// Creates the database and environment for replaying a locally mined block.
2080    ///
2081    /// An empty block execution applies protocol-level pre-execution changes, such as the
2082    /// EIP-2935 parent hash system call, through the same network-specific executor used while
2083    /// mining.
2084    fn prepare_block_replay<'a>(
2085        &self,
2086        block: &Block,
2087        parent_state: &'a StateDb,
2088    ) -> Result<(CacheDB<&'a StateDb>, EvmEnv, FoundryHardfork), BlockchainError> {
2089        self.prepare_block_replay_with_db(block, parent_state)
2090    }
2091
2092    /// Creates an overlay and applies block-start transitions for a locally mined block replay.
2093    fn prepare_block_replay_with_db<DB>(
2094        &self,
2095        block: &Block,
2096        db: DB,
2097    ) -> Result<(CacheDB<DB>, EvmEnv, FoundryHardfork), BlockchainError>
2098    where
2099        DB: DatabaseRef<Error = DatabaseError> + Debug,
2100    {
2101        let (evm_env, hardfork) = self.tx_replay_evm_env(block);
2102        let mut cache_db = AnvilCacheDB::new(db, *evm_env.spec_id());
2103        let inspector_tx_config = self.inspector_tx_config();
2104        let gas_config = self.pool_tx_gas_config(&evm_env);
2105
2106        self.execute_with_block_executor(
2107            &mut cache_db,
2108            &evm_env,
2109            block.header.parent_hash,
2110            hardfork,
2111            block.header.parent_beacon_block_root,
2112            BlockExecutionKind::TransactionPrefix,
2113            &[],
2114            &gas_config,
2115            &inspector_tx_config,
2116            &|_, _| Ok(()),
2117        )?;
2118
2119        Ok((cache_db.0, evm_env, hardfork))
2120    }
2121
2122    /// Replays the stored transaction prefix `[0, end)` into an existing block overlay.
2123    fn replay_mined_transaction_prefix<DB>(
2124        &self,
2125        cache_db: &mut CacheDB<DB>,
2126        evm_env: &EvmEnv,
2127        hardfork: FoundryHardfork,
2128        block: &Block,
2129        end: usize,
2130    ) -> Result<(), BlockchainError>
2131    where
2132        DB: DatabaseRef<Error = DatabaseError> + Debug,
2133    {
2134        let monad_context = self.active_monad_context_for_mined_block(block)?;
2135        for (index, transaction) in block.body.transactions[..end].iter().enumerate() {
2136            let pending = self.pending_mined_transaction(transaction.clone())?;
2137            let mut inspector = AnvilInspector::default();
2138            let transaction_context = monad_execution_context_at(monad_context.as_ref(), index);
2139            let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
2140                cache_db,
2141                evm_env,
2142                &mut inspector,
2143                &pending,
2144                EnvelopeExecution::replay(transaction_context, hardfork),
2145            )?;
2146            cache_db.commit(result.state);
2147        }
2148        Ok(())
2149    }
2150
2151    /// Builds [`Inspector`] with the configured options.
2152    fn build_inspector(&self) -> AnvilInspector {
2153        let mut inspector = AnvilInspector::default();
2154
2155        if self.print_logs {
2156            inspector = inspector.with_log_collector();
2157        }
2158        if self.print_traces {
2159            inspector = inspector.with_trace_printer();
2160        }
2161
2162        inspector
2163    }
2164
2165    /// Builds an inspector configured for block mining (tracing always enabled).
2166    fn build_mining_inspector(&self) -> AnvilInspector {
2167        let mut inspector = AnvilInspector::default().with_tracing();
2168        if self.print_logs {
2169            inspector = inspector.with_log_collector();
2170        }
2171        if self.print_traces {
2172            inspector = inspector.with_trace_printer();
2173        }
2174        inspector
2175    }
2176
2177    /// Returns a new block event stream that yields Notifications when a new block was added or
2178    /// when logs were removed from the canonical chain due to a reorg
2179    pub fn new_block_notifications(&self) -> ChainNotifications {
2180        let (tx, rx) = unbounded();
2181        self.new_block_listeners.lock().push(tx);
2182        trace!(target: "backed", "added new block listener");
2183        rx
2184    }
2185
2186    /// Returns the number of new-block listeners. Closed listeners are pruned lazily on the next
2187    /// new block notification.
2188    pub fn new_block_listeners_count(&self) -> usize {
2189        self.new_block_listeners.lock().len()
2190    }
2191
2192    /// Notifies all `new_block_listeners` about the new block
2193    fn notify_on_new_block(&self, header: Header, hash: B256) {
2194        // cleanup closed notification streams first, if the channel is closed we can remove the
2195        // sender half for the set
2196        self.new_block_listeners.lock().retain(|tx| !tx.is_closed());
2197
2198        let notification =
2199            ChainNotification::Block(NewBlockNotification { hash, header: Arc::new(header) });
2200
2201        self.new_block_listeners
2202            .lock()
2203            .retain(|tx| tx.unbounded_send(notification.clone()).is_ok());
2204    }
2205
2206    /// Notifies all `new_block_listeners` about the logs that were removed from the canonical
2207    /// chain due to a reorg.
2208    fn notify_on_removed_logs(&self, logs: Vec<Log>) {
2209        // cleanup closed notification streams first, if the channel is closed we can remove the
2210        // sender half for the set
2211        self.new_block_listeners.lock().retain(|tx| !tx.is_closed());
2212
2213        let notification = ChainNotification::RemovedLogs(Arc::new(logs));
2214
2215        self.new_block_listeners
2216            .lock()
2217            .retain(|tx| tx.unbounded_send(notification.clone()).is_ok());
2218    }
2219
2220    /// Returns the block number for the given block id
2221    pub fn convert_block_number(&self, block: Option<BlockNumber>) -> u64 {
2222        let current = self.best_number();
2223        // The chain starts at the configured genesis number, so `earliest` and the epoch-based
2224        // tags never resolve below it.
2225        let genesis = self.genesis_number();
2226        match block.unwrap_or(BlockNumber::Latest) {
2227            BlockNumber::Latest | BlockNumber::Pending => current,
2228            BlockNumber::Earliest => genesis,
2229            BlockNumber::Number(num) => num,
2230            BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch).max(genesis),
2231            BlockNumber::Finalized => {
2232                current.saturating_sub(self.slots_in_an_epoch * 2).max(genesis)
2233            }
2234        }
2235    }
2236
2237    /// Returns the canonical hash for the given block number.
2238    pub(crate) fn block_hash_by_number(&self, number: u64) -> Option<B256> {
2239        self.blockchain.hash(BlockId::number(number), self.slots_in_an_epoch)
2240    }
2241
2242    /// Returns the block and its hash for the given id
2243    pub(crate) fn get_block_with_hash(&self, id: impl Into<BlockId>) -> Option<(Block, B256)> {
2244        let hash = self.blockchain.hash(id.into(), self.slots_in_an_epoch)?;
2245        let block = self.get_block_by_hash(hash)?;
2246        Some((block, hash))
2247    }
2248
2249    pub fn get_block(&self, id: impl Into<BlockId>) -> Option<Block> {
2250        self.get_block_with_hash(id).map(|(block, _)| block)
2251    }
2252
2253    pub fn get_block_by_hash(&self, hash: B256) -> Option<Block> {
2254        self.blockchain.get_block_by_hash(&hash)
2255    }
2256
2257    /// Returns the base fees for the block after a fee history range.
2258    ///
2259    /// Mining publishes a new canonical block before advancing the fee manager. Holding the mining
2260    /// lock makes choosing between an existing child and the current head's pending fees atomic
2261    /// with that publication sequence.
2262    pub(crate) async fn fee_history_next_fees(&self, highest: u64) -> Option<(u128, u128)> {
2263        let _mining_guard = self.mining.lock().await;
2264        let next_number = highest.checked_add(1)?;
2265        if let Some(block) = self.get_block(next_number) {
2266            Some((
2267                block.header.base_fee_per_gas.unwrap_or_default() as u128,
2268                block.header.blob_fee(self.blob_params()).unwrap_or_default(),
2269            ))
2270        } else if highest == self.best_number() {
2271            Some((self.fees().base_fee() as u128, self.fees().base_fee_per_blob_gas()))
2272        } else {
2273            None
2274        }
2275    }
2276
2277    /// Returns the traces for the given transaction
2278    pub(crate) fn mined_parity_trace_transaction(
2279        &self,
2280        hash: B256,
2281    ) -> Option<Vec<LocalizedTransactionTrace>> {
2282        self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.parity_traces())
2283    }
2284
2285    /// Returns the traces for the given block
2286    pub(crate) fn mined_parity_trace_block(
2287        &self,
2288        block: u64,
2289    ) -> Option<Vec<LocalizedTransactionTrace>> {
2290        let block = self.get_block(block)?;
2291        let mut traces = vec![];
2292        let storage = self.blockchain.storage.read();
2293        for tx in block.body.transactions {
2294            if let Some(mined_tx) = storage.transactions.get(&tx.hash()) {
2295                traces.extend(mined_tx.parity_traces());
2296            }
2297        }
2298        Some(traces)
2299    }
2300
2301    /// Returns the mined transaction for the given hash
2302    pub(crate) fn mined_transaction(&self, hash: B256) -> Option<MinedTransaction<N>> {
2303        self.blockchain.storage.read().transactions.get(&hash).cloned()
2304    }
2305
2306    /// Overrides the given signature to impersonate the specified address during ecrecover.
2307    pub async fn impersonate_signature(
2308        &self,
2309        signature: Bytes,
2310        address: Address,
2311    ) -> Result<(), BlockchainError> {
2312        self.cheats.add_recover_override(signature, address);
2313        Ok(())
2314    }
2315
2316    /// Returns code by its hash
2317    pub async fn debug_code_by_hash(
2318        &self,
2319        code_hash: B256,
2320        block_id: Option<BlockId>,
2321    ) -> Result<Option<Bytes>, BlockchainError> {
2322        if let Ok(code) = self.db.read().await.code_by_hash_ref(code_hash) {
2323            return Ok(Some(code.original_bytes()));
2324        }
2325        if let Some(fork) = self.get_fork() {
2326            return Ok(fork.debug_code_by_hash(code_hash, block_id).await?);
2327        }
2328
2329        Ok(None)
2330    }
2331
2332    /// Returns the value associated with a key from the database
2333    /// Currently only supports bytecode lookups.
2334    ///
2335    /// Based on Reth implementation: <https://github.com/paradigmxyz/reth/blob/66cfa9ed1a8c4bc2424aacf6fb2c1e67a78ee9a2/crates/rpc/rpc/src/debug.rs#L1146-L1178>
2336    ///
2337    /// Key should be: 0x63 (1-byte prefix) + 32 bytes (code_hash)
2338    /// Total key length must be 33 bytes.
2339    pub async fn debug_db_get(&self, key: String) -> Result<Option<Bytes>, BlockchainError> {
2340        let key_bytes = if key.starts_with("0x") {
2341            hex::decode(&key)
2342                .map_err(|_| BlockchainError::Message("Invalid hex key".to_string()))?
2343        } else {
2344            key.into_bytes()
2345        };
2346
2347        // Validate key length: must be 33 bytes (1 byte prefix + 32 bytes code hash)
2348        if key_bytes.len() != 33 {
2349            return Err(BlockchainError::Message(format!(
2350                "Invalid key length: expected 33 bytes, got {}",
2351                key_bytes.len()
2352            )));
2353        }
2354
2355        // Check for bytecode prefix (0x63 = 'c' in ASCII)
2356        if key_bytes[0] != 0x63 {
2357            return Err(BlockchainError::Message(
2358                "Key prefix must be 0x63 for code hash lookups".to_string(),
2359            ));
2360        }
2361
2362        let code_hash = B256::from_slice(&key_bytes[1..33]);
2363
2364        // Use the existing debug_code_by_hash method to retrieve the bytecode
2365        self.debug_code_by_hash(code_hash, None).await
2366    }
2367
2368    fn mined_block_by_hash(&self, hash: B256) -> Option<AnyRpcBlock> {
2369        let block = self.blockchain.get_block_by_hash(&hash)?;
2370        Some(self.convert_block_with_hash(block, Some(hash)))
2371    }
2372
2373    pub(crate) async fn mined_transactions_by_block_number(
2374        &self,
2375        number: BlockNumber,
2376    ) -> Option<Vec<AnyRpcTransaction>> {
2377        if let Some(block) = self.get_block(number) {
2378            return self.mined_transactions_in_block(&block);
2379        }
2380        None
2381    }
2382
2383    /// Returns all transactions given a block
2384    pub(crate) fn mined_transactions_in_block(
2385        &self,
2386        block: &Block,
2387    ) -> Option<Vec<AnyRpcTransaction>> {
2388        let mut transactions = Vec::with_capacity(block.body.transactions.len());
2389        let base_fee = block.header.base_fee_per_gas();
2390        let storage = self.blockchain.storage.read();
2391        for hash in block.body.transactions.iter().map(|tx| tx.hash()) {
2392            let info = storage.transactions.get(&hash)?.info.clone();
2393            let tx = block.body.transactions.get(info.transaction_index as usize)?.clone();
2394
2395            let tx = transaction_build(Some(hash), tx, Some(block), Some(info), base_fee);
2396            transactions.push(tx);
2397        }
2398        Some(transactions)
2399    }
2400
2401    pub fn mined_block_by_number(&self, number: BlockNumber) -> Option<AnyRpcBlock> {
2402        let (block, hash) = self.get_block_with_hash(number)?;
2403        let mut block = self.convert_block_with_hash(block, Some(hash));
2404        block.transactions.convert_to_hashes();
2405        Some(block)
2406    }
2407
2408    pub fn get_full_block(&self, id: impl Into<BlockId>) -> Option<AnyRpcBlock> {
2409        let (block, hash) = self.get_block_with_hash(id)?;
2410        let transactions = self.mined_transactions_in_block(&block)?;
2411        let mut block = self.convert_block_with_hash(block, Some(hash));
2412        block.inner.transactions = BlockTransactions::Full(transactions);
2413        Some(block)
2414    }
2415
2416    /// Takes a block as it's stored internally and returns the eth api conform block format.
2417    pub fn convert_block(&self, block: Block) -> AnyRpcBlock {
2418        self.convert_block_with_hash(block, None)
2419    }
2420
2421    /// Takes a block as it's stored internally and returns the eth api conform block format.
2422    /// If `known_hash` is provided, it will be used instead of computing `hash_slow()`.
2423    pub fn convert_block_with_hash(&self, block: Block, known_hash: Option<B256>) -> AnyRpcBlock {
2424        let transactions = block.body.transactions.iter().map(|tx| tx.hash()).collect();
2425        let block = canonical_block(block);
2426        let size = U256::from(block.length() as u32);
2427        let header = block.header;
2428
2429        let hash = known_hash.unwrap_or_else(|| header.hash_slow());
2430        let number = header.number();
2431        let withdrawals_root = header.withdrawals_root();
2432        let tempo_fields = header
2433            .as_tempo()
2434            .map(|header| {
2435                (
2436                    header.timestamp_millis(),
2437                    header.general_gas_limit,
2438                    header.shared_gas_limit,
2439                    header.timestamp_millis_part,
2440                )
2441            })
2442            .or_else(|| {
2443                self.is_tempo()
2444                    .then(|| (header.timestamp().saturating_mul(1000), header.gas_limit(), 0, 0))
2445            });
2446
2447        let block = AlloyBlock {
2448            header: AlloyHeader {
2449                inner: AnyHeader::from(header.into_inner()),
2450                hash,
2451                total_difficulty: Some(self.total_difficulty()),
2452                size: Some(size),
2453            },
2454            transactions: alloy_rpc_types::BlockTransactions::Hashes(transactions),
2455            uncles: vec![],
2456            withdrawals: withdrawals_root.map(|_| Default::default()),
2457        };
2458
2459        let mut block = WithOtherFields::new(block);
2460
2461        // If Arbitrum, apply chain specifics to converted block.
2462        if is_arbitrum(self.protocol_chain_id()) {
2463            // Set `l1BlockNumber` field.
2464            block.other.insert(
2465                "l1BlockNumber".to_string(),
2466                serde_json::json!(self.evm_block_number(number)),
2467            );
2468        }
2469
2470        if let Some((
2471            timestamp_millis,
2472            general_gas_limit,
2473            shared_gas_limit,
2474            timestamp_millis_part,
2475        )) = tempo_fields
2476        {
2477            block.other.insert(
2478                "timestampMillis".to_string(),
2479                serde_json::Value::String(format!("0x{timestamp_millis:x}")),
2480            );
2481            block.other.insert(
2482                "mainBlockGeneralGasLimit".to_string(),
2483                serde_json::Value::String(format!("0x{general_gas_limit:x}")),
2484            );
2485            block.other.insert(
2486                "sharedGasLimit".to_string(),
2487                serde_json::Value::String(format!("0x{shared_gas_limit:x}")),
2488            );
2489            block.other.insert(
2490                "timestampMillisPart".to_string(),
2491                serde_json::Value::String(format!("0x{timestamp_millis_part:x}")),
2492            );
2493        }
2494
2495        AnyRpcBlock::from(block)
2496    }
2497
2498    pub async fn block_by_hash(&self, hash: B256) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2499        trace!(target: "backend", "get block by hash {:?}", hash);
2500        if let tx @ Some(_) = self.mined_block_by_hash(hash) {
2501            return Ok(tx);
2502        }
2503
2504        if let Some(fork) = self.get_fork() {
2505            return Ok(fork.block_by_hash(hash).await?);
2506        }
2507
2508        Ok(None)
2509    }
2510
2511    pub async fn block_by_hash_full(
2512        &self,
2513        hash: B256,
2514    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2515        trace!(target: "backend", "get block by hash {:?}", hash);
2516        if let tx @ Some(_) = self.get_full_block(hash) {
2517            return Ok(tx);
2518        }
2519
2520        if let Some(fork) = self.get_fork() {
2521            return Ok(fork.block_by_hash_full(hash).await?);
2522        }
2523
2524        Ok(None)
2525    }
2526
2527    pub async fn block_by_number(
2528        &self,
2529        number: BlockNumber,
2530    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2531        trace!(target: "backend", "get block by number {:?}", number);
2532        if let tx @ Some(_) = self.mined_block_by_number(number) {
2533            return Ok(tx);
2534        }
2535
2536        if let Some(fork) = self.get_fork() {
2537            let number = self.convert_block_number(Some(number));
2538            if fork.predates_fork_inclusive(number) {
2539                return Ok(fork.block_by_number(number).await?);
2540            }
2541        }
2542
2543        Ok(None)
2544    }
2545
2546    pub async fn block_by_number_full(
2547        &self,
2548        number: BlockNumber,
2549    ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2550        trace!(target: "backend", "get block by number {:?}", number);
2551        if let tx @ Some(_) = self.get_full_block(number) {
2552            return Ok(tx);
2553        }
2554
2555        if let Some(fork) = self.get_fork() {
2556            let number = self.convert_block_number(Some(number));
2557            if fork.predates_fork_inclusive(number) {
2558                return Ok(fork.block_by_number_full(number).await?);
2559            }
2560        }
2561
2562        Ok(None)
2563    }
2564
2565    /// Converts the `BlockNumber` into a numeric value
2566    ///
2567    /// # Errors
2568    ///
2569    /// returns an error if the requested number is larger than the current height
2570    pub async fn ensure_block_number<T: Into<BlockId>>(
2571        &self,
2572        block_id: Option<T>,
2573    ) -> Result<u64, BlockchainError> {
2574        let current = self.best_number();
2575        let requested = match block_id.map(Into::into).unwrap_or(BlockId::latest()) {
2576            BlockId::Hash(hash) => {
2577                self.block_by_hash(hash.block_hash)
2578                    .await?
2579                    .ok_or(BlockchainError::BlockNotFound)?
2580                    .header
2581                    .number
2582            }
2583            BlockId::Number(num) => self.convert_block_number(Some(num)),
2584        };
2585
2586        if requested > current {
2587            Err(BlockchainError::BlockOutOfRange(current, requested))
2588        } else {
2589            Ok(requested)
2590        }
2591    }
2592
2593    /// Returns the timestamp represented by a block request.
2594    #[cfg(feature = "base")]
2595    pub async fn block_request_timestamp(
2596        &self,
2597        block_request: &BlockRequest<FoundryTxEnvelope>,
2598    ) -> Result<u64, BlockchainError> {
2599        match block_request {
2600            BlockRequest::Pending(_) => Ok(self.time.current_call_timestamp()),
2601            BlockRequest::Number(number) => self
2602                .block_by_number(BlockNumber::Number(*number))
2603                .await?
2604                .map(|block| block.header.timestamp())
2605                .ok_or(BlockchainError::BlockNotFound),
2606        }
2607    }
2608
2609    /// Injects all configured precompiles into the given precompile map.
2610    ///
2611    /// This applies five layers:
2612    /// 1. Network-specific precompiles (e.g. Tempo, OP)
2613    /// 2. Chain- and timestamp-specific precompiles
2614    /// 3. User-provided precompiles via [`PrecompileFactory`]
2615    /// 4. Cheatcode ecrecover overrides (if active)
2616    /// 5. Block-specific precompiles (e.g. ArbSys)
2617    fn inject_precompiles(&self, precompiles: &mut PrecompilesMap, evm_env: &EvmEnv) {
2618        self.inject_configured_precompiles(precompiles, evm_env);
2619
2620        if let Some(block_number) = self.arbitrum_block_number(evm_env) {
2621            self.inject_arbitrum_precompile_at_block(precompiles, block_number);
2622        }
2623    }
2624
2625    fn inject_configured_precompiles(&self, precompiles: &mut PrecompilesMap, evm_env: &EvmEnv) {
2626        self.networks.inject_precompiles(precompiles);
2627        apply_bsc_p256_precompile(
2628            precompiles,
2629            self.protocol_chain_id(),
2630            evm_env.block_env.timestamp.saturating_to(),
2631        );
2632
2633        if let Some(factory) = &self.precompile_factory {
2634            factory.install(precompiles);
2635        }
2636
2637        let cheats = Arc::new(self.cheats.clone());
2638        if cheats.has_recover_overrides() {
2639            let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats));
2640            precompiles.apply_precompile(&EC_RECOVER, move |_| {
2641                Some(DynPrecompile::new_stateful(
2642                    cheat_ecrecover.precompile_id().clone(),
2643                    move |input| cheat_ecrecover.call(input),
2644                ))
2645            });
2646        }
2647    }
2648
2649    fn inject_arbitrum_precompile_at_block(
2650        &self,
2651        precompiles: &mut PrecompilesMap,
2652        block_number: u64,
2653    ) {
2654        precompiles.apply_precompile(&arbitrum::ARB_SYS_ADDRESS, move |_| {
2655            Some(arbitrum::arb_sys_precompile(block_number))
2656        });
2657    }
2658
2659    fn simulation_precompile_overrides(
2660        &self,
2661        state_overrides: Option<&StateOverride>,
2662        evm_env: &EvmEnv,
2663    ) -> Result<SimulationPrecompileOverrides, BlockchainError> {
2664        let mut moves = state_overrides
2665            .into_iter()
2666            .flatten()
2667            .filter_map(|(source, account)| {
2668                account.move_precompile_to.map(|destination| (*source, destination))
2669            })
2670            .collect::<Vec<_>>();
2671        moves.sort_unstable();
2672        if moves.is_empty() {
2673            return Ok(SimulationPrecompileOverrides::default());
2674        }
2675        #[cfg(feature = "base")]
2676        if self.is_base() {
2677            return Err(simulate_rpc_error(
2678                -32000,
2679                "precompile moves are not supported on this network",
2680            ));
2681        }
2682        if self.is_optimism() || self.is_tempo() || self.is_monad() {
2683            return Err(simulate_rpc_error(
2684                -32000,
2685                "precompile moves are not supported on this network",
2686            ));
2687        }
2688
2689        let mut precompiles = PrecompilesMap::from_static(Precompiles::new(
2690            PrecompileSpecId::from_spec_id(*evm_env.spec_id()),
2691        ));
2692        self.inject_precompiles(&mut precompiles, evm_env);
2693        let precompile_addresses = precompiles.addresses().copied().collect::<HashSet<_>>();
2694
2695        // Validate every source first so invalid-source errors take precedence over the more
2696        // specific move errors below.
2697        for (source, _) in &moves {
2698            if !precompile_addresses.contains(source) {
2699                return Err(simulate_rpc_error(
2700                    -32000,
2701                    format!("account {source} is not a precompile"),
2702                ));
2703            }
2704        }
2705        for (source, destination) in &moves {
2706            if source == destination {
2707                return Err(simulate_rpc_error(
2708                    -38022,
2709                    format!("cannot move precompile {source} to itself"),
2710                ));
2711            }
2712        }
2713        let mut destinations = Vec::with_capacity(moves.len());
2714        for (_, destination) in &moves {
2715            if destinations.contains(destination) {
2716                return Err(simulate_rpc_error(
2717                    -38023,
2718                    format!("multiple precompiles moved to {destination}"),
2719                ));
2720            }
2721            destinations.push(*destination);
2722        }
2723
2724        Ok(SimulationPrecompileOverrides { moves })
2725    }
2726
2727    fn apply_simulation_precompile_overrides(
2728        &self,
2729        precompiles: &mut PrecompilesMap,
2730        overrides: &SimulationPrecompileOverrides,
2731    ) -> Result<alloy_primitives::map::AddressSet, BlockchainError> {
2732        let warm_addresses = precompiles.addresses().copied().collect();
2733        precompiles.move_precompiles(overrides.moves.iter().copied()).map_err(
2734            |MovePrecompileError::NotAPrecompile(address)| {
2735                simulate_rpc_error(-32000, format!("account {address} is not a precompile"))
2736            },
2737        )?;
2738
2739        // A dynamic lookup must not restore a precompile removed from its protocol address.
2740        let moved_sources =
2741            Arc::new(overrides.moves.iter().map(|(source, _)| *source).collect::<HashSet<_>>());
2742        precompiles.map_precompile_lookup(move |address, previous| {
2743            if moved_sources.contains(address) {
2744                None
2745            } else {
2746                previous.and_then(|lookup| lookup.lookup(address))
2747            }
2748        });
2749        Ok(warm_addresses)
2750    }
2751
2752    fn inject_tempo_precompiles<DB, I>(
2753        &self,
2754        evm: &mut tempo_evm::evm::TempoEvm<DB, I>,
2755        evm_env: &EvmEnv,
2756    ) where
2757        DB: Database,
2758        I: Inspector<TempoContext<DB>>,
2759    {
2760        self.inject_configured_precompiles(evm.precompiles_mut(), evm_env);
2761        // Re-extend Tempo precompiles, preserving shared non-creditable slots.
2762        let cfg = evm.ctx().cfg.clone();
2763        let non_creditable_slots = evm.non_creditable_slots();
2764        extend_tempo_precompiles(
2765            evm.precompiles_mut(),
2766            &cfg,
2767            StorageActions::disabled(),
2768            non_creditable_slots,
2769        );
2770    }
2771
2772    /// Executes a call with the Ethereum EVM.
2773    ///
2774    /// Creates an Ethereum EVM, injects precompiles, and transacts with a
2775    /// plain [`TxEnv`].
2776    fn transact_eth_with_inspector_ref<'db, I, DB>(
2777        &self,
2778        db: &'db DB,
2779        evm_env: &EvmEnv,
2780        inspector: &mut I,
2781        tx_env: TxEnv,
2782    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2783    where
2784        DB: DatabaseRef + ?Sized,
2785        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
2786        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2787    {
2788        self.transact_eth_with_inspector_ref_and_precompile_overrides(
2789            db,
2790            evm_env,
2791            inspector,
2792            tx_env,
2793            &SimulationPrecompileOverrides::default(),
2794        )
2795    }
2796
2797    fn transact_eth_with_inspector_ref_and_precompile_overrides<'db, I, DB>(
2798        &self,
2799        db: &'db DB,
2800        evm_env: &EvmEnv,
2801        inspector: &mut I,
2802        tx_env: TxEnv,
2803        overrides: &SimulationPrecompileOverrides,
2804    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2805    where
2806        DB: DatabaseRef + ?Sized,
2807        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
2808        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2809    {
2810        let mut evm = self.prepare_eth_evm(db, evm_env, inspector, overrides)?;
2811        Ok(evm.transact(tx_env)?)
2812    }
2813
2814    fn transact_eth_simulation_with_inspector_ref<'db, I, DB>(
2815        &self,
2816        db: &'db DB,
2817        evm_env: &EvmEnv,
2818        inspector: &mut I,
2819        tx_env: TxEnv,
2820        overrides: &SimulationPrecompileOverrides,
2821    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2822    where
2823        DB: DatabaseRef + ?Sized,
2824        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
2825        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2826    {
2827        let evm = self.prepare_eth_evm(db, evm_env, inspector, overrides)?;
2828        let mut evm = evm.into_inner();
2829        ContextSetters::set_tx(evm.ctx_mut(), tx_env);
2830        let mut handler = SimulationHandler::<
2831            _,
2832            revm::context::result::EVMError<DatabaseError>,
2833            EthFrame<EthInterpreter>,
2834        >::default();
2835        let result = handler.inspect_run(&mut evm)?;
2836        let state = evm.ctx_mut().journal_mut().finalize();
2837        Ok(ResultAndState { result, state })
2838    }
2839
2840    /// Creates an Ethereum EVM with the active precompiles and simulation overrides.
2841    fn prepare_eth_evm<'db, 'inspector, I, DB>(
2842        &self,
2843        db: &'db DB,
2844        evm_env: &EvmEnv,
2845        inspector: &'inspector mut I,
2846        overrides: &SimulationPrecompileOverrides,
2847    ) -> Result<EthEvm<WrapDatabaseRef<&'db DB>, &'inspector mut I, PrecompilesMap>, BlockchainError>
2848    where
2849        DB: DatabaseRef + ?Sized,
2850        I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
2851        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2852    {
2853        let mut evm = EthEvmFactory::default().create_evm_with_inspector(
2854            WrapDatabaseRef(db),
2855            evm_env.clone(),
2856            inspector,
2857        );
2858        self.inject_precompiles(evm.precompiles_mut(), evm_env);
2859        if !overrides.moves.is_empty() {
2860            let warm_addresses =
2861                self.apply_simulation_precompile_overrides(evm.precompiles_mut(), overrides)?;
2862            // EIP-2929 warms protocol precompile addresses, not simulation-only destinations.
2863            evm.ctx_mut().journal_mut().warm_precompiles(&warm_addresses);
2864        }
2865        Ok(evm)
2866    }
2867
2868    /// Executes an envelope through the active network EVM with optional Monad block context.
2869    ///
2870    /// Returns both the execution result and the base [`TxEnv`].
2871    fn transact_envelope_with_inspector_ref_and_context<'db, I, DB>(
2872        &self,
2873        db: &'db DB,
2874        evm_env: &EvmEnv,
2875        inspector: &mut I,
2876        pending: &PendingTransaction<FoundryTxEnvelope>,
2877        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
2878            MonadExecutionContext<'_>,
2879        >,
2880    ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
2881    where
2882        DB: DatabaseRef + ?Sized,
2883        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
2884        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2885    {
2886        self.transact_envelope_with_inspector_ref_and_context_kind(
2887            db,
2888            evm_env,
2889            inspector,
2890            pending,
2891            EnvelopeExecution::transaction(monad_context, self.hardfork()),
2892        )
2893    }
2894
2895    /// Replays a mined envelope through the active network's canonical replay entry point.
2896    fn replay_envelope_with_inspector_ref_and_context<'db, I, DB>(
2897        &self,
2898        db: &'db DB,
2899        evm_env: &EvmEnv,
2900        inspector: &mut I,
2901        pending: &PendingTransaction<FoundryTxEnvelope>,
2902        execution: EnvelopeExecution<'_>,
2903    ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
2904    where
2905        DB: DatabaseRef + ?Sized,
2906        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
2907        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2908    {
2909        self.transact_envelope_with_inspector_ref_and_context_kind(
2910            db, evm_env, inspector, pending, execution,
2911        )
2912    }
2913
2914    fn transact_envelope_with_inspector_ref_and_context_kind<'db, I, DB>(
2915        &self,
2916        db: &'db DB,
2917        evm_env: &EvmEnv,
2918        inspector: &mut I,
2919        pending: &PendingTransaction<FoundryTxEnvelope>,
2920        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] execution: EnvelopeExecution<
2921            '_,
2922        >,
2923    ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
2924    where
2925        DB: DatabaseRef + ?Sized,
2926        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
2927        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2928    {
2929        #[cfg(any(feature = "base", feature = "optimism"))]
2930        let (tx, sender) = (pending.transaction.as_ref(), *pending.sender());
2931        #[cfg(feature = "base")]
2932        if self.is_base() {
2933            let base_tx: BaseTransaction<TxEnv> =
2934                FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
2935            let base = base_tx.base.clone();
2936            let result = self.transact_base_with_inspector_ref(db, evm_env, inspector, base_tx)?;
2937            return Ok((result, base));
2938        }
2939        if self.is_tempo() {
2940            let tx_env: TempoTxEnv = build_tx_env_for_pending(pending, self.cheats());
2941            let base = tx_env.inner.clone();
2942            let result = self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)?;
2943            return Ok((result, base));
2944        }
2945        #[cfg(feature = "optimism")]
2946        if self.is_optimism() {
2947            let op_tx: OpTransaction<TxEnv> =
2948                FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
2949            let base = op_tx.base.clone();
2950            let spec = self.hardfork().into();
2951            let result =
2952                self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx, spec)?;
2953            return Ok((result, base));
2954        }
2955        let tx_env: TxEnv = build_tx_env_for_pending(pending, self.cheats());
2956        let base = tx_env.clone();
2957        #[cfg(feature = "monad")]
2958        let result = if self.is_monad() {
2959            let context = monad::resolve_execution_context(execution.monad_context, &tx_env);
2960            self.transact_monad_with_inspector_ref(
2961                db,
2962                evm_env,
2963                inspector,
2964                tx_env,
2965                monad::PreparedExecution {
2966                    context,
2967                    kind: execution.kind,
2968                    hardfork: monad_revm::MonadHardfork::from(execution.hardfork),
2969                },
2970            )?
2971        } else {
2972            self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?
2973        };
2974        #[cfg(not(feature = "monad"))]
2975        let result = self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?;
2976        Ok((result, base))
2977    }
2978
2979    /// Builds the Tempo [`EvmEnv`] (spec, gas params, [`TempoBlockEnv`]) from a base
2980    /// env.
2981    fn build_tempo_evm_env(
2982        evm_env: &EvmEnv,
2983        hardfork: TempoHardfork,
2984    ) -> EvmEnv<TempoHardfork, TempoBlockEnv> {
2985        EvmEnv::new(
2986            evm_env.cfg_env.clone().with_spec_and_gas_params(hardfork, tempo_gas_params(hardfork)),
2987            TempoBlockEnv {
2988                inner: evm_env.block_env.clone(),
2989                timestamp_millis_part: 0,
2990                ..Default::default()
2991            },
2992        )
2993    }
2994
2995    /// Creates a Tempo EVM, injects precompiles, and transacts with a native [`TempoTxEnv`].
2996    fn transact_tempo_with_inspector_ref<'db, I, DB>(
2997        &self,
2998        db: &'db DB,
2999        evm_env: &EvmEnv,
3000        inspector: &mut I,
3001        tx_env: TempoTxEnv,
3002    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
3003    where
3004        DB: DatabaseRef + ?Sized,
3005        I: Inspector<TempoContext<WrapDatabaseRef<&'db DB>>>,
3006        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
3007    {
3008        let tempo_env = Self::build_tempo_evm_env(evm_env, self.tempo_hardfork());
3009        let mut evm = TempoEvmFactory::default().create_evm_with_inspector(
3010            WrapDatabaseRef(db),
3011            tempo_env,
3012            inspector,
3013        );
3014        self.inject_tempo_precompiles(&mut evm, evm_env);
3015        Ok(evm.transact(tx_env)?)
3016    }
3017
3018    /// Creates a concrete EVM + [`AnvilBlockExecutor`], runs pre-execution changes, and
3019    /// executes pool transactions. Returns the execution results and drops the EVM.
3020    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
3021    fn execute_with_block_executor<DB>(
3022        &self,
3023        db: DB,
3024        evm_env: &EvmEnv,
3025        parent_hash: B256,
3026        hardfork: FoundryHardfork,
3027        parent_beacon_block_root: Option<B256>,
3028        execution_kind: BlockExecutionKind,
3029        pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
3030        gas_config: &PoolTxGasConfig,
3031        inspector_tx_config: &InspectorTxConfig,
3032        validator: &dyn Fn(
3033            &PoolTransaction<FoundryTxEnvelope>,
3034            &AccountInfo,
3035        ) -> Result<(), InvalidTransactionError>,
3036    ) -> Result<
3037        (ExecutedPoolTransactions<FoundryTxEnvelope>, BlockExecutionResult<FoundryReceiptEnvelope>),
3038        BlockchainError,
3039    >
3040    where
3041        DB: StateDB<Error = DatabaseError> + BalIndexedDatabase,
3042    {
3043        let spec_id = *evm_env.spec_id();
3044        #[cfg(feature = "monad")]
3045        if self.is_monad() {
3046            return self.execute_with_monad_block_executor(
3047                db,
3048                evm_env,
3049                parent_hash,
3050                spec_id,
3051                hardfork.into(),
3052                pool_transactions,
3053                gas_config,
3054                inspector_tx_config,
3055                validator,
3056            );
3057        }
3058
3059        let inspector = self.build_mining_inspector();
3060        let transitions =
3061            self.ethereum_block_transitions(hardfork, parent_beacon_block_root, execution_kind);
3062
3063        macro_rules! execute {
3064            ($executor:expr) => {{
3065                let mut executor = $executor;
3066                executor
3067                    .apply_pre_execution_changes()
3068                    .map_err(|err| BlockchainError::Internal(err.to_string()))?;
3069                let pool_result = execute_pool_transactions(
3070                    &mut executor,
3071                    pool_transactions,
3072                    gas_config,
3073                    inspector_tx_config,
3074                    self.cheats(),
3075                    validator,
3076                    &mut execute_pool_transaction,
3077                );
3078                let (_, block_result) =
3079                    executor.finish().map_err(|err| BlockchainError::Internal(err.to_string()))?;
3080                Ok((pool_result, block_result))
3081            }};
3082        }
3083
3084        #[cfg(feature = "base")]
3085        if self.is_base() {
3086            let configured = match hardfork {
3087                FoundryHardfork::Base(upgrade) => upgrade,
3088                _ => BaseUpgrade::Azul,
3089            };
3090            let upgrade = if self.is_fork() {
3091                BaseUpgrade::from_chain_and_timestamp(
3092                    evm_env.cfg_env.chain_id,
3093                    evm_env.block_env.timestamp.saturating_to(),
3094                )
3095                .unwrap_or(configured)
3096            } else {
3097                configured
3098            };
3099            let cfg =
3100                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(BaseSpecId::new(upgrade));
3101            let activation_admin = self.base_activation_admin().or_else(|| {
3102                ChainConfig::activation_admin_address_for_upgrade_by_chain_id(cfg.chain_id, upgrade)
3103            });
3104            let base_env = EvmEnv::new(cfg, evm_env.block_env.clone());
3105            let mut evm = BaseEvmFactory::new(activation_admin)
3106                .create_evm_with_inspector(db, base_env, inspector);
3107            evm.ctx_mut().cfg.tx_chain_id_check = true;
3108            self.inject_precompiles(evm.precompiles_mut(), evm_env);
3109            let mut executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
3110                .with_max_blob_gas_per_block(gas_config.max_blob_gas_per_block);
3111            executor.set_base_upgrade(upgrade);
3112            return execute!(executor);
3113        }
3114
3115        #[cfg(feature = "optimism")]
3116        if self.is_optimism() {
3117            let op_env = EvmEnv::new(
3118                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(hardfork.into()),
3119                evm_env.block_env.clone(),
3120            );
3121            let mut evm =
3122                OpEvmFactory::<OpTx>::default().create_evm_with_inspector(db, op_env, inspector);
3123            self.inject_precompiles(evm.precompiles_mut(), evm_env);
3124            let mut executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
3125                .with_max_blob_gas_per_block(gas_config.max_blob_gas_per_block);
3126            executor.set_optimism_hardfork(hardfork);
3127            return execute!(executor);
3128        }
3129
3130        if self.is_tempo() {
3131            let tempo_env = Self::build_tempo_evm_env(evm_env, self.tempo_hardfork());
3132            let mut evm =
3133                TempoEvmFactory::default().create_evm_with_inspector(db, tempo_env, inspector);
3134            self.inject_precompiles(evm.precompiles_mut(), evm_env);
3135            let executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
3136                .with_max_blob_gas_per_block(gas_config.max_blob_gas_per_block);
3137            return execute!(executor);
3138        }
3139        let mut evm =
3140            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
3141        self.inject_precompiles(evm.precompiles_mut(), evm_env);
3142        let executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
3143            .with_max_blob_gas_per_block(gas_config.max_blob_gas_per_block);
3144        execute!(executor)
3145    }
3146
3147    /// Applies Ethereum block-start transitions to a disposable simulation candidate.
3148    fn apply_simulation_pre_execution_changes<DB>(
3149        &self,
3150        db: DB,
3151        evm_env: &EvmEnv,
3152        parent_hash: B256,
3153        transitions: EthereumBlockTransitions,
3154    ) -> Result<(), BlockchainError>
3155    where
3156        DB: StateDB,
3157    {
3158        let inspector = self.build_mining_inspector();
3159        let mut evm =
3160            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
3161        self.inject_precompiles(evm.precompiles_mut(), evm_env);
3162        apply_ethereum_pre_execution_changes(&mut evm, parent_hash, transitions)
3163            .map_err(|err| BlockchainError::Internal(err.to_string()))
3164    }
3165
3166    /// Applies Ethereum post-block transitions to a disposable simulation candidate.
3167    fn apply_simulation_post_execution_changes<DB>(
3168        &self,
3169        db: DB,
3170        evm_env: &EvmEnv,
3171        transitions: EthereumBlockTransitions,
3172        receipts: &[FoundryReceiptEnvelope],
3173    ) -> Result<alloy_eips::eip7685::Requests, BlockchainError>
3174    where
3175        DB: StateDB,
3176    {
3177        let inspector = self.build_mining_inspector();
3178        let mut evm =
3179            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
3180        self.inject_precompiles(evm.precompiles_mut(), evm_env);
3181        apply_ethereum_post_execution_changes(&mut evm, transitions, receipts)
3182            .map_err(|err| BlockchainError::Internal(err.to_string()))
3183    }
3184
3185    /// ## EVM settings
3186    ///
3187    /// 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>:
3188    ///
3189    ///  - `disable_eip3607` is set to `true`
3190    ///  - the base fee is zero and its check disabled for a zero-fee call, while a priced call must
3191    ///    pay at least the base fee
3192    ///  - the blob base fee is zero for a blob call without a blob fee cap
3193    ///  - `tx_gas_limit_cap` is set to `Some(u64::MAX)` indicating no gas limit cap
3194    ///  - `nonce` check is skipped
3195    fn build_call_env_with_base(
3196        &self,
3197        request: WithOtherFields<TransactionRequest>,
3198        fee_details: FeeDetails,
3199        block_env: BlockEnv,
3200        base_evm_env: Option<&EvmEnv>,
3201    ) -> (EvmEnv, TxEnv, CallTransactionInfo) {
3202        let tx_type = request.minimal_tx_type() as u8;
3203        #[cfg(any(feature = "base", feature = "optimism"))]
3204        let mut transaction_info = CallTransactionInfo::default();
3205        #[cfg(not(any(feature = "base", feature = "optimism")))]
3206        let transaction_info = CallTransactionInfo::default();
3207
3208        let WithOtherFields::<TransactionRequest> {
3209            inner:
3210                TransactionRequest {
3211                    from,
3212                    to,
3213                    gas,
3214                    value,
3215                    input,
3216                    access_list,
3217                    blob_versioned_hashes,
3218                    authorization_list,
3219                    nonce,
3220                    sidecar: _,
3221                    chain_id,
3222                    .. // Rest of the gas fees related fields are taken from `fee_details`
3223                },
3224            other,
3225        } = request;
3226
3227        let FeeDetails {
3228            gas_price,
3229            max_fee_per_gas,
3230            max_priority_fee_per_gas,
3231            max_fee_per_blob_gas,
3232        } = fee_details;
3233
3234        let gas_limit = gas.unwrap_or(block_env.gas_limit);
3235        let mut evm_env = base_evm_env.cloned().unwrap_or_else(|| self.evm_env.read().clone());
3236        evm_env.block_env = block_env;
3237        // we want to disable this in eth_call, since this is common practice used by other node
3238        // impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
3239        evm_env.cfg_env.disable_block_gas_limit = true;
3240        evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX);
3241
3242        // Disable nonce check in revm
3243        evm_env.cfg_env.disable_nonce_check = true;
3244
3245        let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| {
3246            self.fees().raw_gas_price().saturating_add(MIN_SUGGESTED_PRIORITY_FEE)
3247        });
3248        // A zero-fee call runs with a zero base fee, as in geth's eth_call, so BASEFEE never
3249        // exceeds the price the call pays. A priced call keeps the base fee check, so a price
3250        // below the base fee is rejected as it is for a transaction.
3251        if gas_price == 0 {
3252            evm_env.block_env.basefee = 0;
3253        }
3254        // Set the check on every call, because a cloned base environment can carry a disabled
3255        // check. Pre-London blocks have no protocol base fee, so they skip the check too.
3256        evm_env.cfg_env.disable_base_fee =
3257            evm_env.block_env.basefee == 0 || evm_env.cfg_env.spec < SpecId::LONDON;
3258        let caller = from.unwrap_or_default();
3259        let to = to.as_ref().and_then(TxKind::to);
3260        let blob_hashes = blob_versioned_hashes.unwrap_or_default();
3261        // As in geth's eth_call, a blob call without a blob fee cap runs at a zero blob base fee
3262        // and pays no blob fee, while other calls keep the block's blob base fee.
3263        let is_blob_call = !blob_hashes.is_empty() || max_fee_per_blob_gas.is_some();
3264        let max_fee_per_blob_gas = max_fee_per_blob_gas.unwrap_or_default();
3265        if is_blob_call
3266            && max_fee_per_blob_gas == 0
3267            && let Some(blob) = evm_env.block_env.blob_excess_gas_and_price.as_mut()
3268        {
3269            blob.blob_gasprice = 0;
3270        }
3271        let mut tx_env = TxEnv {
3272            caller,
3273            gas_limit,
3274            gas_price,
3275            gas_priority_fee: max_priority_fee_per_gas,
3276            max_fee_per_blob_gas,
3277            kind: match to {
3278                Some(addr) => TxKind::Call(*addr),
3279                None => TxKind::Create,
3280            },
3281            tx_type,
3282            value: value.unwrap_or_default(),
3283            data: input.into_input().unwrap_or_default(),
3284            chain_id: Some(chain_id.unwrap_or(evm_env.cfg_env.chain_id)),
3285            access_list: access_list.unwrap_or_default(),
3286            blob_hashes,
3287            ..Default::default()
3288        };
3289        tx_env.set_signed_authorization(authorization_list.unwrap_or_default());
3290
3291        if let Some(nonce) = nonce {
3292            tx_env.nonce = nonce;
3293        }
3294
3295        // Deposit transaction? (only valid when a deposit-capable network is active)
3296        #[cfg(any(feature = "base", feature = "optimism"))]
3297        {
3298            transaction_info.deposit = if self.ensure_deposits_active().is_ok()
3299                && let Ok(deposit) = get_deposit_tx_parts(&other)
3300            {
3301                deposit
3302            } else {
3303                DepositTransactionParts::default()
3304            };
3305        }
3306        #[cfg(not(any(feature = "base", feature = "optimism")))]
3307        {
3308            // `other` carries OP-only deposit fields; consumed only when feature is enabled.
3309            let _ = &other;
3310        }
3311
3312        (evm_env, tx_env, transaction_info)
3313    }
3314
3315    fn prepare_call_env(
3316        &self,
3317        state: &dyn DatabaseRef,
3318        request: WithOtherFields<TransactionRequest>,
3319        fee_details: FeeDetails,
3320        block_env: BlockEnv,
3321    ) -> Result<PreparedCall, BlockchainError> {
3322        self.prepare_call_env_from_base(state, request, fee_details, block_env, None)
3323    }
3324
3325    fn prepare_call_env_from_base(
3326        &self,
3327        state: &dyn DatabaseRef,
3328        request: WithOtherFields<TransactionRequest>,
3329        fee_details: FeeDetails,
3330        block_env: BlockEnv,
3331        base_evm_env: Option<&EvmEnv>,
3332    ) -> Result<PreparedCall, BlockchainError> {
3333        let gas_omitted = request.gas.is_none();
3334        let request = self.parse_transaction_request(request)?;
3335        let mut prepared = self.prepare_typed_call_env_with_base(
3336            state,
3337            request,
3338            fee_details,
3339            block_env,
3340            base_evm_env,
3341        )?;
3342        // Without a gas limit, a priced call gets at most the gas its sender can pay for, rather
3343        // than failing the funds check for the default limit. Tempo pays fees in tokens instead,
3344        // and OP deposits mint funds before paying for execution.
3345        let cap_by_balance = match &prepared.tx_env {
3346            CallTxEnv::Tempo(_) => false,
3347            #[cfg(feature = "optimism")]
3348            CallTxEnv::Op(tx) => tx.tx_type() != DEPOSIT_TX_TYPE_ID,
3349            _ => true,
3350        };
3351        let min_gas = if prepared.evm_env.cfg_env.enable_amsterdam_eip2780 {
3352            eip2780::TX_BASE_COST
3353        } else {
3354            MIN_TRANSACTION_GAS as u64
3355        };
3356        let tx_env = prepared.tx_env.base_mut();
3357        if gas_omitted && cap_by_balance && tx_env.gas_price > 0 {
3358            let balance =
3359                state.basic_ref(tx_env.caller)?.map(|info| info.balance).unwrap_or_default();
3360            let upfront = tx_env.value.saturating_add(tx_env.calc_max_data_fee());
3361            // A sender that cannot pay for the cheapest transaction keeps the default limit, so
3362            // the call fails the funds check.
3363            if let Some(allowance) = balance
3364                .checked_sub(upfront)
3365                .map(|available| available / U256::from(tx_env.gas_price))
3366                && allowance >= U256::from(min_gas)
3367            {
3368                tx_env.gas_limit = tx_env.gas_limit.min(allowance.saturating_to());
3369            }
3370        }
3371        Ok(prepared)
3372    }
3373
3374    const fn base_call_tx_env(&self, tx_env: TxEnv) -> CallTxEnv {
3375        #[cfg(feature = "monad")]
3376        if self.is_monad() {
3377            return CallTxEnv::Monad(tx_env);
3378        }
3379        CallTxEnv::Eth(tx_env)
3380    }
3381
3382    fn prepare_base_call_env_with_base(
3383        &self,
3384        request: WithOtherFields<TransactionRequest>,
3385        fee_details: FeeDetails,
3386        block_env: BlockEnv,
3387        base_evm_env: Option<&EvmEnv>,
3388    ) -> Result<PreparedCall, BlockchainError> {
3389        let (evm_env, tx_env, transaction_info) =
3390            self.build_call_env_with_base(request, fee_details, block_env, base_evm_env);
3391
3392        #[cfg(feature = "base")]
3393        if self.is_base() {
3394            let mut base_tx = BaseTransaction::new(tx_env);
3395            if base_tx.base.tx_type == DEPOSIT_TRANSACTION_TYPE {
3396                let deposit = &transaction_info.deposit;
3397                base_tx.deposit = BaseDepositTransactionParts::new(
3398                    deposit.source_hash,
3399                    deposit.mint,
3400                    deposit.is_system_transaction,
3401                );
3402            } else {
3403                base_tx.enveloped_tx = Some(Bytes::new());
3404            }
3405            return Ok(PreparedCall {
3406                evm_env,
3407                tx_env: CallTxEnv::Base(Box::new(base_tx)),
3408                simulated_tempo_tx: None,
3409                simulated_envelope: None,
3410            });
3411        }
3412
3413        #[cfg(feature = "optimism")]
3414        let tx_env = if self.is_optimism() {
3415            CallTxEnv::Op(OpTransaction {
3416                base: tx_env,
3417                deposit: transaction_info.deposit,
3418                enveloped_tx: Some(Bytes::new()),
3419            })
3420        } else if self.is_tempo() {
3421            CallTxEnv::Tempo(TempoTxEnv::from(tx_env))
3422        } else {
3423            self.base_call_tx_env(tx_env)
3424        };
3425        #[cfg(not(feature = "optimism"))]
3426        let tx_env = {
3427            let _ = transaction_info;
3428            if self.is_tempo() {
3429                CallTxEnv::Tempo(TempoTxEnv::from(tx_env))
3430            } else {
3431                self.base_call_tx_env(tx_env)
3432            }
3433        };
3434        Ok(PreparedCall {
3435            evm_env,
3436            tx_env,
3437            simulated_tempo_tx: None,
3438            #[cfg(feature = "base")]
3439            simulated_envelope: None,
3440        })
3441    }
3442
3443    /// Classifies an RPC request according to the active network.
3444    pub(crate) fn parse_transaction_request(
3445        &self,
3446        request: WithOtherFields<TransactionRequest>,
3447    ) -> Result<FoundryTransactionRequest, BlockchainError> {
3448        request
3449            .input
3450            .unique_input()
3451            .map_err(|err| BlockchainError::RpcError(RpcError::invalid_params(err.to_string())))?;
3452        // No transaction carries both fee styles, so reject the request rather than pick one.
3453        if request.gas_price.is_some()
3454            && (request.max_fee_per_gas.is_some() || request.max_priority_fee_per_gas.is_some())
3455        {
3456            return Err(BlockchainError::ConflictingFeeFields);
3457        }
3458        let transaction_type = request.transaction_type;
3459        if transaction_type == Some(foundry_primitives::CIP64_TX_TYPE)
3460            || request.other.get("feeCurrency").is_some_and(|value| !value.is_null())
3461        {
3462            self.ensure_cip64_active()?;
3463            return FoundryTransactionRequest::new(request)
3464                .map_err(|err| BlockchainError::InvalidTransactionRequest(err.to_string()));
3465        }
3466        #[cfg(feature = "base")]
3467        if self.is_base() {
3468            let parsed = FoundryTransactionRequest::try_from(request.clone()).map_err(
3469                |err: serde_json::Error| {
3470                    BlockchainError::InvalidTransactionRequest(err.to_string())
3471                },
3472            )?;
3473            if parsed.is_base() {
3474                return Ok(parsed);
3475            }
3476            if transaction_type == Some(DEPOSIT_TX_TYPE_ID)
3477                || get_deposit_tx_parts(&request.other).is_ok()
3478            {
3479                return Ok(FoundryTransactionRequest::Op(request));
3480            }
3481            return Ok(FoundryTransactionRequest::Ethereum(request.into_inner()));
3482        }
3483        #[cfg(feature = "base")]
3484        if transaction_type == Some(foundry_primitives::FoundryTxType::Eip8130.into())
3485            || matches!(
3486                FoundryTransactionRequest::try_from(request.clone()),
3487                Ok(FoundryTransactionRequest::Base(_))
3488            )
3489        {
3490            return Err(BlockchainError::BaseTransactionUnsupported);
3491        }
3492        if !self.is_tempo() && transaction_type != Some(TEMPO_TX_TYPE_ID) {
3493            #[cfg(feature = "optimism")]
3494            if transaction_type == Some(DEPOSIT_TX_TYPE_ID)
3495                || transaction_type == Some(POST_EXEC_TX_TYPE_ID)
3496                || get_deposit_tx_parts(&request.other).is_ok()
3497            {
3498                return Ok(FoundryTransactionRequest::Op(request));
3499            }
3500            return Ok(FoundryTransactionRequest::Ethereum(request.into_inner()));
3501        }
3502
3503        let parsed: FoundryTransactionRequest =
3504            FoundryTransactionRequest::new(request).map_err(|err: serde_json::Error| {
3505                BlockchainError::InvalidTransactionRequest(err.to_string())
3506            })?;
3507        if parsed.is_tempo() {
3508            self.ensure_tempo_active()?;
3509        }
3510        if parsed.is_tempo()
3511            && self.is_tempo()
3512            && transaction_type.is_some_and(|ty| ty != TEMPO_TX_TYPE_ID)
3513        {
3514            return Err(BlockchainError::FailedToDecodeTransaction);
3515        }
3516        Ok(parsed)
3517    }
3518
3519    fn build_tempo_request_env(
3520        request: TempoTransactionRequest,
3521        mut base: TxEnv,
3522        hardfork: TempoHardfork,
3523    ) -> Result<(TempoTxEnv, AASigned), BlockchainError> {
3524        let fee_payer = request.fee_payer_signature.map(|_| {
3525            request.clone().build_aa().ok().and_then(|tx| tx.recover_fee_payer(base.caller).ok())
3526        });
3527
3528        // Build the response representation separately so the mocked execution signature does not
3529        // leak into RPC output.
3530        let mut response_request = request.clone();
3531        response_request.inner.from = Some(base.caller);
3532        response_request.inner.gas = Some(base.gas_limit);
3533        response_request.inner.nonce = Some(base.nonce);
3534        response_request.inner.chain_id = base.chain_id;
3535        response_request.inner.max_fee_per_gas = Some(base.gas_price);
3536        response_request.inner.max_priority_fee_per_gas =
3537            Some(base.gas_priority_fee.unwrap_or_default());
3538        response_request.inner.access_list = Some(base.access_list.clone());
3539        if response_request.calls.is_empty()
3540            && response_request.inner.to.is_none()
3541            && !base.data.is_empty()
3542        {
3543            response_request.inner.to = Some(base.kind);
3544        }
3545        let response_tx = response_request
3546            .build_aa()
3547            .map_err(|err| BlockchainError::InvalidTransactionRequest(err.to_string()))?;
3548        let response_tx = response_tx.into_signed(TempoSignature::default());
3549        let key_type = request.key_type.unwrap_or(SignatureType::Secp256k1);
3550        let key_data = request.key_data.clone();
3551        let key_id = request.key_id;
3552        let signature =
3553            mock_tempo_signature(key_type, key_data, key_id, base.caller, hardfork.is_t1c());
3554        let mut calls = request.calls;
3555        if let Some(to) = request.inner.to {
3556            calls.push(Call {
3557                to,
3558                value: request.inner.value.unwrap_or_default(),
3559                input: request.inner.input.into_input().unwrap_or_default(),
3560            });
3561        } else if calls.is_empty() && !base.data.is_empty() {
3562            // Alloy represents an omitted top-level `to` as `None`; preserve Ethereum CREATE
3563            // semantics by materializing it as the final Tempo call.
3564            calls.push(Call { to: base.kind, value: base.value, input: base.data.clone() });
3565        }
3566        if let Some(first_call) = calls.first() {
3567            base.kind = first_call.to;
3568            base.value = first_call.value;
3569            base.data = first_call.input.clone();
3570        }
3571        let tx_env = TempoTxEnv {
3572            fee_token: request.fee_token,
3573            is_system_tx: false,
3574            execution_context: ExecutionContext::Simulation,
3575            unique_tx_identifier: Some(TEMPO_RPC_SIMULATION_CONTEXT),
3576            fee_payer,
3577            tempo_tx_env: Some(Box::new(TempoBatchCallEnv {
3578                aa_calls: calls,
3579                signature,
3580                tempo_authorization_list: request
3581                    .tempo_authorization_list
3582                    .into_iter()
3583                    .map(RecoveredTempoAuthorization::new)
3584                    .collect(),
3585                nonce_key: request.nonce_key.unwrap_or_default(),
3586                key_authorization: request.key_authorization,
3587                signature_hash: B256::ZERO,
3588                tx_hash: B256::ZERO,
3589                valid_before: request.valid_before.map(|value| value.get()),
3590                valid_after: request.valid_after.map(|value| value.get()),
3591                override_key_id: key_id,
3592                expiring_nonce_idx: None,
3593            })),
3594            inner: base,
3595        };
3596        Ok((tx_env, response_tx))
3597    }
3598
3599    fn prepare_typed_call_env(
3600        &self,
3601        state: &dyn DatabaseRef,
3602        request: FoundryTransactionRequest,
3603        fee_details: FeeDetails,
3604        block_env: BlockEnv,
3605    ) -> Result<PreparedCall, BlockchainError> {
3606        self.prepare_typed_call_env_with_base(state, request, fee_details, block_env, None)
3607    }
3608
3609    fn prepare_typed_call_env_with_base(
3610        &self,
3611        state: &dyn DatabaseRef,
3612        request: FoundryTransactionRequest,
3613        fee_details: FeeDetails,
3614        block_env: BlockEnv,
3615        base_evm_env: Option<&EvmEnv>,
3616    ) -> Result<PreparedCall, BlockchainError> {
3617        match request {
3618            FoundryTransactionRequest::Tempo(tempo_request) => {
3619                self.ensure_tempo_active()?;
3620                let mut tempo_request = *tempo_request;
3621                if tempo_request.inner.nonce.is_none() {
3622                    let caller = tempo_request.inner.from.unwrap_or_default();
3623                    tempo_request.inner.nonce = Some(tempo_nonce(
3624                        state,
3625                        caller,
3626                        tempo_request.nonce_key.unwrap_or_default(),
3627                    )?);
3628                }
3629                let inner = WithOtherFields::new(tempo_request.inner.clone());
3630                let (evm_env, base, _) =
3631                    self.build_call_env_with_base(inner, fee_details, block_env, base_evm_env);
3632                let (tx_env, simulated_tempo_tx) =
3633                    Self::build_tempo_request_env(tempo_request, base, self.tempo_hardfork())?;
3634                Ok(PreparedCall {
3635                    evm_env,
3636                    tx_env: CallTxEnv::Tempo(tx_env),
3637                    simulated_tempo_tx: Some(simulated_tempo_tx),
3638                    #[cfg(feature = "base")]
3639                    simulated_envelope: None,
3640                })
3641            }
3642            #[cfg(feature = "base")]
3643            FoundryTransactionRequest::Base(request) => {
3644                self.ensure_base_eip8130_active_at(block_env.timestamp.saturating_to())?;
3645                let gas_limit = block_env.gas_limit;
3646                let (evm_env, _, _) = self.build_call_env_with_base(
3647                    WithOtherFields::new(request.as_ref().clone()),
3648                    fee_details,
3649                    block_env,
3650                    base_evm_env,
3651                );
3652                let tx = request
3653                    .to_eip8130_simulation_tx(self.chain_id().to(), gas_limit)
3654                    .ok_or_else(|| {
3655                        BlockchainError::InvalidTransactionRequest(
3656                            "invalid EIP-8130 simulation request: missing or conflicting sender, \
3657                             oversized authentication data, or invalid payer authenticator"
3658                                .to_string(),
3659                        )
3660                    })?;
3661                let simulated_envelope = tx
3662                    .eip8130
3663                    .as_ref()
3664                    .map(|parts| FoundryTxEnvelope::Eip8130(parts.signed.clone()));
3665                Ok(PreparedCall {
3666                    evm_env,
3667                    tx_env: CallTxEnv::Base(Box::new(tx)),
3668                    simulated_tempo_tx: None,
3669                    simulated_envelope,
3670                })
3671            }
3672            FoundryTransactionRequest::Ethereum(mut request)
3673            | FoundryTransactionRequest::Celo(foundry_primitives::Cip64TransactionRequest {
3674                inner: mut request,
3675                ..
3676            }) => {
3677                // Tempo charges the account-creation cost for nonce 0, so an omitted nonce must
3678                // not default to it.
3679                if self.is_tempo() && request.nonce.is_none() {
3680                    request.nonce =
3681                        Some(tempo_nonce(state, request.from.unwrap_or_default(), U256::ZERO)?);
3682                }
3683                self.prepare_base_call_env_with_base(
3684                    WithOtherFields::new(request),
3685                    fee_details,
3686                    block_env,
3687                    base_evm_env,
3688                )
3689            }
3690            #[cfg(any(feature = "base", feature = "optimism"))]
3691            FoundryTransactionRequest::Op(request) => {
3692                self.prepare_base_call_env_with_base(request, fee_details, block_env, base_evm_env)
3693            }
3694        }
3695    }
3696
3697    fn transact_call_with_inspector_ref<'db, I, DB>(
3698        &self,
3699        db: &'db DB,
3700        evm_env: &EvmEnv,
3701        inspector: &mut I,
3702        tx_env: CallTxEnv,
3703        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
3704            MonadExecutionContext<'_>,
3705        >,
3706    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
3707    where
3708        DB: DatabaseRef + ?Sized,
3709        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
3710        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
3711    {
3712        self.transact_call_with_inspector_ref_at_hardfork(
3713            db,
3714            evm_env,
3715            inspector,
3716            tx_env,
3717            monad_context,
3718            self.hardfork(),
3719        )
3720    }
3721
3722    fn transact_call_with_inspector_ref_at_hardfork<'db, I, DB>(
3723        &self,
3724        db: &'db DB,
3725        evm_env: &EvmEnv,
3726        inspector: &mut I,
3727        tx_env: CallTxEnv,
3728        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
3729            MonadExecutionContext<'_>,
3730        >,
3731        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] hardfork: FoundryHardfork,
3732    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
3733    where
3734        DB: DatabaseRef + ?Sized,
3735        I: BackendInspector<WrapDatabaseRef<&'db DB>>,
3736        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
3737    {
3738        match tx_env {
3739            CallTxEnv::Eth(tx_env) => {
3740                self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)
3741            }
3742            #[cfg(feature = "base")]
3743            CallTxEnv::Base(tx_env) => {
3744                self.transact_base_with_inspector_ref(db, evm_env, inspector, *tx_env)
3745            }
3746            #[cfg(feature = "monad")]
3747            CallTxEnv::Monad(tx_env) => {
3748                let context = monad::resolve_execution_context(monad_context, &tx_env);
3749                self.transact_monad_with_inspector_ref(
3750                    db,
3751                    evm_env,
3752                    inspector,
3753                    tx_env,
3754                    monad::PreparedExecution {
3755                        context,
3756                        kind: EnvelopeExecutionKind::Transaction,
3757                        hardfork: monad_revm::MonadHardfork::from(hardfork),
3758                    },
3759                )
3760            }
3761            #[cfg(feature = "optimism")]
3762            CallTxEnv::Op(tx_env) => {
3763                let spec = self.hardfork().into();
3764                self.transact_op_with_inspector_ref(db, evm_env, inspector, tx_env, spec)
3765            }
3766            CallTxEnv::Tempo(tx_env) => {
3767                self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)
3768            }
3769        }
3770    }
3771
3772    pub fn call_with_state(
3773        &self,
3774        state: &dyn DatabaseRef,
3775        request: WithOtherFields<TransactionRequest>,
3776        fee_details: FeeDetails,
3777        block_env: BlockEnv,
3778    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3779        self.call_with_state_and_context(state, request, fee_details, block_env, None)
3780    }
3781
3782    pub(crate) fn call_with_state_and_context(
3783        &self,
3784        state: &dyn DatabaseRef,
3785        request: WithOtherFields<TransactionRequest>,
3786        fee_details: FeeDetails,
3787        block_env: BlockEnv,
3788        mut monad_context: Option<MonadReplayContext>,
3789    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3790        let mut inspector = self.build_inspector();
3791        let PreparedCall { evm_env, tx_env, .. } =
3792            self.prepare_call_env(state, request, fee_details, block_env)?;
3793        let ResultAndState { result, state } = self.transact_call_with_inspector_ref(
3794            state,
3795            &evm_env,
3796            &mut inspector,
3797            tx_env,
3798            monad_context.as_mut().map(next_monad_context),
3799        )?;
3800
3801        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3802        inspector.print_logs();
3803
3804        if self.print_traces {
3805            inspector.into_print_traces(self.call_trace_decoder());
3806        }
3807
3808        Ok((exit_reason, out, gas_used as u128, state))
3809    }
3810
3811    pub(crate) fn call_with_state_typed_gas_limit(
3812        &self,
3813        state: &dyn DatabaseRef,
3814        request: FoundryTransactionRequest,
3815        fee_details: FeeDetails,
3816        block_env: BlockEnv,
3817        options: GasEstimateCallOptions,
3818    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3819        let GasEstimateCallOptions { gas_limit, disable_fee_charge, monad_context } = options;
3820        self.call_with_state_typed_inner(
3821            state,
3822            request,
3823            fee_details,
3824            block_env,
3825            TypedCallOverrides {
3826                gas_limit: Some(gas_limit),
3827                disable_fee_charge,
3828                ..Default::default()
3829            },
3830            monad_context,
3831        )
3832    }
3833
3834    pub(crate) fn call_with_state_typed_access_list(
3835        &self,
3836        state: &dyn DatabaseRef,
3837        request: FoundryTransactionRequest,
3838        fee_details: FeeDetails,
3839        block_env: BlockEnv,
3840        access_list: AccessList,
3841        monad_context: Option<MonadReplayContext>,
3842    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3843        self.call_with_state_typed_inner(
3844            state,
3845            request,
3846            fee_details,
3847            block_env,
3848            TypedCallOverrides { access_list: Some(access_list), ..Default::default() },
3849            monad_context,
3850        )
3851    }
3852
3853    fn call_with_state_typed_inner(
3854        &self,
3855        state: &dyn DatabaseRef,
3856        request: FoundryTransactionRequest,
3857        fee_details: FeeDetails,
3858        block_env: BlockEnv,
3859        overrides: TypedCallOverrides,
3860        mut monad_context: Option<MonadReplayContext>,
3861    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3862        // Estimation probes do not return or print traces, but still collect console logs.
3863        let mut inspector = AnvilInspector::default();
3864        if self.print_logs {
3865            inspector = inspector.with_log_collector();
3866        }
3867        let block_fees = (block_env.basefee, block_env.blob_excess_gas_and_price);
3868        let PreparedCall { mut evm_env, mut tx_env, .. } =
3869            self.prepare_typed_call_env(state, request, fee_details, block_env)?;
3870        evm_env.cfg_env.disable_fee_charge = overrides.disable_fee_charge;
3871        if let Some(gas_limit) = overrides.gas_limit {
3872            tx_env.base_mut().gas_limit = gas_limit;
3873        }
3874        if let Some(access_list) = overrides.access_list {
3875            let tx_env = tx_env.base_mut();
3876            Self::use_block_fees(&mut evm_env, tx_env, block_fees);
3877            tx_env.access_list = access_list;
3878            if tx_env.tx_type == TransactionType::Legacy as u8 {
3879                tx_env.tx_type = TransactionType::Eip2930 as u8;
3880            }
3881        }
3882        let ResultAndState { result, state } = self.transact_call_with_inspector_ref(
3883            state,
3884            &evm_env,
3885            &mut inspector,
3886            tx_env,
3887            monad_context.as_mut().map(next_monad_context),
3888        )?;
3889        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3890        inspector.print_logs();
3891        Ok((exit_reason, out, gas_used as u128, state))
3892    }
3893
3894    pub fn build_access_list_with_state(
3895        &self,
3896        state: &dyn DatabaseRef,
3897        request: WithOtherFields<TransactionRequest>,
3898        fee_details: FeeDetails,
3899        block_env: BlockEnv,
3900    ) -> Result<(InstructionResult, Option<Output>, u64, AccessList), BlockchainError> {
3901        self.build_access_list_with_state_and_context(state, request, fee_details, block_env, None)
3902    }
3903
3904    pub(crate) fn build_access_list_with_state_and_context(
3905        &self,
3906        state: &dyn DatabaseRef,
3907        request: WithOtherFields<TransactionRequest>,
3908        fee_details: FeeDetails,
3909        block_env: BlockEnv,
3910        mut monad_context: Option<MonadReplayContext>,
3911    ) -> Result<(InstructionResult, Option<Output>, u64, AccessList), BlockchainError> {
3912        let mut inspector =
3913            AccessListInspector::new(request.access_list.clone().unwrap_or_default());
3914
3915        let block_fees = (block_env.basefee, block_env.blob_excess_gas_and_price);
3916        let PreparedCall { mut evm_env, mut tx_env, .. } =
3917            self.prepare_call_env(state, request, fee_details, block_env)?;
3918        Self::use_block_fees(&mut evm_env, tx_env.base_mut(), block_fees);
3919        let ResultAndState { result, state: _ } = self.transact_call_with_inspector_ref(
3920            state,
3921            &evm_env,
3922            &mut inspector,
3923            tx_env,
3924            monad_context.as_mut().map(next_monad_context),
3925        )?;
3926        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3927        let access_list = inspector.access_list();
3928        #[cfg(feature = "monad")]
3929        let access_list = if self.is_monad() {
3930            monad::normalize_access_list(access_list, self.monad_hardfork())
3931        } else {
3932            access_list
3933        };
3934        Ok((exit_reason, out, gas_used, access_list))
3935    }
3936
3937    /// Restores the block's base and blob base fees that a zero-fee call env clears. Like geth,
3938    /// access lists are built at the block's fees.
3939    fn use_block_fees(
3940        evm_env: &mut EvmEnv,
3941        tx_env: &mut TxEnv,
3942        (basefee, blob_excess_gas_and_price): (u64, Option<BlobExcessGasAndPrice>),
3943    ) {
3944        evm_env.block_env.basefee = basefee;
3945        evm_env.block_env.blob_excess_gas_and_price = blob_excess_gas_and_price;
3946        if !tx_env.blob_hashes.is_empty() && tx_env.max_fee_per_blob_gas == 0 {
3947            tx_env.max_fee_per_blob_gas = evm_env.block_env.blob_gasprice().unwrap_or_default();
3948        }
3949    }
3950
3951    fn arbitrum_block_number(&self, evm_env: &EvmEnv) -> Option<u64> {
3952        if !arbitrum::is_arbitrum_chain(self.protocol_chain_id()) {
3953            return None;
3954        }
3955
3956        let env_block = evm_env.block_env.number;
3957        Some(
3958            self.get_fork()
3959                .map_or_else(|| env_block.saturating_to(), |fork| fork.rpc_block_number(env_block)),
3960        )
3961    }
3962
3963    pub fn get_code_with_state(
3964        &self,
3965        state: &dyn DatabaseRef,
3966        address: Address,
3967    ) -> Result<Bytes, BlockchainError> {
3968        trace!(target: "backend", "get code for {:?}", address);
3969        let account = state.basic_ref(address)?.unwrap_or_default();
3970        if account.is_empty_code_hash() {
3971            // if the code hash is `KECCAK_EMPTY`, we check no further
3972            return Ok(Default::default());
3973        }
3974        let code = if let Some(code) = account.code {
3975            code
3976        } else {
3977            state.code_by_hash_ref(account.code_hash())?
3978        };
3979        Ok(code.original_bytes())
3980    }
3981
3982    pub fn get_balance_with_state<D>(
3983        &self,
3984        state: D,
3985        address: Address,
3986    ) -> Result<U256, BlockchainError>
3987    where
3988        D: DatabaseRef,
3989    {
3990        trace!(target: "backend", "get balance for {:?}", address);
3991        Ok(state.basic_ref(address)?.unwrap_or_default().balance)
3992    }
3993
3994    pub async fn transaction_by_block_number_and_index(
3995        &self,
3996        number: BlockNumber,
3997        index: Index,
3998    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
3999        if let Some(block) = self.mined_block_by_number(number) {
4000            return Ok(self.mined_transaction_by_block_hash_and_index(block.header.hash, index));
4001        }
4002
4003        if let Some(fork) = self.get_fork() {
4004            let number = self.convert_block_number(Some(number));
4005            if fork.predates_fork(number) {
4006                return Ok(fork
4007                    .transaction_by_block_number_and_index(number, index.into())
4008                    .await?);
4009            }
4010        }
4011
4012        Ok(None)
4013    }
4014
4015    pub async fn transaction_by_block_hash_and_index(
4016        &self,
4017        hash: B256,
4018        index: Index,
4019    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
4020        if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) {
4021            return Ok(tx);
4022        }
4023
4024        if let Some(fork) = self.get_fork() {
4025            return Ok(fork.transaction_by_block_hash_and_index(hash, index.into()).await?);
4026        }
4027
4028        Ok(None)
4029    }
4030
4031    pub fn mined_transaction_by_block_hash_and_index(
4032        &self,
4033        block_hash: B256,
4034        index: Index,
4035    ) -> Option<AnyRpcTransaction> {
4036        let (info, block, tx) = {
4037            let storage = self.blockchain.storage.read();
4038            let block = storage.blocks.get(&block_hash).cloned()?;
4039            let index: usize = index.into();
4040            let tx = block.body.transactions.get(index)?.clone();
4041            let info = storage.transactions.get(&tx.hash())?.info.clone();
4042            (info, block, tx)
4043        };
4044
4045        Some(transaction_build(
4046            Some(info.transaction_hash),
4047            tx,
4048            Some(&block),
4049            Some(info),
4050            block.header.base_fee_per_gas(),
4051        ))
4052    }
4053
4054    pub async fn transaction_by_hash(
4055        &self,
4056        hash: B256,
4057    ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
4058        trace!(target: "backend", "transaction_by_hash={:?}", hash);
4059        if let tx @ Some(_) = self.mined_transaction_by_hash(hash) {
4060            return Ok(tx);
4061        }
4062
4063        if let Some(fork) = self.get_fork() {
4064            return fork
4065                .transaction_by_hash(hash)
4066                .await
4067                .map_err(BlockchainError::AlloyForkProvider);
4068        }
4069
4070        Ok(None)
4071    }
4072
4073    pub fn mined_transaction_by_hash(&self, hash: B256) -> Option<AnyRpcTransaction> {
4074        let (info, block) = {
4075            let storage = self.blockchain.storage.read();
4076            let MinedTransaction { info, block_hash, .. } =
4077                storage.transactions.get(&hash)?.clone();
4078            let block = storage.blocks.get(&block_hash).cloned()?;
4079            (info, block)
4080        };
4081        let tx = block.body.transactions.get(info.transaction_index as usize)?.clone();
4082
4083        Some(transaction_build(
4084            Some(info.transaction_hash),
4085            tx,
4086            Some(&block),
4087            Some(info),
4088            block.header.base_fee_per_gas(),
4089        ))
4090    }
4091
4092    /// Returns the traces for the given transaction
4093    pub async fn trace_transaction(
4094        &self,
4095        hash: B256,
4096    ) -> Result<Option<Vec<LocalizedTransactionTrace>>, BlockchainError> {
4097        if let Some(traces) = self.mined_parity_trace_transaction(hash) {
4098            return Ok(Some(traces));
4099        }
4100
4101        if let Some(fork) = self.get_fork() {
4102            return Ok(fork.trace_transaction(hash).await?);
4103        }
4104
4105        Ok(None)
4106    }
4107
4108    /// Returns the transaction trace at the given trace address, where `[]` is the root call.
4109    pub async fn trace_get(
4110        &self,
4111        hash: B256,
4112        indices: Vec<Index>,
4113    ) -> Result<Option<LocalizedTransactionTrace>, BlockchainError> {
4114        if let Some(traces) = self.mined_parity_trace_transaction(hash) {
4115            let trace_address = indices.iter().copied().map(usize::from).collect::<Vec<_>>();
4116            return Ok(traces.into_iter().find(|trace| trace.trace.trace_address == trace_address));
4117        }
4118
4119        if let Some(fork) = self.get_fork() {
4120            return Ok(fork.trace_get(hash, indices).await?);
4121        }
4122
4123        Ok(None)
4124    }
4125
4126    /// Returns the traces for the given block
4127    pub async fn trace_block(
4128        &self,
4129        block: BlockNumber,
4130    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
4131        ensure_mined_trace_block(block)?;
4132        let number = self.convert_block_number(Some(block));
4133        if let Some(traces) = self.mined_parity_trace_block(number) {
4134            return Ok(traces);
4135        }
4136
4137        if let Some(fork) = self.get_fork()
4138            && fork.predates_fork_inclusive(number)
4139        {
4140            return Ok(fork.trace_block(number).await?);
4141        }
4142
4143        Err(BlockchainError::BlockNotFound)
4144    }
4145
4146    /// Executes a transaction call and returns requested parity trace results.
4147    pub async fn trace_call(
4148        &self,
4149        request: WithOtherFields<TransactionRequest>,
4150        fee_details: FeeDetails,
4151        trace_types: HashSet<TraceType>,
4152        block_request: BlockRequest<FoundryTxEnvelope>,
4153        block_id: BlockId,
4154    ) -> Result<TraceResults, BlockchainError>
4155    where
4156        Self: TransactionValidator<FoundryTxEnvelope>,
4157        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
4158    {
4159        if let BlockRequest::Number(number) = &block_request
4160            && let Some(fork) = self.get_fork()
4161            && fork.predates_fork(*number)
4162        {
4163            // Delegate the resolved block number so tags (`latest`/`pending`/`safe`/
4164            // `finalized`) are resolved against the fork's head instead of drifting with
4165            // the upstream chain. Hashes are forwarded unchanged.
4166            let resolved = match block_id {
4167                BlockId::Hash(_) => block_id,
4168                _ => BlockId::number(*number),
4169            };
4170            return Ok(fork.trace_call(request, trace_types, resolved).await?);
4171        }
4172
4173        self.with_database_at_and_context(Some(block_request), |state, block, mut monad_context| {
4174            let cache_db = CacheDB::new(state);
4175            let mut inspector =
4176                TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
4177            let PreparedCall { evm_env, tx_env, .. } =
4178                self.prepare_call_env(&cache_db, request, fee_details, block)?;
4179            let result = self.transact_call_with_inspector_ref(
4180                &cache_db,
4181                &evm_env,
4182                &mut inspector,
4183                tx_env,
4184                monad_context.as_mut().map(next_monad_context),
4185            )?;
4186
4187            parity_trace_results(inspector, &result, &trace_types, &cache_db)
4188        })
4189        .await
4190    }
4191
4192    /// Replays all transactions in a block and returns the requested traces for each transaction,
4193    /// or `None` if the block is unknown.
4194    pub async fn trace_replay_block_transactions(
4195        &self,
4196        block: BlockNumber,
4197        trace_types: HashSet<TraceType>,
4198    ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, BlockchainError> {
4199        ensure_mined_trace_block(block)?;
4200        let block_number = self.convert_block_number(Some(block));
4201
4202        // Try mined blocks first
4203        if let Some(results) =
4204            self.mined_parity_trace_replay_block_transactions(block_number, &trace_types)?
4205        {
4206            return Ok(Some(results));
4207        }
4208
4209        // Fallback to fork if block predates fork
4210        if let Some(fork) = self.get_fork()
4211            && fork.predates_fork_inclusive(block_number)
4212        {
4213            return Ok(Some(
4214                fork.trace_replay_block_transactions(block_number, trace_types).await?,
4215            ));
4216        }
4217
4218        Ok(None)
4219    }
4220
4221    /// Replays a mined transaction and returns the requested traces, or `None` if the transaction
4222    /// is unknown.
4223    pub async fn trace_replay_transaction(
4224        &self,
4225        hash: B256,
4226        trace_types: HashSet<TraceType>,
4227    ) -> Result<Option<TraceResultsWithTransactionHash>, BlockchainError> {
4228        let mined = self.blockchain.storage.read().transactions.contains_key(&hash);
4229
4230        // If the transaction was mined locally, replay it locally. Do not fall
4231        // through to the fork when the local replay fails; that would misreport
4232        // a local data problem as an upstream transaction lookup.
4233        let full_trace = if mined {
4234            let inspector =
4235                TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
4236            self.replay_tx_with_inspector(hash, inspector, |result, cache_db, inspector, _, _| {
4237                parity_trace_results(inspector, &result, &trace_types, &cache_db)
4238            })??
4239        } else if let Some(fork) = self.get_fork() {
4240            // Not known locally: forward to the fork if present.
4241            let Some(full_trace) = fork.trace_replay_transaction(hash, trace_types).await? else {
4242                return Ok(None);
4243            };
4244            full_trace
4245        } else {
4246            return Ok(None);
4247        };
4248
4249        Ok(Some(TraceResultsWithTransactionHash { transaction_hash: hash, full_trace }))
4250    }
4251
4252    /// Traces a raw transaction without committing it to the chain state or mempool.
4253    pub async fn trace_raw_transaction(
4254        &self,
4255        pending_transaction: PendingTransaction<FoundryTxEnvelope>,
4256        trace_types: HashSet<TraceType>,
4257        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4258    ) -> Result<TraceResults, BlockchainError>
4259    where
4260        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
4261    {
4262        let trace_config = TracingInspectorConfig::from_parity_config(&trace_types);
4263
4264        self.with_database_at_and_context(block_request, |state, block_env, mut monad_context| {
4265            let cache_db = CacheDB::new(state);
4266            let mut evm_env = self.evm_env.read().clone();
4267            evm_env.block_env = block_env;
4268            // A signed transaction cannot be impersonated, so reject senders with code that is not
4269            // an EIP-7702 delegation, as block execution would.
4270            evm_env.cfg_env.disable_eip3607 = false;
4271
4272            let mut inspector = TracingInspector::new(trace_config);
4273            let (result, _) = self.transact_envelope_with_inspector_ref_and_context(
4274                &cache_db,
4275                &evm_env,
4276                &mut inspector,
4277                &pending_transaction,
4278                monad_context.as_mut().map(next_monad_context),
4279            )?;
4280
4281            parity_trace_results(inspector, &result, &trace_types, &cache_db)
4282        })
4283        .await
4284    }
4285
4286    /// Traces calls sequentially against a shared in-memory state.
4287    pub async fn trace_call_many(
4288        &self,
4289        calls: Vec<(WithOtherFields<TransactionRequest>, HashSet<TraceType>)>,
4290        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
4291    ) -> Result<Vec<TraceResults>, BlockchainError>
4292    where
4293        N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
4294    {
4295        self.with_database_at_and_context(block_request, |state, block_env, mut monad_context| {
4296            let mut cache_db = CacheDB::new(state);
4297            let mut results = Vec::with_capacity(calls.len());
4298            let mut calls = calls.into_iter().peekable();
4299
4300            while let Some((request, trace_types)) = calls.next() {
4301                let fee_details = FeeDetails::new(
4302                    request.gas_price,
4303                    request.max_fee_per_gas,
4304                    request.max_priority_fee_per_gas,
4305                    request.max_fee_per_blob_gas,
4306                )?
4307                .or_zero_fees();
4308                let PreparedCall { evm_env, mut tx_env, simulated_tempo_tx, .. } =
4309                    self.prepare_call_env(&cache_db, request, fee_details, block_env.clone())?;
4310                apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
4311
4312                let trace_config = TracingInspectorConfig::from_parity_config(&trace_types);
4313                let mut inspector = TracingInspector::new(trace_config);
4314                let result = self.transact_call_with_inspector_ref(
4315                    &cache_db,
4316                    &evm_env,
4317                    &mut inspector,
4318                    tx_env,
4319                    monad_context.as_mut().map(next_monad_context),
4320                )?;
4321
4322                let trace_result =
4323                    parity_trace_results(inspector, &result, &trace_types, &cache_db)?;
4324                results.push(trace_result);
4325
4326                if calls.peek().is_some() {
4327                    cache_db.commit(result.state);
4328                }
4329            }
4330
4331            Ok(results)
4332        })
4333        .await
4334    }
4335
4336    /// Returns the trace results for all transactions in a mined block by replaying them
4337    fn mined_parity_trace_replay_block_transactions(
4338        &self,
4339        block_number: u64,
4340        trace_types: &HashSet<TraceType>,
4341    ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, BlockchainError> {
4342        let Some(block) = self.get_block(block_number) else { return Ok(None) };
4343
4344        // Blocks without transactions, such as genesis, have nothing to replay.
4345        if block.body.transactions.is_empty() {
4346            return Ok(Some(Vec::new()));
4347        }
4348
4349        // Execute this in the context of the parent state
4350        let parent_hash = block.header.parent_hash;
4351        let trace_config = TracingInspectorConfig::from_parity_config(trace_types);
4352
4353        let read_guard = self.states.upgradable_read();
4354        if let Some(state) = read_guard.get_state(&parent_hash) {
4355            self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types)
4356                .map(Some)
4357        } else {
4358            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
4359            let Some(state) = write_guard.get_on_disk_state(&parent_hash) else {
4360                return Err(BlockchainError::HistoricalStateUnavailable(block.header.number()));
4361            };
4362            self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types)
4363                .map(Some)
4364        }
4365    }
4366
4367    /// Replays all transactions in a block with the tracing inspector to generate TraceResults
4368    fn replay_block_transactions_with_inspector(
4369        &self,
4370        block: &Block,
4371        parent_state: &StateDb,
4372        trace_config: TracingInspectorConfig,
4373        trace_types: &HashSet<TraceType>,
4374    ) -> Result<Vec<TraceResultsWithTransactionHash>, BlockchainError> {
4375        let (mut cache_db, evm_env, hardfork) = self.prepare_block_replay(block, parent_state)?;
4376        let mut results = Vec::new();
4377        let monad_context = self.active_monad_context_for_mined_block(block)?;
4378
4379        // Execute each transaction in the block with tracing
4380        for tx_envelope in &block.body.transactions {
4381            let tx_hash = tx_envelope.hash();
4382
4383            // Create a fresh inspector for this transaction
4384            let mut inspector = TracingInspector::new(trace_config);
4385
4386            // Prepare transaction environment and execute
4387            let pending_tx = self.pending_mined_transaction(tx_envelope.clone())?;
4388            let transaction_context =
4389                monad_execution_context_at(monad_context.as_ref(), results.len());
4390            let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
4391                &cache_db,
4392                &evm_env,
4393                &mut inspector,
4394                &pending_tx,
4395                EnvelopeExecution::replay(transaction_context, hardfork),
4396            )?;
4397
4398            // Build TraceResults from the inspector and execution result
4399            let full_trace = parity_trace_results(inspector, &result, trace_types, &cache_db)?;
4400
4401            results.push(TraceResultsWithTransactionHash { transaction_hash: tx_hash, full_trace });
4402
4403            // Commit the state changes for the next transaction
4404            cache_db.commit(result.state);
4405        }
4406
4407        Ok(results)
4408    }
4409
4410    fn replay_tx_with_inspector<I, F, T>(
4411        &self,
4412        hash: B256,
4413        mut inspector: I,
4414        f: F,
4415    ) -> Result<T, BlockchainError>
4416    where
4417        for<'a> I: BackendInspector<WrapDatabaseRef<&'a CacheDB<Box<&'a StateDb>>>> + 'a,
4418        for<'a> F:
4419            FnOnce(ResultAndState<HaltReason>, CacheDB<Box<&'a StateDb>>, I, TxEnv, EvmEnv) -> T,
4420    {
4421        let block = {
4422            let storage = self.blockchain.storage.read();
4423            let MinedTransaction { block_hash, .. } =
4424                storage.transactions.get(&hash).ok_or(BlockchainError::TransactionNotFound)?;
4425
4426            storage.blocks.get(block_hash).cloned().ok_or(BlockchainError::BlockNotFound)?
4427        };
4428
4429        let index = block
4430            .body
4431            .transactions
4432            .iter()
4433            .position(|tx| tx.hash() == hash)
4434            .expect("transaction not found in block");
4435
4436        let trace = |parent_state: &StateDb| -> Result<T, BlockchainError> {
4437            let (mut cache_db, evm_env, hardfork) =
4438                self.prepare_block_replay_with_db(&block, Box::new(parent_state))?;
4439            self.replay_mined_transaction_prefix(&mut cache_db, &evm_env, hardfork, &block, index)?;
4440
4441            let target_tx = block.body.transactions[index].clone();
4442            let target_tx = self.pending_mined_transaction(target_tx)?;
4443            let monad_context = self.active_monad_context_for_mined_block(&block)?;
4444            let transaction_context = monad_execution_context_at(monad_context.as_ref(), index);
4445            let (result, base_tx_env) = self.replay_envelope_with_inspector_ref_and_context(
4446                &cache_db,
4447                &evm_env,
4448                &mut inspector,
4449                &target_tx,
4450                EnvelopeExecution::replay(transaction_context, hardfork),
4451            )?;
4452
4453            Ok(f(result, cache_db, inspector, base_tx_env, evm_env))
4454        };
4455
4456        let read_guard = self.states.upgradable_read();
4457        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
4458            trace(state)
4459        } else {
4460            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
4461            let state = write_guard
4462                .get_on_disk_state(&block.header.parent_hash)
4463                .ok_or(BlockchainError::HistoricalStateUnavailable(block.header.number()))?;
4464            trace(state)
4465        }
4466    }
4467
4468    // Returns the traces matching a given filter
4469    pub async fn trace_filter(
4470        &self,
4471        filter: TraceFilter,
4472    ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
4473        let matcher = filter.matcher();
4474        let best_number = self.best_number();
4475        let start = filter.from_block.unwrap_or(best_number);
4476        let end = filter.to_block.unwrap_or(best_number);
4477
4478        if start > best_number || end > best_number {
4479            return Err(BlockchainError::BlockNotFound);
4480        }
4481        if start > end {
4482            return Err(BlockchainError::RpcError(RpcError::invalid_params(
4483                "invalid block range, ensure that to block is greater than from block".to_string(),
4484            )));
4485        }
4486
4487        let dist = end - start;
4488        if dist > 300 {
4489            return Err(BlockchainError::RpcError(RpcError::invalid_params(
4490                "block range too large, currently limited to 300".to_string(),
4491            )));
4492        }
4493
4494        // Accumulate tasks for block range
4495        let mut trace_tasks = vec![];
4496        for num in start..=end {
4497            trace_tasks.push(self.trace_block(num.into()));
4498        }
4499
4500        // Execute tasks and filter traces
4501        let traces = futures::future::try_join_all(trace_tasks).await?;
4502        let filtered_traces =
4503            traces.into_iter().flatten().filter(|trace| matcher.matches(&trace.trace));
4504
4505        // Apply after and count
4506        let filtered_traces: Vec<_> = if let Some(after) = filter.after {
4507            filtered_traces.skip(after as usize).collect()
4508        } else {
4509            filtered_traces.collect()
4510        };
4511
4512        let filtered_traces: Vec<_> = if let Some(count) = filter.count {
4513            filtered_traces.into_iter().take(count as usize).collect()
4514        } else {
4515            filtered_traces
4516        };
4517
4518        Ok(filtered_traces)
4519    }
4520
4521    pub fn get_blobs_by_block_id(
4522        &self,
4523        id: impl Into<BlockId>,
4524        versioned_hashes: Vec<B256>,
4525    ) -> Result<Option<Vec<alloy_consensus::Blob>>> {
4526        Ok(self.get_block(id).map(|block| {
4527            block
4528                .body
4529                .transactions
4530                .iter()
4531                .filter_map(|tx| tx.as_ref().sidecar())
4532                .flat_map(|sidecar| {
4533                    sidecar.sidecar.blobs().iter().zip(sidecar.sidecar.commitments().iter())
4534                })
4535                .filter(|(_, commitment)| {
4536                    // Filter blobs by versioned_hashes if provided
4537                    versioned_hashes.is_empty()
4538                        || versioned_hashes.contains(&kzg_to_versioned_hash(commitment.as_slice()))
4539                })
4540                .map(|(blob, _)| *blob)
4541                .collect()
4542        }))
4543    }
4544
4545    #[allow(clippy::large_stack_frames)]
4546    pub fn get_blob_by_versioned_hash(&self, hash: B256) -> Result<Option<Blob>> {
4547        let storage = self.blockchain.storage.read();
4548        for block in storage.blocks.values() {
4549            for tx in &block.body.transactions {
4550                let typed_tx = tx.as_ref();
4551                if let Some(sidecar) = typed_tx.sidecar() {
4552                    for versioned_hash in sidecar.sidecar.versioned_hashes() {
4553                        if versioned_hash == hash
4554                            && let Some(index) =
4555                                sidecar.sidecar.commitments().iter().position(|commitment| {
4556                                    kzg_to_versioned_hash(commitment.as_slice()) == *hash
4557                                })
4558                            && let Some(blob) = sidecar.sidecar.blobs().get(index)
4559                        {
4560                            return Ok(Some(*blob));
4561                        }
4562                    }
4563                }
4564            }
4565        }
4566        Ok(None)
4567    }
4568
4569    /// Initialises the balance of the given accounts
4570    #[expect(clippy::too_many_arguments)]
4571    pub async fn with_genesis(
4572        db: Arc<AsyncRwLock<Box<dyn Db>>>,
4573        env: Arc<RwLock<EvmEnv>>,
4574        networks: NetworkConfigs,
4575        genesis: GenesisConfig,
4576        fees: FeeManager,
4577        fork: Arc<RwLock<Option<ClientFork>>>,
4578        enable_steps_tracing: bool,
4579        print_logs: bool,
4580        print_traces: bool,
4581        call_trace_decoder: Arc<CallTraceDecoder>,
4582        prune_state_history_config: PruneStateHistoryConfig,
4583        max_persisted_states: Option<usize>,
4584        transaction_block_keeper: Option<usize>,
4585        automine_block_time: Option<Duration>,
4586        cache_path: Option<PathBuf>,
4587        node_config: Arc<AsyncRwLock<NodeConfig>>,
4588    ) -> Result<Self> {
4589        let last_fork_cache_source = fork.read().as_ref().and_then(ForkCacheSource::from_fork);
4590        // if this is a fork then adjust the blockchain storage
4591        let blockchain = if let Some(fork) = fork.read().as_ref() {
4592            trace!(target: "backend", "using forked blockchain at {}", fork.block_number());
4593            Blockchain::forked(fork.block_number(), fork.block_hash(), fork.total_difficulty())
4594        } else {
4595            let header = if let Some(genesis) = genesis.genesis_init.as_ref() {
4596                genesis_json_header(genesis)
4597            } else {
4598                genesis_header(
4599                    &env.read(),
4600                    fees.is_eip1559().then(|| fees.base_fee()),
4601                    genesis.timestamp,
4602                    genesis.number,
4603                )
4604            };
4605            Blockchain::new(foundry_header(&networks, header))
4606        };
4607
4608        // Sync EVM block.number with genesis for non-fork mode.
4609        // Fork mode syncs in setup_fork_db_config() instead.
4610        if fork.read().is_none() {
4611            env.write().block_env.number = U256::from(genesis.number);
4612
4613            // The genesis block keeps its base fee, but the next block must already follow Tempo's
4614            // rules (e.g. T7 clamps the seed down to the cap). Fork mode seeds this from the fork
4615            // block instead.
4616            if fees.tempo_hardfork().is_some() {
4617                let env = env.read();
4618                let next_base_fee = fees.get_next_block_base_fee_per_gas(
4619                    0,
4620                    env.block_env.gas_limit,
4621                    env.block_env.basefee,
4622                );
4623                drop(env);
4624                fees.set_base_fee(next_base_fee);
4625            }
4626        }
4627
4628        let start_timestamp = if let Some(fork) = fork.read().as_ref() {
4629            fork.timestamp()
4630        } else {
4631            genesis.timestamp
4632        };
4633
4634        let mut states = if prune_state_history_config.is_config_enabled() {
4635            // if prune state history is enabled, configure the state cache only for memory
4636            prune_state_history_config
4637                .max_memory_history
4638                .map(|limit| InMemoryBlockStates::new(limit, 0))
4639                .unwrap_or_default()
4640                .memory_only()
4641        } else if max_persisted_states.is_some() {
4642            max_persisted_states
4643                .map(|limit| InMemoryBlockStates::new(DEFAULT_HISTORY_LIMIT, limit))
4644                .unwrap_or_default()
4645        } else {
4646            Default::default()
4647        };
4648
4649        if let Some(cache_path) = cache_path {
4650            states = states.disk_path(cache_path);
4651        }
4652
4653        let (slots_in_an_epoch, precompile_factory, disable_pool_balance_checks, hardfork) = {
4654            let cfg = node_config.read().await;
4655            (
4656                cfg.slots_in_an_epoch,
4657                cfg.precompile_factory.clone(),
4658                cfg.disable_pool_balance_checks,
4659                cfg.get_hardfork(),
4660            )
4661        };
4662        let startup_cache_lease = if fork.read().is_some() {
4663            let db = db.read().await;
4664            let inner = db
4665                .maybe_inner()
4666                .map_err(|err| eyre::eyre!("fork database is missing its cache backend: {err}"))?;
4667            StagedForkCacheLease::for_db(inner)
4668        } else {
4669            StagedForkCacheLease::default()
4670        };
4671        let startup_fork_cache_user =
4672            StagedForkDbUser { db: Some(Arc::clone(&db)), cache_lease: startup_cache_lease };
4673        #[cfg(feature = "base")]
4674        let base_activation_admin = node_config.read().await.base_activation_admin;
4675
4676        let backend = Self {
4677            db,
4678            blockchain,
4679            states: Arc::new(RwLock::new(states)),
4680            evm_env: env,
4681            networks,
4682            #[cfg(feature = "base")]
4683            base_activation_admin,
4684            hardfork: Arc::new(RwLock::new(hardfork)),
4685            fork,
4686            last_fork_cache_source: Arc::new(RwLock::new(last_fork_cache_source)),
4687            time: TimeManager::new(start_timestamp),
4688            cheats: Default::default(),
4689            new_block_listeners: Default::default(),
4690            fees,
4691            genesis,
4692            active_state_snapshots: Arc::new(Mutex::new(Default::default())),
4693            enable_steps_tracing,
4694            print_logs,
4695            print_traces,
4696            call_trace_decoder: Arc::new(RwLock::new(call_trace_decoder)),
4697            prune_state_history_config,
4698            transaction_block_keeper,
4699            node_config,
4700            slots_in_an_epoch,
4701            precompile_factory,
4702            mining: Arc::new(tokio::sync::Mutex::new(())),
4703            #[cfg(test)]
4704            mining_commit_hook: Default::default(),
4705            disable_pool_balance_checks,
4706            startup_fork_cache_user,
4707        };
4708
4709        #[cfg(feature = "monad")]
4710        let monad_fork =
4711            if backend.networks.is_monad() { backend.fork.read().clone() } else { None };
4712        #[cfg(feature = "monad")]
4713        if let Some(fork) = monad_fork {
4714            monad::cache_fork_context(&fork).await?;
4715        }
4716
4717        if let Some(interval_block_time) = automine_block_time {
4718            backend.update_interval_mine_block_time(interval_block_time);
4719        }
4720
4721        // Note: this can only fail in forking mode, in which case we can't recover
4722        backend.apply_genesis().await.wrap_err("failed to create genesis")?;
4723        Ok(backend)
4724    }
4725
4726    /// Applies the configured genesis settings
4727    ///
4728    /// This will fund, create the genesis accounts
4729    async fn apply_genesis(&self) -> Result<(), DatabaseError> {
4730        trace!(target: "backend", "setting genesis balances");
4731
4732        if self.fork.read().is_some() {
4733            let parent_env = self.evm_env.read().clone();
4734            return self
4735                .apply_fork_genesis(
4736                    Arc::clone(&self.db),
4737                    self.startup_fork_cache_user.cache_lease.clone(),
4738                    &parent_env,
4739                    self.best_hash(),
4740                    self.hardfork(),
4741                )
4742                .await;
4743        }
4744
4745        let mut db = self.db.write().await;
4746        for (account, info) in self.genesis.account_infos() {
4747            db.insert_account(account, info);
4748        }
4749
4750        // insert the new genesis hash to the database so it's available for the next block in
4751        // the evm
4752        db.insert_block_hash(U256::from(self.best_number()), self.best_hash());
4753
4754        if let Some(transitions) =
4755            self.ethereum_block_transitions(self.hardfork(), None, BlockExecutionKind::Complete)
4756        {
4757            if transitions.hardfork >= EthereumHardfork::Cancun {
4758                db.set_code(eip4788::BEACON_ROOTS_ADDRESS, eip4788::BEACON_ROOTS_CODE.clone())?;
4759            }
4760            if transitions.hardfork >= EthereumHardfork::Prague {
4761                db.set_code(
4762                    eip2935::HISTORY_STORAGE_ADDRESS,
4763                    eip2935::HISTORY_STORAGE_CODE.clone(),
4764                )?;
4765                db.set_code(
4766                    eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
4767                    eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone(),
4768                )?;
4769                db.set_code(
4770                    eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
4771                    eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_CODE.clone(),
4772                )?;
4773            }
4774        }
4775        // apply the genesis.json alloc
4776        self.genesis.apply_genesis_json_alloc(&mut **db)?;
4777        drop(db);
4778        self.apply_funded_accounts(&self.db).await?;
4779
4780        // Seed Base protocol accounts and one-time state transitions for standalone nodes.
4781        #[cfg(feature = "base")]
4782        if self.is_base() && !self.is_fork() {
4783            let upgrade = self.base_upgrade();
4784            Self::apply_base_genesis(&mut **self.db.write().await, upgrade)?;
4785        }
4786
4787        // Initialize Tempo precompiles and fee tokens when in Tempo mode (not in fork mode).
4788        // In fork mode, precompiles are inherited from the forked origin.
4789        if self.networks.is_tempo() && !self.is_fork() {
4790            let chain_id = self.evm_env.read().cfg_env.chain_id;
4791            let timestamp = self.genesis.timestamp;
4792            let test_accounts: Vec<Address> = self.genesis.accounts.clone();
4793            let hardfork = self.tempo_hardfork();
4794            let mut db = self.db.write().await;
4795            crate::eth::backend::tempo::initialize_tempo_precompiles(
4796                &mut **db,
4797                chain_id,
4798                timestamp,
4799                &test_accounts,
4800                hardfork,
4801            )
4802            .map_err(|e| {
4803                tracing::error!(target: "backend", "failed to initialize Tempo precompiles: {e}");
4804                DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))
4805            })?;
4806            trace!(target: "backend", "initialized Tempo precompiles and fee tokens for {} accounts", test_accounts.len());
4807        }
4808
4809        trace!(target: "backend", "set genesis balances");
4810
4811        Ok(())
4812    }
4813
4814    /// Applies genesis allocations to a fork database before it becomes live.
4815    async fn apply_fork_genesis(
4816        &self,
4817        db: Arc<AsyncRwLock<Box<dyn Db>>>,
4818        cache_lease: StagedForkCacheLease,
4819        parent_env: &EvmEnv,
4820        parent_hash: B256,
4821        hardfork: FoundryHardfork,
4822    ) -> Result<(), DatabaseError> {
4823        let user = StagedForkDbUser { db: Some(db), cache_lease };
4824        let mut genesis_accounts = JoinSet::new();
4825        for address in self.genesis.accounts.iter().copied() {
4826            let task_user = StagedForkDbUser {
4827                db: Some(Arc::clone(user.db())),
4828                cache_lease: user.cache_lease.clone(),
4829            };
4830
4831            // The fork database can fetch independent accounts concurrently.
4832            genesis_accounts.spawn(async move {
4833                let db = task_user.db().read().await;
4834                let info = db.basic_ref(address)?.unwrap_or_default();
4835                Ok::<_, DatabaseError>((address, info))
4836            });
4837        }
4838
4839        let mut account_infos = Vec::with_capacity(self.genesis.accounts.len());
4840        while let Some(result) = genesis_accounts.join_next().await {
4841            match result {
4842                Ok(Ok(account)) => account_infos.push(account),
4843                Ok(Err(err)) => {
4844                    genesis_accounts.shutdown().await;
4845                    return Err(err);
4846                }
4847                Err(err) => {
4848                    genesis_accounts.shutdown().await;
4849                    return Err(DatabaseError::AnyRequest(Arc::new(eyre::eyre!(
4850                        "fork genesis account task failed: {err}"
4851                    ))));
4852                }
4853            }
4854        }
4855        let mut db_guard = user.db().write().await;
4856        for (address, mut info) in account_infos {
4857            info.balance = self.genesis.balance;
4858            db_guard.insert_account(address, info);
4859        }
4860        self.genesis.apply_genesis_json_alloc(&mut **db_guard)?;
4861        // A fork taken before the remote chain activated Denim has no BaseTime deployment, but
4862        // Denim blocks mined locally still send the BaseTime deposit. Existing deployments and
4863        // their admin are preserved.
4864        #[cfg(feature = "base")]
4865        if matches!(hardfork, FoundryHardfork::Base(upgrade) if upgrade >= BaseUpgrade::Denim) {
4866            ensure_base_time_predeploy(&mut **db_guard, false)?;
4867            self.ensure_fork_accepts_system_transactions(
4868                &**db_guard,
4869                parent_env,
4870                parent_hash,
4871                hardfork,
4872            )?;
4873        }
4874        #[cfg(not(feature = "base"))]
4875        let _ = (parent_env, parent_hash, hardfork);
4876        drop(db_guard);
4877        self.apply_funded_accounts(user.db()).await
4878    }
4879
4880    /// Applies explicit `--fund` balances while preserving account metadata inherited from a fork.
4881    async fn apply_funded_accounts(
4882        &self,
4883        db: &Arc<AsyncRwLock<Box<dyn Db>>>,
4884    ) -> Result<(), DatabaseError> {
4885        let funded_accounts = self.node_config.read().await.funded_accounts.clone();
4886        let mut accounts = Vec::with_capacity(funded_accounts.len());
4887        for (address, balance) in funded_accounts {
4888            let mut info = db.read().await.basic_ref(address)?.unwrap_or_default();
4889            info.balance = balance;
4890            accounts.push((address, info));
4891        }
4892        let mut db = db.write().await;
4893        for (address, info) in accounts {
4894            db.insert_account(address, info);
4895        }
4896        Ok(())
4897    }
4898
4899    /// Seeds Base protocol accounts and one-time state transitions for a standalone node.
4900    #[cfg(feature = "base")]
4901    fn apply_base_genesis(mut db: &mut dyn Db, upgrade: BaseUpgrade) -> Result<(), DatabaseError> {
4902        for address in [
4903            Predeploys::L1_BLOCK_INFO,
4904            Predeploys::SEQUENCER_FEE_VAULT,
4905            Predeploys::BASE_FEE_VAULT,
4906            Predeploys::L1_FEE_VAULT,
4907            Predeploys::OPERATOR_FEE_VAULT,
4908            Predeploys::BASE_TIME,
4909        ] {
4910            if db.basic(address)?.is_none() {
4911                db.insert_account(address, AccountInfo::default());
4912            }
4913        }
4914
4915        db.set_storage_at(
4916            Predeploys::L1_BLOCK_INFO,
4917            L1BlockInfo::L1_BASE_FEE_SLOT.into(),
4918            U256::from(DEFAULT_BASE_L1_BASE_FEE).into(),
4919        )?;
4920        db.set_storage_at(
4921            Predeploys::L1_BLOCK_INFO,
4922            L1BlockInfo::ECOTONE_L1_BLOB_BASE_FEE_SLOT.into(),
4923            B256::with_last_byte(1),
4924        )?;
4925        let mut l1_fee_scalars = [0u8; 32];
4926        l1_fee_scalars
4927            [L1BlockInfo::BASE_FEE_SCALAR_OFFSET..L1BlockInfo::BASE_FEE_SCALAR_OFFSET + 4]
4928            .copy_from_slice(&DEFAULT_BASE_L1_FEE_SCALAR.to_be_bytes());
4929        db.set_storage_at(
4930            Predeploys::L1_BLOCK_INFO,
4931            L1BlockInfo::ECOTONE_L1_FEE_SCALARS_SLOT.into(),
4932            B256::from(l1_fee_scalars),
4933        )?;
4934
4935        if upgrade >= BaseUpgrade::Canyon {
4936            let upgrades = ChainUpgrades::new([(BaseUpgrade::Canyon, ForkCondition::Timestamp(1))]);
4937            ensure_create2_deployer(upgrades, 1, &mut db)?;
4938        }
4939        if upgrade >= BaseUpgrade::Denim {
4940            ensure_base_time_predeploy(&mut *db, true)?;
4941        }
4942        if upgrade >= BaseUpgrade::Zenith {
4943            // Zenith is genesis-only and is not stored by ChainUpgrades.
4944            let mut upgrades = RollupConfig::default();
4945            upgrades.set_upgrade_activation_timestamp(BaseUpgrade::Zenith, 0);
4946            ensure_eip8130_system_accounts(upgrades, 1, &mut db)?;
4947        }
4948
4949        // Give the installed Base precompiles a sentinel byte so Solidity's `extcodesize`
4950        // check on high-level calls to functions without return data does not revert in the
4951        // caller. `ensure_eip8130_system_accounts` only covers the Zenith nonce manager.
4952        for address in base_code_sentinel_addresses(upgrade) {
4953            let mut account = db.basic(address)?.unwrap_or_default();
4954            if account.code.as_ref().is_none_or(|code| code.is_empty()) {
4955                let code =
4956                    revm::state::Bytecode::new_legacy(Bytes::from_static(SYSTEM_PRECOMPILE_STUB));
4957                account.set_code(code);
4958                db.insert_account(address, account);
4959            }
4960        }
4961
4962        Ok(())
4963    }
4964
4965    /// Populates a detached in-memory database from explicit reset inputs.
4966    #[allow(clippy::too_many_arguments)]
4967    fn populate_memory_db(
4968        db: &mut dyn Db,
4969        genesis: &GenesisConfig,
4970        funded_accounts: &HashMap<Address, U256>,
4971        hardfork: FoundryHardfork,
4972        chain_id: u64,
4973        is_tempo: bool,
4974        tempo_hardfork: Option<TempoHardfork>,
4975        genesis_hash: B256,
4976        install_create2_deployer: bool,
4977    ) -> Result<(), DatabaseError> {
4978        for (account, info) in genesis.account_infos() {
4979            db.insert_account(account, info);
4980        }
4981        db.insert_block_hash(U256::from(genesis.number), genesis_hash);
4982
4983        if let FoundryHardfork::Ethereum(hardfork) = hardfork {
4984            if hardfork >= EthereumHardfork::Cancun {
4985                db.set_code(eip4788::BEACON_ROOTS_ADDRESS, eip4788::BEACON_ROOTS_CODE.clone())?;
4986            }
4987            if hardfork >= EthereumHardfork::Prague {
4988                db.set_code(
4989                    eip2935::HISTORY_STORAGE_ADDRESS,
4990                    eip2935::HISTORY_STORAGE_CODE.clone(),
4991                )?;
4992                db.set_code(
4993                    eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
4994                    eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone(),
4995                )?;
4996                db.set_code(
4997                    eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
4998                    eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_CODE.clone(),
4999                )?;
5000            }
5001        }
5002
5003        genesis.apply_genesis_json_alloc(db)?;
5004        for (&address, &balance) in funded_accounts {
5005            let mut info = db.basic_ref(address)?.unwrap_or_default();
5006            info.balance = balance;
5007            db.insert_account(address, info);
5008        }
5009
5010        if is_tempo {
5011            let hardfork = tempo_hardfork.ok_or_else(|| {
5012                DatabaseError::AnyRequest(Arc::new(eyre::eyre!(
5013                    "missing Tempo hardfork during memory reset"
5014                )))
5015            })?;
5016            crate::eth::backend::tempo::initialize_tempo_precompiles(
5017                db,
5018                chain_id,
5019                genesis.timestamp,
5020                &genesis.accounts,
5021                hardfork,
5022            )
5023            .map_err(|err| DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{err}"))))?;
5024        }
5025
5026        if install_create2_deployer {
5027            db.set_code(
5028                DEFAULT_CREATE2_DEPLOYER,
5029                Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE),
5030            )?;
5031        }
5032
5033        Ok(())
5034    }
5035
5036    /// Prepares a fresh fork without mutating the live backend.
5037    pub(crate) async fn prepare_fork_reset(
5038        &self,
5039        forking: Forking,
5040        serving_instance_id: B256,
5041    ) -> Result<StagedForkReset, BlockchainError> {
5042        let previous_fork = self.get_fork();
5043        let previous_source = self
5044            .last_fork_cache_source
5045            .read()
5046            .clone()
5047            .or_else(|| previous_fork.as_ref().and_then(ForkCacheSource::from_fork));
5048        let configured_rpc_urls = self.node_config.read().await.fork_urls.clone();
5049        let rpc_url_was_provided = forking.json_rpc_url.is_some();
5050        let target_rpc_urls = if let Some(url) = forking.json_rpc_url {
5051            vec![url]
5052        } else if !configured_rpc_urls.is_empty() {
5053            configured_rpc_urls
5054        } else {
5055            previous_fork
5056                .as_ref()
5057                .map(|fork| fork.config.read().fork_urls.clone())
5058                .filter(|urls| !urls.is_empty())
5059                .ok_or_else(|| {
5060                    RpcError::invalid_params(
5061                        "Forking not enabled and RPC URL not provided to start forking",
5062                    )
5063                })?
5064        };
5065        let flush_old_cache = previous_fork.is_some();
5066        if flush_old_cache {
5067            // Staging opens a separate BlockchainDb from disk. Persist the live remote cache first
5068            // so an unchanged source and block can reuse it without copying locally modified state.
5069            self.db.write().await.maybe_flush_cache().map_err(BlockchainError::Internal)?;
5070        }
5071
5072        for _ in 0..3 {
5073            if let Some(staged) = self
5074                .stage_fork_reset(
5075                    &target_rpc_urls,
5076                    forking.block_number,
5077                    serving_instance_id,
5078                    previous_source.clone(),
5079                    flush_old_cache,
5080                    rpc_url_was_provided,
5081                )
5082                .await?
5083            {
5084                return Ok(staged);
5085            }
5086        }
5087        Err(BlockchainError::Internal(
5088            "fork endpoint changed while the replacement was being staged".to_string(),
5089        ))
5090    }
5091
5092    /// Builds and validates one complete fork replacement without mutating the live backend.
5093    async fn stage_fork_reset(
5094        &self,
5095        target_rpc_urls: &[String],
5096        block_number: Option<u64>,
5097        serving_instance_id: B256,
5098        previous_source: Option<ForkCacheSource>,
5099        flush_old_cache: bool,
5100        rpc_url_was_provided: bool,
5101    ) -> Result<Option<StagedForkReset>, BlockchainError> {
5102        let target_rpc_url = target_rpc_urls.first().ok_or_else(|| {
5103            BlockchainError::Internal("at least one fork URL is required".to_string())
5104        })?;
5105        let mut staged_config = self.node_config.read().await.clone();
5106        if rpc_url_was_provided {
5107            staged_config.fork_chain_id = None;
5108        }
5109        let configured_endpoint_is_anvil = staged_config.fork_endpoint_is_anvil
5110            && staged_config.fork_urls.contains(target_rpc_url);
5111        let cached_endpoint_is_anvil = previous_source.as_ref().is_some_and(|source| {
5112            source.rpc_url == *target_rpc_url && source.endpoint_identity.is_authoritative()
5113        });
5114        staged_config.fork_endpoint_is_anvil =
5115            configured_endpoint_is_anvil || cached_endpoint_is_anvil;
5116        staged_config.fork_urls = target_rpc_urls.to_vec();
5117        staged_config.fork_choice = block_number.map(|number| ForkChoice::Block(number as i128));
5118        let mut staged_env = self.evm_env.read().clone();
5119        staged_config.apply_tempo_fork_beneficiary_default(&mut staged_env);
5120        let staged_fees = self.fees.detached();
5121        let (mut staged_db, staged_client_config) = staged_config
5122            .setup_fork_db_config_for_reset(
5123                target_rpc_url.clone(),
5124                &mut staged_env,
5125                &staged_fees,
5126                self.networks,
5127            )
5128            .await?;
5129        let cache_lease = StagedForkCacheLease::for_db(staged_db.inner());
5130        let cache_identity_changed = previous_source.as_ref().is_some_and(|source| {
5131            source.authoritative_identity_changed_at_same_url(
5132                target_rpc_url,
5133                staged_client_config.endpoint_identity,
5134            )
5135        });
5136        if cache_identity_changed {
5137            staged_db.clear_into_state_snapshot();
5138            staged_db.insert_block_hash(
5139                U256::from(staged_client_config.block_number),
5140                staged_client_config.block_hash,
5141            );
5142        }
5143        if !staged_config.no_bal && !staged_config.no_fork_node_info {
5144            staged_client_config.prefill_cache(staged_db.inner()).await;
5145        }
5146        let mut invalidated_cache_namespaces = Vec::new();
5147        if cache_identity_changed && !staged_config.no_storage_caching {
5148            if let Some(source) = &previous_source
5149                && let Some(namespace) = ForkCacheNamespace::new(
5150                    source.endpoint_identity.source_chain_id,
5151                    target_rpc_url,
5152                )
5153            {
5154                invalidated_cache_namespaces.push(namespace);
5155            }
5156            if let Some(namespace) =
5157                ForkCacheNamespace::new(staged_client_config.chain_id, target_rpc_url)
5158                && !invalidated_cache_namespaces.contains(&namespace)
5159            {
5160                invalidated_cache_namespaces.push(namespace);
5161            }
5162        }
5163        let discard_old_cached_state =
5164            cache_identity_changed && flush_old_cache && !invalidated_cache_namespaces.is_empty();
5165        let staged_db: Arc<AsyncRwLock<Box<dyn Db>>> =
5166            Arc::new(AsyncRwLock::new(Box::new(staged_db)));
5167        let staged_fork = ClientFork::new(staged_client_config.clone(), Arc::clone(&staged_db));
5168        let attempt = async {
5169            let target_networks =
5170                staged_client_config.endpoint_identity.network_profile.unwrap_or_default();
5171            if !staged_config.has_explicit_network_selection()
5172                && !self.networks.supports_fork_source(&target_networks)
5173            {
5174                return Err(RpcError::invalid_params(format!(
5175                    "cannot reset Anvil across network families ({} -> {}); start a new \
5176                     instance with matching network configuration",
5177                    self.execution_profile_name(),
5178                    target_networks.execution_profile_name()
5179                ))
5180                .into());
5181            }
5182            if staged_client_config.endpoint_identity.instance_id == Some(serving_instance_id) {
5183                return Err(
5184                    RpcError::invalid_params("cannot reset Anvil to its own RPC endpoint").into()
5185                );
5186            }
5187            let fork_block = staged_fork
5188                .block_by_number(staged_fork.block_number())
5189                .await?
5190                .ok_or(BlockchainError::BlockNotFound)?;
5191            if fork_block.header.hash != staged_client_config.block_hash {
5192                return Ok(None);
5193            }
5194            self.apply_fork_genesis(
5195                Arc::clone(&staged_db),
5196                cache_lease.clone(),
5197                &staged_env,
5198                staged_client_config.block_hash,
5199                staged_client_config.hardfork.unwrap_or_else(|| staged_config.get_hardfork()),
5200            )
5201            .await?;
5202
5203            #[cfg(feature = "monad")]
5204            if self.is_monad() {
5205                monad::cache_fork_context(&staged_fork).await?;
5206            }
5207
5208            if !staged_config
5209                .fork_urls_match_context(
5210                    target_rpc_urls,
5211                    staged_client_config.endpoint_identity,
5212                    staged_client_config.block_number,
5213                    staged_client_config.block_hash,
5214                )
5215                .await?
5216            {
5217                return Ok(None);
5218            }
5219
5220            Ok(Some((fork_block, staged_fork.storage.read().clone())))
5221        }
5222        .await;
5223        drop(staged_fork);
5224        let (fork_block, staged_storage) = match attempt {
5225            Ok(Some(staged)) => staged,
5226            Ok(None) => {
5227                drop(staged_db);
5228                self.rollback_staged_fork_cache(cache_lease, flush_old_cache).await?;
5229                return Ok(None);
5230            }
5231            Err(err) => {
5232                drop(staged_db);
5233                self.rollback_staged_fork_cache(cache_lease, flush_old_cache).await?;
5234                return Err(err);
5235            }
5236        };
5237        let staged_db = match Arc::try_unwrap(staged_db) {
5238            Ok(staged_db) => staged_db.into_inner(),
5239            Err(staged_db) => {
5240                drop(staged_db);
5241                self.rollback_staged_fork_cache(cache_lease, flush_old_cache).await?;
5242                return Err(BlockchainError::Internal(
5243                    "staged fork database still has active references".to_string(),
5244                ));
5245            }
5246        };
5247
5248        let fork = ClientFork::new(staged_client_config, Arc::clone(&self.db));
5249        *fork.storage.write() = staged_storage;
5250        Ok(Some(StagedForkReset {
5251            node_config: staged_config,
5252            db: staged_db,
5253            fees: staged_fees,
5254            evm_env: staged_env,
5255            fork,
5256            timestamp: fork_block.header.timestamp(),
5257            discard_old_cached_state,
5258            invalidated_cache_namespaces,
5259            flush_old_cache,
5260            cache_lease,
5261        }))
5262    }
5263
5264    async fn rollback_staged_fork_cache(
5265        &self,
5266        cache_lease: StagedForkCacheLease,
5267        restore_live_cache: bool,
5268    ) -> Result<(), BlockchainError> {
5269        let rollback_err = cache_lease.rollback().err();
5270        // If immediate invalidation failed, dropping the final lease retries cleanup before the
5271        // live cache is restored at a potentially shared path.
5272        drop(cache_lease);
5273        let restore_err =
5274            if restore_live_cache { self.db.read().await.maybe_flush_cache().err() } else { None };
5275        match (rollback_err, restore_err) {
5276            (None, None) => Ok(()),
5277            (Some(err), None) => Err(err),
5278            (None, Some(err)) => Err(BlockchainError::Internal(format!(
5279                "failed to restore the live fork cache after staged reset rollback: {err}"
5280            ))),
5281            (Some(rollback), Some(restore)) => Err(BlockchainError::Internal(format!(
5282                "{rollback}; restoring the live fork cache also failed: {restore}"
5283            ))),
5284        }
5285    }
5286
5287    /// Atomically publishes a fully prepared fork replacement.
5288    pub(crate) async fn commit_fork_reset(
5289        &self,
5290        staged: StagedForkReset,
5291    ) -> Result<(), BlockchainError> {
5292        let fork_block_number = staged.fork.block_number();
5293        let fork_block_hash = staged.fork.block_hash();
5294        let fork_total_difficulty = staged.fork.total_difficulty();
5295        let fork_cache_source = ForkCacheSource::from_fork(&staged.fork);
5296
5297        // Acquire asynchronous write guards before flushing and replacing the live database so no
5298        // old-context request can populate its cache between those operations.
5299        let mut node_config = self.node_config.write().await;
5300        let mut db = self.db.write().await;
5301        if staged.flush_old_cache {
5302            db.maybe_flush_cache().map_err(BlockchainError::Internal)?;
5303        }
5304        if let Err(err) =
5305            staged.invalidated_cache_namespaces.iter().try_for_each(ForkCacheNamespace::invalidate)
5306        {
5307            // Prevent the rejected staged backend from flushing partial target state, then restore
5308            // the still-live cache after the endpoint namespace cleanup failed.
5309            let StagedForkReset { db: staged_db, cache_lease, flush_old_cache, .. } = staged;
5310            drop(staged_db);
5311            let rollback_err = cache_lease.rollback().err();
5312            drop(cache_lease);
5313            let restore_err = if flush_old_cache { db.maybe_flush_cache().err() } else { None };
5314            let mut message = err.to_string();
5315            if let Some(err) = rollback_err {
5316                message.push_str(&format!("; staged cache rollback also failed: {err}"));
5317            }
5318            if let Some(err) = restore_err {
5319                message.push_str(&format!("; restoring the live fork cache also failed: {err}"));
5320            }
5321            return Err(BlockchainError::Internal(message));
5322        }
5323        if staged.discard_old_cached_state {
5324            db.clear_into_state_snapshot();
5325        }
5326        staged.cache_lease.disarm();
5327        let StagedForkReset {
5328            node_config: staged_node_config,
5329            db: staged_db,
5330            fees,
5331            evm_env,
5332            fork,
5333            timestamp,
5334            ..
5335        } = staged;
5336        *node_config = staged_node_config;
5337        *db = staged_db;
5338        self.fees.replace_from(&fees);
5339        *self.evm_env.write() = evm_env;
5340        *self.fork.write() = Some(fork);
5341        *self.last_fork_cache_source.write() = fork_cache_source;
5342        *self.blockchain.storage.write() =
5343            BlockchainStorage::forked(fork_block_number, fork_block_hash, fork_total_difficulty);
5344        self.states.write().clear();
5345        self.active_state_snapshots.lock().clear();
5346        self.time.reset(timestamp);
5347        self.cheats.clear_next_block_overrides();
5348
5349        trace!(target: "backend", "reset fork");
5350        Ok(())
5351    }
5352
5353    /// Builds a complete in-memory replacement without mutating the live backend.
5354    pub(crate) async fn prepare_memory_reset(
5355        &self,
5356    ) -> Result<StagedMemoryReset<N>, BlockchainError> {
5357        let reset_from_fork = self.is_fork();
5358        let genesis_timestamp = self.genesis.timestamp;
5359        let genesis_number = self.genesis.number;
5360        let mut staged_config = self.node_config.read().await.clone();
5361        staged_config.fork_source_chain_id = None;
5362        staged_config.fork_execution_chain_id = None;
5363        staged_config.fork_endpoint_is_anvil = false;
5364        staged_config.restore_fork_overrides();
5365        let local_chain_id = staged_config.get_chain_id();
5366        staged_config.update_wallet_chain_id(local_chain_id);
5367        let (
5368            local_gas_limit,
5369            local_base_fee,
5370            local_base_fee_is_explicit,
5371            local_gas_price,
5372            local_blob_params,
5373            local_blob_excess_gas_and_price,
5374            local_beneficiary,
5375            install_create2_deployer,
5376        ) = {
5377            (
5378                staged_config.gas_limit(),
5379                staged_config.get_base_fee(),
5380                staged_config.base_fee.is_some()
5381                    || staged_config
5382                        .genesis
5383                        .as_ref()
5384                        .is_some_and(|genesis| genesis.base_fee_per_gas.is_some()),
5385                staged_config.get_gas_price(),
5386                staged_config.get_blob_params(),
5387                staged_config.get_blob_excess_gas_and_price(),
5388                staged_config.genesis.as_ref().map(|genesis| genesis.coinbase).unwrap_or_default(),
5389                !staged_config.disable_default_create2_deployer,
5390            )
5391        };
5392
5393        let local_hardfork = staged_config.get_hardfork();
5394        let local_spec = SpecId::from(local_hardfork);
5395        let local_tempo_hardfork =
5396            self.networks.is_tempo().then(|| TempoHardfork::from(local_hardfork));
5397        let staged_fees = self.fees.detached();
5398        staged_fees.set_execution_rules(
5399            local_spec,
5400            self.networks.base_fee_params(genesis_timestamp),
5401            local_tempo_hardfork,
5402        );
5403        #[cfg(feature = "optimism")]
5404        if self.networks.is_optimism() {
5405            staged_fees.set_optimism_hardfork(local_hardfork.into());
5406        }
5407        staged_fees.set_blob_params(local_blob_params);
5408        staged_fees.set_blob_excess_gas_and_price(local_blob_excess_gas_and_price);
5409
5410        // Explicit local configuration always wins, while Tempo configuration uses the
5411        // hardfork's own seed. An implicit in-memory Ethereum chain otherwise keeps its live base
5412        // fee in the reset genesis; returning from a fork restores the local default instead of
5413        // leaking remote fee state. Compute this up front so the env, storage, and fee manager all
5414        // agree.
5415        let preserve_live_base_fee =
5416            !reset_from_fork && !local_base_fee_is_explicit && local_tempo_hardfork.is_none();
5417        let genesis_base_fee =
5418            if preserve_live_base_fee { staged_fees.base_fee() } else { local_base_fee };
5419
5420        let mut staged_cfg = CfgEnv::new();
5421        staged_cfg.set_spec_and_mainnet_gas_params(local_spec);
5422        staged_cfg.chain_id = local_chain_id;
5423        staged_cfg.limit_contract_code_size = staged_config.code_size_limit;
5424        staged_cfg.disable_eip3607 = true;
5425        staged_cfg.disable_block_gas_limit = staged_config.disable_block_gas_limit;
5426        if !staged_config.enable_tx_gas_limit {
5427            staged_cfg.tx_gas_limit_cap = Some(u64::MAX);
5428        }
5429        if let Some(memory_limit) = staged_config.memory_limit {
5430            staged_cfg.memory_limit = memory_limit;
5431        }
5432        let staged_env = EvmEnv::new(
5433            staged_cfg,
5434            BlockEnv {
5435                number: U256::from(genesis_number),
5436                beneficiary: local_beneficiary,
5437                timestamp: U256::from(genesis_timestamp),
5438                gas_limit: local_gas_limit,
5439                basefee: genesis_base_fee,
5440                prevrandao: Some(B256::ZERO),
5441                ..Default::default()
5442            },
5443        );
5444
5445        let base_fee = staged_fees.is_eip1559().then_some(genesis_base_fee);
5446        let header = genesis_header(&staged_env, base_fee, genesis_timestamp, genesis_number);
5447        let staged_storage = BlockchainStorage::new(foundry_header(&self.networks, header));
5448
5449        // Seed the next block's fee state. Tempo always advances through its hardfork rule, an
5450        // implicit in-memory Ethereum reset restores Anvil's default, and explicit or
5451        // fork-to-memory Ethereum resets retain the local configured value.
5452        staged_fees.set_base_fee(genesis_base_fee);
5453        if staged_fees.is_eip1559() {
5454            let next_base_fee = if staged_fees.tempo_hardfork().is_some() {
5455                staged_fees.get_next_block_base_fee_per_gas(
5456                    0,
5457                    staged_env.block_env.gas_limit,
5458                    genesis_base_fee,
5459                )
5460            } else if preserve_live_base_fee {
5461                crate::eth::fees::INITIAL_BASE_FEE
5462            } else {
5463                genesis_base_fee
5464            };
5465            staged_fees.set_base_fee(next_base_fee);
5466        }
5467        staged_fees.set_gas_price(local_gas_price);
5468
5469        let mut staged_db: Box<dyn Db> = Box::new(StateRootDb::new(
5470            self.prune_state_history_config.is_state_history_supported(),
5471        ));
5472        Self::populate_memory_db(
5473            &mut *staged_db,
5474            &self.genesis,
5475            &staged_config.funded_accounts,
5476            local_hardfork,
5477            local_chain_id,
5478            self.networks.is_tempo(),
5479            local_tempo_hardfork,
5480            staged_storage.genesis_hash,
5481            install_create2_deployer,
5482        )?;
5483        #[cfg(feature = "base")]
5484        if self.is_base() {
5485            let upgrade = match local_hardfork {
5486                FoundryHardfork::Base(upgrade) => upgrade,
5487                _ => BaseUpgrade::Azul,
5488            };
5489            Self::apply_base_genesis(&mut *staged_db, upgrade)?;
5490        }
5491
5492        Ok(StagedMemoryReset {
5493            node_config: staged_config,
5494            db: staged_db,
5495            fees: staged_fees,
5496            evm_env: staged_env,
5497            hardfork: local_hardfork,
5498            storage: staged_storage,
5499            timestamp: genesis_timestamp,
5500            flush_old_cache: reset_from_fork,
5501        })
5502    }
5503
5504    /// Atomically publishes a fully prepared in-memory replacement.
5505    pub(crate) async fn commit_memory_reset(
5506        &self,
5507        staged: StagedMemoryReset<N>,
5508    ) -> Result<(), BlockchainError> {
5509        let StagedMemoryReset {
5510            node_config,
5511            db,
5512            fees,
5513            evm_env,
5514            hardfork,
5515            storage,
5516            timestamp,
5517            flush_old_cache,
5518        } = staged;
5519        let mut live_config = self.node_config.write().await;
5520        let mut live_db = self.db.write().await;
5521        if flush_old_cache {
5522            live_db.maybe_flush_cache().map_err(BlockchainError::Internal)?;
5523        }
5524
5525        *live_config = node_config;
5526        *live_db = db;
5527        self.fees.replace_from(&fees);
5528        *self.evm_env.write() = evm_env;
5529        *self.hardfork.write() = hardfork;
5530        *self.fork.write() = None;
5531        *self.blockchain.storage.write() = storage;
5532        self.states.write().clear();
5533        self.active_state_snapshots.lock().clear();
5534        self.time.reset(timestamp);
5535        self.cheats.clear_next_block_overrides();
5536        trace!(target: "backend", "reset to fresh in-memory state");
5537        Ok(())
5538    }
5539
5540    /// Reverts the state to the state snapshot identified by the given `id`.
5541    pub async fn revert_state_snapshot(&self, id: U256) -> Result<bool, BlockchainError>
5542    where
5543        N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
5544    {
5545        let Some((num, hash, fees, time, next_block)) =
5546            self.active_state_snapshots.lock().get(&id).map(|snapshot| {
5547                (
5548                    snapshot.block_number,
5549                    snapshot.block_hash,
5550                    snapshot.fees,
5551                    snapshot.time,
5552                    snapshot.next_block,
5553                )
5554            })
5555        else {
5556            return Ok(false);
5557        };
5558        let block = self.block_by_hash(hash).await?.ok_or(BlockchainError::BlockNotFound)?;
5559        let removed_logs = self.removed_logs_since(num);
5560        if !self.db.write().await.revert_state(id, RevertStateSnapshotAction::RevertRemove) {
5561            return Ok(false);
5562        }
5563        {
5564            let mut snapshots = self.active_state_snapshots.lock();
5565            snapshots.remove(&id);
5566            snapshots.retain(|snapshot_id, _| *snapshot_id < id);
5567        }
5568        // Revert the storage that's newer than the snapshot.
5569        let removed_blocks = self.blockchain.storage.write().unwind_to(num, hash);
5570        let removed_hashes: Vec<_> = removed_blocks.iter().map(|b| b.header.hash_slow()).collect();
5571        self.states.write().remove_block_states(&removed_hashes);
5572        if !removed_logs.is_empty() {
5573            self.notify_on_removed_logs(removed_logs);
5574        }
5575
5576        self.time.restore(time);
5577        self.cheats.restore_next_block_overrides(&next_block);
5578
5579        {
5580            let mut env = self.evm_env.write();
5581            env.block_env = BlockEnv {
5582                number: self.evm_block_number(num),
5583                timestamp: U256::from(block.header.timestamp()),
5584                difficulty: block.header.difficulty(),
5585                // ensures prevrandao is set
5586                prevrandao: Some(block.header.mix_hash().unwrap_or_default()),
5587                gas_limit: block.header.gas_limit(),
5588                // Keep previous `beneficiary` and `basefee` value
5589                beneficiary: env.block_env.beneficiary,
5590                basefee: env.block_env.basefee,
5591                ..Default::default()
5592            };
5593        }
5594        self.fees.restore(fees);
5595        Ok(true)
5596    }
5597
5598    /// executes the transactions without writing to the underlying database
5599    pub async fn inspect_tx(
5600        &self,
5601        tx: Arc<PoolTransaction<FoundryTxEnvelope>>,
5602    ) -> Result<
5603        (InstructionResult, Option<Output>, u64, State, Vec<revm::primitives::Log>),
5604        BlockchainError,
5605    > {
5606        let db = self.db.read().await;
5607        let evm_env = self.next_evm_env(&**db)?;
5608        let mut inspector = self.build_inspector();
5609        #[cfg(feature = "monad")]
5610        let mut monad_context = self
5611            .is_monad()
5612            .then(|| self.monad_context_for_child_of(self.best_hash()))
5613            .transpose()?;
5614        #[cfg(not(feature = "monad"))]
5615        let mut monad_context = None;
5616        let (ResultAndState { result, state }, _) = self
5617            .transact_envelope_with_inspector_ref_and_context(
5618                &**db,
5619                &evm_env,
5620                &mut inspector,
5621                &tx.pending_transaction,
5622                monad_context.as_mut().map(next_monad_context),
5623            )?;
5624        let (exit_reason, gas_used, out, logs) = unpack_execution_result(result);
5625
5626        inspector.print_logs();
5627
5628        if self.print_traces {
5629            inspector.print_traces(self.call_trace_decoder());
5630        }
5631
5632        Ok((exit_reason, out, gas_used, state, logs))
5633    }
5634}
5635
5636impl<N: Network> Backend<N>
5637where
5638    N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
5639{
5640    /// Returns all `Log`s mined by the node that were emitted in the `block` and match the `Filter`
5641    fn mined_logs_for_block(&self, filter: Filter, block: Block, block_hash: B256) -> Vec<Log> {
5642        let mut all_logs = Vec::new();
5643        let mut block_log_index = 0u32;
5644
5645        let storage = self.blockchain.storage.read();
5646
5647        for tx in block.body.transactions {
5648            let Some(tx) = storage.transactions.get(&tx.hash()) else {
5649                continue;
5650            };
5651
5652            let logs = tx.receipt.logs();
5653            let transaction_hash = tx.info.transaction_hash;
5654
5655            for log in logs {
5656                if filter.matches(log) {
5657                    all_logs.push(Log {
5658                        inner: log.clone(),
5659                        block_hash: Some(block_hash),
5660                        block_number: Some(block.header.number()),
5661                        block_timestamp: Some(block.header.timestamp()),
5662                        transaction_hash: Some(transaction_hash),
5663                        transaction_index: Some(tx.info.transaction_index),
5664                        log_index: Some(block_log_index as u64),
5665                        removed: false,
5666                    });
5667                }
5668                block_log_index += 1;
5669            }
5670        }
5671        all_logs
5672    }
5673
5674    /// Returns all logs of the blocks with a number greater than `block_number`, marked as
5675    /// removed.
5676    ///
5677    /// This captures the logs of blocks that are about to be unwound before their transactions
5678    /// and receipts are cleared from storage, so they can be re-delivered to log subscriptions
5679    /// and filters with `removed: true`.
5680    fn removed_logs_since(&self, block_number: u64) -> Vec<Log> {
5681        let storage = self.blockchain.storage.read();
5682        let mut all_logs = Vec::new();
5683
5684        for num in (block_number + 1)..=storage.best_number {
5685            if let Some(hash) = storage.hashes.get(&num)
5686                && let Some(block) = storage.blocks.get(hash)
5687            {
5688                let mut block_log_index = 0u64;
5689                for tx in &block.body.transactions {
5690                    if let Some(tx) = storage.transactions.get(&tx.hash()) {
5691                        for log in tx.receipt.logs() {
5692                            all_logs.push(Log {
5693                                inner: log.clone(),
5694                                block_hash: Some(*hash),
5695                                block_number: Some(num),
5696                                block_timestamp: Some(block.header.timestamp()),
5697                                transaction_hash: Some(tx.info.transaction_hash),
5698                                transaction_index: Some(tx.info.transaction_index),
5699                                log_index: Some(block_log_index),
5700                                removed: true,
5701                            });
5702                            block_log_index += 1;
5703                        }
5704                    }
5705                }
5706            }
5707        }
5708
5709        all_logs
5710    }
5711
5712    /// Returns the logs of the block that match the filter
5713    async fn logs_for_block(
5714        &self,
5715        filter: Filter,
5716        hash: B256,
5717    ) -> Result<Vec<Log>, BlockchainError> {
5718        if let Some(block) = self.blockchain.get_block_by_hash(&hash) {
5719            return Ok(self.mined_logs_for_block(filter, block, hash));
5720        }
5721
5722        if let Some(fork) = self.get_fork() {
5723            return Ok(fork.logs(&filter).await?);
5724        }
5725
5726        Err(BlockchainError::UnknownBlock)
5727    }
5728
5729    /// Returns the logs that match the filter in the given range of blocks
5730    async fn logs_for_range(
5731        &self,
5732        filter: &Filter,
5733        mut from: u64,
5734        to: u64,
5735    ) -> Result<Vec<Log>, BlockchainError> {
5736        let mut all_logs = Vec::new();
5737
5738        // get the range that predates the fork if any
5739        if let Some(fork) = self.get_fork() {
5740            let to_on_fork = if fork.predates_fork(to) {
5741                to
5742            } else {
5743                // adjust the ranges
5744                fork.block_number()
5745            };
5746
5747            if fork.predates_fork_inclusive(from) {
5748                // this data is only available on the forked client
5749                let filter = filter.clone().from_block(from).to_block(to_on_fork);
5750                all_logs = fork.logs(&filter).await?;
5751
5752                // update the range
5753                from = fork.block_number() + 1;
5754            }
5755        }
5756
5757        for number in from..=to {
5758            if let Some((block, hash)) = self.get_block_with_hash(number) {
5759                all_logs.extend(self.mined_logs_for_block(filter.clone(), block, hash));
5760            }
5761        }
5762
5763        Ok(all_logs)
5764    }
5765
5766    /// Returns the logs according to the filter
5767    pub async fn logs(&self, filter: Filter) -> Result<Vec<Log>, BlockchainError> {
5768        trace!(target: "backend", "get logs [{:?}]", filter);
5769        if let Some(hash) = filter.get_block_hash() {
5770            self.logs_for_block(filter, hash).await
5771        } else {
5772            let best = self.best_number();
5773            let to_block =
5774                self.convert_block_number(filter.block_option.get_to_block().copied()).min(best);
5775            let from_block =
5776                self.convert_block_number(filter.block_option.get_from_block().copied());
5777            if from_block > best {
5778                return Err(BlockchainError::BlockOutOfRange(best, from_block));
5779            }
5780
5781            self.logs_for_range(&filter, from_block, to_block).await
5782        }
5783    }
5784
5785    /// Returns all receipts of the block
5786    pub fn mined_receipts(&self, hash: B256) -> Option<Vec<N::ReceiptEnvelope>> {
5787        let storage = self.blockchain.storage.read();
5788        let block = storage.blocks.get(&hash)?;
5789        block
5790            .body
5791            .transactions
5792            .iter()
5793            .map(|transaction| {
5794                storage.transactions.get(&transaction.hash()).map(|tx| tx.receipt.clone())
5795            })
5796            .collect()
5797    }
5798
5799    /// Returns the EIP-7928 block access list stored for a locally mined block hash.
5800    pub fn block_access_list_by_hash(&self, hash: B256) -> Option<BlockAccessList> {
5801        self.blockchain.storage.read().block_access_lists.get(&hash).cloned()
5802    }
5803
5804    /// Returns the EIP-7928 block access list stored for a locally mined block number.
5805    pub fn block_access_list_by_number(&self, number: u64) -> Option<BlockAccessList> {
5806        let storage = self.blockchain.storage.read();
5807        storage.hashes.get(&number).and_then(|hash| storage.block_access_lists.get(hash)).cloned()
5808    }
5809}
5810
5811// Mining methods — generic over N: Network, with Foundry-associated-type bounds for now.
5812impl<N: Network> Backend<N>
5813where
5814    Self: TransactionValidator<FoundryTxEnvelope>,
5815    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
5816{
5817    /// Mines a new block and stores it.
5818    ///
5819    /// this will execute all transaction in the order they come in and return all the markers they
5820    /// provide.
5821    pub async fn mine_block(
5822        &self,
5823        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
5824    ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
5825        self.do_mine_block(pool_transactions).await
5826    }
5827
5828    /// Mines a new block while the caller holds the mining lock.
5829    pub(crate) async fn mine_block_locked(
5830        &self,
5831        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
5832    ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
5833        self.do_mine_block_locked(pool_transactions).await
5834    }
5835
5836    /// Replays a transaction-hash fork prefix before the live pool and miner are created.
5837    pub(crate) async fn apply_fork_transaction_replay(
5838        &self,
5839        replay: ForkTransactionReplay,
5840    ) -> Result<()> {
5841        let source_chain_id = self.protocol_chain_id();
5842        let arbitrum_block_numbers = is_arbitrum(source_chain_id).then(|| {
5843            (
5844                replay.source_block.header().number(),
5845                arbitrum_replay_block_number(&replay.source_block),
5846            )
5847        });
5848        let prepared = prepare_fork_transaction_replay(replay, self.is_monad(), self.is_celo())?;
5849        let fallback_execution_chain_id = self
5850            .get_fork()
5851            .map(|fork| fork.execution_chain_id())
5852            .unwrap_or_else(|| self.chain_id().to());
5853        let execution_chain_id = prepared.execution_chain_id(fallback_execution_chain_id)?;
5854        let PreparedForkTransactionReplay { transactions, timestamp, parent_beacon_block_root } =
5855            prepared;
5856        eyre::ensure!(!transactions.is_empty(), "fork transaction replay prefix is empty");
5857        let next_timestamp = timestamp.checked_add(1).ok_or_else(|| {
5858            eyre::eyre!("fork transaction replay timestamp cannot be incremented")
5859        })?;
5860
5861        let _mining_guard = self.mining.lock().await;
5862        let current_base_fee = self.base_fee();
5863        let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price();
5864        let mut evm_env = self.evm_env.read().clone();
5865        if evm_env.block_env.basefee == 0 {
5866            evm_env.cfg_env.disable_base_fee = true;
5867        }
5868
5869        let best_number = self.blockchain.storage.read().best_number;
5870        let block_number = best_number.saturating_add(1);
5871        evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::ONE);
5872        evm_env.block_env.basefee = current_base_fee;
5873        evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price;
5874        evm_env.block_env.timestamp = U256::from(timestamp);
5875
5876        let best_hash = self.blockchain.storage.read().best_hash;
5877        let mut prevrandao_input = [0u8; 40];
5878        prevrandao_input[..32].copy_from_slice(best_hash.as_slice());
5879        prevrandao_input[32..].copy_from_slice(&block_number.to_le_bytes());
5880        evm_env.block_env.prevrandao = Some(
5881            self.cheats.take_next_block_prevrandao().unwrap_or_else(|| keccak256(prevrandao_input)),
5882        );
5883
5884        let mut replay_env = evm_env.clone();
5885        let arbitrum_rpc_block_number = arbitrum_block_numbers.map(|(rpc, evm)| {
5886            replay_env.block_env.number = evm;
5887            rpc
5888        });
5889        replay_env.cfg_env.chain_id = execution_chain_id;
5890        apply_chain_specific_tx_replay_env_changes_for_chain(&mut replay_env, source_chain_id);
5891        let inspector_tx_config = self.inspector_tx_config();
5892
5893        let scheduled_hardfork =
5894            source_hardfork(self.networks.execution_network(), source_chain_id, timestamp);
5895        #[cfg(feature = "monad")]
5896        let mut monad_replay = self
5897            .prepare_monad_fork_replay(
5898                source_chain_id,
5899                execution_chain_id,
5900                timestamp,
5901                best_hash,
5902                &transactions,
5903            )
5904            .await?;
5905        #[cfg(feature = "monad")]
5906        let hardfork = monad_replay
5907            .as_ref()
5908            .map(monad::ForkReplay::hardfork)
5909            .unwrap_or_else(|| scheduled_hardfork.unwrap_or_else(|| self.hardfork()));
5910        #[cfg(not(feature = "monad"))]
5911        let hardfork = scheduled_hardfork.unwrap_or_else(|| self.hardfork());
5912        if !self.is_optimism() && !self.is_tempo() {
5913            replay_env.cfg_env.spec = SpecId::from(hardfork);
5914        }
5915        let replay_spec = SpecId::from(hardfork);
5916        // Cancun-family execution requires blob excess gas even for non-blob transactions. Some
5917        // OP Stack source headers omit it, so use the neutral value for historical replay.
5918        if !self.is_tempo()
5919            && replay_spec >= SpecId::CANCUN
5920            && replay_env.block_env.blob_excess_gas_and_price.is_none()
5921        {
5922            replay_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(
5923                0,
5924                get_blob_base_fee_update_fraction_by_spec_id(replay_spec),
5925            ));
5926        }
5927
5928        #[cfg(feature = "monad")]
5929        let monad_context = monad_replay.as_mut().and_then(monad::ForkReplay::take_context);
5930        #[cfg(not(feature = "monad"))]
5931        let monad_context = None;
5932
5933        let (block_info, state_changes, block_hash) = {
5934            let db = self.db.read().await;
5935            let mut overlay = AnvilCacheDB::new(&**db, *replay_env.spec_id());
5936            let ExecutedHistoricalReplay {
5937                block_result,
5938                transactions,
5939                transaction_infos,
5940                state_changes,
5941            } = self.execute_with_replay_block_executor(
5942                &mut overlay,
5943                &replay_env,
5944                best_hash,
5945                arbitrum_rpc_block_number,
5946                hardfork,
5947                parent_beacon_block_root,
5948                &transactions,
5949                &inspector_tx_config,
5950                monad_context,
5951            )?;
5952            let state_root = overlay.maybe_state_root().unwrap_or_default();
5953            let block_info = self.build_block_info(
5954                &replay_env,
5955                best_hash,
5956                block_number,
5957                state_root,
5958                block_result,
5959                transactions,
5960                transaction_infos,
5961                parent_beacon_block_root,
5962                None,
5963            );
5964            let block_hash = block_info.block.header.hash_slow();
5965            (block_info, state_changes, block_hash)
5966        };
5967
5968        if self.prune_state_history_config.is_state_history_supported() {
5969            let state = self.db.read().await.current_state();
5970            self.states.write().insert(best_hash, state);
5971        }
5972
5973        {
5974            let mut db = self.db.write().await;
5975            for state in state_changes {
5976                db.commit(state);
5977            }
5978            db.insert_block_hash(U256::from(block_number), block_hash);
5979        }
5980
5981        let BlockInfo { block, transactions, receipts } = block_info;
5982        let header = block.header.clone();
5983        {
5984            let mut storage = self.blockchain.storage.write();
5985            storage.best_number = block_number;
5986            storage.best_hash = block_hash;
5987            if !self.is_eip3675() {
5988                storage.total_difficulty =
5989                    storage.total_difficulty.saturating_add(header.difficulty);
5990            }
5991            storage.blocks.insert(block_hash, block);
5992            storage.hashes.insert(block_number, block_hash);
5993            #[cfg(feature = "monad")]
5994            if let Some(replay) = &mut monad_replay {
5995                replay.store_metadata(&mut storage, block_hash);
5996            }
5997            for (info, receipt) in transactions.into_iter().zip(receipts) {
5998                let mined_tx = MinedTransaction { info, receipt, block_hash, block_number };
5999                storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx);
6000            }
6001
6002            if let Some(transaction_block_keeper) = self.transaction_block_keeper
6003                && storage.blocks.len() > transaction_block_keeper
6004            {
6005                let to_clear = block_number
6006                    .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX));
6007                storage.remove_block_transactions_by_number(to_clear)
6008            }
6009        }
6010
6011        #[cfg(feature = "monad")]
6012        if let Some(replay) = &monad_replay {
6013            self.finalize_monad_fork_replay(replay, &mut evm_env);
6014        }
6015
6016        evm_env.block_env.difficulty = U256::ZERO;
6017        *self.evm_env.write() = evm_env;
6018        self.time.reset(timestamp);
6019        self.time.set_next_block_timestamp(next_timestamp)?;
6020
6021        #[cfg(feature = "optimism")]
6022        if self.is_optimism() {
6023            self.fees.set_optimism_base_fee_rules(header.extra_data());
6024        }
6025        let next_block_base_fee = self.fees.get_next_block_base_fee_from_header(&header);
6026        let next_block_excess_blob_gas = self.networks.next_block_blob_excess_gas(
6027            self.fees.blob_params(),
6028            header.excess_blob_gas.unwrap_or_default(),
6029            header.blob_gas_used.unwrap_or_default(),
6030            header.base_fee_per_gas.unwrap_or_default(),
6031        );
6032        self.fees.set_base_fee(next_block_base_fee);
6033        self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
6034            next_block_excess_blob_gas,
6035            get_blob_base_fee_update_fraction_by_spec_id(*self.evm_env.read().spec_id()),
6036        ));
6037        self.notify_on_new_block(header.into_inner(), block_hash);
6038
6039        Ok(())
6040    }
6041
6042    #[allow(clippy::too_many_arguments)]
6043    fn execute_with_replay_block_executor<DB>(
6044        &self,
6045        db: DB,
6046        evm_env: &EvmEnv,
6047        parent_hash: B256,
6048        arbitrum_rpc_block_number: Option<u64>,
6049        hardfork: FoundryHardfork,
6050        parent_beacon_block_root: Option<B256>,
6051        transactions: &[HistoricalReplayTransaction],
6052        inspector_tx_config: &InspectorTxConfig,
6053        #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
6054            MonadReplayContext,
6055        >,
6056    ) -> Result<ExecutedHistoricalReplay>
6057    where
6058        DB: StateDB<Error = DatabaseError> + BalIndexedDatabase,
6059    {
6060        #[cfg(feature = "monad")]
6061        if self.is_monad() {
6062            return self.execute_with_monad_replay_block_executor(
6063                db,
6064                evm_env,
6065                parent_hash,
6066                hardfork.into(),
6067                transactions,
6068                inspector_tx_config,
6069                monad_context
6070                    .ok_or_else(|| eyre::eyre!("Monad replay ancestor context is unavailable"))?,
6071            );
6072        }
6073
6074        let inspector = self.build_mining_inspector();
6075        let spec_id = *evm_env.spec_id();
6076        let transitions = self.ethereum_block_transitions(
6077            hardfork,
6078            parent_beacon_block_root,
6079            BlockExecutionKind::TransactionPrefix,
6080        );
6081
6082        macro_rules! execute {
6083            ($executor:expr) => {{
6084                let mut executor = $executor;
6085                executor
6086                    .apply_pre_execution_changes()
6087                    .wrap_err("failed to apply replay block-start transitions")?;
6088                let (stored_transactions, transaction_infos) =
6089                    execute_historical_replay(&mut executor, transactions, inspector_tx_config)?;
6090                let state_changes = executor.take_state_changes();
6091                let (_, block_result) =
6092                    executor.finish().wrap_err("failed to finish replay block execution")?;
6093                Ok(ExecutedHistoricalReplay {
6094                    block_result,
6095                    transactions: stored_transactions,
6096                    transaction_infos,
6097                    state_changes,
6098                })
6099            }};
6100        }
6101
6102        #[cfg(feature = "base")]
6103        if self.is_base() {
6104            let upgrade =
6105                self.base_upgrade_at_timestamp(evm_env.block_env.timestamp.saturating_to());
6106            let cfg =
6107                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(BaseSpecId::new(upgrade));
6108            let activation_admin = self.base_activation_admin().or_else(|| {
6109                ChainConfig::activation_admin_address_for_upgrade_by_chain_id(cfg.chain_id, upgrade)
6110            });
6111            let base_env = EvmEnv::new(cfg, evm_env.block_env.clone());
6112            let mut evm = BaseEvmFactory::new(activation_admin)
6113                .create_evm_with_inspector(db, base_env, inspector);
6114            evm.ctx_mut().cfg.tx_chain_id_check = true;
6115            self.inject_precompiles(evm.precompiles_mut(), evm_env);
6116            if let Some(block_number) = arbitrum_rpc_block_number {
6117                self.inject_arbitrum_precompile_at_block(evm.precompiles_mut(), block_number);
6118            }
6119            let mut executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
6120                .with_state_changes();
6121            executor.set_base_upgrade(upgrade);
6122            return execute!(executor);
6123        }
6124
6125        #[cfg(feature = "optimism")]
6126        if self.is_optimism() {
6127            let op_env = EvmEnv::new(
6128                evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(hardfork.into()),
6129                evm_env.block_env.clone(),
6130            );
6131            let mut evm =
6132                OpEvmFactory::<OpTx>::default().create_evm_with_inspector(db, op_env, inspector);
6133            self.inject_precompiles(evm.precompiles_mut(), evm_env);
6134            if let Some(block_number) = arbitrum_rpc_block_number {
6135                self.inject_arbitrum_precompile_at_block(evm.precompiles_mut(), block_number);
6136            }
6137            // Historical replay has no local blob budget. OP still configures its Jovian DA budget.
6138            let mut executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
6139                .with_state_changes();
6140            executor.set_optimism_hardfork(hardfork);
6141            return execute!(executor);
6142        }
6143
6144        if self.is_tempo() {
6145            let tempo_env = Self::build_tempo_evm_env(evm_env, self.tempo_hardfork());
6146            let mut evm =
6147                TempoEvmFactory::default().create_evm_with_inspector(db, tempo_env, inspector);
6148            self.inject_precompiles(evm.precompiles_mut(), evm_env);
6149            if let Some(block_number) = arbitrum_rpc_block_number {
6150                self.inject_arbitrum_precompile_at_block(evm.precompiles_mut(), block_number);
6151            }
6152            let executor = AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions)
6153                .with_state_changes();
6154            return execute!(executor);
6155        }
6156
6157        let mut evm =
6158            EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
6159        self.inject_precompiles(evm.precompiles_mut(), evm_env);
6160        if let Some(block_number) = arbitrum_rpc_block_number {
6161            self.inject_arbitrum_precompile_at_block(evm.precompiles_mut(), block_number);
6162        }
6163        let executor =
6164            AnvilBlockExecutor::new(evm, parent_hash, spec_id, transitions).with_state_changes();
6165        execute!(executor)
6166    }
6167
6168    /// Builds a [`BlockInfo`] from the EVM environment, execution results, and transactions.
6169    #[allow(clippy::too_many_arguments)]
6170    fn build_block_info(
6171        &self,
6172        evm_env: &EvmEnv,
6173        parent_hash: B256,
6174        number: u64,
6175        state_root: B256,
6176        block_result: BlockExecutionResult<FoundryReceiptEnvelope>,
6177        transactions: Vec<MaybeImpersonatedTransaction<FoundryTxEnvelope>>,
6178        transaction_infos: Vec<TransactionInfo>,
6179        parent_beacon_block_root: Option<B256>,
6180        block_access_list: Option<&BlockAccessList>,
6181    ) -> BlockInfo<N> {
6182        let spec_id = *evm_env.spec_id();
6183        let is_shanghai = spec_id >= SpecId::SHANGHAI;
6184        let is_cancun = spec_id >= SpecId::CANCUN;
6185        let is_prague = spec_id >= SpecId::PRAGUE;
6186
6187        let receipts_root = calculate_receipt_root(&block_result.receipts);
6188        let cumulative_blob_gas_used = is_cancun.then_some(block_result.blob_gas_used);
6189        let bloom = block_result.receipts.iter().fold(Bloom::default(), |mut b, r| {
6190            b.accrue_bloom(r.logs_bloom());
6191            b
6192        });
6193
6194        let header = Header {
6195            parent_hash,
6196            ommers_hash: Default::default(),
6197            beneficiary: evm_env.block_env.beneficiary,
6198            state_root,
6199            transactions_root: Default::default(),
6200            receipts_root,
6201            logs_bloom: bloom,
6202            difficulty: evm_env.block_env.difficulty,
6203            number,
6204            gas_limit: evm_env.block_env.gas_limit,
6205            gas_used: block_result.gas_used,
6206            timestamp: evm_env.block_env.timestamp.saturating_to(),
6207            extra_data: self.fees.base_fee_extra_data(),
6208            mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(),
6209            nonce: Default::default(),
6210            base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(evm_env.block_env.basefee),
6211            parent_beacon_block_root: is_cancun
6212                .then(|| parent_beacon_block_root.unwrap_or_default()),
6213            blob_gas_used: cumulative_blob_gas_used,
6214            excess_blob_gas: if is_cancun { evm_env.block_env.blob_excess_gas() } else { None },
6215            withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
6216            requests_hash: is_prague.then(|| block_result.requests.requests_hash()),
6217            block_access_list_hash: block_access_list
6218                .map(|bal| compute_block_access_list_hash(bal.as_slice())),
6219            slot_number: None,
6220        };
6221
6222        let block = create_block(foundry_header(&self.networks, header), transactions);
6223        BlockInfo { block, transactions: transaction_infos, receipts: block_result.receipts }
6224    }
6225
6226    async fn do_mine_block(
6227        &self,
6228        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
6229    ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
6230        let _mining_guard = self.mining.lock().await;
6231        self.do_mine_block_locked(pool_transactions).await
6232    }
6233
6234    /// Mines a block while the caller holds the mining lock.
6235    async fn do_mine_block_locked(
6236        &self,
6237        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
6238    ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
6239        trace!(target: "backend", "creating new block with {} transactions", pool_transactions.len());
6240
6241        let (outcome, header, block_hash) = {
6242            let current_base_fee = self.base_fee();
6243            let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price();
6244
6245            let mut evm_env = self.evm_env.read().clone();
6246            #[cfg(feature = "monad")]
6247            let execution_chain_id = evm_env.cfg_env.chain_id;
6248            let hardfork = self.hardfork();
6249
6250            if evm_env.block_env.basefee == 0 {
6251                // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee`
6252                // 0 is only possible if it's manually set
6253                evm_env.cfg_env.disable_base_fee = true;
6254            }
6255
6256            let block_number = self.blockchain.storage.read().best_number.saturating_add(1);
6257
6258            // Advance the EVM number independently of the RPC header number. On Arbitrum
6259            // forks this preserves the L1 number read by NUMBER while the header tracks L2.
6260            evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::ONE);
6261
6262            evm_env.block_env.basefee = current_base_fee;
6263            evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price;
6264
6265            let best_hash = self.blockchain.storage.read().best_hash;
6266
6267            let mut input = [0u8; 40];
6268            input[..32].copy_from_slice(best_hash.as_slice());
6269            input[32..].copy_from_slice(&block_number.to_le_bytes());
6270            // Use the values set via `anvil_setNextBlockPrevRandao` and
6271            // `anvil_setNextBlockParentBeaconBlockRoot` for this block if provided, otherwise
6272            // derive `prevrandao` from the parent hash and block number and use the zero root.
6273            // The overrides are consumed once the block is committed so they only apply to it.
6274            let next_block = self.cheats.next_block_overrides();
6275            evm_env.block_env.prevrandao =
6276                Some(next_block.prevrandao.value.unwrap_or_else(|| keccak256(input)));
6277            let parent_beacon_block_root =
6278                Some(next_block.parent_beacon_block_root.value.unwrap_or_default());
6279
6280            let (
6281                db_guard,
6282                block_info,
6283                included,
6284                stale,
6285                invalid,
6286                not_yet_valid,
6287                block_hash,
6288                parent_state,
6289                next_block_base_fee,
6290                next_block_blob_fees,
6291                block_access_list,
6292            ) = {
6293                let mut db = self.db.write().await;
6294
6295                // finally set the next block timestamp, this is done just before execution, because
6296                // there can be concurrent requests that can delay acquiring the db lock and we want
6297                // to ensure the timestamp is as close as possible to the actual execution.
6298                let pending_timestamp = self
6299                    .prepare_block_timestamp(&**db, evm_env.block_env.timestamp.saturating_to())?;
6300                let block_timestamp = pending_timestamp.timestamp;
6301                evm_env.block_env.timestamp = U256::from(block_timestamp);
6302
6303                // Forced historical transactions bypass pool admission and are replayed while
6304                // mining. Keep this exception local to the disposable mining environment.
6305                let mut mining_evm_env = evm_env.clone();
6306                if pool_transactions.iter().any(|tx| tx.is_replay) {
6307                    apply_chain_specific_tx_replay_env_changes_for_chain(
6308                        &mut mining_evm_env,
6309                        self.protocol_chain_id(),
6310                    );
6311                }
6312
6313                #[cfg(feature = "base")]
6314                let (protocol_transactions, system_transaction_count) = {
6315                    let mut transactions = self.base_system_transactions(
6316                        &**db,
6317                        block_number,
6318                        block_timestamp,
6319                        best_hash,
6320                    )?;
6321                    let system_transaction_count = transactions.len();
6322                    transactions.extend(pool_transactions);
6323                    (transactions, system_transaction_count)
6324                };
6325                #[cfg(not(feature = "base"))]
6326                let protocol_transactions = pool_transactions;
6327
6328                let inspector_tx_config = self.inspector_tx_config();
6329                let gas_config = self.pool_tx_gas_config(&mining_evm_env);
6330
6331                let mut candidate_db = AnvilCacheDB::new(&**db, *mining_evm_env.spec_id());
6332                if matches!(
6333                    hardfork,
6334                    FoundryHardfork::Ethereum(hardfork) if hardfork >= EthereumHardfork::Amsterdam
6335                ) {
6336                    candidate_db.enable_bal_recording();
6337                }
6338                let (pool_result, block_result) = self.execute_with_block_executor(
6339                    &mut candidate_db,
6340                    &mining_evm_env,
6341                    best_hash,
6342                    hardfork,
6343                    parent_beacon_block_root,
6344                    BlockExecutionKind::Complete,
6345                    &protocol_transactions,
6346                    &gas_config,
6347                    &inspector_tx_config,
6348                    &|pool_tx, account| {
6349                        let validation_env =
6350                            if pool_tx.is_replay { &mining_evm_env } else { &evm_env };
6351                        self.validate_mining_pool_transaction_for(pool_tx, account, validation_env)
6352                    },
6353                )?;
6354                let block_access_list = candidate_db.take_block_access_list();
6355
6356                #[cfg(feature = "base")]
6357                base::validate_system_transactions(
6358                    &protocol_transactions[..system_transaction_count],
6359                    &pool_result,
6360                )?;
6361
6362                #[cfg_attr(not(feature = "base"), allow(unused_mut))]
6363                let mut included = pool_result.included;
6364                // System deposits are generated per block rather than taken from the pool. Report
6365                // only pool transactions so the pool does not treat every block as progress.
6366                #[cfg(feature = "base")]
6367                included.drain(..system_transaction_count);
6368                let stale = pool_result.stale;
6369                let invalid = pool_result.invalid;
6370                let not_yet_valid = pool_result.not_yet_valid;
6371
6372                let CacheDB { cache, db: _ } = candidate_db.0;
6373                let parent_state = self
6374                    .prune_state_history_config
6375                    .is_state_history_supported()
6376                    .then(|| db.current_state());
6377                commit_cache(&mut **db, cache)?;
6378                let state_root = db.maybe_state_root().unwrap_or_default();
6379                let block_info = self.build_block_info(
6380                    &mining_evm_env,
6381                    best_hash,
6382                    block_number,
6383                    state_root,
6384                    block_result,
6385                    pool_result.txs,
6386                    pool_result.tx_info,
6387                    parent_beacon_block_root,
6388                    block_access_list.as_ref(),
6389                );
6390
6391                // Update the new blockhash in the db itself.
6392                let block_hash = block_info.block.header.hash_slow();
6393                db.insert_block_hash(U256::from(block_info.block.header.number()), block_hash);
6394                self.time.commit_next_timestamp(pending_timestamp);
6395                self.cheats.consume_next_block_overrides(&next_block);
6396
6397                let header = &block_info.block.header;
6398                let next_block_base_fee = self.fees.get_next_block_base_fee_from_header(header);
6399                let next_block_excess_blob_gas = self.networks.next_block_blob_excess_gas(
6400                    self.fees.blob_params(),
6401                    header.excess_blob_gas.unwrap_or_default(),
6402                    header.blob_gas_used.unwrap_or_default(),
6403                    header.base_fee_per_gas.unwrap_or_default(),
6404                );
6405                let next_block_blob_fees = BlobExcessGasAndPrice::new(
6406                    next_block_excess_blob_gas,
6407                    self.fees.blob_params().update_fraction as u64,
6408                );
6409
6410                (
6411                    db,
6412                    block_info,
6413                    included,
6414                    stale,
6415                    invalid,
6416                    not_yet_valid,
6417                    block_hash,
6418                    parent_state,
6419                    next_block_base_fee,
6420                    next_block_blob_fees,
6421                    block_access_list,
6422                )
6423            };
6424
6425            // create the new block with the current timestamp
6426            let BlockInfo { block, transactions, receipts } = block_info;
6427
6428            let header = block.header.clone();
6429            #[cfg(feature = "monad")]
6430            let monad_participants = self.is_monad().then(|| {
6431                let tx_envs = included
6432                    .iter()
6433                    .map(|pool_tx| {
6434                        build_tx_env_for_pending::<FoundryTxEnvelope, TxEnv>(
6435                            &pool_tx.pending_transaction,
6436                            self.cheats(),
6437                        )
6438                    })
6439                    .collect::<Vec<_>>();
6440                foundry_evm::core::evm::monad_block_participants(&tx_envs)
6441            });
6442
6443            if let Some(parent_state) = parent_state {
6444                self.states.write().insert(best_hash, parent_state);
6445            }
6446
6447            trace!(
6448                target: "backend",
6449                "Mined block {} with {} tx {:?}",
6450                block_number,
6451                transactions.len(),
6452                transactions.iter().map(|tx| tx.transaction_hash).collect::<Vec<_>>()
6453            );
6454            #[cfg(test)]
6455            let mining_commit_hook = { self.mining_commit_hook.lock().clone() };
6456            #[cfg(test)]
6457            if let Some(hook) = mining_commit_hook {
6458                hook.reached.notify_one();
6459                hook.resume.notified().await;
6460            }
6461            let mut storage = self.blockchain.storage.write();
6462            // update block metadata
6463            storage.best_number = block_number;
6464            storage.best_hash = block_hash;
6465            // Difficulty is removed and not used after Paris (aka TheMerge). Value is replaced with
6466            // prevrandao. https://github.com/bluealloy/revm/blob/1839b3fce8eaeebb85025576f2519b80615aca1e/crates/interpreter/src/instructions/host_env.rs#L27
6467            if !self.is_eip3675() {
6468                storage.total_difficulty =
6469                    storage.total_difficulty.saturating_add(header.difficulty);
6470            }
6471
6472            storage.blocks.insert(block_hash, block);
6473            storage.hashes.insert(block_number, block_hash);
6474            if let Some(block_access_list) = block_access_list {
6475                storage.block_access_lists.insert(block_hash, block_access_list);
6476            }
6477            #[cfg(feature = "monad")]
6478            if let Some(participants) = monad_participants {
6479                monad::store_block_metadata(
6480                    &mut storage,
6481                    block_hash,
6482                    participants,
6483                    execution_chain_id,
6484                    hardfork,
6485                );
6486            }
6487
6488            node_info!("");
6489            // insert all transactions
6490            for (info, receipt) in transactions.into_iter().zip(receipts) {
6491                // log some tx info
6492                node_info!("    Transaction: {:?}", info.transaction_hash);
6493                if let Some(contract) = &info.contract_address {
6494                    node_info!("    Contract created: {contract}");
6495                }
6496                node_info!("    Gas used: {}", info.gas_used);
6497                if !info.exit.is_ok() {
6498                    let r = RevertDecoder::new().decode(
6499                        info.out.as_ref().map(|b| &b[..]).unwrap_or_default(),
6500                        Some(info.exit),
6501                    );
6502                    node_info!("    Error: reverted with: {r}");
6503                }
6504                node_info!("");
6505
6506                let mined_tx = MinedTransaction { info, receipt, block_hash, block_number };
6507                storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx);
6508            }
6509
6510            // remove old transactions that exceed the transaction block keeper
6511            if let Some(transaction_block_keeper) = self.transaction_block_keeper
6512                && storage.blocks.len() > transaction_block_keeper
6513            {
6514                let to_clear = block_number
6515                    .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX));
6516                storage.remove_block_transactions_by_number(to_clear)
6517            }
6518
6519            // Live execution readers acquire the database read lock before cloning the block
6520            // environment, next-block fee state, and canonical head. Publish the complete snapshot
6521            // while retaining the database write lock so readers observe either the parent or newly
6522            // mined state. The database is always the outer lock, and no path holds a storage,
6523            // environment, or fee guard while waiting for it, so these acquisitions cannot form a
6524            // lock cycle.
6525            evm_env.block_env.difficulty = U256::ZERO;
6526            *self.evm_env.write() = evm_env;
6527            self.fees.set_next_block_fees(next_block_base_fee, next_block_blob_fees);
6528            drop(storage);
6529            drop(db_guard);
6530
6531            self.time.mark_block_created();
6532
6533            let timestamp = utc_from_secs(header.timestamp);
6534
6535            node_info!("    Block Number: {}", block_number);
6536            node_info!("    Block Hash: {:?}", block_hash);
6537            if timestamp.year() > 9999 {
6538                // rf2822 panics with more than 4 digits
6539                node_info!("    Block Time: {:?}\n", timestamp.to_rfc3339());
6540            } else {
6541                node_info!("    Block Time: {:?}\n", timestamp.to_rfc2822());
6542            }
6543
6544            let outcome =
6545                MinedBlockOutcome { block_number, included, stale, invalid, not_yet_valid };
6546
6547            (outcome, header, block_hash)
6548        };
6549        // notify all listeners
6550        self.notify_on_new_block(header.into_inner(), block_hash);
6551
6552        Ok(outcome)
6553    }
6554
6555    /// Mines replacement blocks after the caller has rewound the chain for a reorg.
6556    ///
6557    /// The caller must hold the mining lock across ancestor selection, rollback, and this method.
6558    pub async fn reorg(
6559        &self,
6560        depth: u64,
6561        tx_pairs: HashMap<u64, Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>>,
6562        _mining_guard: &tokio::sync::MutexGuard<'_, ()>,
6563    ) -> Result<(), BlockchainError> {
6564        // Create the new reorged chain, filling the blocks with transactions if supplied
6565        for i in 0..depth {
6566            let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new);
6567            let outcome = self.do_mine_block_locked(to_be_mined).await?;
6568            node_info!(
6569                "    Mined reorg block number {}. With {} valid txs and with invalid {} txs",
6570                outcome.block_number,
6571                outcome.included.len(),
6572                outcome.invalid.len()
6573            );
6574        }
6575
6576        Ok(())
6577    }
6578
6579    /// Creates the pending block
6580    ///
6581    /// This will execute all transaction in the order they come but will not mine the block
6582    pub async fn pending_block(
6583        &self,
6584        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
6585    ) -> Result<BlockInfo<N>, BlockchainError> {
6586        self.with_pending_block(pool_transactions, |_, block| block).await
6587    }
6588
6589    /// Creates the pending block
6590    ///
6591    /// This will execute all transaction in the order they come but will not mine the block
6592    pub async fn with_pending_block<F, T>(
6593        &self,
6594        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
6595        f: F,
6596    ) -> Result<T, BlockchainError>
6597    where
6598        F: FnOnce(Box<dyn MaybeFullDatabase + '_>, BlockInfo<N>) -> T,
6599    {
6600        let db = self.db.read().await;
6601        let evm_env = self.next_evm_env(&**db)?;
6602
6603        let mut cache_db = AnvilCacheDB::new(&*db, *evm_env.spec_id());
6604
6605        let parent_hash = self.blockchain.storage.read().best_hash;
6606        let block_number = self.best_number().saturating_add(1);
6607
6608        #[cfg(feature = "base")]
6609        let (pool_transactions, system_transaction_count) = {
6610            let mut transactions = self.base_system_transactions(
6611                &**db,
6612                block_number,
6613                evm_env.block_env.timestamp.saturating_to(),
6614                parent_hash,
6615            )?;
6616            let system_transaction_count = transactions.len();
6617            transactions.extend(pool_transactions);
6618            (transactions, system_transaction_count)
6619        };
6620        // Read the next block's beacon root override without consuming it, so the pending block
6621        // sees the root the next mined block will use.
6622        let parent_beacon_block_root = Some(
6623            self.cheats.next_block_overrides().parent_beacon_block_root.value.unwrap_or_default(),
6624        );
6625
6626        let inspector_tx_config = self.inspector_tx_config();
6627        let gas_config = self.pool_tx_gas_config(&evm_env);
6628
6629        let (pool_result, block_result) = self.execute_with_block_executor(
6630            &mut cache_db,
6631            &evm_env,
6632            parent_hash,
6633            self.hardfork(),
6634            parent_beacon_block_root,
6635            BlockExecutionKind::Complete,
6636            &pool_transactions,
6637            &gas_config,
6638            &inspector_tx_config,
6639            &|pool_tx, account| {
6640                self.validate_pool_transaction_for(&pool_tx.pending_transaction, account, &evm_env)
6641            },
6642        )?;
6643
6644        #[cfg(feature = "base")]
6645        base::validate_system_transactions(
6646            &pool_transactions[..system_transaction_count],
6647            &pool_result,
6648        )?;
6649
6650        // Extract inner CacheDB (which implements MaybeFullDatabase)
6651        let cache_db = cache_db.0;
6652
6653        let state_root = cache_db.maybe_state_root().unwrap_or_default();
6654        let block_info = self.build_block_info(
6655            &evm_env,
6656            parent_hash,
6657            block_number,
6658            state_root,
6659            block_result,
6660            pool_result.txs,
6661            pool_result.tx_info,
6662            parent_beacon_block_root,
6663            None,
6664        );
6665
6666        Ok(f(Box::new(cache_db), block_info))
6667    }
6668
6669    /// Returns the ERC20/TIP20 token balance for an account.
6670    ///
6671    /// Calls `balanceOf(address)` on the token contract. Returns `U256::ZERO` if
6672    /// the call fails (e.g. the token contract doesn't exist).
6673    pub async fn get_fee_token_balance(
6674        &self,
6675        token: Address,
6676        account: Address,
6677    ) -> Result<U256, BlockchainError> {
6678        // balanceOf(address) selector: 0x70a08231
6679        let mut calldata = vec![0x70, 0xa0, 0x82, 0x31];
6680        // ABI-encode the address (left-padded to 32 bytes)
6681        calldata.extend_from_slice(&[0u8; 12]);
6682        calldata.extend_from_slice(account.as_slice());
6683
6684        let request = WithOtherFields::new(TransactionRequest {
6685            from: Some(Address::ZERO),
6686            to: Some(TxKind::Call(token)),
6687            input: calldata.into(),
6688            ..Default::default()
6689        });
6690
6691        let fee_details = FeeDetails::zero();
6692        let (exit, out, _, _) = self.call(request, fee_details, None, Default::default()).await?;
6693
6694        // Check if call succeeded
6695        if exit != InstructionResult::Return && exit != InstructionResult::Stop {
6696            // Return zero balance if call failed (token might not exist)
6697            return Ok(U256::ZERO);
6698        }
6699
6700        // Decode U256 from output
6701        match out {
6702            Some(Output::Call(data)) if data.len() >= 32 => Ok(U256::from_be_slice(&data[..32])),
6703            _ => Ok(U256::ZERO),
6704        }
6705    }
6706
6707    /// Returns the account used to sponsor Tempo fee-payer requests handled by this node.
6708    ///
6709    /// Returns `None` on non-Tempo networks.
6710    pub async fn tempo_fee_payer(&self) -> Option<Address> {
6711        if !self.is_tempo() {
6712            return None;
6713        }
6714        self.node_config.read().await.tempo_fee_payer_address()
6715    }
6716
6717    /// Returns the fee token an account pays with, as stored in the Tempo fee manager.
6718    ///
6719    /// Falls back to PathUSD when the account has no stored preference or the lookup fails.
6720    pub async fn tempo_user_fee_token(&self, account: Address) -> Result<Address, BlockchainError> {
6721        let calldata = IFeeManager::userTokensCall { user: account }.abi_encode();
6722
6723        let request = WithOtherFields::new(TransactionRequest {
6724            from: Some(Address::ZERO),
6725            to: Some(TxKind::Call(TIP_FEE_MANAGER_ADDRESS)),
6726            input: calldata.into(),
6727            ..Default::default()
6728        });
6729
6730        let (exit, out, _, _) =
6731            self.call(request, FeeDetails::zero(), None, Default::default()).await?;
6732
6733        let token = if exit == InstructionResult::Return
6734            && let Some(Output::Call(data)) = out
6735        {
6736            IFeeManager::userTokensCall::abi_decode_returns(&data).unwrap_or(Address::ZERO)
6737        } else {
6738            Address::ZERO
6739        };
6740
6741        if token.is_zero() {
6742            return Ok(foundry_evm::core::tempo::PATH_USD_ADDRESS);
6743        }
6744        Ok(token)
6745    }
6746
6747    /// Executes the [TransactionRequest] without writing to the DB
6748    ///
6749    /// # Errors
6750    ///
6751    /// Returns an error if the `block_number` is greater than the current height
6752    pub async fn call(
6753        &self,
6754        request: WithOtherFields<TransactionRequest>,
6755        fee_details: FeeDetails,
6756        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6757        overrides: EvmOverrides,
6758    ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
6759        self.with_database_at_and_context(block_request, |state, mut block, monad_context| {
6760            let block_number = block.number;
6761            let (exit, out, gas, state) = {
6762                let mut cache_db = CacheDB::new(state);
6763                if let Some(state_overrides) = overrides.state {
6764                    apply_state_overrides(state_overrides.into_iter().collect(), &mut cache_db)?;
6765                }
6766                if let Some(block_overrides) = overrides.block {
6767                    cache_db.apply_block_overrides(*block_overrides, &mut block);
6768                }
6769                self.call_with_state_and_context(
6770                    &cache_db,
6771                    request,
6772                    fee_details,
6773                    block,
6774                    monad_context,
6775                )
6776            }?;
6777            trace!(target: "backend", "call return {:?} out: {:?} gas {} on block {}", exit, out, gas, block_number);
6778            Ok((exit, out, gas, state))
6779        })
6780        .await
6781    }
6782
6783    pub async fn call_with_tracing(
6784        &self,
6785        request: WithOtherFields<TransactionRequest>,
6786        fee_details: FeeDetails,
6787        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6788        opts: GethDebugTracingCallOptions,
6789    ) -> Result<GethTrace, BlockchainError> {
6790        let GethDebugTracingCallOptions {
6791            tracing_options,
6792            block_overrides,
6793            state_overrides,
6794            tx_index,
6795        } = opts;
6796
6797        if let Some(tx_index) = tx_index {
6798            return self
6799                .call_with_tracing_at_tx_index(
6800                    request,
6801                    fee_details,
6802                    block_request,
6803                    tx_index,
6804                    tracing_options,
6805                    state_overrides,
6806                    block_overrides,
6807                )
6808                .await;
6809        }
6810
6811        self.with_database_at_and_context(block_request, |state, block, monad_context| {
6812            let cache_db = CacheDB::new(state);
6813            self.trace_call_with_state(
6814                request,
6815                fee_details,
6816                block,
6817                cache_db,
6818                tracing_options,
6819                state_overrides,
6820                block_overrides,
6821                monad_context,
6822                None,
6823            )
6824        })
6825        .await
6826    }
6827
6828    #[allow(clippy::too_many_arguments)]
6829    async fn call_with_tracing_at_tx_index(
6830        &self,
6831        request: WithOtherFields<TransactionRequest>,
6832        fee_details: FeeDetails,
6833        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6834        tx_index: u64,
6835        tracing_options: GethDebugTracingOptions,
6836        state_overrides: Option<StateOverride>,
6837        block_overrides: Option<BlockOverrides>,
6838    ) -> Result<GethTrace, BlockchainError> {
6839        let tx_index = usize::try_from(tx_index).map_err(|_| {
6840            BlockchainError::RpcError(RpcError::invalid_params(format!(
6841                "tx_index {tx_index} does not fit in usize"
6842            )))
6843        })?;
6844        let block_number = match block_request {
6845            Some(BlockRequest::Pending(_)) => {
6846                return Err(BlockchainError::RpcError(RpcError::invalid_params(
6847                    "tx_index is not supported for pending blocks".to_string(),
6848                )));
6849            }
6850            Some(BlockRequest::Number(number)) => number,
6851            None => self.best_number(),
6852        };
6853        let block_id = BlockId::number(block_number);
6854
6855        if let Some(block) = self.get_block(block_id) {
6856            return self.mined_trace_call_at_tx_index(
6857                request,
6858                fee_details,
6859                &block,
6860                tx_index,
6861                tracing_options,
6862                state_overrides,
6863                block_overrides,
6864            );
6865        }
6866
6867        if let Some(fork) = self.get_fork()
6868            && fork.predates_fork_inclusive(block_number)
6869        {
6870            let opts = GethDebugTracingCallOptions {
6871                tracing_options,
6872                state_overrides,
6873                block_overrides,
6874                tx_index: Some(tx_index as u64),
6875            };
6876            return Ok(fork.debug_trace_call(request, block_id, opts).await?);
6877        }
6878
6879        Err(BlockchainError::BlockNotFound)
6880    }
6881
6882    #[allow(clippy::too_many_arguments)]
6883    fn mined_trace_call_at_tx_index(
6884        &self,
6885        request: WithOtherFields<TransactionRequest>,
6886        fee_details: FeeDetails,
6887        block: &Block,
6888        tx_index: usize,
6889        tracing_options: GethDebugTracingOptions,
6890        state_overrides: Option<StateOverride>,
6891        block_overrides: Option<BlockOverrides>,
6892    ) -> Result<GethTrace, BlockchainError> {
6893        let transaction_count = block.body.transactions.len();
6894        if tx_index >= transaction_count {
6895            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
6896                "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
6897            ))));
6898        }
6899
6900        let trace = |parent_state: &StateDb| -> Result<GethTrace, BlockchainError> {
6901            let db = Box::new(parent_state) as Box<dyn MaybeFullDatabase + '_>;
6902            let (mut cache_db, evm_env, hardfork) = self.prepare_block_replay_with_db(block, db)?;
6903            self.replay_mined_transaction_prefix(
6904                &mut cache_db,
6905                &evm_env,
6906                hardfork,
6907                block,
6908                tx_index,
6909            )?;
6910            self.trace_call_with_state(
6911                request,
6912                fee_details,
6913                evm_env.block_env.clone(),
6914                cache_db,
6915                tracing_options,
6916                state_overrides,
6917                block_overrides,
6918                self.active_monad_context_before_mined_transaction(block, tx_index)?,
6919                Some((evm_env, hardfork)),
6920            )
6921        };
6922
6923        let read_guard = self.states.upgradable_read();
6924        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
6925            trace(state)
6926        } else {
6927            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6928            let state = write_guard
6929                .get_on_disk_state(&block.header.parent_hash)
6930                .ok_or(BlockchainError::HistoricalStateUnavailable(block.header.number()))?;
6931            trace(state)
6932        }
6933    }
6934
6935    #[allow(clippy::too_many_arguments)]
6936    fn trace_call_with_state(
6937        &self,
6938        request: WithOtherFields<TransactionRequest>,
6939        fee_details: FeeDetails,
6940        mut block: BlockEnv,
6941        mut cache_db: CacheDB<Box<dyn MaybeFullDatabase + '_>>,
6942        tracing_options: GethDebugTracingOptions,
6943        state_overrides: Option<StateOverride>,
6944        block_overrides: Option<BlockOverrides>,
6945        mut monad_context: Option<MonadReplayContext>,
6946        historical_execution: Option<(EvmEnv, FoundryHardfork)>,
6947    ) -> Result<GethTrace, BlockchainError> {
6948        let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options;
6949        let block_number = block.number;
6950        let base_evm_env = historical_execution.as_ref().map(|(evm_env, _)| evm_env);
6951        let hardfork = historical_execution
6952            .as_ref()
6953            .map(|(_, hardfork)| *hardfork)
6954            .unwrap_or_else(|| self.hardfork());
6955
6956        if let Some(state_overrides) = state_overrides {
6957            apply_state_overrides(state_overrides, &mut cache_db)?;
6958        }
6959        if let Some(block_overrides) = block_overrides {
6960            cache_db.apply_block_overrides(block_overrides, &mut block);
6961        }
6962
6963        if let Some(tracer) = tracer {
6964            return match tracer {
6965                GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
6966                    GethDebugBuiltInTracerType::CallTracer => {
6967                        let call_config = call_config_from_tracer_config(tracer_config)
6968                            .map_err(|e| RpcError::invalid_params(e.to_string()))?;
6969
6970                        let mut inspector = self.build_inspector().with_tracing_config(
6971                            TracingInspectorConfig::from_geth_call_config(&call_config),
6972                        );
6973
6974                        let PreparedCall { evm_env, tx_env, .. } = self
6975                            .prepare_call_env_from_base(
6976                                &cache_db,
6977                                request,
6978                                fee_details,
6979                                block,
6980                                base_evm_env,
6981                            )?;
6982                        let ResultAndState { result, state: _ } = self
6983                            .transact_call_with_inspector_ref_at_hardfork(
6984                                &cache_db,
6985                                &evm_env,
6986                                &mut inspector,
6987                                tx_env,
6988                                monad_context.as_mut().map(next_monad_context),
6989                                hardfork,
6990                            )?;
6991
6992                        inspector.print_logs();
6993                        if self.print_traces {
6994                            inspector.print_traces(self.call_trace_decoder());
6995                        }
6996
6997                        let tracing_inspector = inspector.tracer.expect("tracer disappeared");
6998
6999                        Ok(tracing_inspector
7000                            .into_geth_builder()
7001                            .geth_call_traces(call_config, result.tx_gas_used())
7002                            .into())
7003                    }
7004                    GethDebugBuiltInTracerType::PreStateTracer => {
7005                        let pre_state_config = tracer_config
7006                            .into_pre_state_config()
7007                            .map_err(|e| RpcError::invalid_params(e.to_string()))?;
7008
7009                        let mut inspector = TracingInspector::new(
7010                            TracingInspectorConfig::from_geth_prestate_config(&pre_state_config),
7011                        );
7012
7013                        let PreparedCall { evm_env, tx_env, .. } = self
7014                            .prepare_call_env_from_base(
7015                                &cache_db,
7016                                request,
7017                                fee_details,
7018                                block,
7019                                base_evm_env,
7020                            )?;
7021                        let result = self.transact_call_with_inspector_ref_at_hardfork(
7022                            &cache_db,
7023                            &evm_env,
7024                            &mut inspector,
7025                            tx_env,
7026                            monad_context.as_mut().map(next_monad_context),
7027                            hardfork,
7028                        )?;
7029
7030                        Ok(inspector
7031                            .into_geth_builder()
7032                            .geth_prestate_traces(&result, &pre_state_config, cache_db)?
7033                            .into())
7034                    }
7035                    GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()),
7036                    GethDebugBuiltInTracerType::FourByteTracer
7037                    | GethDebugBuiltInTracerType::MuxTracer
7038                    | GethDebugBuiltInTracerType::FlatCallTracer
7039                    | GethDebugBuiltInTracerType::Erc7562Tracer
7040                    | GethDebugBuiltInTracerType::StateGasTracer => {
7041                        Err(RpcError::invalid_params("unsupported tracer type").into())
7042                    }
7043                },
7044                #[cfg(not(feature = "js-tracer"))]
7045                GethDebugTracerType::JsTracer(_) => {
7046                    Err(RpcError::invalid_params("unsupported tracer type").into())
7047                }
7048                #[cfg(feature = "js-tracer")]
7049                GethDebugTracerType::JsTracer(code) => {
7050                    let config = tracer_config.into_json();
7051                    let mut inspector =
7052                        revm_inspectors::tracing::js::JsInspector::new(code, config)
7053                            .map_err(|err| BlockchainError::Message(err.to_string()))?;
7054
7055                    let PreparedCall { evm_env, tx_env, .. } = self.prepare_call_env_from_base(
7056                        &cache_db,
7057                        request,
7058                        fee_details,
7059                        block.clone(),
7060                        base_evm_env,
7061                    )?;
7062                    let result = self.transact_call_with_inspector_ref_at_hardfork(
7063                        &cache_db,
7064                        &evm_env,
7065                        &mut inspector,
7066                        tx_env.clone(),
7067                        monad_context.as_mut().map(next_monad_context),
7068                        hardfork,
7069                    )?;
7070                    let res = inspector
7071                        .json_result(result, tx_env.base(), &block, &cache_db)
7072                        .map_err(|err| BlockchainError::Message(err.to_string()))?;
7073
7074                    Ok(GethTrace::JS(res))
7075                }
7076            };
7077        }
7078
7079        // defaults to StructLog tracer used since no tracer is specified
7080        let mut inspector = self
7081            .build_inspector()
7082            .with_tracing_config(TracingInspectorConfig::from_geth_config(&config));
7083
7084        let PreparedCall { evm_env, tx_env, .. } =
7085            self.prepare_call_env_from_base(&cache_db, request, fee_details, block, base_evm_env)?;
7086        let ResultAndState { result, state: _ } = self
7087            .transact_call_with_inspector_ref_at_hardfork(
7088                &cache_db,
7089                &evm_env,
7090                &mut inspector,
7091                tx_env,
7092                monad_context.as_mut().map(next_monad_context),
7093                hardfork,
7094            )?;
7095
7096        let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
7097
7098        let tracing_inspector = inspector.tracer.expect("tracer disappeared");
7099        let return_value = out.as_ref().map(|o| o.data()).cloned().unwrap_or_default();
7100
7101        trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call");
7102
7103        let res = tracing_inspector
7104            .into_geth_builder()
7105            .geth_traces(gas_used, return_value, config)
7106            .into();
7107
7108        Ok(res)
7109    }
7110
7111    /// Helper function to execute a closure with the database at a specific block
7112    pub async fn with_database_at<F, T>(
7113        &self,
7114        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7115        f: F,
7116    ) -> Result<T, BlockchainError>
7117    where
7118        F: FnOnce(Box<dyn MaybeFullDatabase + '_>, BlockEnv) -> T,
7119    {
7120        let block_number = match block_request {
7121            Some(BlockRequest::Pending(pool_transactions)) => {
7122                let result = self
7123                    .with_pending_block(pool_transactions, |state, block| {
7124                        let block = block.block;
7125                        f(state, self.block_env_from_header(&block.header))
7126                    })
7127                    .await?;
7128                return Ok(result);
7129            }
7130            Some(BlockRequest::Number(bn)) => Some(BlockNumber::Number(bn)),
7131            None => None,
7132        };
7133        let block_number = self.convert_block_number(block_number);
7134        let current_number = self.best_number();
7135
7136        // Reject requests for future blocks that don't exist yet
7137        if block_number > current_number {
7138            return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
7139        }
7140
7141        // The head can advance while waiting for the database lock. Serve the live state only if
7142        // the requested block is still the head or no state history is kept. Otherwise hold the
7143        // lock so the requested block's state cannot be pruned before it is read below.
7144        let _head_guard = if block_number == current_number {
7145            let db = self.db.read().await;
7146            if self.best_number() == block_number
7147                || !self.prune_state_history_config.is_state_history_supported()
7148            {
7149                let block = self.evm_env.read().block_env.clone();
7150                return Ok(f(Box::new(&**db), block));
7151            }
7152            Some(db)
7153        } else {
7154            None
7155        };
7156
7157        if let Some((block_hash, block)) = self
7158            .block_by_number(BlockNumber::Number(block_number))
7159            .await?
7160            .map(|block| (block.header.hash, block))
7161        {
7162            let read_guard = self.states.upgradable_read();
7163            if let Some(state_db) = read_guard.get_state(&block_hash) {
7164                return Ok(f(Box::new(state_db), self.block_env_from_header(&block.header)));
7165            }
7166
7167            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
7168            if let Some(state) = write_guard.get_on_disk_state(&block_hash) {
7169                return Ok(f(Box::new(state), self.block_env_from_header(&block.header)));
7170            }
7171        }
7172
7173        warn!(target: "backend", "Not historic state found for block={}", block_number);
7174        Err(BlockchainError::BlockOutOfRange(self.best_number(), block_number))
7175    }
7176
7177    /// Executes a closure with both state and network context at a specific block.
7178    pub(crate) async fn with_database_at_and_context<F, T>(
7179        &self,
7180        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7181        f: F,
7182    ) -> Result<T, BlockchainError>
7183    where
7184        F: FnOnce(
7185            Box<dyn MaybeFullDatabase + '_>,
7186            BlockEnv,
7187            Option<MonadReplayContext>,
7188        ) -> Result<T, BlockchainError>,
7189    {
7190        let block_number = match block_request {
7191            Some(BlockRequest::Pending(pool_transactions)) => {
7192                return self
7193                    .with_pending_block(pool_transactions, |state, block_info| {
7194                        let context = self.active_monad_context_before_mined_transaction(
7195                            &block_info.block,
7196                            block_info.block.body.transactions.len(),
7197                        )?;
7198                        let block_env = self.block_env_from_header(&block_info.block.header);
7199                        f(state, block_env, context)
7200                    })
7201                    .await?;
7202            }
7203            Some(BlockRequest::Number(number)) => Some(BlockNumber::Number(number)),
7204            None => None,
7205        };
7206        let block_number = self.convert_block_number(block_number);
7207        let current_number = self.best_number();
7208
7209        if block_number > current_number {
7210            return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
7211        }
7212
7213        #[cfg(feature = "monad")]
7214        let context = if self.is_monad() {
7215            Some(self.monad_context_for_child_of_block_number(block_number).await?)
7216        } else {
7217            None
7218        };
7219        #[cfg(not(feature = "monad"))]
7220        let context = None;
7221
7222        // See `with_database_at`: re-check the head under the read guard.
7223        let _head_guard = if block_number == current_number {
7224            let db = self.db.read().await;
7225            if self.best_number() == block_number
7226                || !self.prune_state_history_config.is_state_history_supported()
7227            {
7228                let block = self.evm_env.read().block_env.clone();
7229                return f(Box::new(&**db), block, context);
7230            }
7231            Some(db)
7232        } else {
7233            None
7234        };
7235
7236        if let Some((block_hash, block)) = self
7237            .block_by_number(BlockNumber::Number(block_number))
7238            .await?
7239            .map(|block| (block.header.hash, block))
7240        {
7241            let read_guard = self.states.upgradable_read();
7242            if let Some(state_db) = read_guard.get_state(&block_hash) {
7243                return f(Box::new(state_db), self.block_env_from_header(&block.header), context);
7244            }
7245
7246            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
7247            if let Some(state) = write_guard.get_on_disk_state(&block_hash) {
7248                return f(Box::new(state), self.block_env_from_header(&block.header), context);
7249            }
7250        }
7251
7252        warn!(target: "backend", "Not historic state found for block={}", block_number);
7253        Err(BlockchainError::BlockOutOfRange(self.best_number(), block_number))
7254    }
7255
7256    pub async fn storage_at(
7257        &self,
7258        address: Address,
7259        index: U256,
7260        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7261    ) -> Result<B256, BlockchainError> {
7262        self.with_database_at(block_request, |db, _| {
7263            trace!(target: "backend", "get storage for {:?} at {:?}", address, index);
7264            let val = db.storage_ref(address, index)?;
7265            Ok(val.into())
7266        })
7267        .await?
7268    }
7269
7270    pub async fn tempo_nonce(
7271        &self,
7272        caller: Address,
7273        nonce_key: U256,
7274        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7275    ) -> Result<u64, BlockchainError> {
7276        self.with_database_at(block_request, |state, _| tempo_nonce(&state, caller, nonce_key))
7277            .await?
7278    }
7279
7280    /// Returns storage values for multiple accounts and slots in a single call.
7281    pub async fn storage_values(
7282        &self,
7283        requests: HashMap<Address, Vec<B256>>,
7284        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7285    ) -> Result<HashMap<Address, Vec<B256>>, BlockchainError> {
7286        self.with_database_at(block_request, |db, _| {
7287            trace!(target: "backend", "get storage values for {} addresses", requests.len());
7288            let mut result: HashMap<Address, Vec<B256>> = HashMap::default();
7289            for (address, slots) in &requests {
7290                let mut values = Vec::with_capacity(slots.len());
7291                for slot in slots {
7292                    let val = db.storage_ref(*address, (*slot).into())?;
7293                    values.push(val.into());
7294                }
7295                result.insert(*address, values);
7296            }
7297            Ok(result)
7298        })
7299        .await?
7300    }
7301
7302    /// Returns the code of the address
7303    ///
7304    /// If the code is not present and fork mode is enabled then this will try to fetch it from the
7305    /// forked client
7306    pub async fn get_code(
7307        &self,
7308        address: Address,
7309        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7310    ) -> Result<Bytes, BlockchainError> {
7311        self.with_database_at(block_request, |db, _| self.get_code_with_state(&db, address)).await?
7312    }
7313
7314    /// Returns the balance of the address
7315    ///
7316    /// If the requested number predates the fork then this will fetch it from the endpoint
7317    pub async fn get_balance(
7318        &self,
7319        address: Address,
7320        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7321    ) -> Result<U256, BlockchainError> {
7322        self.with_database_at(block_request, |db, _| self.get_balance_with_state(db, address))
7323            .await?
7324    }
7325
7326    pub async fn get_account_at_block(
7327        &self,
7328        address: Address,
7329        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7330    ) -> Result<TrieAccount, BlockchainError> {
7331        self.with_database_at(block_request, |block_db, _| {
7332            let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
7333            let account = db.get(&address).cloned().unwrap_or_default();
7334            let storage_root = storage_root(&account.storage);
7335            let code_hash = account.info.code_hash();
7336            let balance = account.info.balance;
7337            let nonce = account.info.nonce;
7338            Ok(TrieAccount { balance, nonce, code_hash, storage_root })
7339        })
7340        .await?
7341    }
7342
7343    pub async fn get_account_info_at_block(
7344        &self,
7345        address: Address,
7346        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7347    ) -> Result<RpcAccountInfo, BlockchainError> {
7348        self.with_database_at(block_request, |db, _| {
7349            let account = db.basic_ref(address)?.unwrap_or_default();
7350            let code = self.get_code_with_state(&db, address)?;
7351            Ok(RpcAccountInfo { balance: account.balance, nonce: account.nonce, code })
7352        })
7353        .await?
7354    }
7355
7356    /// Returns the nonce of the address
7357    ///
7358    /// If the requested number predates the fork then this will fetch it from the endpoint
7359    pub async fn get_nonce(
7360        &self,
7361        address: Address,
7362        block_request: BlockRequest<FoundryTxEnvelope>,
7363    ) -> Result<u64, BlockchainError> {
7364        if let BlockRequest::Pending(pool_transactions) = &block_request
7365            && let Some(value) = get_pool_transactions_nonce(pool_transactions, address)
7366        {
7367            return Ok(value);
7368        }
7369        let final_block_request = match block_request {
7370            BlockRequest::Pending(_) => BlockRequest::Number(self.best_number()),
7371            BlockRequest::Number(bn) => BlockRequest::Number(bn),
7372        };
7373
7374        self.with_database_at(Some(final_block_request), |db, _| {
7375            trace!(target: "backend", "get nonce for {:?}", address);
7376            Ok(db.basic_ref(address)?.unwrap_or_default().nonce)
7377        })
7378        .await?
7379    }
7380
7381    /// Traces the transaction with the js tracer
7382    #[cfg(feature = "js-tracer")]
7383    pub async fn trace_tx_with_js_tracer(
7384        &self,
7385        hash: B256,
7386        code: String,
7387        opts: GethDebugTracingOptions,
7388    ) -> Result<GethTrace, BlockchainError> {
7389        let GethDebugTracingOptions { tracer_config, .. } = opts;
7390        let config = tracer_config.into_json();
7391        let inspector = revm_inspectors::tracing::js::JsInspector::new(code, config)
7392            .map_err(|err| BlockchainError::Message(err.to_string()))?;
7393        let trace = self.replay_tx_with_inspector(
7394            hash,
7395            inspector,
7396            |result, cache_db, mut inspector, tx_env, evm_env| {
7397                inspector
7398                    .json_result(
7399                        result,
7400                        &alloy_evm::IntoTxEnv::into_tx_env(tx_env),
7401                        evm_env.block_env(),
7402                        &cache_db,
7403                    )
7404                    .map_err(|e| BlockchainError::Message(e.to_string()))
7405            },
7406        )??;
7407        Ok(GethTrace::JS(trace))
7408    }
7409
7410    /// Prove an account's existence or nonexistence in the state trie.
7411    ///
7412    /// Returns a merkle proof of the account's trie node, `account_key` == keccak(address)
7413    pub async fn prove_account_at(
7414        &self,
7415        address: Address,
7416        keys: Vec<B256>,
7417        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7418    ) -> Result<AccountProof, BlockchainError> {
7419        let block_number = block_request.as_ref().map(|r| r.block_number());
7420
7421        self.with_database_at(block_request, |block_db, _| {
7422            trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number);
7423            let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
7424            let account = db.get(&address).cloned().unwrap_or_default();
7425
7426            let mut builder = HashBuilder::default()
7427                .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))]));
7428
7429            for (key, account) in trie_accounts(db) {
7430                builder.add_leaf(key, &account);
7431            }
7432
7433            let _ = builder.root();
7434
7435            let proof = builder
7436                .take_proof_nodes()
7437                .into_nodes_sorted()
7438                .into_iter()
7439                .map(|(_, v)| v)
7440                .collect();
7441            let (storage_hash, storage_proofs) = prove_storage(&account.storage, &keys);
7442
7443            let account_proof = AccountProof {
7444                address,
7445                balance: account.info.balance,
7446                nonce: account.info.nonce,
7447                code_hash: account.info.code_hash(),
7448                storage_hash,
7449                account_proof: proof,
7450                storage_proof: keys
7451                    .into_iter()
7452                    .zip(storage_proofs)
7453                    .map(|(key, proof)| {
7454                        let storage_key: U256 = key.into();
7455                        let value = account.storage.get(&storage_key).copied().unwrap_or_default();
7456                        StorageProof { key: JsonStorageKey::Hash(key), value, proof }
7457                    })
7458                    .collect(),
7459            };
7460
7461            Ok(account_proof)
7462        })
7463        .await?
7464    }
7465}
7466
7467impl<N: Network> Backend<N>
7468where
7469    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
7470{
7471    /// Returns opcode gas usage for the given transaction.
7472    pub async fn trace_transaction_opcode_gas(
7473        &self,
7474        hash: B256,
7475    ) -> Result<Option<TransactionOpcodeGas>, BlockchainError> {
7476        match self.replay_tx_with_inspector(
7477            hash,
7478            OpcodeGasInspector::new(),
7479            move |_, _, inspector, _, _| TransactionOpcodeGas {
7480                transaction_hash: hash,
7481                opcode_gas: inspector.opcode_gas_iter().collect(),
7482            },
7483        ) {
7484            Ok(trace) => Ok(Some(trace)),
7485            Err(BlockchainError::TransactionNotFound) => {
7486                if let Some(fork) = self.get_fork() {
7487                    return Ok(fork.trace_transaction_opcode_gas(hash).await?);
7488                }
7489
7490                Ok(None)
7491            }
7492            Err(err) => Err(err),
7493        }
7494    }
7495
7496    /// Returns opcode gas usage for all transactions in the given block.
7497    pub async fn trace_block_opcode_gas(
7498        &self,
7499        block_id: BlockId,
7500    ) -> Result<Option<BlockOpcodeGas>, BlockchainError> {
7501        if let Some((block, block_hash)) = self.get_block_with_hash(block_id) {
7502            return self.mined_block_opcode_gas(&block, block_hash).map(Some);
7503        }
7504
7505        if let Some(fork) = self.get_fork() {
7506            let number = self.ensure_block_number(Some(block_id)).await?;
7507            if fork.predates_fork_inclusive(number) {
7508                // Delegate the resolved block number so tags (`latest`/`pending`/`safe`/
7509                // `finalized`) are resolved against the fork's head instead of drifting with
7510                // the upstream chain. Hashes are forwarded unchanged.
7511                let resolved = match block_id {
7512                    BlockId::Hash(_) => block_id,
7513                    _ => BlockId::number(number),
7514                };
7515                return Ok(fork.trace_block_opcode_gas(resolved).await?);
7516            }
7517        }
7518
7519        Err(BlockchainError::BlockNotFound)
7520    }
7521
7522    fn mined_block_opcode_gas(
7523        &self,
7524        block: &Block,
7525        block_hash: B256,
7526    ) -> Result<BlockOpcodeGas, BlockchainError> {
7527        // Genesis has no parent state or protocol pre-execution to replay.
7528        if block.header.number() == self.genesis_number() {
7529            return Ok(BlockOpcodeGas {
7530                block_hash,
7531                block_number: block.header.number(),
7532                transactions: Vec::new(),
7533            });
7534        }
7535
7536        let parent_hash = block.header.parent_hash;
7537
7538        let trace = |parent_state: &StateDb| -> Result<Vec<TransactionOpcodeGas>, BlockchainError> {
7539            let (mut cache_db, evm_env, hardfork) =
7540                self.prepare_block_replay(block, parent_state)?;
7541            let mut transactions = Vec::with_capacity(block.body.transactions.len());
7542            let monad_context = self.active_monad_context_for_mined_block(block)?;
7543
7544            for tx_envelope in &block.body.transactions {
7545                let mut inspector = OpcodeGasInspector::new();
7546                let pending_tx = self.pending_mined_transaction(tx_envelope.clone())?;
7547                let transaction_context =
7548                    monad_execution_context_at(monad_context.as_ref(), transactions.len());
7549                let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
7550                    &cache_db,
7551                    &evm_env,
7552                    &mut inspector,
7553                    &pending_tx,
7554                    EnvelopeExecution::replay(transaction_context, hardfork),
7555                )?;
7556
7557                transactions.push(TransactionOpcodeGas {
7558                    transaction_hash: tx_envelope.hash(),
7559                    opcode_gas: inspector.opcode_gas_iter().collect(),
7560                });
7561
7562                cache_db.commit(result.state);
7563            }
7564
7565            Ok(transactions)
7566        };
7567
7568        let read_guard = self.states.upgradable_read();
7569        let transactions = if let Some(state) = read_guard.get_state(&parent_hash) {
7570            trace(state)?
7571        } else {
7572            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
7573            let state = write_guard
7574                .get_on_disk_state(&parent_hash)
7575                .ok_or(BlockchainError::HistoricalStateUnavailable(block.header.number()))?;
7576            trace(state)?
7577        };
7578
7579        Ok(BlockOpcodeGas { block_hash, block_number: block.header.number(), transactions })
7580    }
7581
7582    /// Returns a best-effort execution witness for the given block, in the same format as reth's
7583    /// `debug_executionWitness`.
7584    ///
7585    /// Anvil does not track which state a block's execution actually touched, so this returns a
7586    /// witness for the entire parent state instead: the RLP encoding of every node of the parent
7587    /// state trie (including all storage tries), all contract codes, and the preimages of all
7588    /// account addresses and storage slots. This is a strict superset of the minimal witness, so
7589    /// stateless re-execution of the block against it works, but the witness size grows with the
7590    /// total state instead of the state accessed by the block.
7591    ///
7592    /// Limitations:
7593    /// - Not supported while forking: only remotely accessed accounts are known locally, and the
7594    ///   locally computed state roots do not match the remote chain's roots.
7595    /// - The parent block's state must still be available in the state history, i.e. it must not
7596    ///   have been discarded via `--prune-history`.
7597    /// - The genesis block has no witness since it has no parent state.
7598    /// - `headers` contains the ancestor headers within the 256 block `BLOCKHASH` window that are
7599    ///   known locally, which may be fewer than 256.
7600    pub async fn debug_execution_witness(
7601        &self,
7602        block: BlockNumber,
7603    ) -> Result<ExecutionWitness, BlockchainError> {
7604        let number = self.convert_block_number(Some(block));
7605        let best = self.best_number();
7606        if number > best {
7607            return Err(BlockchainError::BlockOutOfRange(best, number));
7608        }
7609        let Some(parent) = number.checked_sub(1) else {
7610            return Err(BlockchainError::Message(
7611                "genesis block has no parent state to build a witness from".to_string(),
7612            ));
7613        };
7614
7615        let mut headers = Vec::new();
7616        for ancestor in (number.saturating_sub(BLOCKHASH_HISTORY)..number).rev() {
7617            let Some(block) = self.get_block(ancestor) else { break };
7618            headers.push(alloy_rlp::encode(&block.header).into());
7619        }
7620
7621        self.with_database_at(Some(BlockRequest::Number(parent)), |state, _| {
7622            let Some(accounts) = state.maybe_full_db() else {
7623                return Err(BlockchainError::Message(
7624                    "debug_executionWitness is not supported while forking".to_string(),
7625                ));
7626            };
7627
7628            let (_, nodes) = state_trie_witness(&accounts);
7629            let mut codes = Vec::new();
7630            let mut seen_codes = B256Set::default();
7631            let mut keys = Vec::new();
7632            for (address, account) in &accounts {
7633                keys.push(Bytes::copy_from_slice(address.as_slice()));
7634                for slot in account.storage.keys() {
7635                    keys.push(Bytes::from(slot.to_be_bytes::<32>()));
7636                }
7637                if !account.info.is_empty_code_hash() && seen_codes.insert(account.info.code_hash())
7638                {
7639                    let code = match &account.info.code {
7640                        Some(code) => code.original_bytes(),
7641                        None => state.code_by_hash_ref(account.info.code_hash())?.original_bytes(),
7642                    };
7643                    codes.push(code);
7644                }
7645            }
7646            keys.sort_unstable();
7647            keys.dedup();
7648
7649            Ok(ExecutionWitness { state: nodes, codes, keys, headers })
7650        })
7651        .await?
7652    }
7653
7654    /// Returns account information after replaying a block through the transaction at `tx_index`.
7655    pub async fn debug_account_info_at(
7656        &self,
7657        block_id: BlockId,
7658        tx_index: Index,
7659        address: Address,
7660    ) -> Result<Option<RpcAccountInfo>, BlockchainError> {
7661        if let Some((block, _)) = self.get_block_with_hash(block_id) {
7662            return self.mined_debug_account_info_at(&block, tx_index, address).map(Some);
7663        }
7664
7665        if let Some(fork) = self.get_fork() {
7666            let number = self.ensure_block_number(Some(block_id)).await?;
7667            if fork.predates_fork_inclusive(number) {
7668                // Delegate the resolved block number so tags (`latest`/`pending`/`safe`/
7669                // `finalized`) are resolved against the fork's head instead of drifting with
7670                // the upstream chain. Hashes are forwarded unchanged.
7671                let resolved = match block_id {
7672                    BlockId::Hash(_) => block_id,
7673                    _ => BlockId::number(number),
7674                };
7675                return Ok(fork.debug_account_info_at(resolved, tx_index, address).await?);
7676            }
7677        }
7678
7679        Err(BlockchainError::BlockNotFound)
7680    }
7681
7682    fn mined_debug_account_info_at(
7683        &self,
7684        block: &Block,
7685        tx_index: Index,
7686        address: Address,
7687    ) -> Result<RpcAccountInfo, BlockchainError> {
7688        let tx_index = tx_index.0;
7689        let transaction_count = block.body.transactions.len();
7690        if tx_index >= transaction_count {
7691            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
7692                "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
7693            ))));
7694        }
7695
7696        let trace = |parent_state: &StateDb| -> Result<RpcAccountInfo, BlockchainError> {
7697            let (mut cache_db, evm_env, hardfork) =
7698                self.prepare_block_replay_with_db(block, Box::new(parent_state))?;
7699            self.replay_mined_transaction_prefix(
7700                &mut cache_db,
7701                &evm_env,
7702                hardfork,
7703                block,
7704                tx_index + 1,
7705            )?;
7706            let account = revm::DatabaseRef::basic_ref(&cache_db, address)?.unwrap_or_default();
7707            let code = self.get_code_with_state(&cache_db, address)?;
7708            Ok(RpcAccountInfo { balance: account.balance, nonce: account.nonce, code })
7709        };
7710
7711        let read_guard = self.states.upgradable_read();
7712        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
7713            trace(state)
7714        } else {
7715            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
7716            let state = write_guard
7717                .get_on_disk_state(&block.header.parent_hash)
7718                .ok_or(BlockchainError::HistoricalStateUnavailable(block.header.number()))?;
7719            trace(state)
7720        }
7721    }
7722
7723    /// Rollback the chain to a common height.
7724    ///
7725    /// The state of the chain is rewound using `rewind` to the common block, including the db,
7726    /// storage, and env.
7727    pub async fn rollback(
7728        &self,
7729        common_block: Block,
7730        _mining_guard: &tokio::sync::MutexGuard<'_, ()>,
7731    ) -> Result<(), BlockchainError> {
7732        let hash = common_block.header.hash_slow();
7733
7734        // Get the database at the common block
7735        let (common_state, replace_accounts) = {
7736            let return_state_or_throw_err =
7737                |db: Option<&StateDb>| -> Result<AddressMap<DbAccount>, BlockchainError> {
7738                    let state_db = db.ok_or(BlockchainError::DataUnavailable)?;
7739                    let db_full =
7740                        state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
7741                    Ok(db_full.clone())
7742                };
7743
7744            let read_guard = self.states.upgradable_read();
7745            if let Some(db) = read_guard.get_state(&hash) {
7746                (return_state_or_throw_err(Some(db))?, true)
7747            } else {
7748                let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
7749                (return_state_or_throw_err(write_guard.get_on_disk_state(&hash))?, false)
7750            }
7751        };
7752
7753        // Acquire the only awaited mutation lock before publishing any part of the rewind.
7754        let mut db = self.db.write().await;
7755        {
7756            // Collect the logs of the blocks that are about to be removed from the canonical
7757            // chain, while their transactions and receipts are still in storage
7758            let removed_logs = self.removed_logs_since(common_block.header.number());
7759
7760            // Unwind the storage back to the common ancestor first
7761            let removed_blocks =
7762                self.blockchain.storage.write().unwind_to(common_block.header.number(), hash);
7763
7764            // Clean up in-memory and on-disk states for removed blocks
7765            let removed_hashes: Vec<_> =
7766                removed_blocks.iter().map(|b| b.header.hash_slow()).collect();
7767            self.states.write().remove_block_states(&removed_hashes);
7768
7769            // Notify all log subscriptions and filters about the removed logs, so they receive
7770            // them again marked as removed, before any new chain notifications are emitted
7771            if !removed_logs.is_empty() {
7772                self.notify_on_removed_logs(removed_logs);
7773            }
7774
7775            // Set environment back to common block
7776            let mut env = self.evm_env.write();
7777            env.block_env.number = self.evm_block_number(common_block.header.number());
7778            env.block_env.timestamp = U256::from(common_block.header.timestamp());
7779            env.block_env.gas_limit = common_block.header.gas_limit();
7780            env.block_env.difficulty = common_block.header.difficulty();
7781            env.block_env.prevrandao = common_block.header.mix_hash();
7782
7783            self.time.reset(env.block_env.timestamp.saturating_to());
7784            // drop any pending next-block overrides so they do not leak into a block
7785            self.cheats.clear_next_block_overrides();
7786        }
7787
7788        // Only collect the last 256 block hashes since that's all BLOCKHASH can access.
7789        let block_hashes: Vec<_> = {
7790            let storage = self.blockchain.storage.read();
7791            let min_block = common_block.header.number().saturating_sub(256);
7792            storage
7793                .hashes
7794                .iter()
7795                .filter(|(num, _)| **num >= min_block)
7796                .map(|(&num, &hash)| (num, hash))
7797                .collect()
7798        };
7799
7800        db.clear();
7801
7802        if replace_accounts && let Some(accounts) = db.maybe_as_full_db_mut() {
7803            *accounts = common_state;
7804        } else {
7805            // Insert account info before storage to prevent fork-mode RPC fetches after clear.
7806            for (address, acc) in common_state {
7807                db.insert_account(address, acc.info);
7808                for (key, value) in acc.storage {
7809                    db.set_storage_at(address, key.into(), value.into())?;
7810                }
7811            }
7812        }
7813
7814        // Restore block hashes from blockchain storage (now unwound, contains only valid blocks).
7815        for (block_num, hash) in block_hashes {
7816            db.insert_block_hash(U256::from(block_num), hash);
7817        }
7818
7819        Ok(())
7820    }
7821
7822    /// Returns the traces for the given transaction
7823    pub async fn debug_trace_transaction(
7824        &self,
7825        hash: B256,
7826        opts: GethDebugTracingOptions,
7827    ) -> Result<GethTrace, BlockchainError> {
7828        #[cfg(feature = "js-tracer")]
7829        if let Some(tracer_type) = opts.tracer.as_ref()
7830            && tracer_type.is_js()
7831        {
7832            return self
7833                .trace_tx_with_js_tracer(hash, tracer_type.as_str().to_string(), opts.clone())
7834                .await;
7835        }
7836
7837        if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()).await {
7838            return trace;
7839        }
7840
7841        if let Some(fork) = self.get_fork() {
7842            return Ok(fork.debug_trace_transaction(hash, opts).await?);
7843        }
7844
7845        Err(BlockchainError::TransactionNotFound)
7846    }
7847
7848    /// Returns geth-style traces for all transactions in an RLP-encoded block.
7849    pub async fn debug_trace_block(
7850        &self,
7851        rlp_block: Bytes,
7852        opts: GethDebugTracingOptions,
7853    ) -> Result<Vec<TraceResult>, BlockchainError> {
7854        let mut rlp = rlp_block.as_ref();
7855        let block = Block::<FoundryTxEnvelope>::decode(&mut rlp).map_err(|err| {
7856            BlockchainError::RpcError(RpcError::invalid_params(format!(
7857                "failed to decode block: {err}"
7858            )))
7859        })?;
7860        if !rlp.is_empty() {
7861            return Err(BlockchainError::RpcError(RpcError::invalid_params(
7862                "failed to decode block: trailing bytes".to_string(),
7863            )));
7864        }
7865
7866        self.debug_trace_block_by_hash(block.header.hash_slow(), opts).await
7867    }
7868
7869    /// Returns geth-style traces for all transactions in a block by hash.
7870    pub async fn debug_trace_block_by_hash(
7871        &self,
7872        block_hash: B256,
7873        opts: GethDebugTracingOptions,
7874    ) -> Result<Vec<TraceResult>, BlockchainError> {
7875        if let Some(block) = self.blockchain.get_block_by_hash(&block_hash) {
7876            return Ok(self.debug_trace_mined_block(&block, opts).await);
7877        }
7878
7879        if let Some(fork) = self.get_fork() {
7880            return Ok(fork.debug_trace_block_by_hash(block_hash, opts).await?);
7881        }
7882
7883        Err(BlockchainError::BlockNotFound)
7884    }
7885
7886    /// Returns geth-style traces for all transactions in a block by number.
7887    pub async fn debug_trace_block_by_number(
7888        &self,
7889        block_number: BlockNumber,
7890        opts: GethDebugTracingOptions,
7891    ) -> Result<Vec<TraceResult>, BlockchainError> {
7892        let number = self.convert_block_number(Some(block_number));
7893
7894        if let Some(block) = self.get_block(BlockId::number(number)) {
7895            return Ok(self.debug_trace_mined_block(&block, opts).await);
7896        }
7897
7898        if let Some(fork) = self.get_fork() {
7899            return Ok(fork.debug_trace_block_by_number(number, opts).await?);
7900        }
7901
7902        Err(BlockchainError::BlockNotFound)
7903    }
7904
7905    async fn debug_trace_mined_block(
7906        &self,
7907        block: &Block,
7908        opts: GethDebugTracingOptions,
7909    ) -> Vec<TraceResult> {
7910        if block.body.transactions.is_empty() {
7911            return Vec::new();
7912        }
7913
7914        // A single transaction has no prefix replay to share.
7915        if block.body.transactions.len() > 1 {
7916            let traces = match &opts.tracer {
7917                Some(GethDebugTracerType::BuiltInTracer(
7918                    GethDebugBuiltInTracerType::CallTracer,
7919                )) => {
7920                    call_config_from_tracer_config(opts.tracer_config.clone()).ok().map(|config| {
7921                        self.replay_block_geth_traces(
7922                            block,
7923                            TracingInspectorConfig::from_geth_call_config(&config),
7924                            |inspector, gas_used, _| {
7925                                inspector.geth_builder().geth_call_traces(config, gas_used).into()
7926                            },
7927                        )
7928                    })
7929                }
7930                // Without steps tracing, struct logs come from the traces stored while mining.
7931                None if self.enable_steps_tracing => Some(self.replay_block_geth_traces(
7932                    block,
7933                    TracingInspectorConfig::from_geth_config(&opts.config),
7934                    |inspector, gas_used, output| {
7935                        inspector.geth_builder().geth_traces(gas_used, output, opts.config).into()
7936                    },
7937                )),
7938                _ => None,
7939            };
7940            if let Some(Ok(traces)) = traces {
7941                return traces;
7942            }
7943        }
7944
7945        // Preserve per-transaction errors and fork lookup when sequential replay is unavailable.
7946        let mut traces = Vec::with_capacity(block.body.transactions.len());
7947        for tx in &block.body.transactions {
7948            let tx_hash = tx.hash();
7949            traces.push(match self.debug_trace_transaction(tx_hash, opts.clone()).await {
7950                Ok(result) => TraceResult::Success { result, tx_hash: Some(tx_hash) },
7951                Err(error) => {
7952                    TraceResult::Error { error: error.to_string(), tx_hash: Some(tx_hash) }
7953                }
7954            });
7955        }
7956        traces
7957    }
7958
7959    /// Replays a local block once, keeping each transaction's committed state for the next call.
7960    fn replay_block_geth_traces(
7961        &self,
7962        block: &Block,
7963        tracing_config: TracingInspectorConfig,
7964        build_trace: impl Fn(&TracingInspector, u64, Bytes) -> GethTrace,
7965    ) -> Result<Vec<TraceResult>, BlockchainError> {
7966        let outcomes = {
7967            let storage = self.blockchain.storage.read();
7968            let block_hash = block.header.hash_slow();
7969            block
7970                .body
7971                .transactions
7972                .iter()
7973                .enumerate()
7974                .map(|(index, tx)| {
7975                    let hash = tx.hash();
7976                    let mined = storage
7977                        .transactions
7978                        .get(&hash)
7979                        .ok_or(BlockchainError::TransactionNotFound)?;
7980                    if mined.block_hash != block_hash
7981                        || mined.info.transaction_index as usize != index
7982                        || mined.info.transaction_hash != hash
7983                    {
7984                        return Err(BlockchainError::TransactionNotFound);
7985                    }
7986                    Ok((mined.info.gas_used, mined.info.out.clone().unwrap_or_default()))
7987                })
7988                .collect::<Result<Vec<_>, BlockchainError>>()?
7989        };
7990
7991        let trace = |parent_state: &StateDb| -> Result<Vec<TraceResult>, BlockchainError> {
7992            let (mut cache_db, evm_env, hardfork) =
7993                self.prepare_block_replay(block, parent_state)?;
7994            let monad_context = self.active_monad_context_for_mined_block(block)?;
7995            let mut traces = Vec::with_capacity(block.body.transactions.len());
7996            for (index, (tx, (gas_used, output))) in
7997                block.body.transactions.iter().zip(&outcomes).enumerate()
7998            {
7999                let mut inspector = TracingInspector::new(tracing_config);
8000                let pending = self.pending_mined_transaction(tx.clone())?;
8001                let transaction_context = monad_execution_context_at(monad_context.as_ref(), index);
8002                let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
8003                    &cache_db,
8004                    &evm_env,
8005                    &mut inspector,
8006                    &pending,
8007                    EnvelopeExecution::replay(transaction_context, hardfork),
8008                )?;
8009                traces.push(TraceResult::Success {
8010                    result: build_trace(&inspector, *gas_used, output.clone()),
8011                    tx_hash: Some(tx.hash()),
8012                });
8013                cache_db.commit(result.state);
8014            }
8015            Ok(traces)
8016        };
8017
8018        let read_guard = self.states.upgradable_read();
8019        if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
8020            trace(state)
8021        } else {
8022            let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
8023            let state = write_guard
8024                .get_on_disk_state(&block.header.parent_hash)
8025                .ok_or(BlockchainError::HistoricalStateUnavailable(block.header.number()))?;
8026            trace(state)
8027        }
8028    }
8029
8030    fn geth_trace(
8031        &self,
8032        tx: &MinedTransaction<N>,
8033        opts: GethDebugTracingOptions,
8034    ) -> Result<GethTrace, BlockchainError> {
8035        let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts;
8036
8037        if let Some(tracer) = tracer {
8038            match tracer {
8039                GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
8040                    GethDebugBuiltInTracerType::FourByteTracer => {
8041                        let inspector = FourByteInspector::default();
8042                        let res = self.replay_tx_with_inspector(
8043                            tx.info.transaction_hash,
8044                            inspector,
8045                            |_, _, inspector, _, _| FourByteFrame::from(inspector).into(),
8046                        )?;
8047                        return Ok(res);
8048                    }
8049                    GethDebugBuiltInTracerType::CallTracer => {
8050                        return match call_config_from_tracer_config(tracer_config) {
8051                            Ok(call_config) => {
8052                                let inspector = TracingInspector::new(
8053                                    TracingInspectorConfig::from_geth_call_config(&call_config),
8054                                );
8055                                let frame = self.replay_tx_with_inspector(
8056                                    tx.info.transaction_hash,
8057                                    inspector,
8058                                    |_, _, inspector, _, _| {
8059                                        inspector
8060                                            .geth_builder()
8061                                            .geth_call_traces(call_config, tx.info.gas_used)
8062                                            .into()
8063                                    },
8064                                )?;
8065                                Ok(frame)
8066                            }
8067                            Err(e) => Err(RpcError::invalid_params(e.to_string()).into()),
8068                        };
8069                    }
8070                    GethDebugBuiltInTracerType::PreStateTracer => {
8071                        return match tracer_config.into_pre_state_config() {
8072                            Ok(pre_state_config) => {
8073                                let inspector = TracingInspector::new(
8074                                    TracingInspectorConfig::from_geth_prestate_config(
8075                                        &pre_state_config,
8076                                    ),
8077                                );
8078                                let frame = self.replay_tx_with_inspector(
8079                                    tx.info.transaction_hash,
8080                                    inspector,
8081                                    |state, db, inspector, _, _| {
8082                                        inspector.geth_builder().geth_prestate_traces(
8083                                            &state,
8084                                            &pre_state_config,
8085                                            db,
8086                                        )
8087                                    },
8088                                )??;
8089                                Ok(frame.into())
8090                            }
8091                            Err(e) => Err(RpcError::invalid_params(e.to_string()).into()),
8092                        };
8093                    }
8094                    GethDebugBuiltInTracerType::NoopTracer
8095                    | GethDebugBuiltInTracerType::MuxTracer
8096                    | GethDebugBuiltInTracerType::Erc7562Tracer
8097                    | GethDebugBuiltInTracerType::FlatCallTracer
8098                    | GethDebugBuiltInTracerType::StateGasTracer => {}
8099                },
8100                GethDebugTracerType::JsTracer(_code) => {}
8101            }
8102
8103            return Ok(NoopFrame::default().into());
8104        }
8105
8106        // default structlog tracer
8107        let return_value = tx.info.out.clone().unwrap_or_default();
8108
8109        // Steps are not recorded when mining because they would be kept for every transaction, so
8110        // replay the transaction to record only what this request asks for.
8111        if self.enable_steps_tracing {
8112            let inspector =
8113                TracingInspector::new(TracingInspectorConfig::from_geth_config(&config));
8114            return self.replay_tx_with_inspector(
8115                tx.info.transaction_hash,
8116                inspector,
8117                |_, _, inspector, _, _| {
8118                    inspector
8119                        .geth_builder()
8120                        .geth_traces(tx.info.gas_used, return_value, config)
8121                        .into()
8122                },
8123            );
8124        }
8125
8126        Ok(GethTraceBuilder::new_borrowed(&tx.info.traces)
8127            .geth_traces(tx.info.gas_used, return_value, config)
8128            .into())
8129    }
8130
8131    async fn mined_geth_trace_transaction(
8132        &self,
8133        hash: B256,
8134        opts: GethDebugTracingOptions,
8135    ) -> Option<Result<GethTrace, BlockchainError>> {
8136        self.blockchain.storage.read().transactions.get(&hash).map(|tx| self.geth_trace(tx, opts))
8137    }
8138
8139    pub async fn transaction_receipt(
8140        &self,
8141        hash: B256,
8142    ) -> Result<Option<FoundryTxReceipt>, BlockchainError> {
8143        if let Some(receipt) = self.mined_transaction_receipt(hash) {
8144            return Ok(Some(receipt.inner));
8145        }
8146
8147        if let Some(fork) = self.get_fork() {
8148            let receipt = fork.transaction_receipt(hash).await?;
8149            let number = self.convert_block_number(
8150                receipt.clone().and_then(|r| r.block_number()).map(BlockNumber::from),
8151            );
8152
8153            if fork.predates_fork_inclusive(number) {
8154                return Ok(receipt);
8155            }
8156        }
8157
8158        Ok(None)
8159    }
8160
8161    /// Returns all transaction receipts of the block
8162    pub fn mined_block_receipts(&self, id: impl Into<BlockId>) -> Option<Vec<FoundryTxReceipt>> {
8163        let storage = self.blockchain.storage.read();
8164        let hash = match id.into() {
8165            BlockId::Hash(hash) => hash.block_hash,
8166            BlockId::Number(number) => storage.hash(number, self.slots_in_an_epoch)?,
8167        };
8168        let block = storage.blocks.get(&hash)?.clone();
8169
8170        if block.body.transactions.iter().enumerate().any(|(index, transaction)| {
8171            storage.transactions.get(&transaction.hash()).is_none_or(|transaction| {
8172                transaction.block_hash != hash
8173                    || transaction.info.transaction_index as usize != index
8174            })
8175        }) {
8176            drop(storage);
8177            return block
8178                .body
8179                .transactions
8180                .into_iter()
8181                .map(|transaction| {
8182                    self.mined_transaction_receipt(transaction.hash()).map(|receipt| receipt.inner)
8183                })
8184                .collect();
8185        }
8186
8187        let mut receipts = Vec::with_capacity(block.body.transactions.len());
8188        let mut next_log_index = 0;
8189
8190        for block_transaction in &block.body.transactions {
8191            let transaction = storage.transactions.get(&block_transaction.hash())?;
8192            let log_count = transaction.receipt.logs().len();
8193            let receipt = self.build_mined_transaction_receipt(
8194                &transaction.info,
8195                transaction.receipt.clone(),
8196                transaction.block_hash,
8197                &block,
8198                next_log_index,
8199            );
8200            receipts.push(receipt.inner);
8201            next_log_index += log_count;
8202        }
8203
8204        Some(receipts)
8205    }
8206
8207    /// Returns the transaction receipt for the given hash
8208    pub(crate) fn mined_transaction_receipt(
8209        &self,
8210        hash: B256,
8211    ) -> Option<MinedTransactionReceipt<FoundryNetwork>> {
8212        let storage = self.blockchain.storage.read();
8213        let transaction = storage.transactions.get(&hash)?;
8214
8215        let index = transaction.info.transaction_index as usize;
8216        let block = storage.blocks.get(&transaction.block_hash)?;
8217        let mut next_log_index = 0;
8218        for block_transaction in &block.body.transactions[..index] {
8219            next_log_index +=
8220                storage.transactions.get(&block_transaction.hash())?.receipt.logs().len();
8221        }
8222
8223        Some(self.build_mined_transaction_receipt(
8224            &transaction.info,
8225            transaction.receipt.clone(),
8226            transaction.block_hash,
8227            block,
8228            next_log_index,
8229        ))
8230    }
8231
8232    fn build_mined_transaction_receipt(
8233        &self,
8234        info: &TransactionInfo,
8235        tx_receipt: FoundryReceiptEnvelope,
8236        block_hash: B256,
8237        block: &Block,
8238        next_log_index: usize,
8239    ) -> MinedTransactionReceipt<FoundryNetwork> {
8240        let transaction = block.body.transactions[info.transaction_index as usize].clone();
8241
8242        // Cancun specific
8243        let excess_blob_gas = block.header.excess_blob_gas();
8244        let blob_gas_used = transaction.blob_gas_used();
8245        let blob_gas_price = blob_gas_used
8246            .map(|_| alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas.unwrap_or_default()));
8247
8248        let effective_gas_price = transaction.effective_gas_price(block.header.base_fee_per_gas());
8249
8250        #[cfg(feature = "base")]
8251        let eip8130_phase_statuses = tx_receipt.eip8130_phase_statuses().to_vec();
8252        let tx_receipt = tx_receipt.convert_logs_rpc(
8253            BlockNumHash::new(block.header.number(), block_hash),
8254            block.header.timestamp(),
8255            info.transaction_hash,
8256            info.transaction_index,
8257            next_log_index,
8258        );
8259
8260        let receipt = TransactionReceipt {
8261            inner: tx_receipt,
8262            transaction_hash: info.transaction_hash,
8263            transaction_index: Some(info.transaction_index),
8264            block_number: Some(block.header.number()),
8265            gas_used: info.gas_used,
8266            contract_address: info.contract_address,
8267            effective_gas_price,
8268            block_hash: Some(block_hash),
8269            from: info.from,
8270            to: info.to,
8271            blob_gas_price,
8272            blob_gas_used,
8273        };
8274
8275        // Include timestamp in receipt to avoid extra block lookups (e.g., in Otterscan API)
8276        let mut inner = FoundryTxReceipt::with_timestamp(receipt, block.header.timestamp());
8277        if let FoundryTxEnvelope::Celo(tx) = &*transaction {
8278            inner
8279                .0
8280                .other
8281                .insert("feeCurrency".into(), serde_json::to_value(tx.tx().fee_currency).unwrap());
8282        }
8283        if self.is_tempo() {
8284            let fee_payer = match &*transaction {
8285                FoundryTxEnvelope::Tempo(tx) => match tx.tx().recover_fee_payer(info.from) {
8286                    Ok(fee_payer) => fee_payer,
8287                    Err(error) => {
8288                        warn!(
8289                            target: "backend",
8290                            %error,
8291                            tx_hash = ?info.transaction_hash,
8292                            "failed to recover Tempo fee payer for mined receipt"
8293                        );
8294                        info.from
8295                    }
8296                },
8297                _ => info.from,
8298            };
8299            inner = inner.with_fee_payer(fee_payer);
8300
8301            // Match Tempo's receipt conversion: the final log of every non-free
8302            // transaction is the fee token transfer to TIPFeeManager.
8303            if inner.effective_gas_price() > 0
8304                && inner.gas_used() > 0
8305                && let Some(fee_token) = inner.0.inner.logs().last().map(|log| log.address())
8306            {
8307                inner = inner.with_fee_token(fee_token);
8308            }
8309        }
8310        #[cfg(feature = "base")]
8311        if self.is_base()
8312            && let FoundryTxEnvelope::Eip8130(tx) = transaction.as_ref()
8313        {
8314            let fields = Eip8130ReceiptFields {
8315                payer: Some(tx.tx().payer.unwrap_or(info.from)),
8316                phase_statuses: eip8130_phase_statuses,
8317                metadata: (!tx.tx().metadata.is_empty()).then(|| tx.tx().metadata.clone()),
8318            };
8319            inner
8320                .0
8321                .other
8322                .extend(OtherFields::try_from(fields).expect("EIP-8130 receipt fields serialize"));
8323        }
8324        MinedTransactionReceipt { inner, out: info.out.clone() }
8325    }
8326
8327    /// Executes the pending block and returns its transaction receipts.
8328    pub async fn pending_block_receipts(
8329        &self,
8330        pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
8331    ) -> Result<Vec<FoundryTxReceipt>, BlockchainError> {
8332        let BlockInfo { block, transactions, receipts } =
8333            self.pending_block(pool_transactions).await?;
8334        let block_hash = block.header.hash_slow();
8335        let mut pending_receipts = Vec::with_capacity(receipts.len());
8336        let mut next_log_index = 0;
8337
8338        for (info, receipt) in transactions.iter().zip(receipts) {
8339            let log_count = receipt.logs().len();
8340            let receipt = self.build_mined_transaction_receipt(
8341                info,
8342                receipt,
8343                block_hash,
8344                &block,
8345                next_log_index,
8346            );
8347            pending_receipts.push(receipt.inner);
8348            next_log_index += log_count;
8349        }
8350
8351        Ok(pending_receipts)
8352    }
8353
8354    /// Returns the blocks receipts for the given number
8355    pub async fn block_receipts(
8356        &self,
8357        number: BlockId,
8358    ) -> Result<Option<Vec<FoundryTxReceipt>>, BlockchainError> {
8359        if let Some(receipts) = self.mined_block_receipts(number) {
8360            return Ok(Some(receipts));
8361        }
8362
8363        if let Some(fork) = self.get_fork() {
8364            let number = match self.ensure_block_number(Some(number)).await {
8365                Err(_) => return Ok(None),
8366                Ok(n) => n,
8367            };
8368
8369            if fork.predates_fork_inclusive(number) {
8370                let receipts = fork.block_receipts(number).await?;
8371
8372                return Ok(receipts);
8373            }
8374        }
8375
8376        Ok(None)
8377    }
8378}
8379
8380impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> Backend<N> {
8381    /// Get the current state.
8382    pub async fn serialized_state(
8383        &self,
8384        preserve_historical_states: bool,
8385    ) -> Result<SerializableState, BlockchainError> {
8386        // Keep account state and head metadata coherent across mining and state replacement.
8387        let _mining_guard = self.mining.lock().await;
8388        let at = self.evm_env.read().block_env.clone();
8389        #[cfg(feature = "monad")]
8390        let mut monad_block_participants = BTreeMap::new();
8391        #[cfg(feature = "monad")]
8392        let mut monad_block_replay_profiles = BTreeMap::new();
8393        let (best_number, blocks, transactions) = {
8394            let storage = self.blockchain.storage.read();
8395            #[cfg(feature = "monad")]
8396            if self.is_monad() {
8397                monad_block_participants = storage
8398                    .monad_block_participants
8399                    .iter()
8400                    .filter(|(hash, _)| storage.blocks.contains_key(*hash))
8401                    .map(|(hash, participants)| (*hash, participants.iter().copied().collect()))
8402                    .collect();
8403                monad_block_replay_profiles = storage
8404                    .monad_block_replay_profiles
8405                    .iter()
8406                    .filter(|(hash, _)| storage.blocks.contains_key(*hash))
8407                    .map(|(hash, profile)| (*hash, *profile))
8408                    .collect();
8409            }
8410            (storage.best_number, storage.serialized_blocks(), storage.serialized_transactions())
8411        };
8412        let historical_states =
8413            preserve_historical_states.then(|| self.states.write().serialized_states());
8414
8415        let state = self
8416            .db
8417            .read()
8418            .await
8419            .dump_state(at, best_number, blocks, transactions, historical_states)?
8420            .ok_or_else(|| {
8421                BlockchainError::RpcError(RpcError::invalid_params(
8422                    "Dumping state not supported with the current configuration",
8423                ))
8424            })?;
8425        #[cfg(feature = "monad")]
8426        let state = {
8427            let mut state = state;
8428            state.monad_block_participants = monad_block_participants;
8429            state.monad_block_replay_profiles = monad_block_replay_profiles;
8430            state
8431        };
8432        Ok(state)
8433    }
8434
8435    /// Write all chain data to serialized bytes buffer
8436    pub async fn dump_state(
8437        &self,
8438        preserve_historical_states: bool,
8439    ) -> Result<Bytes, BlockchainError> {
8440        let state = self.serialized_state(preserve_historical_states).await?;
8441        let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
8442        encoder
8443            .write_all(&serde_json::to_vec(&state).unwrap_or_default())
8444            .map_err(|_| BlockchainError::DataUnavailable)?;
8445        Ok(encoder.finish().unwrap_or_default().into())
8446    }
8447
8448    /// Apply [SerializableState] data to the backend storage.
8449    pub async fn load_state(&self, mut state: SerializableState) -> Result<bool, BlockchainError> {
8450        let _mining_guard = self.mining.lock().await;
8451        let mut block_env = state.block.take();
8452        let mut fork_block_boundary = None;
8453        let mut selected_head = None;
8454        let mut selected_header = None;
8455        let mut checkpoint = None;
8456        let fork_head = self.get_fork().map(|f| (f.block_number(), f.block_hash(), f.timestamp()));
8457        if let Some(block) = &mut block_env {
8458            if self.is_tempo() && self.is_fork() && block.beneficiary.is_zero() {
8459                block.beneficiary = TIP_FEE_MANAGER_ADDRESS;
8460            }
8461            // Set the current best block number.
8462            // Defaults to block number for compatibility with existing state files.
8463            let best_number = state.best_block_number.unwrap_or(block.number.saturating_to());
8464            let (selected_best_number, selected_best_hash) = if let Some((number, hash, _)) =
8465                fork_head
8466            {
8467                trace!(target: "backend", state_block_number=?best_number, fork_block_number=?number);
8468                // If the state.block_number is greater than the fork block number, set best number
8469                // to the state block number.
8470                // Ref: https://github.com/foundry-rs/foundry/issues/9539
8471                if best_number > number {
8472                    (best_number, None)
8473                } else {
8474                    // If loading state file on a fork, set best number to the fork block number.
8475                    // Ref: https://github.com/foundry-rs/foundry/pull/9215#issue-2618681838
8476                    fork_block_boundary = Some(number);
8477                    (number, Some(hash))
8478                }
8479            } else {
8480                (best_number, None)
8481            };
8482
8483            let best_hash = if let Some(hash) = selected_best_hash {
8484                selected_header = state
8485                    .blocks
8486                    .iter()
8487                    .rev()
8488                    .find(|block| block.header.hash_slow() == hash)
8489                    .map(|block| block.header.clone());
8490                hash
8491            } else if state.blocks.is_empty() {
8492                let spec_id = self.spec_id();
8493                let is_cancun = spec_id >= SpecId::CANCUN;
8494                let parent_hash = selected_best_number
8495                    .checked_sub(1)
8496                    .and_then(|number| self.blockchain.storage.read().hashes.get(&number).copied())
8497                    .unwrap_or_default();
8498                let header = Header {
8499                    parent_hash,
8500                    beneficiary: block.beneficiary,
8501                    difficulty: block.difficulty,
8502                    number: selected_best_number,
8503                    gas_limit: block.gas_limit,
8504                    timestamp: block.timestamp.saturating_to(),
8505                    mix_hash: block.prevrandao.unwrap_or_default(),
8506                    base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(block.basefee),
8507                    parent_beacon_block_root: is_cancun.then_some(Default::default()),
8508                    blob_gas_used: is_cancun.then_some(0),
8509                    excess_blob_gas: if is_cancun { block.blob_excess_gas() } else { None },
8510                    withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
8511                    requests_hash: (spec_id >= SpecId::PRAGUE).then_some(EMPTY_REQUESTS_HASH),
8512                    ..Default::default()
8513                };
8514                let header = foundry_header(&self.networks, header);
8515                let best_hash = header.hash_slow();
8516                selected_header = Some(header.clone());
8517                checkpoint = Some(create_block(
8518                    header,
8519                    Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
8520                ));
8521                warn!(
8522                    target: "backend",
8523                    block_number = selected_best_number,
8524                    "state dump has no block history; created a synthetic checkpoint block"
8525                );
8526                best_hash
8527            } else if let Some(header) = state
8528                .blocks
8529                .iter()
8530                .rev()
8531                .find(|block| block.header.number() == selected_best_number)
8532                .map(|block| block.header.clone())
8533            {
8534                let best_hash = header.hash_slow();
8535                selected_header = Some(header);
8536                best_hash
8537            } else {
8538                return Err(BlockchainError::RpcError(RpcError::internal_error_with(format!(
8539                    "Best hash not found for best number {selected_best_number}",
8540                ))));
8541            };
8542
8543            selected_head = Some((selected_best_number, best_hash));
8544        }
8545
8546        // Stage the complete chain update first. Besides keeping blocks and transactions atomic for
8547        // concurrent readers, this ensures validation failures cannot leave a partially loaded
8548        // chain behind.
8549        let blocks = std::mem::take(&mut state.blocks);
8550        let transactions = std::mem::take(&mut state.transactions);
8551        let mut storage = self.blockchain.storage.read().clone();
8552        storage.load_blocks(blocks, fork_block_boundary);
8553        if !self.is_celo()
8554            && storage.blocks.values().any(|block| {
8555                block
8556                    .body
8557                    .transactions
8558                    .iter()
8559                    .any(|tx| matches!(tx.as_ref(), FoundryTxEnvelope::Celo(_)))
8560            })
8561        {
8562            self.ensure_cip64_active()?;
8563        }
8564        storage.load_transactions(transactions, fork_block_boundary);
8565        if let Some(checkpoint) = checkpoint {
8566            storage.insert_block(checkpoint);
8567        }
8568        if let Some((number, hash)) = selected_head {
8569            storage.hashes.insert(number, hash);
8570            storage.best_number = number;
8571            storage.best_hash = hash;
8572            storage.hashes.retain(|block_number, _| *block_number <= number);
8573        }
8574
8575        #[cfg(feature = "monad")]
8576        if self.is_monad() {
8577            for (hash, profile) in &state.monad_block_replay_profiles {
8578                if storage.blocks.contains_key(hash) {
8579                    storage.monad_block_replay_profiles.insert(*hash, *profile);
8580                }
8581            }
8582            for (hash, participants) in &state.monad_block_participants {
8583                if storage.blocks.contains_key(hash) {
8584                    storage
8585                        .monad_block_participants
8586                        .insert(*hash, participants.iter().copied().collect());
8587                }
8588            }
8589            self.rebuild_monad_block_participant_cache(&mut storage)?;
8590            // Reject state that cannot supply the ancestor metadata required by the next block
8591            // before changing the live chain, EVM environment, or database.
8592            self.monad_context_for_child_of_in_storage(&storage, storage.best_hash)?;
8593        }
8594
8595        // Re-anchor block time to the canonical head selected above so the next blocks continue
8596        // its timeline: the saved one when the loaded head stays canonical, the fork's when the
8597        // state file is at or below the fork block. Resolve the timestamp from staged storage so a
8598        // later validation or database failure cannot modify the live clock.
8599        let canonical_timestamp = match fork_head {
8600            Some((_, fork_hash, fork_timestamp)) if storage.best_hash == fork_hash => {
8601                Some(fork_timestamp)
8602            }
8603            _ => storage.blocks.get(&storage.best_hash).map(|block| block.header.timestamp),
8604        };
8605
8606        if let Some(block) = block_env.as_mut() {
8607            // Keep NUMBER aligned with the canonical local head chosen above. Arbitrum state dumps
8608            // can intentionally keep BlockEnv.number distinct from the best L2 block number.
8609            if !is_arbitrum(self.protocol_chain_id())
8610                && let Some((number, _)) = selected_head
8611            {
8612                block.number = U256::from(number);
8613            }
8614        }
8615
8616        let next_fees = selected_header.as_ref().map(|header| {
8617            let parent_fees = self.fees.get_parent_header_fees(header);
8618            let next_block_excess_blob_gas = self.networks.next_block_blob_excess_gas(
8619                self.fees.blob_params(),
8620                header.excess_blob_gas().unwrap_or_default(),
8621                header.blob_gas_used().unwrap_or_default(),
8622                header.base_fee_per_gas().unwrap_or_default(),
8623            );
8624            let blob_excess_gas_and_price = BlobExcessGasAndPrice::new(
8625                next_block_excess_blob_gas,
8626                get_blob_base_fee_update_fraction(
8627                    self.evm_env.read().cfg_env.chain_id,
8628                    header.timestamp,
8629                ),
8630            );
8631            (parent_fees, blob_excess_gas_and_price)
8632        });
8633
8634        let historical_states = state.historical_states.take();
8635        let mut db = self.db.write().await;
8636        let db_snapshot = db.snapshot_state();
8637        let load_result = (|| -> Result<(), BlockchainError> {
8638            if !db.load_state(state)? {
8639                return Err(RpcError::invalid_params(
8640                    "Loading state not supported with the current configuration",
8641                )
8642                .into());
8643            }
8644            // A pre-Denim dump has no BaseTime deployment, but Denim blocks still send its
8645            // deposit.
8646            #[cfg(feature = "base")]
8647            if self.is_base() && self.base_upgrade() >= BaseUpgrade::Denim {
8648                ensure_base_time_predeploy(&mut **db, false)?;
8649            }
8650            Ok(())
8651        })();
8652        if let Err(err) = load_result {
8653            db.revert_state(db_snapshot, RevertStateSnapshotAction::RevertRemove);
8654            return Err(err);
8655        }
8656        db.delete_state_snapshot(db_snapshot);
8657        drop(db);
8658
8659        // Backfill the EVM-level block hash cache from the freshly loaded blocks so that the
8660        // BLOCKHASH opcode stays consistent after loading state. Reuses the hashes already
8661        // computed by `load_blocks` above. Only collect the last 256 blocks since that's all
8662        // BLOCKHASH can access.
8663        let block_hashes = {
8664            let min_block = storage.best_number.saturating_sub(256);
8665            storage
8666                .hashes
8667                .iter()
8668                .filter(|(num, _)| (min_block..=storage.best_number).contains(*num))
8669                .map(|(&num, &hash)| (U256::from(num), hash))
8670                .collect()
8671        };
8672
8673        *self.blockchain.storage.write() = storage;
8674        if let Some(timestamp) = canonical_timestamp {
8675            self.time.reset(timestamp);
8676        }
8677        if let Some(block_env) = block_env {
8678            self.evm_env.write().block_env = block_env;
8679        }
8680        if let Some((parent_fees, blob_excess_gas_and_price)) = next_fees {
8681            #[cfg(feature = "optimism")]
8682            if self.is_optimism() {
8683                self.fees.set_optimism_base_fee_rules(&parent_fees.extra_data);
8684            }
8685            self.fees.set_base_fee(parent_fees.base_fee);
8686            self.fees.set_blob_excess_gas_and_price(blob_excess_gas_and_price);
8687        }
8688
8689        self.db.write().await.set_block_hashes(block_hashes);
8690
8691        if let Some(historical_states) = historical_states {
8692            self.states.write().load_states(historical_states);
8693        }
8694
8695        Ok(true)
8696    }
8697
8698    /// Deserialize and add all chain data to the backend storage
8699    pub async fn load_state_bytes(&self, buf: Bytes) -> Result<bool, BlockchainError> {
8700        let orig_buf = &buf.0[..];
8701        let mut decoder = GzDecoder::new(orig_buf);
8702        let mut decoded_data = Vec::new();
8703
8704        let state: SerializableState = serde_json::from_slice(if decoder.header().is_some() {
8705            decoder
8706                .read_to_end(decoded_data.as_mut())
8707                .map_err(|_| BlockchainError::FailedToDecodeStateDump)?;
8708            &decoded_data
8709        } else {
8710            &buf.0
8711        })
8712        .map_err(|_| BlockchainError::FailedToDecodeStateDump)?;
8713
8714        self.load_state(state).await
8715    }
8716}
8717
8718impl Backend<FoundryNetwork> {
8719    /// Simulates a bundle of signed transactions and returns Flashbots-compatible results.
8720    pub async fn call_bundle(
8721        &self,
8722        bundle: EthCallBundle,
8723        transactions: Vec<PendingTransaction<FoundryTxEnvelope>>,
8724        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
8725    ) -> Result<EthCallBundleResponse, BlockchainError> {
8726        let EthCallBundle {
8727            block_number,
8728            coinbase,
8729            timestamp,
8730            gas_limit,
8731            difficulty,
8732            base_fee,
8733            ..
8734        } = bundle;
8735
8736        let blob_gas_used = transactions
8737            .iter()
8738            .filter_map(|transaction| transaction.transaction.blob_gas_used())
8739            .sum::<u64>();
8740        let max_blob_gas = self.blob_params().max_blob_gas_per_block();
8741        if blob_gas_used > max_blob_gas {
8742            return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
8743                "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
8744            ))));
8745        }
8746
8747        self.with_database_at_and_context(
8748            block_request,
8749            |state, mut block_env, mut monad_context| {
8750                let state_block_number = block_env.number.to::<u64>();
8751                block_env.number = U256::from(block_number);
8752                block_env.timestamp = timestamp
8753                    .map(U256::from)
8754                    .unwrap_or_else(|| block_env.timestamp.saturating_add(U256::from(12)));
8755                if let Some(coinbase) = coinbase {
8756                    block_env.beneficiary = coinbase;
8757                }
8758                if let Some(gas_limit) = gas_limit {
8759                    block_env.gas_limit = gas_limit;
8760                }
8761                if let Some(difficulty) = difficulty {
8762                    block_env.difficulty = difficulty;
8763                }
8764                if let Some(base_fee) = base_fee {
8765                    block_env.basefee = base_fee.try_into().unwrap_or(u64::MAX);
8766                }
8767
8768                let mut evm_env = self.evm_env.read().clone();
8769                evm_env.block_env = block_env;
8770                let coinbase = evm_env.block_env.beneficiary;
8771                let base_fee = evm_env.block_env.basefee;
8772                let mut cache_db = CacheDB::new(state);
8773                let initial_coinbase = revm::DatabaseRef::basic_ref(&cache_db, coinbase)?
8774                    .map(|account| account.balance)
8775                    .unwrap_or_default();
8776                let mut coinbase_balance_before_tx = initial_coinbase;
8777                let mut coinbase_balance_after_tx = initial_coinbase;
8778                let mut total_gas_used = 0u64;
8779                let mut total_gas_fees = U256::ZERO;
8780                let mut bundle_hash = alloy_primitives::Keccak256::new();
8781                let mut results = Vec::with_capacity(transactions.len());
8782
8783                for transaction in transactions {
8784                    let sender = *transaction.sender();
8785                    let tx = transaction.transaction.as_ref();
8786                    let tx_hash = tx.hash();
8787                    bundle_hash.update(tx_hash);
8788
8789                    let mut inspector = self.build_inspector();
8790                    let (ResultAndState { result, state }, _) = self
8791                        .transact_envelope_with_inspector_ref_and_context(
8792                            &cache_db,
8793                            &evm_env,
8794                            &mut inspector,
8795                            &transaction,
8796                            monad_context.as_mut().map(next_monad_context),
8797                        )?;
8798
8799                    let gas_price = tx.effective_tip_per_gas(base_fee).unwrap_or_default();
8800                    let gas_used = result.tx_gas_used();
8801                    let gas_fees = U256::from(gas_used) * U256::from(gas_price);
8802                    total_gas_used += gas_used;
8803                    total_gas_fees += gas_fees;
8804
8805                    coinbase_balance_after_tx = state
8806                        .get(&coinbase)
8807                        .map(|account| account.info.balance)
8808                        .unwrap_or(coinbase_balance_before_tx);
8809                    let coinbase_diff =
8810                        coinbase_balance_after_tx.saturating_sub(coinbase_balance_before_tx);
8811                    let eth_sent_to_coinbase = coinbase_diff.saturating_sub(gas_fees);
8812                    coinbase_balance_before_tx = coinbase_balance_after_tx;
8813
8814                    let output = result.output().cloned().unwrap_or_default();
8815                    let (value, revert) = if result.is_success() {
8816                        (Some(output), None)
8817                    } else {
8818                        (None, Some(output))
8819                    };
8820
8821                    results.push(EthCallBundleTransactionResult {
8822                        coinbase_diff,
8823                        eth_sent_to_coinbase,
8824                        from_address: sender,
8825                        gas_fees,
8826                        gas_price: U256::from(gas_price),
8827                        gas_used,
8828                        to_address: tx.to(),
8829                        tx_hash,
8830                        value,
8831                        revert,
8832                    });
8833                    cache_db.commit(state);
8834                }
8835
8836                let coinbase_diff = coinbase_balance_after_tx.saturating_sub(initial_coinbase);
8837                let eth_sent_to_coinbase = coinbase_diff.saturating_sub(total_gas_fees);
8838                let bundle_gas_price =
8839                    coinbase_diff.checked_div(U256::from(total_gas_used)).unwrap_or_default();
8840
8841                Ok(EthCallBundleResponse {
8842                    bundle_hash: bundle_hash.finalize(),
8843                    bundle_gas_price,
8844                    coinbase_diff,
8845                    eth_sent_to_coinbase,
8846                    gas_fees: total_gas_fees,
8847                    results,
8848                    state_block_number,
8849                    total_gas_used,
8850                })
8851            },
8852        )
8853        .await
8854    }
8855
8856    /// Executes bundles of call requests and returns each call output.
8857    pub async fn call_many(
8858        &self,
8859        bundles: Vec<Bundle<WithOtherFields<TransactionRequest>>>,
8860        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
8861        state_override: Option<alloy_rpc_types::state::StateOverride>,
8862    ) -> Result<Vec<Vec<EthCallResponse>>, BlockchainError> {
8863        if bundles.is_empty() {
8864            return Err(BlockchainError::RpcError(RpcError::invalid_params(
8865                "bundles are empty.".to_string(),
8866            )));
8867        }
8868
8869        self.with_database_at_and_context(
8870            block_request,
8871            |state, mut block_env, mut monad_context| {
8872                let mut cache_db = CacheDB::new(state);
8873                if let Some(state_override) = state_override {
8874                    apply_state_overrides(state_override, &mut cache_db)?;
8875                }
8876
8877                let mut results = Vec::with_capacity(bundles.len());
8878                for bundle in bundles {
8879                    let Bundle { transactions, block_override } = bundle;
8880                    if let Some(block_override) = block_override {
8881                        cache_db.apply_block_overrides(block_override, &mut block_env);
8882                    }
8883
8884                    let mut bundle_results = Vec::with_capacity(transactions.len());
8885                    for request in transactions {
8886                        let fee_details = FeeDetails::new(
8887                            request.gas_price,
8888                            request.max_fee_per_gas,
8889                            request.max_priority_fee_per_gas,
8890                            request.max_fee_per_blob_gas,
8891                        )?
8892                        .or_zero_fees();
8893                        let PreparedCall { evm_env, mut tx_env, simulated_tempo_tx, .. } = self
8894                            .prepare_call_env(&cache_db, request, fee_details, block_env.clone())?;
8895                        apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
8896
8897                        let mut inspector = self.build_inspector();
8898                        let ResultAndState { result, state } = self
8899                            .transact_call_with_inspector_ref(
8900                                &cache_db,
8901                                &evm_env,
8902                                &mut inspector,
8903                                tx_env,
8904                                monad_context.as_mut().map(next_monad_context),
8905                            )?;
8906
8907                        let output = result.output().cloned().unwrap_or_default();
8908                        let response = if result.is_success() {
8909                            EthCallResponse { value: Some(output), error: None }
8910                        } else {
8911                            let error = RevertDecoder::new()
8912                                .maybe_decode(&output, None)
8913                                .unwrap_or_else(|| "execution failed".to_string());
8914                            EthCallResponse { value: None, error: Some(error) }
8915                        };
8916
8917                        cache_db.commit(state);
8918                        bundle_results.push(response);
8919                    }
8920
8921                    results.push(bundle_results);
8922                    block_env.number = block_env.number.saturating_add(U256::ONE);
8923                    block_env.timestamp = block_env.timestamp.saturating_add(U256::ONE);
8924                    #[cfg(feature = "monad")]
8925                    self::monad::advance_block_context(&mut monad_context);
8926                }
8927
8928                Ok(results)
8929            },
8930        )
8931        .await
8932    }
8933
8934    /// Simulates the payload by executing the calls in request.
8935    pub async fn simulate(
8936        &self,
8937        request: SimulatePayload,
8938        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
8939        block_interval: u64,
8940    ) -> Result<Vec<SimulatedBlock<AnyRpcBlock>>, BlockchainError> {
8941        self.simulate_raw(
8942            preserve_simulation_request_fields(request),
8943            block_request,
8944            block_interval,
8945        )
8946        .await
8947    }
8948
8949    /// Simulates a payload while preserving transaction extension fields.
8950    pub(crate) async fn simulate_raw(
8951        &self,
8952        request: SimulatePayload<WithOtherFields<TransactionRequest>>,
8953        block_request: Option<BlockRequest<FoundryTxEnvelope>>,
8954        block_interval: u64,
8955    ) -> Result<Vec<SimulatedBlock<AnyRpcBlock>>, BlockchainError> {
8956        let simulate_at = |state: Box<dyn MaybeFullDatabase + '_>,
8957                           base_block_env: BlockEnv,
8958                           base_number,
8959                           base_timestamp,
8960                           base_hash,
8961                           base_fee,
8962                           mut monad_context: Option<MonadReplayContext>,
8963                           base_base_fee_per_gas,
8964                           base_excess_blob_gas,
8965                           base_blob_gas_used,
8966                           base_fee_extra_data: Bytes,
8967                           optimism_jovian: bool| {
8968            let SimulatePayload {
8969                block_state_calls,
8970                trace_transfers,
8971                validation,
8972                return_full_transactions,
8973            } = request;
8974            let block_state_calls = sanitize_simulation_blocks(
8975                block_state_calls,
8976                base_number,
8977                base_timestamp,
8978                block_interval,
8979            )?;
8980            let mut cache_db = BalDatabase::new(CacheDB::new(state));
8981            cache_db.cache.block_hashes.insert(U256::from(base_number), base_hash);
8982            let mut block_res = Vec::with_capacity(block_state_calls.len());
8983            // A single block cannot amortize building an incremental trie.
8984            let cache_state_roots = block_state_calls.len() > 1;
8985            let mut state_root_cache = None::<(StateRootCache, AddressMap<DbAccount>)>;
8986            let mut parent_hash = base_hash;
8987            let mut next_base_fee = base_fee;
8988            let mut inherited_block_env = base_block_env;
8989            let (is_merge, is_cancun, is_amsterdam, tx_gas_limit_cap) = {
8990                let cfg_env = &self.evm_env.read().cfg_env;
8991                (
8992                    cfg_env.spec >= SpecId::MERGE,
8993                    cfg_env.spec >= SpecId::CANCUN,
8994                    cfg_env.spec >= SpecId::AMSTERDAM,
8995                    cfg_env.tx_gas_limit_cap(),
8996                )
8997            };
8998            let mut parent_base_fee_per_gas = base_base_fee_per_gas;
8999            let mut parent_excess_blob_gas = base_excess_blob_gas;
9000            let mut parent_blob_gas_used = base_blob_gas_used;
9001            let mut rpc_gas_budget = SIMULATE_GAS_CAP;
9002
9003            // execute the blocks
9004            for block in block_state_calls {
9005                let SimBlock { block_overrides, state_overrides, calls } = block;
9006                let mut block_env = inherited_block_env.clone();
9007                let overridden_beacon_root =
9008                    block_overrides.as_ref().and_then(|overrides| overrides.beacon_root);
9009                let block_timestamp = block_overrides
9010                    .as_ref()
9011                    .and_then(|overrides| overrides.time)
9012                    .unwrap_or_else(|| block_env.timestamp.saturating_to());
9013                let blob_params = self.simulation_blob_params_at_timestamp(block_timestamp);
9014                if is_cancun {
9015                    let excess_blob_gas = self.networks.next_block_blob_excess_gas(
9016                        blob_params,
9017                        parent_excess_blob_gas,
9018                        parent_blob_gas_used,
9019                        parent_base_fee_per_gas,
9020                    );
9021                    block_env.set_blob_excess_gas_and_price(
9022                        excess_blob_gas,
9023                        blob_params.update_fraction as u64,
9024                    );
9025                } else {
9026                    block_env.blob_excess_gas_and_price = None;
9027                }
9028                block_env.basefee = if validation { next_base_fee } else { 0 };
9029                block_env.prevrandao = Some(B256::ZERO);
9030                if is_merge && !is_arbitrum(self.protocol_chain_id()) {
9031                    block_env.difficulty = U256::ZERO;
9032                }
9033                let mut call_res = Vec::with_capacity(calls.len());
9034                let mut log_index = 0;
9035                let mut cumulative_gas_used = 0;
9036                let mut block_regular_gas_used = 0;
9037                let mut block_state_gas_used = 0;
9038                let mut block_blob_gas_used = 0u64;
9039                let mut transactions = Vec::with_capacity(calls.len());
9040                let mut transaction_envelopes = Vec::with_capacity(calls.len());
9041                let mut receipts = Vec::with_capacity(calls.len());
9042                let overridden_block_hashes = block_overrides
9043                    .as_ref()
9044                    .and_then(|overrides| overrides.block_hash.as_ref())
9045                    .map(|overrides| {
9046                        overrides
9047                            .keys()
9048                            .map(|number| {
9049                                let number = U256::from(*number);
9050                                (number, cache_db.cache.block_hashes.get(&number).copied())
9051                            })
9052                            .collect::<Vec<_>>()
9053                    })
9054                    .unwrap_or_default();
9055
9056                if let Some(block_overrides) = block_overrides {
9057                    cache_db.apply_block_overrides(block_overrides, &mut block_env);
9058                }
9059                // Simulation overrides and RPC results use L2 numbers, while Arbitrum
9060                // execution reads the corresponding L1 number through NUMBER.
9061                let rpc_block_number = block_env.number.saturating_to();
9062                block_env.number = self.evm_block_number(rpc_block_number);
9063                let simulation_evm_env =
9064                    EvmEnv::new(self.evm_env.read().cfg_env.clone(), block_env.clone());
9065                let spec_id = *simulation_evm_env.spec_id();
9066                let ethereum_transitions = self
9067                    .ethereum_block_transitions(self.hardfork(), None, BlockExecutionKind::Complete)
9068                    .map(|mut transitions| {
9069                        transitions.parent_beacon_block_root = (transitions.hardfork
9070                            >= EthereumHardfork::Cancun)
9071                            .then_some(overridden_beacon_root.unwrap_or_default());
9072                        transitions
9073                    });
9074                let precompile_overrides = self.simulation_precompile_overrides(
9075                    state_overrides.as_ref(),
9076                    &simulation_evm_env,
9077                )?;
9078
9079                // Apply state overrides after validating precompile moves against this block's
9080                // active precompile set.
9081                if let Some(mut state_overrides) = state_overrides {
9082                    state_overrides.retain(|_, account| {
9083                        account.balance.is_some()
9084                            || account.nonce.is_some()
9085                            || account.code.is_some()
9086                            || account.state.is_some()
9087                            || account
9088                                .state_diff
9089                                .as_ref()
9090                                .is_some_and(|state_diff| !state_diff.is_empty())
9091                    });
9092                    let previously_deleted = previously_deleted_accounts(
9093                        &cache_db.cache.accounts,
9094                        state_overrides.keys().copied(),
9095                    );
9096                    apply_state_overrides(state_overrides, &mut cache_db.db)?;
9097                    preserve_deleted_storage(&mut cache_db.cache.accounts, previously_deleted);
9098                }
9099
9100                // Overrides define the starting state, so only execution contributes BAL writes.
9101                if ethereum_transitions
9102                    .is_some_and(|transitions| transitions.hardfork >= EthereumHardfork::Amsterdam)
9103                {
9104                    cache_db.bal_state = BalState::new().with_bal_builder();
9105                }
9106
9107                if let Some(transitions) = ethereum_transitions {
9108                    self.apply_simulation_pre_execution_changes(
9109                        &mut cache_db,
9110                        &simulation_evm_env,
9111                        parent_hash,
9112                        transitions,
9113                    )?;
9114                }
9115
9116                cache_db.bump_bal_index();
9117
9118                // execute all calls in that block
9119                for (req_idx, mut request) in calls.into_iter().enumerate() {
9120                    #[cfg(feature = "base")]
9121                    if request.transaction_type == Some(EIP8130_TRANSACTION_TYPE) {
9122                        return Err(BlockchainError::InvalidTransactionRequest(
9123                            "eth_simulateV1 does not accept EIP-8130 transaction requests"
9124                                .to_string(),
9125                        ));
9126                    }
9127
9128                    let classified_request = self.parse_transaction_request(request.clone())?;
9129                    #[cfg(feature = "base")]
9130                    if classified_request.is_base() {
9131                        return Err(BlockchainError::InvalidTransactionRequest(
9132                            "eth_simulateV1 does not accept EIP-8130 transaction requests"
9133                                .to_string(),
9134                        ));
9135                    }
9136                    let is_ethereum_request = classified_request.is_ethereum();
9137                    let mut parsed_request = self.is_tempo().then_some(classified_request);
9138                    if is_ethereum_request {
9139                        request.populate_blob_hashes();
9140                        let preferred_type = request.preferred_type();
9141                        request.transaction_type = Some(preferred_type as u8);
9142                        request.trim_conflicting_keys();
9143                        request.populate_blob_hashes();
9144                    }
9145                    let request_blob_gas_used = if is_ethereum_request && !optimism_jovian {
9146                        u64::try_from(request.blob_versioned_hashes.as_ref().map_or(0, Vec::len))
9147                            .unwrap_or(u64::MAX)
9148                            .saturating_mul(DATA_GAS_PER_BLOB)
9149                    } else {
9150                        0
9151                    };
9152                    let max_blob_gas = block_blob_gas_limit(
9153                        optimism_jovian,
9154                        block_env.gas_limit,
9155                        blob_params.max_blob_gas_per_block(),
9156                    );
9157                    if !optimism_jovian
9158                        && block_blob_gas_used.saturating_add(request_blob_gas_used) > max_blob_gas
9159                    {
9160                        return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
9161                            "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
9162                        ))));
9163                    }
9164                    block_blob_gas_used = block_blob_gas_used.saturating_add(request_blob_gas_used);
9165
9166                    let inner = request.as_ref();
9167                    let remaining_regular_gas =
9168                        block_env.gas_limit.saturating_sub(block_regular_gas_used);
9169                    let remaining_state_gas =
9170                        block_env.gas_limit.saturating_sub(block_state_gas_used);
9171                    let remaining_gas = if is_amsterdam {
9172                        remaining_regular_gas.min(remaining_state_gas)
9173                    } else {
9174                        block_env.gas_limit.saturating_sub(cumulative_gas_used)
9175                    };
9176                    let requested_gas = inner.gas.unwrap_or(remaining_gas);
9177                    let exceeds_gas_limit = if is_amsterdam {
9178                        let requested_regular_gas = requested_gas.min(tx_gas_limit_cap);
9179                        requested_regular_gas > remaining_regular_gas
9180                            || requested_gas > remaining_state_gas
9181                    } else {
9182                        requested_gas > remaining_gas
9183                    };
9184                    if exceeds_gas_limit {
9185                        return Err(BlockchainError::RpcError(RpcError {
9186                            code: ErrorCode::ServerError(-38015),
9187                            message: format!(
9188                                "block gas limit exceeded: remaining {remaining_gas}, requested {requested_gas}"
9189                            )
9190                            .into(),
9191                            data: None,
9192                        }));
9193                    }
9194                    let execution_gas_limit = requested_gas.min(rpc_gas_budget);
9195                    let preserve_signed_gas = matches!(
9196                        &parsed_request,
9197                        Some(FoundryTransactionRequest::Tempo(request))
9198                            if request.fee_payer_signature.is_some()
9199                    );
9200                    request.gas = Some(execution_gas_limit);
9201                    if !preserve_signed_gas && let Some(parsed_request) = &mut parsed_request {
9202                        parsed_request.as_mut().gas = Some(execution_gas_limit);
9203                    }
9204
9205                    let caller = request.from.unwrap_or_default();
9206                    let caller_nonce = RevmDatabase::basic(&mut cache_db.db, caller)?
9207                        .map(|account| account.nonce)
9208                        .unwrap_or_default();
9209                    let tempo_nonce_key =
9210                        parsed_request.as_ref().and_then(|request| match request {
9211                            FoundryTransactionRequest::Tempo(request) => request.nonce_key,
9212                            _ => None,
9213                        });
9214                    if request.nonce.is_none() {
9215                        let nonce = tempo_nonce_key.map_or(Ok(caller_nonce), |nonce_key| {
9216                            tempo_nonce(&cache_db.db, caller, nonce_key)
9217                        })?;
9218                        request.nonce = Some(nonce);
9219                        if let Some(parsed_request) = &mut parsed_request {
9220                            parsed_request.as_mut().nonce = Some(nonce);
9221                        }
9222                    }
9223
9224                    if is_ethereum_request {
9225                        let mut canonical_request =
9226                            FoundryTransactionRequest::Ethereum(request.inner.clone());
9227                        canonical_request.prep_for_submission();
9228                        request.inner = canonical_request.as_ref().clone();
9229                        if let Some(parsed_request) = &mut parsed_request {
9230                            *parsed_request = canonical_request;
9231                        }
9232                    }
9233
9234                    let fee_details = FeeDetails::new(
9235                        request.gas_price,
9236                        request.max_fee_per_gas,
9237                        request.max_priority_fee_per_gas,
9238                        request.max_fee_per_blob_gas,
9239                    )?
9240                    .or_zero_fees();
9241
9242                    let PreparedCall {
9243                        mut evm_env,
9244                        mut tx_env,
9245                        simulated_tempo_tx,
9246                        #[cfg(feature = "base")]
9247                        simulated_envelope,
9248                    } = if let Some(parsed_request) = parsed_request {
9249                        self.prepare_typed_call_env(
9250                            &cache_db.db,
9251                            parsed_request,
9252                            fee_details,
9253                            block_env.clone(),
9254                        )?
9255                    } else {
9256                        self.prepare_call_env(
9257                            &cache_db.db,
9258                            request.clone(),
9259                            fee_details,
9260                            block_env.clone(),
9261                        )?
9262                    };
9263                    tx_env.base_mut().gas_limit = execution_gas_limit;
9264                    apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
9265                    if !validation
9266                        && tempo_nonce_key.is_none_or(|key| key.is_zero())
9267                        && request.nonce == Some(u64::MAX)
9268                    {
9269                        tx_env.base_mut().nonce = 0;
9270                    }
9271                    let uses_protocol_call_nonce = tx_env.uses_protocol_call_nonce();
9272                    #[cfg(feature = "base")]
9273                    let simulated_envelope = simulated_envelope
9274                        .or_else(|| simulated_tempo_tx.map(FoundryTxEnvelope::Tempo));
9275                    #[cfg(not(feature = "base"))]
9276                    let simulated_envelope = simulated_tempo_tx.map(FoundryTxEnvelope::Tempo);
9277
9278                    if is_amsterdam {
9279                        // Ensure simulated Amsterdam calls use EIP-8037's split gas schedule.
9280                        let spec = evm_env.cfg_env.spec;
9281                        evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec);
9282                    }
9283
9284                    // Always disable EIP-3607
9285                    evm_env.cfg_env.disable_eip3607 = true;
9286
9287                    // Simulated blocks keep their own base and blob base fees, which are what
9288                    // BASEFEE and BLOBBASEFEE read and what validation checks fees against.
9289                    evm_env.block_env.basefee = block_env.basefee;
9290                    evm_env.block_env.blob_excess_gas_and_price =
9291                        block_env.blob_excess_gas_and_price;
9292                    // Without validation, calls run at any price against the simulated block's
9293                    // fees.
9294                    evm_env.cfg_env.disable_base_fee = !validation;
9295                    if validation {
9296                        evm_env.cfg_env.disable_nonce_check = false;
9297                        evm_env.cfg_env.disable_block_gas_limit = false;
9298                    }
9299
9300                    let mut inspector = self.build_inspector();
9301
9302                    // transact
9303                    inspector = inspector.with_simulation_logs(trace_transfers);
9304                    trace!(target: "backend", env=?evm_env, spec=?evm_env.spec_id(),"simulate evm env");
9305                    let execution_result = match tx_env {
9306                        CallTxEnv::Eth(tx_env)
9307                            if !validation
9308                                && tx_env.tx_type == 3
9309                                && tx_env.max_fee_per_blob_gas == 0 =>
9310                        {
9311                            self.transact_eth_simulation_with_inspector_ref(
9312                                &cache_db.db,
9313                                &evm_env,
9314                                &mut inspector,
9315                                tx_env,
9316                                &precompile_overrides,
9317                            )
9318                        }
9319                        tx_env if precompile_overrides.moves.is_empty() => self
9320                            .transact_call_with_inspector_ref(
9321                                &cache_db.db,
9322                                &evm_env,
9323                                &mut inspector,
9324                                tx_env,
9325                                monad_context.as_mut().map(next_monad_context),
9326                            ),
9327                        tx_env => self.transact_eth_with_inspector_ref_and_precompile_overrides(
9328                            &cache_db.db,
9329                            &evm_env,
9330                            &mut inspector,
9331                            tx_env.into_base(),
9332                            &precompile_overrides,
9333                        ),
9334                    };
9335                    let ResultAndState { result, mut state } = match execution_result {
9336                        Err(BlockchainError::InvalidTransaction(error)) => {
9337                            return Err(simulate_transaction_error(error));
9338                        }
9339                        result => result?,
9340                    };
9341                    if !validation
9342                        && caller_nonce == u64::MAX
9343                        && uses_protocol_call_nonce
9344                        && let Some(account) = state.get_mut(&caller)
9345                    {
9346                        account.info.nonce = 0;
9347                    }
9348                    trace!(target: "backend", ?result, ?request, "simulate call");
9349
9350                    let canonical_logs = result.clone().into_logs();
9351                    let (response_logs, attempted_log_count) = inspector
9352                        .take_simulation_logs(&canonical_logs, result.is_success())
9353                        .expect("simulation log collector is installed");
9354                    inspector.print_logs();
9355                    if self.print_traces {
9356                        inspector.into_print_traces(self.call_trace_decoder());
9357                    }
9358
9359                    // REVM turns a previously deleted account into `Touched` when a later call
9360                    // recreates it without storage. Preserve the cleared-storage provenance so
9361                    // subsequent calls and the recursively merged state cannot reload old slots.
9362                    let previously_deleted = previously_deleted_accounts(
9363                        &cache_db.cache.accounts,
9364                        state.keys().copied(),
9365                    );
9366
9367                    rpc_gas_budget = rpc_gas_budget.saturating_sub(result.tx_gas_used());
9368                    cumulative_gas_used = cumulative_gas_used.saturating_add(result.tx_gas_used());
9369                    block_regular_gas_used = block_regular_gas_used
9370                        .saturating_add(result.gas().block_regular_gas_used());
9371                    block_state_gas_used =
9372                        block_state_gas_used.saturating_add(result.gas().block_state_gas_used());
9373
9374                    // create the transaction from a request
9375                    let from = caller;
9376                    request.sidecar = None;
9377                    let tx = if let Some(envelope) = simulated_envelope {
9378                        MaybeImpersonatedTransaction::impersonated(envelope, from)
9379                    } else {
9380                        if request.to.is_none() {
9381                            request.to = Some(TxKind::Create);
9382                        }
9383                        let mut request = self.parse_transaction_request(request)?;
9384                        request.prep_for_submission();
9385                        let typed_tx = request.build_unsigned().map_err(|e| {
9386                            BlockchainError::InvalidTransactionRequest(e.to_string())
9387                        })?;
9388                        MaybeImpersonatedTransaction::impersonated(
9389                            typed_tx.into_impersonated(),
9390                            from,
9391                        )
9392                    };
9393                    let tx_hash = tx.as_ref().hash();
9394                    #[cfg(feature = "optimism")]
9395                    if optimism_jovian {
9396                        let tx_blob_gas = crate::eth::backend::executor::optimism::blob_gas_used(
9397                            &mut cache_db.db,
9398                            tx.as_ref(),
9399                            true,
9400                        )
9401                        .map_err(|err| BlockchainError::Internal(err.to_string()))?;
9402                        if block_blob_gas_used.saturating_add(tx_blob_gas) > max_blob_gas {
9403                            return Err(BlockchainError::RpcError(RpcError::invalid_params(
9404                                format!(
9405                                    "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
9406                                ),
9407                            )));
9408                        }
9409                        block_blob_gas_used = block_blob_gas_used.saturating_add(tx_blob_gas);
9410                    }
9411
9412                    // Commit after calculating the footprint so the scalar comes from pre-tx
9413                    // state, matching the upstream OP block executor.
9414                    cache_db.commit(state);
9415                    cache_db.bump_bal_index();
9416                    preserve_deleted_storage(&mut cache_db.cache.accounts, previously_deleted);
9417                    #[cfg(any(feature = "base", feature = "optimism"))]
9418                    let receipt = if tx.as_ref().is_deposit() {
9419                        crate::eth::backend::executor::optimism::build_simulated_deposit_receipt(
9420                            self.hardfork(),
9421                            caller_nonce,
9422                            &result,
9423                            canonical_logs.clone(),
9424                            cumulative_gas_used,
9425                        )
9426                    } else {
9427                        FoundryReceiptBuilder::build_simulated_receipt(
9428                            tx.as_ref().tx_type(),
9429                            &result,
9430                            canonical_logs.clone(),
9431                            cumulative_gas_used,
9432                        )
9433                    };
9434                    #[cfg(not(any(feature = "base", feature = "optimism")))]
9435                    let receipt = FoundryReceiptBuilder::build_simulated_receipt(
9436                        tx.as_ref().tx_type(),
9437                        &result,
9438                        canonical_logs.clone(),
9439                        cumulative_gas_used,
9440                    );
9441                    receipts.push(receipt);
9442                    transaction_envelopes.push(tx.as_ref().clone());
9443                    let rpc_tx =
9444                        transaction_build(Some(tx_hash), tx, None, None, Some(block_env.basefee));
9445                    transactions.push(rpc_tx);
9446
9447                    let return_data = if result.is_success() {
9448                        result.output().cloned().unwrap_or_default()
9449                    } else {
9450                        Bytes::new()
9451                    };
9452                    let sim_res = SimCallResult {
9453                        return_data,
9454                        gas_used: result.tx_gas_used(),
9455                        max_used_gas: Some(
9456                            result.gas().total_gas_spent().max(result.gas().floor_gas()),
9457                        ),
9458                        status: result.is_success(),
9459                        error: match &result {
9460                            ExecutionResult::Success { .. } => None,
9461                            ExecutionResult::Revert { output, .. } => {
9462                                let message = RevertDecoder::new()
9463                                    .maybe_decode(output, None)
9464                                    .map(|reason| format!("execution reverted: {reason}"))
9465                                    .unwrap_or_else(|| "execution reverted".to_string());
9466                                Some(SimulateError {
9467                                    code: SimulateError::EXECUTION_REVERTED_CODE,
9468                                    message,
9469                                    data: Some(output.clone()),
9470                                })
9471                            }
9472                            ExecutionResult::Halt { reason, .. } => Some(SimulateError {
9473                                code: SimulateError::VM_EXECUTION_ERROR_CODE,
9474                                message: if matches!(reason, HaltReason::OutOfGas(_)) {
9475                                    "out of gas".to_string()
9476                                } else {
9477                                    format!("vm execution error: {reason}")
9478                                },
9479                                data: None,
9480                            }),
9481                        },
9482                        logs: response_logs
9483                            .into_iter()
9484                            .map(|(idx, log)| Log {
9485                                inner: log,
9486                                block_number: Some(rpc_block_number),
9487                                block_timestamp: Some(block_env.timestamp.saturating_to()),
9488                                transaction_index: Some(req_idx as u64),
9489                                log_index: Some(idx + log_index),
9490                                removed: false,
9491
9492                                block_hash: None,
9493                                transaction_hash: Some(tx_hash),
9494                            })
9495                            .collect(),
9496                    };
9497                    log_index += attempted_log_count;
9498                    call_res.push(sim_res);
9499                }
9500
9501                for (number, hash) in overridden_block_hashes {
9502                    if let Some(hash) = hash {
9503                        cache_db.cache.block_hashes.insert(number, hash);
9504                    } else {
9505                        cache_db.cache.block_hashes.remove(&number);
9506                    }
9507                }
9508
9509                let gas_used = if is_amsterdam {
9510                    block_regular_gas_used.max(block_state_gas_used)
9511                } else {
9512                    cumulative_gas_used
9513                };
9514                let requests = if let Some(transitions) = ethereum_transitions {
9515                    self.apply_simulation_post_execution_changes(
9516                        &mut cache_db,
9517                        &simulation_evm_env,
9518                        transitions,
9519                        &receipts,
9520                    )?
9521                } else {
9522                    Default::default()
9523                };
9524
9525                // Fork databases are partial, so their synthetic blocks use a zero state root.
9526                let incremental = state_root_cache.as_mut().is_some_and(|(cache, previous)| {
9527                    cache.record_overlay(&cache_db.cache.accounts, previous)
9528                });
9529                let state_root = if incremental {
9530                    let (cache, previous) = state_root_cache.as_mut().unwrap();
9531                    let root = cache.root(&cache_db.cache.accounts);
9532                    previous.clone_from(&cache_db.cache.accounts);
9533                    root
9534                } else {
9535                    cache_db
9536                        .maybe_full_db()
9537                        .map(|accounts| {
9538                            // A deleted base account can retain storage written by
9539                            // anvil_setStorageAt. A later touch exposes it in the full merge,
9540                            // although it has no trie leaf. Keep the full rebuild in that case.
9541                            if cache_state_roots
9542                                && accounts.values().all(|account| {
9543                                    account.account_state != AccountState::NotExisting
9544                                        || account.storage.is_empty()
9545                                })
9546                            {
9547                                let (cache, previous) =
9548                                    state_root_cache.get_or_insert_with(Default::default);
9549                                let root = cache.root(&accounts);
9550                                previous.clone_from(&cache_db.cache.accounts);
9551                                root
9552                            } else {
9553                                state_root_cache = None;
9554                                state_root(&accounts)
9555                            }
9556                        })
9557                        .unwrap_or_default()
9558                };
9559                let block_access_list_hash = cache_db
9560                    .bal_state
9561                    .take_built_alloy_bal()
9562                    .map(|bal| compute_block_access_list_hash(bal.as_slice()));
9563                let header = Header {
9564                    block_access_list_hash,
9565                    logs_bloom: receipts.iter().fold(Bloom::ZERO, |mut bloom, receipt| {
9566                        bloom.accrue_bloom(receipt.logs_bloom());
9567                        bloom
9568                    }),
9569                    transactions_root: calculate_transaction_root(&transaction_envelopes),
9570                    receipts_root: calculate_receipt_root(&receipts),
9571                    parent_hash,
9572                    beneficiary: block_env.beneficiary,
9573                    state_root,
9574                    difficulty: block_env.difficulty,
9575                    number: rpc_block_number,
9576                    gas_limit: block_env.gas_limit,
9577                    gas_used,
9578                    timestamp: block_env.timestamp.saturating_to(),
9579                    extra_data: base_fee_extra_data.clone(),
9580                    mix_hash: block_env.prevrandao.unwrap_or_default(),
9581                    nonce: Default::default(),
9582                    base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(block_env.basefee),
9583                    withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
9584                    blob_gas_used: is_cancun.then_some(block_blob_gas_used),
9585                    excess_blob_gas: if is_cancun { block_env.blob_excess_gas() } else { None },
9586                    parent_beacon_block_root: ethereum_transitions.and_then(|transitions| {
9587                        (transitions.hardfork >= EthereumHardfork::Cancun)
9588                            .then_some(transitions.parent_beacon_block_root.unwrap_or_default())
9589                    }),
9590                    requests_hash: ethereum_transitions.and_then(|transitions| {
9591                        (transitions.hardfork >= EthereumHardfork::Prague)
9592                            .then(|| requests.requests_hash())
9593                    }),
9594                    ..Default::default()
9595                };
9596                let block_hash = header.hash_slow();
9597                let withdrawals = (spec_id >= SpecId::SHANGHAI).then_some(Default::default());
9598                let size = U256::from(
9599                    BlockBody {
9600                        transactions: transaction_envelopes,
9601                        ommers: vec![],
9602                        withdrawals: withdrawals.clone(),
9603                    }
9604                    .into_block(header.clone())
9605                    .length(),
9606                );
9607                for (transaction_index, transaction) in transactions.iter_mut().enumerate() {
9608                    transaction.block_hash = Some(block_hash);
9609                    transaction.block_number = Some(header.number);
9610                    transaction.transaction_index = Some(transaction_index as u64);
9611                    transaction.block_timestamp = Some(header.timestamp);
9612                }
9613                let mut block = alloy_rpc_types::Block {
9614                    header: AnyRpcHeader {
9615                        hash: block_hash,
9616                        inner: header.into(),
9617                        total_difficulty: None,
9618                        size: Some(size),
9619                    },
9620                    uncles: vec![],
9621                    transactions: BlockTransactions::Full(transactions),
9622                    withdrawals,
9623                };
9624
9625                if !return_full_transactions {
9626                    block.transactions.convert_to_hashes();
9627                }
9628
9629                for res in &mut call_res {
9630                    res.logs.iter_mut().for_each(|log| {
9631                        log.block_hash = Some(block.header.hash);
9632                    });
9633                }
9634
9635                let mut block = AnyRpcBlock::new(WithOtherFields::new(block));
9636                if is_arbitrum(self.protocol_chain_id()) {
9637                    block
9638                        .other
9639                        .insert("l1BlockNumber".to_string(), serde_json::json!(block_env.number));
9640                }
9641                let simulated_block = SimulatedBlock { inner: block, calls: call_res };
9642
9643                parent_hash = block_hash;
9644                cache_db.cache.block_hashes.insert(U256::from(rpc_block_number), block_hash);
9645                inherited_block_env.beneficiary = block_env.beneficiary;
9646                inherited_block_env.difficulty = block_env.difficulty;
9647                inherited_block_env.gas_limit = block_env.gas_limit;
9648                // Route through the fee manager so Tempo chains use their own base fee rules.
9649                let header = &simulated_block.inner.header;
9650                next_base_fee = self.fees.calculate_next_block_base_fee_from_header(&header.inner);
9651                parent_base_fee_per_gas = header.base_fee_per_gas().unwrap_or_default();
9652                parent_excess_blob_gas = header.excess_blob_gas().unwrap_or_default();
9653                parent_blob_gas_used = header.blob_gas_used().unwrap_or_default();
9654
9655                block_res.push(simulated_block);
9656                #[cfg(feature = "monad")]
9657                self::monad::advance_block_context(&mut monad_context);
9658            }
9659
9660            Ok(block_res)
9661        };
9662
9663        match block_request {
9664            Some(BlockRequest::Pending(pool_transactions)) => {
9665                self.with_pending_block(pool_transactions, |state, block| {
9666                    let header = &block.block.header;
9667                    let parent_fees = self.fees.calculate_parent_header_fees(header);
9668                    let optimism_jovian =
9669                        self.is_optimism_jovian_at_header(header, parent_fees.optimism_jovian);
9670                    let monad_context = self.active_monad_context_before_mined_transaction(
9671                        &block.block,
9672                        block.block.body.transactions.len(),
9673                    )?;
9674                    #[cfg(feature = "monad")]
9675                    let monad_context = {
9676                        let mut monad_context = monad_context;
9677                        self::monad::advance_block_context(&mut monad_context);
9678                        monad_context
9679                    };
9680                    simulate_at(
9681                        state,
9682                        self.block_env_from_header(header),
9683                        header.number(),
9684                        header.timestamp(),
9685                        header.hash_slow(),
9686                        parent_fees.base_fee,
9687                        monad_context,
9688                        header.base_fee_per_gas().unwrap_or_default(),
9689                        header.excess_blob_gas().unwrap_or_default(),
9690                        header.blob_gas_used().unwrap_or_default(),
9691                        parent_fees.extra_data,
9692                        optimism_jovian,
9693                    )
9694                })
9695                .await?
9696            }
9697            block_request => {
9698                let base_block_number = match block_request.as_ref() {
9699                    Some(BlockRequest::Number(number)) => BlockNumber::Number(*number),
9700                    Some(BlockRequest::Pending(_)) => unreachable!(),
9701                    None => BlockNumber::Latest,
9702                };
9703                let base_block = self
9704                    .block_by_number(base_block_number)
9705                    .await?
9706                    .ok_or(BlockchainError::BlockNotFound)?;
9707                let base_number = base_block.header.number();
9708                let base_timestamp = base_block.header.timestamp();
9709                let base_hash = base_block.header.hash;
9710                let parent_fees = self.fees.calculate_parent_header_fees(&base_block.header.inner);
9711                let optimism_jovian = self.is_optimism_jovian_at_header(
9712                    &base_block.header.inner,
9713                    parent_fees.optimism_jovian,
9714                );
9715
9716                #[cfg(feature = "monad")]
9717                let monad_context = if self.is_monad() {
9718                    Some(self.monad_context_for_child_of_block_number(base_number).await?)
9719                } else {
9720                    None
9721                };
9722                #[cfg(not(feature = "monad"))]
9723                let monad_context = None;
9724
9725                self.with_database_at(block_request, |state, block_env| {
9726                    simulate_at(
9727                        state,
9728                        block_env,
9729                        base_number,
9730                        base_timestamp,
9731                        base_hash,
9732                        parent_fees.base_fee,
9733                        monad_context,
9734                        base_block.header.base_fee_per_gas().unwrap_or_default(),
9735                        base_block.header.excess_blob_gas().unwrap_or_default(),
9736                        base_block.header.blob_gas_used().unwrap_or_default(),
9737                        parent_fees.extra_data,
9738                        optimism_jovian,
9739                    )
9740                })
9741                .await?
9742            }
9743        }
9744    }
9745
9746    pub fn get_blob_by_tx_hash(&self, hash: B256) -> Result<Option<Vec<alloy_consensus::Blob>>> {
9747        let storage = self.blockchain.storage.read();
9748        Ok(storage.transactions.get(&hash).and_then(|mined| {
9749            storage
9750                .blocks
9751                .get(&mined.block_hash)?
9752                .body
9753                .transactions
9754                .get(mined.info.transaction_index as usize)?
9755                .as_ref()
9756                .sidecar()
9757                .map(|sidecar| sidecar.sidecar.blobs().to_vec())
9758        }))
9759    }
9760
9761    /// Sets the fee token for a user address (Tempo-only).
9762    pub async fn set_fee_token(&self, user: Address, token: Address) -> DatabaseResult<()> {
9763        self.with_tempo_storage(|| {
9764            let mut fee_manager = TipFeeManager::new();
9765            fee_manager
9766                .set_user_token(user, IFeeManager::setUserTokenCall { token })
9767                .map_err(tempo_db_err)
9768        })
9769        .await
9770    }
9771
9772    /// Sets the fee token for a validator address (Tempo-only).
9773    pub async fn set_validator_fee_token(
9774        &self,
9775        validator: Address,
9776        token: Address,
9777    ) -> DatabaseResult<()> {
9778        self.with_tempo_storage(|| {
9779            let mut fee_manager = TipFeeManager::new();
9780            // Use Address::ZERO as beneficiary so the check `sender != beneficiary` passes
9781            fee_manager
9782                .set_validator_token(
9783                    validator,
9784                    IFeeManager::setValidatorTokenCall { token },
9785                    Address::ZERO,
9786                )
9787                .map_err(tempo_db_err)
9788        })
9789        .await
9790    }
9791
9792    /// Mints FeeAMM liquidity for a token pair (Tempo-only).
9793    pub async fn set_fee_amm_liquidity(
9794        &self,
9795        user_token: Address,
9796        validator_token: Address,
9797        amount: U256,
9798    ) -> DatabaseResult<()> {
9799        // T3+ rejects minting to the zero address.
9800        let admin = Address::repeat_byte(0x11);
9801        self.with_tempo_storage(|| {
9802            // Mint the required tokens to admin so it can provide liquidity.
9803            // grant_role_internal bypasses the caller check, matching genesis seeding.
9804            for &token_address in &[user_token, validator_token] {
9805                let mut token = TIP20Token::from_address(token_address).map_err(tempo_db_err)?;
9806                token
9807                    .grant_role_internal(admin, TIP20Token::issuer_role())
9808                    .map_err(tempo_db_err)?;
9809                token.mint(admin, ITIP20::mintCall { to: admin, amount }).map_err(tempo_db_err)?;
9810            }
9811            let mut fee_manager = TipFeeManager::new();
9812            fee_manager
9813                .mint(admin, user_token, validator_token, amount, admin)
9814                .map_err(tempo_db_err)?;
9815            Ok(())
9816        })
9817        .await
9818    }
9819
9820    /// Sets an account's balance for a deployed TIP-20 token (Tempo-only).
9821    pub async fn set_tip20_balance(
9822        &self,
9823        address: Address,
9824        token_address: Address,
9825        balance: U256,
9826    ) -> DatabaseResult<()> {
9827        if self.try_set_tip20_balance(address, token_address, balance).await? {
9828            return Ok(());
9829        }
9830
9831        Err(tempo_db_err(format!("address {token_address} is not a deployed TIP-20 token")))
9832    }
9833
9834    /// Sets an account's balance if the address is a deployed TIP-20 token (Tempo-only).
9835    pub async fn try_set_tip20_balance(
9836        &self,
9837        address: Address,
9838        token_address: Address,
9839        balance: U256,
9840    ) -> DatabaseResult<bool> {
9841        self.with_tempo_storage(|| {
9842            if !TIP20Factory::new().is_tip20(token_address).map_err(tempo_db_err)? {
9843                return Ok(false);
9844            }
9845
9846            let mut token = TIP20Token::from_address(token_address).map_err(tempo_db_err)?;
9847            token.balances[address].write(balance).map_err(tempo_db_err)?;
9848            Ok(true)
9849        })
9850        .await
9851    }
9852
9853    /// Runs `f` inside a Tempo storage context initialized from the current state
9854    /// (Tempo-only).
9855    async fn with_tempo_storage<R>(&self, f: impl FnOnce() -> R) -> R {
9856        let hardfork = self.hardfork();
9857        // One consistent snapshot of the current env to build the storage context.
9858        let (chain_id, timestamp, block_number) = {
9859            let env = self.evm_env.read();
9860            (env.cfg_env.chain_id, env.block_env.timestamp, env.block_env.number.to::<u64>())
9861        };
9862        let mut db = self.db.write().await;
9863        let mut storage = AnvilStorageProvider::new(
9864            &mut **db,
9865            chain_id,
9866            timestamp,
9867            block_number,
9868            hardfork.into(),
9869        );
9870        StorageCtx::enter(&mut storage, f)
9871    }
9872}
9873
9874/// Converts a Tempo error into an anvil [`DatabaseError`].
9875fn tempo_db_err<E: std::fmt::Display>(e: E) -> DatabaseError {
9876    DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))
9877}
9878
9879/// Get max nonce from transaction pool by address.
9880fn get_pool_transactions_nonce(
9881    pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
9882    address: Address,
9883) -> Option<u64> {
9884    if let Some(highest_nonce) = pool_transactions
9885        .iter()
9886        .filter(|tx| {
9887            *tx.pending_transaction.sender() == address
9888                && !tx.pending_transaction.transaction.as_ref().has_nonzero_tempo_nonce_key()
9889        })
9890        .map(|tx| tx.pending_transaction.nonce())
9891        .max()
9892    {
9893        let tx_count = highest_nonce.saturating_add(1);
9894        return Some(tx_count);
9895    }
9896    None
9897}
9898
9899impl<N: Network> Backend<N>
9900where
9901    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
9902{
9903    /// Validates a transaction candidate selected for mining.
9904    fn validate_mining_pool_transaction_for(
9905        &self,
9906        pool_tx: &PoolTransaction<FoundryTxEnvelope>,
9907        account: &AccountInfo,
9908        evm_env: &EvmEnv,
9909    ) -> Result<(), InvalidTransactionError> {
9910        #[cfg(feature = "monad")]
9911        if self.validate_monad_mining_pool_transaction_for(pool_tx, account, evm_env)? {
9912            return Ok(());
9913        }
9914
9915        self.validate_pool_transaction_for(&pool_tx.pending_transaction, account, evm_env)
9916    }
9917
9918    /// Runs Tempo's transaction-pool validation for `pending` against the latest state.
9919    ///
9920    /// This is the pipeline the node runs before admitting a transaction: it resolves the fee
9921    /// token the way execution will, including a token inferred from the called TIP20 or DEX
9922    /// swap and a preference the transaction itself sets, charges intrinsic gas, and checks the
9923    /// fee payer's balance against the maximum fee with execution's rounding, all without
9924    /// committing state.
9925    async fn validate_tempo_pool_transaction(
9926        &self,
9927        pending: &PendingTransaction<FoundryTxEnvelope>,
9928        evm_env: &EvmEnv,
9929    ) -> Result<(), BlockchainError> {
9930        let tx = pending.transaction.as_ref();
9931        let tx_env: TempoTxEnv =
9932            FromTxWithEncoded::from_encoded_tx(tx, *pending.sender(), tx.encoded_2718().into());
9933        let evm_env = evm_env.clone();
9934        self.with_database_at(None, move |state, _| {
9935            let cache_db = CacheDB::new(state);
9936            let mut inspector = self.build_inspector();
9937            let mut evm = TempoEvmFactory::default().create_evm_with_inspector(
9938                WrapDatabaseRef(&cache_db),
9939                Self::build_tempo_evm_env(&evm_env, self.tempo_hardfork()),
9940                &mut inspector,
9941            );
9942            self.inject_tempo_precompiles(&mut evm, &evm_env);
9943            evm.configure_for_pool();
9944            let (result, _) = evm.validate_pool_transaction(tx_env);
9945            result.map(drop).map_err(|err| match err {
9946                // Tempo reports a fee token shortfall through the native funds error; name the
9947                // token balance instead, since the sender holds no native balance to speak of.
9948                EVMError::Transaction(TempoInvalidTransaction::EthInvalidTransaction(
9949                    InvalidTransaction::LackOfFundForMaxFee { fee, balance },
9950                )) => InvalidTransactionError::TempoInsufficientFeeTokenBalance {
9951                    balance: *balance,
9952                    required: *fee,
9953                }
9954                .into(),
9955                err => err.into(),
9956            })
9957        })
9958        .await?
9959    }
9960}
9961
9962#[async_trait::async_trait]
9963impl<N: Network> TransactionValidator<FoundryTxEnvelope> for Backend<N>
9964where
9965    N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
9966{
9967    async fn validate_pool_transaction(
9968        &self,
9969        tx: &PendingTransaction<FoundryTxEnvelope>,
9970    ) -> Result<(), BlockchainError> {
9971        let address = *tx.sender();
9972        let account = self.get_account(address).await?;
9973        #[cfg_attr(not(feature = "base"), allow(unused_mut))]
9974        let mut evm_env = self.next_evm_env(&**self.db.read().await)?;
9975        #[cfg(feature = "base")]
9976        if tx.transaction.as_ref().is_eip8130() {
9977            evm_env.block_env.timestamp = U256::from(self.eip8130_pool_timestamp());
9978        }
9979
9980        #[cfg(feature = "base")]
9981        if let FoundryTxEnvelope::Eip8130(signed) = tx.transaction.as_ref() {
9982            let body = signed.tx();
9983            let now = self.eip8130_pool_timestamp_ms();
9984            signed
9985                .validate_admission_static(self.chain_id().to())
9986                .map_err(|error| BlockchainError::Eip8130TransactionRejected(error.to_string()))?;
9987            signed
9988                .validate_timestamp(now)
9989                .map_err(|error| BlockchainError::Eip8130TransactionRejected(error.to_string()))?;
9990
9991            let (nonce_key_first_use, ready_for_execution) =
9992                if body.nonce_key == Eip8130Constants::NONCE_KEY_MAX {
9993                    (false, true)
9994                } else if body.nonce_key.is_zero() {
9995                    (account.nonce == 0, body.nonce_sequence == account.nonce)
9996                } else {
9997                    let slot = NonceManagerStorage::nonce_slot(address, body.nonce_key).map_err(
9998                        |error| BlockchainError::Eip8130TransactionRejected(error.to_string()),
9999                    )?;
10000                    let word = self.storage_at(NonceManagerStorage::ADDRESS, slot, None).await?;
10001                    let channel_nonce = Eip8130Nonce::decode_channel_nonce(word.into()).to::<u64>();
10002                    (channel_nonce == 0, body.nonce_sequence == channel_nonce)
10003                };
10004            let intrinsic = IntrinsicGas::compute(
10005                signed,
10006                &signed.encoded_2718(),
10007                &IntrinsicGasInput::worst_case(
10008                    nonce_key_first_use,
10009                    IntrinsicGasInput::sender_auto_delegated(&body.account_changes),
10010                    body.payer.is_some(),
10011                ),
10012            )
10013            .map_err(|error| BlockchainError::Eip8130TransactionRejected(error.to_string()))?;
10014            if intrinsic.execution_gas_available(body.gas_limit).is_none() {
10015                return Err(BlockchainError::Eip8130TransactionRejected(
10016                    "sender intrinsic gas exceeds transaction gas limit".to_string(),
10017                ));
10018            }
10019
10020            let payer = body.payer.unwrap_or(address);
10021            let payer_account =
10022                if payer == address { account.clone() } else { self.get_account(payer).await? };
10023            FeeCheck::validate_balance(
10024                payer_account.balance,
10025                body.gas_limit,
10026                intrinsic.payer_auth,
10027                body.max_fee_per_gas,
10028            )
10029            .map_err(|error| BlockchainError::Eip8130TransactionRejected(error.to_string()))?;
10030
10031            // Run once against read-only state so authorization, account changes, replay checks,
10032            // and payer rules fail at admission instead of after entering the pool. A buffered
10033            // nonce is the sole expected inclusion error: Base's pool accepts it until the lane
10034            // gap is filled, while every earlier authorize/apply step has still been validated.
10035            let db = self.db.read().await;
10036            let mut inspector = NoOpInspector;
10037            let validation = self.transact_envelope_with_inspector_ref_and_context(
10038                &**db,
10039                &evm_env,
10040                &mut inspector,
10041                tx,
10042                None,
10043            );
10044            Eip8130PhaseStatuses::clear();
10045            match validation {
10046                Ok(_) => {}
10047                Err(BlockchainError::Eip8130TransactionRejected(reason))
10048                    if !ready_for_execution && reason.contains("is ahead of the channel nonce") => {
10049                }
10050                Err(error) => return Err(error),
10051            }
10052        }
10053
10054        // Tempo AA: validate time bounds (async checks)
10055        if let FoundryTxEnvelope::Tempo(aa_tx) = tx.transaction.as_ref() {
10056            let tempo_tx = aa_tx.tx();
10057            let current_time = evm_env.block_env.timestamp.saturating_to::<u64>();
10058
10059            // Reject if valid_before is expired or too close to current time (< 3 seconds)
10060            const AA_VALID_BEFORE_MIN_SECS: u64 = 3;
10061            if let Some(valid_before) = tempo_tx.valid_before.map(|v| v.get()) {
10062                let min_allowed = current_time.saturating_add(AA_VALID_BEFORE_MIN_SECS);
10063                if valid_before <= min_allowed {
10064                    return Err(InvalidTransactionError::TempoValidBeforeExpired {
10065                        valid_before,
10066                        min_allowed,
10067                    }
10068                    .into());
10069                }
10070
10071                let hardfork = self.tempo_hardfork();
10072                if hardfork.is_t1() && tempo_tx.is_expiring_nonce_tx() {
10073                    let max_expiry_secs = hardfork.expiring_nonce_max_expiry_secs();
10074                    let max_allowed = current_time.saturating_add(max_expiry_secs);
10075                    if valid_before > max_allowed {
10076                        return Err(InvalidTransactionError::TempoValidBeforeTooFar {
10077                            valid_before,
10078                            max_expiry_secs,
10079                            max_allowed,
10080                        }
10081                        .into());
10082                    }
10083                }
10084            }
10085
10086            // Reject if valid_after is too far in the future. Mirrors Tempo's default pool limit
10087            // (`DEFAULT_AA_VALID_AFTER_MAX_SECS`), which is aligned with its queued transaction
10088            // lifetime.
10089            const AA_VALID_AFTER_MAX_SECS: u64 = 120;
10090            if let Some(valid_after) = tempo_tx.valid_after.map(|v| v.get()) {
10091                let max_allowed = current_time.saturating_add(AA_VALID_AFTER_MAX_SECS);
10092                if valid_after > max_allowed {
10093                    return Err(InvalidTransactionError::TempoValidAfterTooFar {
10094                        valid_after,
10095                        max_valid_after_secs: AA_VALID_AFTER_MAX_SECS,
10096                        max_allowed,
10097                    }
10098                    .into());
10099                }
10100            }
10101        }
10102
10103        self.validate_pool_transaction_for(tx, &account, &evm_env)?;
10104
10105        // Tempo charges gas in a fee token for every transaction type, never in the native token,
10106        // so the node's own pool validation replaces the native balance check skipped above.
10107        if self.is_tempo() && !self.disable_pool_balance_checks {
10108            self.validate_tempo_pool_transaction(tx, &evm_env).await?;
10109        }
10110
10111        Ok(())
10112    }
10113
10114    fn validate_pool_transaction_for(
10115        &self,
10116        pending: &PendingTransaction<FoundryTxEnvelope>,
10117        account: &AccountInfo,
10118        evm_env: &EvmEnv,
10119    ) -> Result<(), InvalidTransactionError> {
10120        let tx = &pending.transaction;
10121
10122        if let Some(tx_chain_id) = tx.chain_id() {
10123            let chain_id = self.chain_id();
10124            if chain_id.to::<u64>() != tx_chain_id {
10125                if let FoundryTxEnvelope::Legacy(tx) = tx.as_ref() {
10126                    // <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md>
10127                    if evm_env.cfg_env.spec >= SpecId::SPURIOUS_DRAGON && tx.chain_id().is_none() {
10128                        debug!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V");
10129                        return Err(InvalidTransactionError::IncompatibleEIP155);
10130                    }
10131                } else {
10132                    debug!(target: "backend", ?chain_id, ?tx_chain_id, "invalid chain id");
10133                    return Err(InvalidTransactionError::InvalidChainId);
10134                }
10135            }
10136        }
10137
10138        // Reject native value transfers on Tempo networks
10139        if self.is_tempo() && !tx.value().is_zero() {
10140            warn!(target: "backend", "[{:?}] native value transfer not allowed in Tempo mode", tx.hash());
10141            return Err(InvalidTransactionError::TempoNativeValueTransfer);
10142        }
10143
10144        // Tempo AA T5: cap authorization list size
10145        if self.is_tempo_hardfork_active(TempoHardfork::T5)
10146            && let FoundryTxEnvelope::Tempo(aa_tx) = tx.as_ref()
10147        {
10148            const MAX_TEMPO_AUTHORIZATIONS: usize = 16;
10149            let auth_count = aa_tx.tx().tempo_authorization_list.len();
10150            if auth_count > MAX_TEMPO_AUTHORIZATIONS {
10151                warn!(target: "backend", "[{:?}] Tempo tx has too many authorizations: {}", tx.hash(), auth_count);
10152                return Err(InvalidTransactionError::TempoTooManyAuthorizations {
10153                    count: auth_count,
10154                    max: MAX_TEMPO_AUTHORIZATIONS,
10155                });
10156            }
10157        }
10158
10159        #[cfg(feature = "base")]
10160        if let FoundryTxEnvelope::Eip8130(signed) = pending.transaction.as_ref() {
10161            signed
10162                .validate_admission_static(evm_env.cfg_env.chain_id)
10163                .map_err(|error| InvalidTransactionError::Eip8130(error.to_string()))?;
10164            signed
10165                .validate_timestamp(
10166                    evm_env.block_env.timestamp.saturating_to::<u64>().saturating_mul(1_000),
10167                )
10168                .map_err(|error| InvalidTransactionError::Eip8130(error.to_string()))?;
10169        }
10170
10171        // Nonce validation — skip for deposits (L1→L2), EIP-8130, and Tempo.
10172        #[cfg(any(feature = "base", feature = "optimism"))]
10173        let is_deposit_tx = pending.transaction.as_ref().is_deposit();
10174        #[cfg(not(any(feature = "base", feature = "optimism")))]
10175        let is_deposit_tx = false;
10176        #[cfg(feature = "base")]
10177        let is_eip8130_tx = pending.transaction.as_ref().is_eip8130();
10178        #[cfg(not(feature = "base"))]
10179        let is_eip8130_tx = false;
10180        let is_tempo_tx = pending.transaction.as_ref().is_tempo();
10181        let nonce = tx.nonce();
10182        if nonce < account.nonce && !is_deposit_tx && !is_eip8130_tx && !is_tempo_tx {
10183            debug!(target: "backend", "[{:?}] nonce too low", tx.hash());
10184            return Err(InvalidTransactionError::NonceTooLow);
10185        }
10186
10187        #[cfg(feature = "monad")]
10188        self.validate_monad_transaction_type(tx)?;
10189
10190        // EIP-4844 structural validation
10191        if evm_env.cfg_env.spec >= SpecId::CANCUN && tx.is_eip4844() {
10192            // Heavy (blob validation) checks
10193            let blob_tx = match tx.as_ref() {
10194                FoundryTxEnvelope::Eip4844(tx) => tx.tx(),
10195                _ => unreachable!(),
10196            };
10197
10198            let blob_count = blob_tx.tx().blob_versioned_hashes.len();
10199
10200            // Ensure there are blob hashes.
10201            if blob_count == 0 {
10202                return Err(InvalidTransactionError::NoBlobHashes);
10203            }
10204
10205            // Ensure the tx does not exceed the max blobs per transaction.
10206            let max_blobs_per_tx = self.blob_params().max_blobs_per_tx as usize;
10207            if blob_count > max_blobs_per_tx {
10208                return Err(InvalidTransactionError::TooManyBlobs(blob_count, max_blobs_per_tx));
10209            }
10210
10211            // Check for any blob validation errors if not impersonating.
10212            if !self.skip_blob_validation(Some(*pending.sender()))
10213                && let Err(err) = blob_tx.validate(EnvKzgSettings::default().get())
10214            {
10215                return Err(InvalidTransactionError::BlobTransactionValidationError(err));
10216            }
10217        }
10218
10219        // EIP-3860 initcode size validation, respects --code-size-limit / --disable-code-size-limit
10220        if evm_env.cfg_env.spec >= SpecId::SHANGHAI && tx.kind().is_create() {
10221            let max_initcode_size = evm_env
10222                .cfg_env
10223                .limit_contract_code_size
10224                .map(|limit| limit.saturating_mul(2))
10225                .unwrap_or_else(|| self.max_initcode_size(evm_env));
10226            if tx.input().len() > max_initcode_size {
10227                return Err(InvalidTransactionError::MaxInitCodeSizeExceeded);
10228            }
10229        }
10230
10231        // Balance and fee related checks
10232        if !self.disable_pool_balance_checks {
10233            // Gas limit validation. Under EIP-2780 a transaction's intrinsic gas starts at a lower
10234            // base, which execution then checks in full.
10235            let min_gas = if evm_env.cfg_env.enable_amsterdam_eip2780 {
10236                eip2780::TX_BASE_COST
10237            } else {
10238                MIN_TRANSACTION_GAS as u64
10239            };
10240            if tx.gas_limit() < min_gas {
10241                debug!(target: "backend", "[{:?}] gas too low", tx.hash());
10242                return Err(InvalidTransactionError::GasTooLow);
10243            }
10244
10245            // Check tx gas limit against block gas limit, if block gas limit is set.
10246            if !evm_env.cfg_env.disable_block_gas_limit
10247                && tx.gas_limit() > evm_env.block_env.gas_limit
10248            {
10249                debug!(target: "backend", "[{:?}] gas too high", tx.hash());
10250                return Err(InvalidTransactionError::GasTooHigh(ErrDetail {
10251                    detail: String::from("tx.gas_limit > env.block.gas_limit"),
10252                }));
10253            }
10254
10255            // Check tx gas limit against tx gas limit cap (Osaka hard fork and later).
10256            if evm_env.cfg_env.tx_gas_limit_cap.is_none()
10257                && tx.gas_limit() > self.tx_gas_limit_cap(evm_env)
10258            {
10259                debug!(target: "backend", "[{:?}] gas too high", tx.hash());
10260                return Err(InvalidTransactionError::GasTooHigh(ErrDetail {
10261                    detail: String::from("tx.gas_limit > resolved tx gas limit cap"),
10262                }));
10263            }
10264
10265            // EIP-1559 fee validation (London hard fork and later).
10266            if evm_env.cfg_env.spec >= SpecId::LONDON {
10267                if tx.max_fee_per_gas() < evm_env.block_env.basefee.into() && !is_deposit_tx {
10268                    debug!(target: "backend", "max fee per gas={}, too low, block basefee={}", tx.max_fee_per_gas(), evm_env.block_env.basefee);
10269                    return Err(InvalidTransactionError::FeeCapTooLow);
10270                }
10271
10272                if !evm_env.cfg_env.disable_priority_fee_check
10273                    && let (Some(max_priority_fee_per_gas), max_fee_per_gas) =
10274                        (tx.as_ref().max_priority_fee_per_gas(), tx.as_ref().max_fee_per_gas())
10275                    && max_priority_fee_per_gas > max_fee_per_gas
10276                {
10277                    debug!(target: "backend", "max priority fee per gas={}, too high, max fee per gas={}", max_priority_fee_per_gas, max_fee_per_gas);
10278                    return Err(InvalidTransactionError::TipAboveFeeCap);
10279                }
10280            }
10281
10282            // EIP-4844 blob fee validation
10283            if evm_env.cfg_env.spec >= SpecId::CANCUN
10284                && tx.is_eip4844()
10285                && let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas()
10286                && let Some(blob_gas_and_price) = &evm_env.block_env.blob_excess_gas_and_price
10287                && max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice
10288            {
10289                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);
10290                return Err(InvalidTransactionError::BlobFeeCapTooLow(
10291                    max_fee_per_blob_gas,
10292                    blob_gas_and_price.blob_gasprice,
10293                ));
10294            }
10295
10296            let value = tx.value();
10297            match tx.as_ref() {
10298                #[cfg(any(feature = "base", feature = "optimism"))]
10299                FoundryTxEnvelope::Deposit(deposit_tx) => {
10300                    // Deposit transactions
10301                    // https://specs.optimism.io/protocol/deposits.html#execution
10302                    // 1. no gas cost check required since already have prepaid gas from L1
10303                    // 2. increment account balance by deposited amount before checking for
10304                    //    sufficient funds `tx.value <= existing account value + deposited value`
10305                    if value > account.balance + U256::from(deposit_tx.mint) {
10306                        debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + U256::from(deposit_tx.mint), value, *pending.sender());
10307                        return Err(InvalidTransactionError::InsufficientFunds);
10308                    }
10309                }
10310                #[cfg(feature = "base")]
10311                FoundryTxEnvelope::Eip8130(_) => {
10312                    // EIP-8130 affordability is checked asynchronously against
10313                    // the resolved payer, including payer-authentication gas.
10314                }
10315                FoundryTxEnvelope::Tempo(_) => {
10316                    // Tempo AA transactions pay gas with fee tokens, not ETH.
10317                    // Fee token balance is validated in validate_pool_transaction (async).
10318                }
10319                _ if self.is_tempo() => {
10320                    // Every transaction on Tempo pays gas in a fee token; forked mainnet senders
10321                    // hold no native balance at all. The fee token balance is validated in
10322                    // validate_pool_transaction (async).
10323                }
10324                #[cfg(feature = "monad")]
10325                _ if self.validate_monad_transaction_funds(pending, account, evm_env)? => {}
10326                _ => {
10327                    let max_cost = (tx.gas_limit() as u128)
10328                        .saturating_mul(tx.max_fee_per_gas())
10329                        .saturating_add(
10330                            tx.blob_gas_used()
10331                                .map(|g| g as u128)
10332                                .unwrap_or(0)
10333                                .mul(tx.max_fee_per_blob_gas().unwrap_or(0)),
10334                        );
10335                    // check sufficient funds: `gas * price + value`
10336                    let req_funds =
10337                        max_cost.checked_add(value.saturating_to()).ok_or_else(|| {
10338                            debug!(target: "backend", "[{:?}] cost too high", tx.hash());
10339                            InvalidTransactionError::InsufficientFunds
10340                        })?;
10341                    if account.balance < U256::from(req_funds) {
10342                        debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender());
10343                        return Err(InvalidTransactionError::InsufficientFunds);
10344                    }
10345                }
10346            }
10347        }
10348        Ok(())
10349    }
10350
10351    fn validate_for(
10352        &self,
10353        tx: &PendingTransaction<FoundryTxEnvelope>,
10354        account: &AccountInfo,
10355        evm_env: &EvmEnv,
10356    ) -> Result<(), InvalidTransactionError> {
10357        self.validate_pool_transaction_for(tx, account, evm_env)?;
10358        // EIP-8130 counts nonces per channel in the nonce manager, so `tx.nonce()` is not
10359        // comparable to the account's protocol nonce. Admission already validated the channel.
10360        #[cfg(feature = "base")]
10361        if tx.transaction.as_ref().is_eip8130() {
10362            return Ok(());
10363        }
10364        if tx.nonce() > account.nonce {
10365            return Err(InvalidTransactionError::NonceTooHigh);
10366        }
10367        Ok(())
10368    }
10369}
10370
10371/// Replaces the cached hash of a [`Signed`] transaction, preserving the inner tx and signature.
10372fn rehash<T>(signed: Signed<T>, hash: B256) -> Signed<T>
10373where
10374    T: alloy_consensus::transaction::RlpEcdsaEncodableTx,
10375{
10376    let (t, sig, _) = signed.into_parts();
10377    Signed::new_unchecked(t, sig, hash)
10378}
10379
10380fn build_rpc_transaction(
10381    envelope: AnyTxEnvelope,
10382    from: Address,
10383    block: Option<&Block>,
10384    info: Option<&TransactionInfo>,
10385    effective_gas_price: Option<u128>,
10386) -> AnyRpcTransaction {
10387    let tx = Transaction {
10388        inner: Recovered::new_unchecked(envelope, from),
10389        block_hash: block.map(|block| block.header.hash_slow()),
10390        block_number: block.map(|block| block.header.number()),
10391        transaction_index: info.map(|info| info.transaction_index),
10392        effective_gas_price,
10393        block_timestamp: block.map(|block| block.header.timestamp()),
10394    };
10395    AnyRpcTransaction::from(WithOtherFields::new(tx))
10396}
10397
10398/// Creates a `AnyRpcTransaction` as it's expected for the `eth` RPC api from storage data
10399pub fn transaction_build(
10400    tx_hash: Option<B256>,
10401    eth_transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>,
10402    block: Option<&Block>,
10403    info: Option<TransactionInfo>,
10404    base_fee: Option<u64>,
10405) -> AnyRpcTransaction {
10406    let mined_from = info.as_ref().map(|info| info.from);
10407
10408    #[cfg(any(feature = "base", feature = "optimism"))]
10409    if let FoundryTxEnvelope::Deposit(deposit_tx) = eth_transaction.as_ref() {
10410        let dep_tx = deposit_tx;
10411
10412        let ser = serde_json::to_value(dep_tx).expect("could not serialize TxDeposit");
10413        let maybe_deposit_fields = OtherFields::try_from(ser);
10414
10415        match maybe_deposit_fields {
10416            Ok(mut fields) => {
10417                // Add zeroed signature fields for backwards compatibility
10418                // https://specs.optimism.io/protocol/deposits.html#the-deposited-transaction-type
10419                fields.insert("v".to_string(), serde_json::to_value("0x0").unwrap());
10420                fields.insert("r".to_string(), serde_json::to_value(B256::ZERO).unwrap());
10421                fields.insert(String::from("s"), serde_json::to_value(B256::ZERO).unwrap());
10422                fields.insert(String::from("nonce"), serde_json::to_value("0x0").unwrap());
10423
10424                let inner = UnknownTypedTransaction {
10425                    ty: AnyTxType(DEPOSIT_TX_TYPE_ID),
10426                    fields,
10427                    memo: Default::default(),
10428                };
10429
10430                let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
10431                    hash: tx_hash.unwrap_or_else(|| eth_transaction.hash()),
10432                    inner,
10433                });
10434
10435                return build_rpc_transaction(
10436                    envelope,
10437                    mined_from.unwrap_or(deposit_tx.from),
10438                    block,
10439                    info.as_ref(),
10440                    None,
10441                );
10442            }
10443            Err(_) => {
10444                error!(target: "backend", "failed to serialize deposit transaction");
10445            }
10446        }
10447    }
10448
10449    #[cfg(feature = "base")]
10450    if let FoundryTxEnvelope::Eip8130(eip8130_tx) = eth_transaction.as_ref() {
10451        let from = mined_from.unwrap_or_else(|| eth_transaction.recover().unwrap_or_default());
10452        let rpc_tx = BaseRpcTransaction::from_transaction(
10453            Recovered::new_unchecked(BaseTxEnvelope::Eip8130(eip8130_tx.clone()), from),
10454            BaseTransactionInfo::default(),
10455        );
10456        let mut fields = OtherFields::try_from(
10457            serde_json::to_value(rpc_tx)
10458                .expect("could not serialize canonical EIP-8130 transaction"),
10459        )
10460        .expect("EIP-8130 transaction must serialize to an object");
10461        for key in
10462            ["hash", "from", "blockHash", "blockNumber", "transactionIndex", "effectiveGasPrice"]
10463        {
10464            fields.remove(key);
10465        }
10466        let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
10467            hash: eth_transaction.hash(),
10468            inner: UnknownTypedTransaction {
10469                ty: AnyTxType(EIP8130_TRANSACTION_TYPE),
10470                fields,
10471                memo: Default::default(),
10472            },
10473        });
10474        return build_rpc_transaction(envelope, from, block, info.as_ref(), None);
10475    }
10476
10477    if let FoundryTxEnvelope::Celo(tx) = eth_transaction.as_ref() {
10478        let mut fields =
10479            OtherFields::try_from(serde_json::to_value(tx).expect("valid CIP-64 transaction"))
10480                .expect("CIP-64 transaction serializes to an object");
10481        fields.remove("hash");
10482        let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
10483            hash: tx_hash.unwrap_or_else(|| eth_transaction.hash()),
10484            inner: UnknownTypedTransaction {
10485                ty: AnyTxType(foundry_primitives::CIP64_TX_TYPE),
10486                fields,
10487                memo: Default::default(),
10488            },
10489        });
10490        let from = mined_from.unwrap_or_else(|| eth_transaction.recover().unwrap_or_default());
10491        let effective_gas_price = block.map(|_| eth_transaction.effective_gas_price(base_fee));
10492        return build_rpc_transaction(envelope, from, block, info.as_ref(), effective_gas_price);
10493    }
10494    if let FoundryTxEnvelope::Tempo(tempo_tx) = eth_transaction.as_ref() {
10495        let from = mined_from.unwrap_or_else(|| eth_transaction.recover().unwrap_or_default());
10496        let ser = serde_json::to_value(tempo_tx).expect("could not serialize Tempo transaction");
10497        let maybe_tempo_fields = OtherFields::try_from(ser);
10498
10499        match maybe_tempo_fields {
10500            Ok(fields) => {
10501                let inner = UnknownTypedTransaction {
10502                    ty: AnyTxType(TEMPO_TX_TYPE_ID),
10503                    fields,
10504                    memo: Default::default(),
10505                };
10506
10507                let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
10508                    hash: tx_hash.unwrap_or_else(|| eth_transaction.hash()),
10509                    inner,
10510                });
10511
10512                return build_rpc_transaction(envelope, from, block, info.as_ref(), None);
10513            }
10514            Err(_) => {
10515                error!(target: "backend", "failed to serialize tempo transaction");
10516            }
10517        }
10518    }
10519
10520    let from = mined_from.unwrap_or_else(|| eth_transaction.recover().unwrap_or_default());
10521    let effective_gas_price = eth_transaction.effective_gas_price(base_fee);
10522
10523    // if a specific hash was provided we update the transaction's hash
10524    // This is important for impersonated transactions since they all use the
10525    // `BYPASS_SIGNATURE` which would result in different hashes
10526    // Note: for impersonated transactions this only concerns pending transactions because
10527    // there's no `info` yet.
10528    let hash = tx_hash.unwrap_or_else(|| eth_transaction.hash());
10529
10530    let eth_envelope = FoundryTxEnvelope::from(eth_transaction)
10531        .try_into_eth()
10532        .expect("non-standard transactions are handled above");
10533
10534    let envelope = match eth_envelope {
10535        TxEnvelope::Legacy(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Legacy(rehash(s, hash))),
10536        TxEnvelope::Eip1559(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip1559(rehash(s, hash))),
10537        TxEnvelope::Eip2930(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip2930(rehash(s, hash))),
10538        TxEnvelope::Eip4844(s) => {
10539            let s = if block.is_some() { s.map(TxEip4844Variant::drop_sidecar) } else { s };
10540            AnyTxEnvelope::Ethereum(TxEnvelope::Eip4844(rehash(s, hash)))
10541        }
10542        TxEnvelope::Eip7702(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(rehash(s, hash))),
10543    };
10544
10545    build_rpc_transaction(envelope, from, block, info.as_ref(), Some(effective_gas_price))
10546}
10547
10548/// Prove a storage key's existence or nonexistence in the account's storage trie.
10549///
10550/// `storage_key` is the hash of the desired storage key, meaning
10551/// this will only work correctly under a secure trie.
10552/// `storage_key` == keccak(key)
10553pub fn prove_storage(
10554    storage: &alloy_primitives::map::U256Map<U256>,
10555    keys: &[B256],
10556) -> (B256, Vec<Vec<Bytes>>) {
10557    let keys: Vec<_> = keys.iter().map(|key| Nibbles::unpack(keccak256(key))).collect();
10558
10559    let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(keys.clone()));
10560
10561    for (key, value) in trie_storage(storage) {
10562        builder.add_leaf(key, &value);
10563    }
10564
10565    let root = builder.root();
10566
10567    let mut proofs = Vec::new();
10568    let all_proof_nodes = builder.take_proof_nodes();
10569
10570    for proof_key in keys {
10571        // Iterate over all proof nodes and find the matching ones.
10572        // The filtered results are guaranteed to be in order.
10573        let matching_proof_nodes =
10574            all_proof_nodes.matching_nodes_sorted(&proof_key).into_iter().map(|(_, node)| node);
10575        proofs.push(matching_proof_nodes.collect());
10576    }
10577
10578    (root, proofs)
10579}
10580
10581pub fn is_arbitrum(chain_id: u64) -> bool {
10582    if let Ok(chain) = NamedChain::try_from(chain_id) {
10583        return chain.is_arbitrum();
10584    }
10585    false
10586}
10587
10588/// Commits a fully executed candidate cache to the live database.
10589fn commit_cache(db: &mut dyn Db, cache: revm::database::Cache) -> Result<(), BlockchainError> {
10590    let revm::database::Cache { accounts, contracts, .. } = cache;
10591    let mut changes = EvmState::default();
10592    for (address, db_account) in accounts {
10593        if db_account.account_state == AccountState::None {
10594            continue;
10595        }
10596
10597        let DbAccount { mut info, account_state, storage } = db_account;
10598        // `CacheDB` also records absent-account reads as `NotExisting`. They are not state changes
10599        // and must not become synthetic selfdestructs in the live database.
10600        if account_state == AccountState::NotExisting && db.basic(address)?.is_none() {
10601            continue;
10602        }
10603        if info.code.is_none() {
10604            info.code = contracts.get(&info.code_hash()).cloned();
10605        }
10606        let mut account = Account::from(info);
10607        account.mark_touch();
10608        match account_state {
10609            AccountState::NotExisting => account.mark_selfdestruct(),
10610            AccountState::StorageCleared => account.mark_created(),
10611            AccountState::Touched => {}
10612            AccountState::None => unreachable!(),
10613        }
10614        for (slot, value) in storage {
10615            let original = if account_state.is_storage_cleared() {
10616                U256::ZERO
10617            } else {
10618                db.storage(address, slot)?
10619            };
10620            account
10621                .storage
10622                .insert(slot, EvmStorageSlot::new_changed(original, value, TransactionId::ZERO));
10623        }
10624        changes.insert(address, account);
10625    }
10626    db.commit(changes);
10627    Ok(())
10628}
10629
10630fn simulate_rpc_error(code: i64, message: impl Into<String>) -> BlockchainError {
10631    BlockchainError::RpcError(RpcError {
10632        code: ErrorCode::from(code),
10633        message: message.into().into(),
10634        data: None,
10635    })
10636}
10637
10638fn previously_deleted_accounts(
10639    accounts: &AddressMap<DbAccount>,
10640    addresses: impl IntoIterator<Item = Address>,
10641) -> Vec<Address> {
10642    addresses
10643        .into_iter()
10644        .filter(|address| {
10645            accounts
10646                .get(address)
10647                .is_some_and(|account| account.account_state == AccountState::NotExisting)
10648        })
10649        .collect()
10650}
10651
10652fn preserve_deleted_storage(
10653    accounts: &mut AddressMap<DbAccount>,
10654    previously_deleted: Vec<Address>,
10655) {
10656    for address in previously_deleted {
10657        if let Some(account) = accounts.get_mut(&address)
10658            && account.account_state != AccountState::NotExisting
10659        {
10660            account.account_state = AccountState::StorageCleared;
10661        }
10662    }
10663}
10664
10665fn simulate_transaction_error(error: InvalidTransactionError) -> BlockchainError {
10666    let code = match &error {
10667        InvalidTransactionError::NonceTooLow => -38010,
10668        InvalidTransactionError::NonceTooHigh => -38011,
10669        InvalidTransactionError::NonceMaxValue => -32603,
10670        InvalidTransactionError::FeeCapTooLow => -38012,
10671        InvalidTransactionError::GasTooLow | InvalidTransactionError::GasTooHigh(_) => -38013,
10672        InvalidTransactionError::InsufficientFunds
10673        | InvalidTransactionError::InsufficientFundsForTransfer => -38014,
10674        _ => return BlockchainError::InvalidTransaction(error),
10675    };
10676
10677    simulate_rpc_error(code, format!("err: {error}"))
10678}
10679
10680pub(in crate::eth) fn sanitize_simulation_blocks<T>(
10681    blocks: Vec<SimBlock<T>>,
10682    base_number: u64,
10683    base_timestamp: u64,
10684    block_interval: u64,
10685) -> Result<Vec<SimBlock<T>>, BlockchainError> {
10686    let block_interval = block_interval.max(1);
10687    let mut sanitized = Vec::with_capacity(blocks.len());
10688    let mut previous_number = base_number;
10689    let mut previous_timestamp = base_timestamp;
10690
10691    for mut block in blocks {
10692        let mut overrides = block.block_overrides.take().unwrap_or_default();
10693        let default_number = previous_number.checked_add(1).ok_or_else(|| {
10694            simulate_rpc_error(-38020, "block number overflow while constructing sequence")
10695        })?;
10696        let number =
10697            overrides.number.map(|number| number.saturating_to()).unwrap_or(default_number);
10698
10699        if number <= previous_number {
10700            return Err(simulate_rpc_error(
10701                -38020,
10702                format!("block numbers must be in order: {number} <= {previous_number}"),
10703            ));
10704        }
10705
10706        let gap = number - previous_number - 1;
10707        let remaining = MAX_SIMULATE_BLOCKS as usize - sanitized.len();
10708        if gap as usize >= remaining {
10709            return Err(simulate_rpc_error(-38026, "too many blocks"));
10710        }
10711
10712        for offset in 0..gap {
10713            let timestamp = previous_timestamp.checked_add(block_interval).ok_or_else(|| {
10714                simulate_rpc_error(-38021, "block timestamp overflow while filling number gap")
10715            })?;
10716            sanitized.push(SimBlock {
10717                block_overrides: Some(BlockOverrides {
10718                    number: Some(U256::from(default_number + offset)),
10719                    time: Some(timestamp),
10720                    ..Default::default()
10721                }),
10722                state_overrides: None,
10723                calls: Vec::new(),
10724            });
10725            previous_timestamp = timestamp;
10726        }
10727
10728        let timestamp = match overrides.time {
10729            Some(timestamp) => timestamp,
10730            None => previous_timestamp.checked_add(block_interval).ok_or_else(|| {
10731                simulate_rpc_error(-38021, "block timestamp overflow while constructing sequence")
10732            })?,
10733        };
10734        if timestamp <= previous_timestamp {
10735            return Err(simulate_rpc_error(
10736                -38021,
10737                format!("block timestamps must be in order: {timestamp} <= {previous_timestamp}"),
10738            ));
10739        }
10740
10741        overrides.number = Some(U256::from(number));
10742        overrides.time = Some(timestamp);
10743        block.block_overrides = Some(overrides);
10744        sanitized.push(block);
10745        previous_number = number;
10746        previous_timestamp = timestamp;
10747    }
10748
10749    Ok(sanitized)
10750}
10751
10752/// Unpacks an [`ExecutionResult`] into its exit reason, gas used, output, and logs.
10753fn unpack_execution_result<H: IntoInstructionResult>(
10754    result: ExecutionResult<H>,
10755) -> (InstructionResult, u64, Option<Output>, Vec<revm::primitives::Log>) {
10756    match result {
10757        ExecutionResult::Success { reason, gas, output, logs, .. } => {
10758            (reason.into(), gas.tx_gas_used(), Some(output), logs)
10759        }
10760        ExecutionResult::Revert { gas, output, logs, .. } => {
10761            (InstructionResult::Revert, gas.tx_gas_used(), Some(Output::Call(output)), logs)
10762        }
10763        ExecutionResult::Halt { reason, gas, logs, .. } => {
10764            (reason.into_instruction_result(), gas.tx_gas_used(), None, logs)
10765        }
10766    }
10767}
10768
10769fn arbitrum_replay_block_number(block: &AnyRpcBlock) -> U256 {
10770    block
10771        .other
10772        .get("l1BlockNumber")
10773        .cloned()
10774        .and_then(|number| serde_json::from_value(number).ok())
10775        .unwrap_or_else(|| U256::from(block.header().number()))
10776}
10777
10778/// Converts a halt reason into an [`InstructionResult`].
10779///
10780/// Abstracts over network-specific halt reason types (`HaltReason`, `OpHaltReason`)
10781/// so that anvil code doesn't need to match on each variant directly.
10782pub use foundry_evm::core::evm::IntoInstructionResult;
10783
10784/// Creates an Ethereum-shaped genesis header from the EVM environment.
10785fn genesis_header(
10786    evm_env: &EvmEnv,
10787    base_fee: Option<u64>,
10788    timestamp: u64,
10789    genesis_number: u64,
10790) -> Header {
10791    let spec_id = *evm_env.spec_id();
10792    Header {
10793        timestamp,
10794        base_fee_per_gas: base_fee,
10795        gas_limit: evm_env.block_env.gas_limit,
10796        beneficiary: evm_env.block_env.beneficiary,
10797        difficulty: evm_env.block_env.difficulty,
10798        blob_gas_used: evm_env.block_env.blob_excess_gas_and_price.as_ref().map(|_| 0),
10799        excess_blob_gas: evm_env.block_env.blob_excess_gas(),
10800        number: genesis_number,
10801        parent_beacon_block_root: (spec_id >= SpecId::CANCUN).then_some(Default::default()),
10802        withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
10803        requests_hash: (spec_id >= SpecId::PRAGUE).then_some(EMPTY_REQUESTS_HASH),
10804        ..Default::default()
10805    }
10806}
10807
10808/// Creates an Ethereum header from a `genesis.json` configuration.
10809fn genesis_json_header(genesis: &Genesis) -> Header {
10810    let number = genesis.number.unwrap_or_default();
10811    let timestamp = genesis.timestamp;
10812    let is_london = genesis.config.is_london_active_at_block(number);
10813    let is_shanghai = genesis.config.is_shanghai_active_at_block_and_timestamp(number, timestamp);
10814    let is_cancun = genesis.config.is_cancun_active_at_block_and_timestamp(number, timestamp);
10815    let is_prague = genesis.config.prague_time.is_some_and(|fork| fork <= timestamp);
10816    let is_amsterdam = genesis.config.amsterdam_time.is_some_and(|fork| fork <= timestamp);
10817
10818    Header {
10819        number,
10820        parent_hash: genesis.parent_hash.unwrap_or_default(),
10821        gas_limit: genesis.gas_limit,
10822        difficulty: genesis.difficulty,
10823        nonce: genesis.nonce.into(),
10824        extra_data: genesis.extra_data.clone(),
10825        state_root: state_root_ref_unhashed(&genesis.alloc),
10826        timestamp,
10827        mix_hash: genesis.mix_hash,
10828        beneficiary: genesis.coinbase,
10829        base_fee_per_gas: is_london.then(|| {
10830            genesis
10831                .base_fee_per_gas
10832                .map(|fee| fee as u64)
10833                .unwrap_or(crate::eth::fees::INITIAL_BASE_FEE)
10834        }),
10835        withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
10836        parent_beacon_block_root: is_cancun.then_some(B256::ZERO),
10837        blob_gas_used: is_cancun.then_some(genesis.blob_gas_used.unwrap_or_default()),
10838        excess_blob_gas: is_cancun.then_some(genesis.excess_blob_gas.unwrap_or_default()),
10839        requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH),
10840        block_access_list_hash: is_amsterdam.then_some(EMPTY_BLOCK_ACCESS_LIST_HASH),
10841        slot_number: is_amsterdam.then_some(genesis.slot_number.unwrap_or_default()),
10842        ..Default::default()
10843    }
10844}
10845
10846/// Wraps an Ethereum-shaped header in the selected network's consensus header.
10847fn foundry_header(networks: &NetworkConfigs, header: Header) -> FoundryHeader {
10848    if networks.is_tempo() { FoundryHeader::tempo(header) } else { header.into() }
10849}
10850
10851#[cfg(test)]
10852mod tests {
10853    use super::{
10854        BlockRequest, FeeDetails, ForkCacheNamespace, ForkCacheSource, InstructionResult,
10855        MiningCommitHook, Output, StagedForkCacheLease, StagedForkDbUser,
10856        arbitrum_replay_block_number,
10857    };
10858    use crate::{NodeConfig, config::ForkTransactionReplay, spawn};
10859    use alloy_network::{AnyHeader, AnyRpcBlock, AnyRpcHeader, TransactionBuilder};
10860    use alloy_primitives::{Address, B256, Bytes, U256};
10861    use alloy_provider::Provider;
10862    use alloy_rpc_types::{Block, BlockTransactions, TransactionRequest, state::EvmOverrides};
10863    use alloy_serde::WithOtherFields;
10864    use foundry_config::NamedChain;
10865    use foundry_evm::{
10866        backend::{BlockchainDb, BlockchainDbMeta},
10867        hardfork::{EthereumHardfork, FoundryHardfork},
10868    };
10869    use foundry_evm_networks::arbitrum;
10870    use std::sync::Arc;
10871    use tempfile::tempdir;
10872    use tokio::sync::Notify;
10873
10874    #[cfg(feature = "base")]
10875    use base_common_precompiles::{ActivationRegistryStorage, B20FactoryStorage};
10876    #[cfg(feature = "base")]
10877    use foundry_evm::{hardforks::BaseUpgrade, traces::CallTraceDecoderBuilder};
10878
10879    #[cfg(all(feature = "base", feature = "optimism"))]
10880    use alloy_consensus::Header;
10881    #[cfg(all(feature = "base", feature = "optimism"))]
10882    use foundry_evm::hardfork::OpHardfork;
10883    #[cfg(all(feature = "base", feature = "optimism"))]
10884    use foundry_evm_networks::NetworkConfigs;
10885
10886    fn test_cache_db(cache_path: std::path::PathBuf) -> BlockchainDb {
10887        let db = BlockchainDb::new(BlockchainDbMeta::default(), Some(cache_path));
10888        db.block_hashes().write().insert(U256::ZERO, B256::repeat_byte(0x11));
10889        db.cache().flush();
10890        db
10891    }
10892
10893    #[test]
10894    fn arbitrum_transaction_replay_uses_l1_block_number() {
10895        let header = AnyHeader { number: 75_219_831, ..Default::default() };
10896        let mut block = AnyRpcBlock::new(
10897            Block::new(
10898                AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
10899                BlockTransactions::Full(Vec::new()),
10900            )
10901            .into(),
10902        );
10903        block.other.insert("l1BlockNumber".to_string(), serde_json::json!("0x10276d3"));
10904
10905        assert_eq!(arbitrum_replay_block_number(&block), U256::from(16_938_707));
10906    }
10907
10908    #[cfg(all(feature = "base", feature = "optimism"))]
10909    #[tokio::test(flavor = "multi_thread")]
10910    async fn optimism_fork_uses_optimism_schedule_for_base_headers() {
10911        let timestamp = 1_710_374_401;
10912        let (_, origin) = spawn(
10913            NodeConfig::test_base()
10914                .with_hardfork(Some(BaseUpgrade::Ecotone.into()))
10915                .with_genesis_timestamp(Some(timestamp)),
10916        )
10917        .await;
10918        let (api, _) = spawn(
10919            NodeConfig::test()
10920                .with_networks(NetworkConfigs::with_optimism())
10921                .with_eth_rpc_url(Some(origin.http_endpoint())),
10922        )
10923        .await;
10924
10925        assert!(api.backend.is_optimism());
10926        assert_eq!(api.backend.hardfork(), OpHardfork::Ecotone.into());
10927        assert!(
10928            !api.backend
10929                .is_optimism_jovian_at_header(&Header { timestamp, ..Default::default() }, None)
10930        );
10931    }
10932
10933    #[tokio::test]
10934    async fn fork_arbitrum_transaction_replay_preserves_rpc_block_number() {
10935        const GENESIS_BLOCK: u64 = 101;
10936        const L1_BLOCK: u64 = 10;
10937        const REPLAY_BLOCK: u64 = GENESIS_BLOCK + 1;
10938
10939        let config = || {
10940            NodeConfig::test()
10941                .with_chain_id(Some(NamedChain::Arbitrum as u64))
10942                .with_genesis_block_number(Some(GENESIS_BLOCK))
10943        };
10944        let (_source_api, source_handle) = spawn(config()).await;
10945        let source_provider = source_handle.http_provider();
10946        let sender = source_provider.get_accounts().await.unwrap()[0];
10947        let receipt = source_provider
10948            .send_transaction(WithOtherFields::new(
10949                TransactionRequest::default()
10950                    .with_from(sender)
10951                    .with_to(arbitrum::ARB_SYS_ADDRESS)
10952                    .with_input(Bytes::from(arbitrum::ARB_BLOCK_NUMBER_SELECTOR)),
10953            ))
10954            .await
10955            .unwrap()
10956            .get_receipt()
10957            .await
10958            .unwrap();
10959        let mut source_block = source_provider
10960            .get_block_by_hash(receipt.block_hash.unwrap())
10961            .full()
10962            .await
10963            .unwrap()
10964            .unwrap();
10965        source_block
10966            .other
10967            .insert("l1BlockNumber".to_string(), serde_json::json!(format!("0x{L1_BLOCK:x}")));
10968
10969        let (replay_api, _replay_handle) = spawn(config()).await;
10970        replay_api
10971            .backend
10972            .apply_fork_transaction_replay(ForkTransactionReplay { source_block, target_index: 0 })
10973            .await
10974            .unwrap();
10975
10976        let expected = arbitrum::arb_block_number_output(REPLAY_BLOCK);
10977        let replayed =
10978            replay_api.backend.mined_transaction_receipt(receipt.transaction_hash).unwrap();
10979        assert_eq!(replayed.out.unwrap(), expected);
10980        assert_eq!(replay_api.block_number().unwrap(), U256::from(REPLAY_BLOCK));
10981
10982        let output = replay_api
10983            .call(
10984                WithOtherFields::new(
10985                    TransactionRequest::default()
10986                        .with_to(arbitrum::ARB_SYS_ADDRESS)
10987                        .with_input(Bytes::from(arbitrum::ARB_BLOCK_NUMBER_SELECTOR)),
10988                ),
10989                None,
10990                EvmOverrides::default(),
10991            )
10992            .await
10993            .unwrap();
10994        assert_eq!(output, expected);
10995    }
10996
10997    fn test_endpoint_identity(
10998        hardfork: Option<FoundryHardfork>,
10999        instance_id: Option<B256>,
11000    ) -> super::ForkEndpointIdentity {
11001        super::ForkEndpointIdentity {
11002            execution_chain_id: 1,
11003            source_chain_id: 1,
11004            network: None,
11005            network_profile: None,
11006            hardfork,
11007            instance_id,
11008            source_fork_block_number: None,
11009            source_fork_block_hash: None,
11010        }
11011    }
11012
11013    #[tokio::test]
11014    async fn missing_snapshot_block_does_not_change_head() {
11015        let (api, _handle) = spawn(NodeConfig::test()).await;
11016        let snapshot_hash = api.backend.best_hash();
11017        let snapshot = api.backend.create_state_snapshot().await;
11018        api.mine_one().await.unwrap();
11019        let best_number = api.backend.best_number();
11020        let best_hash = api.backend.best_hash();
11021        let head_wall_time = api.backend.time().last_block_wall_time();
11022
11023        api.backend.blockchain.storage.write().blocks.remove(&snapshot_hash);
11024        let err = api.backend.revert_state_snapshot(snapshot).await.unwrap_err();
11025
11026        assert!(matches!(err, super::BlockchainError::BlockNotFound));
11027        assert_eq!(api.backend.best_number(), best_number);
11028        assert_eq!(api.backend.best_hash(), best_hash);
11029        assert_eq!(api.backend.time().last_block_wall_time(), head_wall_time);
11030    }
11031
11032    #[tokio::test(flavor = "multi_thread")]
11033    async fn live_execution_snapshot_is_coherent_during_mining() {
11034        let (api, handle) = spawn(NodeConfig::test().with_no_mining(true)).await;
11035        let sender = handle.dev_wallets().next().unwrap().address();
11036        let recipient = Address::repeat_byte(0x11);
11037        let contract = Address::repeat_byte(0x22);
11038
11039        // Return the recipient balance followed by NUMBER. At block 0 both are zero; at block 1,
11040        // after the queued transfer is mined, both are one.
11041        let mut code = vec![0x73];
11042        code.extend_from_slice(recipient.as_slice());
11043        code.extend_from_slice(&[
11044            0x31, 0x60, 0x00, 0x52, 0x43, 0x60, 0x20, 0x52, 0x60, 0x40, 0x60, 0x00, 0xf3,
11045        ]);
11046        api.anvil_set_code(contract, code.into()).await.unwrap();
11047        api.send_transaction(WithOtherFields::new(
11048            TransactionRequest::default().from(sender).to(recipient).value(U256::ONE),
11049        ))
11050        .await
11051        .unwrap();
11052
11053        let resolved_head = api.backend.best_number();
11054        let db_guard = api.backend.db.write().await;
11055
11056        // Polling while holding the database makes the lock queue deterministic: mining owns the
11057        // first waiter and the call that already resolved `latest` owns the second.
11058        let mining_api = api.clone();
11059        let mut mining = Box::pin(async move { mining_api.mine_one().await });
11060        assert!(futures::poll!(mining.as_mut()).is_pending());
11061
11062        let request = WithOtherFields::new(TransactionRequest::default().to(contract));
11063        let mut call = Box::pin(api.backend.call(
11064            request,
11065            FeeDetails::zero(),
11066            Some(BlockRequest::Number(resolved_head)),
11067            EvmOverrides::default(),
11068        ));
11069        assert!(futures::poll!(call.as_mut()).is_pending());
11070
11071        let mut pending_block = Box::pin(api.backend.pending_block(Vec::new()));
11072        assert!(futures::poll!(pending_block.as_mut()).is_pending());
11073
11074        let mut state = Box::pin(api.serialized_state(false));
11075        assert!(futures::poll!(state.as_mut()).is_pending());
11076
11077        // Pause mining after the database commit but before canonical publication without locking
11078        // storage, so an incorrectly unblocked pending reader can observe the old parent.
11079        let hook =
11080            MiningCommitHook { reached: Arc::new(Notify::new()), resume: Arc::new(Notify::new()) };
11081        *api.backend.mining_commit_hook.lock() = Some(hook.clone());
11082        drop(db_guard);
11083        let mining = tokio::spawn(mining);
11084        hook.reached.notified().await;
11085
11086        // Mining retains the database write lock while waiting to publish the canonical head, so
11087        // neither a live call nor a pending block can observe the partially published snapshot.
11088        assert!(futures::poll!(call.as_mut()).is_pending());
11089        assert!(futures::poll!(pending_block.as_mut()).is_pending());
11090        assert!(futures::poll!(state.as_mut()).is_pending());
11091
11092        hook.resume.notify_one();
11093        mining.await.unwrap().unwrap();
11094
11095        let (exit, output, _, _) = call.await.unwrap();
11096        let pending_block = pending_block.await.unwrap();
11097        let state = state.await.unwrap();
11098
11099        assert_eq!(state.accounts[&recipient].balance, U256::ONE);
11100        assert_eq!(state.block.unwrap().number, U256::ONE);
11101        assert_eq!(state.best_block_number, Some(1));
11102
11103        assert_eq!(exit, InstructionResult::Return);
11104        let Some(Output::Call(output)) = output else { panic!("call did not return data") };
11105        assert_eq!(output.len(), 64);
11106        let balance = U256::from_be_slice(&output[..32]);
11107        let block_number = U256::from_be_slice(&output[32..]);
11108        // The call requested block 0, so it sees block 0's balance and number.
11109        assert_eq!((balance, block_number), (U256::ZERO, U256::ZERO));
11110
11111        assert_eq!(pending_block.block.header.number, api.backend.best_number() + 1);
11112        assert_eq!(pending_block.block.header.parent_hash, api.backend.best_hash());
11113        assert_eq!(pending_block.block.header.base_fee_per_gas, Some(875_175_000));
11114    }
11115
11116    /// Reads the recipient balance at head block 0 while block 1, which funds it, is being mined.
11117    async fn read_head_balance_while_mining(config: NodeConfig) -> U256 {
11118        let (api, handle) = spawn(config.with_no_mining(true)).await;
11119        let sender = handle.dev_wallets().next().unwrap().address();
11120        let recipient = Address::repeat_byte(0x11);
11121        api.send_transaction(WithOtherFields::new(
11122            TransactionRequest::default().from(sender).to(recipient).value(U256::from(1)),
11123        ))
11124        .await
11125        .unwrap();
11126
11127        // Pause mining after the next block's state is committed while it still holds the
11128        // database write lock and before it publishes the new head.
11129        let hook =
11130            MiningCommitHook { reached: Arc::new(Notify::new()), resume: Arc::new(Notify::new()) };
11131        *api.backend.mining_commit_hook.lock() = Some(hook.clone());
11132        let mining_api = api.clone();
11133        let mining = tokio::spawn(async move { mining_api.mine_one().await });
11134        hook.reached.notified().await;
11135
11136        // The read names the current head, block 0, and queues behind the miner's write lock.
11137        let head = api.backend.best_number();
11138        assert_eq!(head, 0);
11139        let mut balance =
11140            Box::pin(api.backend.get_balance(recipient, Some(BlockRequest::Number(head))));
11141        assert!(futures::poll!(balance.as_mut()).is_pending());
11142
11143        hook.resume.notify_one();
11144        mining.await.unwrap().unwrap();
11145        assert_eq!(api.backend.best_number(), 1);
11146
11147        balance.await.unwrap()
11148    }
11149
11150    #[tokio::test(flavor = "multi_thread")]
11151    async fn head_number_read_waiting_on_mining_keeps_requested_state() {
11152        // The read must be answered from block 0, not from block 1 mined while it waited.
11153        assert_eq!(read_head_balance_while_mining(NodeConfig::test()).await, U256::ZERO);
11154    }
11155
11156    #[tokio::test(flavor = "multi_thread")]
11157    async fn head_number_read_waiting_on_mining_without_history_reads_live_state() {
11158        // Without state history block 0's state is gone, so the read is served from the live state
11159        // instead of failing.
11160        let config = NodeConfig::test().set_pruned_history(Some(None));
11161        assert_eq!(read_head_balance_while_mining(config).await, U256::from(1));
11162    }
11163
11164    struct CacheFlushingDb(BlockchainDb);
11165
11166    impl Drop for CacheFlushingDb {
11167        fn drop(&mut self) {
11168            self.0.block_hashes().write().insert(U256::ONE, B256::repeat_byte(0x22));
11169            self.0.cache().flush();
11170        }
11171    }
11172
11173    #[test]
11174    fn staged_fork_cache_lease_waits_for_last_owner() {
11175        let root = tempdir().unwrap();
11176        let block_cache_dir = root.path().join("1");
11177        let cache_path = block_cache_dir.join("storage.json");
11178        let sibling_cache_path = block_cache_dir.join("storage-sibling.json");
11179        let db = test_cache_db(cache_path.clone());
11180        std::fs::write(&sibling_cache_path, b"sibling").unwrap();
11181        let lease = StagedForkCacheLease::new(db.clone(), Some(cache_path.clone()));
11182        let last_user = lease.clone();
11183
11184        lease.rollback().unwrap();
11185        assert!(!db.block_hashes().read().is_empty());
11186        drop(lease);
11187        assert!(!db.block_hashes().read().is_empty());
11188
11189        drop(last_user);
11190        assert!(db.block_hashes().read().is_empty());
11191        assert!(!cache_path.exists());
11192        assert!(sibling_cache_path.exists());
11193    }
11194
11195    #[tokio::test]
11196    async fn staged_fork_cache_lease_survives_task_cancellation() {
11197        let root = tempdir().unwrap();
11198        let block_cache_dir = root.path().join("1");
11199        let cache_path = block_cache_dir.join("storage.json");
11200        let cache_db = test_cache_db(cache_path.clone());
11201        let lease = StagedForkCacheLease::new(cache_db.clone(), Some(cache_path.clone()));
11202        let db = Arc::new(tokio::sync::RwLock::new(CacheFlushingDb(cache_db.clone())));
11203        let task_user = StagedForkDbUser { db: Some(Arc::clone(&db)), cache_lease: lease.clone() };
11204        drop(db);
11205        let task = tokio::spawn(async move {
11206            let _db = task_user.db().read().await;
11207            std::future::pending::<()>().await;
11208        });
11209        tokio::task::yield_now().await;
11210
11211        // Precise closure capture must not detach the lease from the task's database handle.
11212        assert_eq!(Arc::strong_count(lease.0.as_ref().unwrap()), 2);
11213        lease.rollback().unwrap();
11214        drop(lease);
11215
11216        task.abort();
11217        assert!(task.await.unwrap_err().is_cancelled());
11218
11219        assert!(cache_db.block_hashes().read().is_empty());
11220        assert!(!cache_path.exists());
11221    }
11222
11223    #[test]
11224    fn staged_fork_cache_lease_disarm_preserves_committed_cache() {
11225        let root = tempdir().unwrap();
11226        let block_cache_dir = root.path().join("1");
11227        let cache_path = block_cache_dir.join("storage.json");
11228        let db = test_cache_db(cache_path.clone());
11229        let lease = StagedForkCacheLease::new(db.clone(), Some(cache_path.clone()));
11230
11231        lease.disarm();
11232        drop(lease);
11233
11234        assert!(!db.block_hashes().read().is_empty());
11235        assert!(cache_path.exists());
11236    }
11237
11238    #[test]
11239    fn fork_cache_namespace_preserves_sibling_endpoints() {
11240        let root = tempdir().unwrap();
11241        let target_file = "storage-target.json";
11242        let sibling_file = "storage-sibling.json";
11243        let regular_cache_entry = root.path().join("latest.json");
11244        std::fs::write(&regular_cache_entry, b"ordinary RPC cache entry").unwrap();
11245        for block in ["1", "2"] {
11246            let block_dir = root.path().join(block);
11247            std::fs::create_dir_all(&block_dir).unwrap();
11248            std::fs::write(block_dir.join(target_file), b"target").unwrap();
11249            std::fs::write(block_dir.join(sibling_file), b"sibling").unwrap();
11250        }
11251        let namespace = ForkCacheNamespace {
11252            chain_cache_dir: root.path().to_path_buf(),
11253            file_name: target_file.to_string(),
11254        };
11255
11256        namespace.invalidate().unwrap();
11257
11258        for block in ["1", "2"] {
11259            let block_dir = root.path().join(block);
11260            assert!(!block_dir.join(target_file).exists());
11261            assert!(block_dir.join(sibling_file).exists());
11262        }
11263        assert!(regular_cache_entry.exists());
11264    }
11265
11266    #[test]
11267    fn startup_fork_cache_user_rolls_back_after_database_flush() {
11268        let root = tempdir().unwrap();
11269        let block_cache_dir = root.path().join("1");
11270        let cache_path = block_cache_dir.join("storage.json");
11271        let cache_db = test_cache_db(cache_path.clone());
11272        let user = StagedForkDbUser {
11273            db: Some(Arc::new(tokio::sync::RwLock::new(CacheFlushingDb(cache_db.clone())))),
11274            cache_lease: StagedForkCacheLease::new(cache_db.clone(), Some(cache_path.clone())),
11275        };
11276
11277        drop(user);
11278
11279        assert!(cache_db.block_hashes().read().is_empty());
11280        assert!(!cache_path.exists());
11281    }
11282
11283    #[test]
11284    fn startup_fork_cache_user_preserves_cache_after_commit() {
11285        let root = tempdir().unwrap();
11286        let block_cache_dir = root.path().join("1");
11287        let cache_path = block_cache_dir.join("storage.json");
11288        let cache_db = test_cache_db(cache_path.clone());
11289        let user = StagedForkDbUser {
11290            db: Some(Arc::new(tokio::sync::RwLock::new(CacheFlushingDb(cache_db.clone())))),
11291            cache_lease: StagedForkCacheLease::new(cache_db.clone(), Some(cache_path.clone())),
11292        };
11293        user.cache_lease.disarm();
11294
11295        drop(user);
11296
11297        assert_eq!(cache_db.block_hashes().read().get(&U256::ONE), Some(&B256::repeat_byte(0x22)));
11298        assert!(cache_path.exists());
11299    }
11300
11301    #[test]
11302    fn fork_cache_source_invalidates_only_same_url_authoritative_replacements() {
11303        let anonymous = test_endpoint_identity(None, None);
11304        let anonymous_source = ForkCacheSource {
11305            rpc_url: "http://localhost".to_string(),
11306            endpoint_identity: anonymous,
11307        };
11308        let mut changed_anonymous = anonymous;
11309        changed_anonymous.source_chain_id = 2;
11310
11311        assert!(
11312            !anonymous_source
11313                .authoritative_identity_changed_at_same_url("http://localhost", changed_anonymous)
11314        );
11315        assert!(
11316            !anonymous_source
11317                .authoritative_identity_changed_at_same_url("http://mirror", anonymous)
11318        );
11319
11320        let hardfork = Some(FoundryHardfork::Ethereum(EthereumHardfork::Prague));
11321        let authoritative = test_endpoint_identity(hardfork, Some(B256::repeat_byte(0x22)));
11322        let authoritative_source = ForkCacheSource {
11323            rpc_url: "http://localhost".to_string(),
11324            endpoint_identity: authoritative,
11325        };
11326        let replacement = test_endpoint_identity(hardfork, Some(B256::repeat_byte(0x33)));
11327
11328        assert!(
11329            !authoritative_source
11330                .authoritative_identity_changed_at_same_url("http://localhost", authoritative)
11331        );
11332        assert!(
11333            authoritative_source
11334                .authoritative_identity_changed_at_same_url("http://localhost", replacement)
11335        );
11336        assert!(
11337            anonymous_source
11338                .authoritative_identity_changed_at_same_url("http://localhost", authoritative)
11339        );
11340        assert!(
11341            authoritative_source
11342                .authoritative_identity_changed_at_same_url("http://localhost", anonymous)
11343        );
11344    }
11345
11346    #[tokio::test]
11347    async fn test_deterministic_block_mining() {
11348        // Test that mine_block produces deterministic block hashes with same initial conditions
11349        let genesis_timestamp = 1743944919u64;
11350
11351        // Create two identical backends
11352        let config_a = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into());
11353        let config_b = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into());
11354
11355        let (api_a, _handle_a) = spawn(config_a).await;
11356        let (api_b, _handle_b) = spawn(config_b).await;
11357
11358        // Mine empty blocks (no transactions) on both backends
11359        let outcome_a_1 = api_a.backend.mine_block(vec![]).await.unwrap();
11360        let outcome_b_1 = api_b.backend.mine_block(vec![]).await.unwrap();
11361
11362        // Both should mine the same block number
11363        assert_eq!(outcome_a_1.block_number, outcome_b_1.block_number);
11364
11365        // Get the actual blocks to compare hashes
11366        let block_a_1 =
11367            api_a.block_by_number(outcome_a_1.block_number.into()).await.unwrap().unwrap();
11368        let block_b_1 =
11369            api_b.block_by_number(outcome_b_1.block_number.into()).await.unwrap().unwrap();
11370
11371        // The block hashes should be identical
11372        assert_eq!(
11373            block_a_1.header.hash, block_b_1.header.hash,
11374            "Block hashes should be deterministic. Got {} vs {}",
11375            block_a_1.header.hash, block_b_1.header.hash
11376        );
11377
11378        // Mine another block to ensure it remains deterministic
11379        let outcome_a_2 = api_a.backend.mine_block(vec![]).await.unwrap();
11380        let outcome_b_2 = api_b.backend.mine_block(vec![]).await.unwrap();
11381
11382        let block_a_2 =
11383            api_a.block_by_number(outcome_a_2.block_number.into()).await.unwrap().unwrap();
11384        let block_b_2 =
11385            api_b.block_by_number(outcome_b_2.block_number.into()).await.unwrap().unwrap();
11386
11387        assert_eq!(
11388            block_a_2.header.hash, block_b_2.header.hash,
11389            "Second block hashes should also be deterministic. Got {} vs {}",
11390            block_a_2.header.hash, block_b_2.header.hash
11391        );
11392
11393        // Ensure the blocks are different (sanity check)
11394        assert_ne!(
11395            block_a_1.header.hash, block_a_2.header.hash,
11396            "Different blocks should have different hashes"
11397        );
11398    }
11399
11400    #[cfg(feature = "monad")]
11401    #[tokio::test]
11402    async fn monad_load_state_rebuilds_participant_cache() {
11403        use alloy_network::TransactionBuilder as _;
11404        use alloy_provider::Provider as _;
11405
11406        let config = || {
11407            NodeConfig::test_monad()
11408                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
11409        };
11410        let (api, handle) = spawn(config()).await;
11411        let provider = handle.http_provider();
11412        let accounts = provider.get_accounts().await.unwrap();
11413        let sender = accounts[0];
11414
11415        let receipt = provider
11416            .send_transaction(
11417                alloy_rpc_types::TransactionRequest::default()
11418                    .with_from(sender)
11419                    .with_to(accounts[1])
11420                    .with_value(U256::ONE)
11421                    .into(),
11422            )
11423            .await
11424            .unwrap()
11425            .get_receipt()
11426            .await
11427            .unwrap();
11428        let block_hash = receipt.block_hash.unwrap();
11429        let state = api.serialized_state(false).await.unwrap();
11430
11431        let (loaded_api, _handle) = spawn(config()).await;
11432        loaded_api.backend.load_state(state).await.unwrap();
11433
11434        let storage = loaded_api.backend.blockchain.storage.read();
11435        let participants = storage.monad_block_participants.get(&block_hash).unwrap();
11436        assert!(participants.contains(&sender));
11437    }
11438
11439    #[cfg(feature = "monad")]
11440    #[tokio::test]
11441    async fn monad_load_state_restores_pruned_participant_cache() {
11442        use alloy_consensus::BlockHeader as _;
11443        use alloy_network::TransactionBuilder as _;
11444        use alloy_provider::Provider as _;
11445
11446        let config = || {
11447            NodeConfig::test_monad()
11448                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
11449                .with_transaction_block_keeper(Some(1usize))
11450        };
11451        let (api, handle) = spawn(config()).await;
11452        let provider = handle.http_provider();
11453        let accounts = provider.get_accounts().await.unwrap();
11454        let sender = accounts[0];
11455
11456        let first_receipt = provider
11457            .send_transaction(
11458                alloy_rpc_types::TransactionRequest::default()
11459                    .with_from(sender)
11460                    .with_to(accounts[1])
11461                    .with_value(U256::ONE)
11462                    .into(),
11463            )
11464            .await
11465            .unwrap()
11466            .get_receipt()
11467            .await
11468            .unwrap();
11469        let second_receipt = provider
11470            .send_transaction(
11471                alloy_rpc_types::TransactionRequest::default()
11472                    .with_from(sender)
11473                    .with_to(accounts[1])
11474                    .with_value(U256::ONE)
11475                    .into(),
11476            )
11477            .await
11478            .unwrap()
11479            .get_receipt()
11480            .await
11481            .unwrap();
11482        let first_block_hash = first_receipt.block_hash.unwrap();
11483        let second_block_hash = second_receipt.block_hash.unwrap();
11484
11485        {
11486            let storage = api.backend.blockchain.storage.read();
11487            let first_block = storage.blocks.get(&first_block_hash).unwrap();
11488            assert!(first_block.body.transactions.is_empty());
11489            assert_ne!(
11490                first_block.header.transactions_root(),
11491                alloy_consensus::constants::EMPTY_ROOT_HASH
11492            );
11493            assert!(storage.monad_block_participants[&first_block_hash].contains(&sender));
11494            assert!(!storage.blocks[&second_block_hash].body.transactions.is_empty());
11495        }
11496
11497        let state = api.serialized_state(false).await.unwrap();
11498        assert!(state.monad_block_participants[&first_block_hash].contains(&sender));
11499        assert_eq!(state.monad_block_participants.len(), 2);
11500        assert!(state.monad_block_participants[&second_block_hash].contains(&sender));
11501
11502        let (loaded_api, _handle) = spawn(config()).await;
11503        loaded_api.backend.load_state(state).await.unwrap();
11504
11505        {
11506            let storage = loaded_api.backend.blockchain.storage.read();
11507            assert!(storage.monad_block_participants[&first_block_hash].contains(&sender));
11508            assert!(storage.monad_block_participants[&second_block_hash].contains(&sender));
11509        }
11510
11511        let outcome = loaded_api.backend.mine_block(vec![]).await.unwrap();
11512        assert_eq!(outcome.block_number, 3);
11513    }
11514
11515    #[cfg(feature = "monad")]
11516    #[tokio::test]
11517    async fn monad_load_state_rejection_is_atomic() {
11518        use alloy_consensus::BlockHeader as _;
11519        use alloy_network::TransactionBuilder as _;
11520        use alloy_provider::Provider as _;
11521
11522        let config = || {
11523            NodeConfig::test_monad()
11524                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
11525                .with_transaction_block_keeper(Some(1usize))
11526        };
11527        let (source_api, source_handle) = spawn(config()).await;
11528        let source_provider = source_handle.http_provider();
11529        let accounts = source_provider.get_accounts().await.unwrap();
11530        let sentinel = alloy_primitives::Address::repeat_byte(0x77);
11531
11532        source_api.anvil_set_balance(sentinel, U256::from(123)).await.unwrap();
11533        for nonce in 0..2 {
11534            source_provider
11535                .send_transaction(
11536                    alloy_rpc_types::TransactionRequest::default()
11537                        .with_from(accounts[0])
11538                        .with_to(accounts[1])
11539                        .with_nonce(nonce)
11540                        .with_value(U256::ONE)
11541                        .into(),
11542                )
11543                .await
11544                .unwrap()
11545                .get_receipt()
11546                .await
11547                .unwrap();
11548        }
11549
11550        let mut invalid_state = source_api.serialized_state(false).await.unwrap();
11551        let pruned_block_hash = invalid_state
11552            .blocks
11553            .iter()
11554            .find(|block| {
11555                block.transactions.is_empty()
11556                    && block.header.transactions_root()
11557                        != alloy_consensus::constants::EMPTY_ROOT_HASH
11558            })
11559            .unwrap()
11560            .header
11561            .hash_slow();
11562        assert!(invalid_state.monad_block_participants.remove(&pruned_block_hash).is_some());
11563
11564        let (target_api, _handle) = spawn(config()).await;
11565        target_api.anvil_set_balance(sentinel, U256::from(7)).await.unwrap();
11566        target_api.mine_one().await.unwrap();
11567        let original_best_hash = target_api.backend.best_hash();
11568        let original_best_number = target_api.backend.best_number();
11569        let original_block_env = target_api.backend.evm_env.read().block_env.clone();
11570        let original_balance = target_api.backend.current_balance(sentinel).await.unwrap();
11571
11572        let err = target_api.backend.load_state(invalid_state).await.unwrap_err();
11573        assert!(matches!(err, super::BlockchainError::DataUnavailable));
11574        assert_eq!(target_api.backend.best_hash(), original_best_hash);
11575        assert_eq!(target_api.backend.best_number(), original_best_number);
11576        assert_eq!(target_api.backend.evm_env.read().block_env, original_block_env);
11577        assert_eq!(target_api.backend.current_balance(sentinel).await.unwrap(), original_balance);
11578
11579        let outcome = target_api.backend.mine_block(vec![]).await.unwrap();
11580        assert_eq!(outcome.block_number, original_best_number + 1);
11581    }
11582
11583    #[tokio::test]
11584    async fn trace_decoder_follows_executed_hardfork_for_cross_namespace_override() {
11585        let (api, _) = spawn(
11586            NodeConfig::test_tempo()
11587                .with_hardfork(Some(FoundryHardfork::Ethereum(EthereumHardfork::Prague))),
11588        )
11589        .await;
11590
11591        let decoder = api.backend.call_trace_decoder();
11592        assert_eq!(decoder.hardfork(), Some(FoundryHardfork::Tempo(api.backend.tempo_hardfork())));
11593        // The refresh compares the same coerced value, so repeated calls stay stable.
11594        assert!(Arc::ptr_eq(&decoder, &api.backend.call_trace_decoder()));
11595    }
11596
11597    #[cfg(feature = "monad")]
11598    #[tokio::test]
11599    async fn monad_trace_decoder_follows_resolved_hardfork() {
11600        let (monad_eight, _) = spawn(
11601            NodeConfig::test_monad()
11602                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadEight.into())),
11603        )
11604        .await;
11605        let stale_monad_nine = foundry_evm::traces::CallTraceDecoderBuilder::new()
11606            .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
11607            .build();
11608        *monad_eight.backend.call_trace_decoder.write() = Arc::new(stale_monad_nine);
11609
11610        let decoder = monad_eight.backend.call_trace_decoder();
11611        assert_eq!(
11612            decoder.hardfork(),
11613            Some(foundry_evm::hardfork::MonadHardfork::MonadEight.into())
11614        );
11615        assert!(
11616            !decoder
11617                .labels
11618                .contains_key(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
11619        );
11620
11621        let (monad_nine, _) = spawn(
11622            NodeConfig::test_monad()
11623                .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into())),
11624        )
11625        .await;
11626        let stale_monad_eight = foundry_evm::traces::CallTraceDecoderBuilder::new()
11627            .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadEight.into()))
11628            .build();
11629        *monad_nine.backend.call_trace_decoder.write() = Arc::new(stale_monad_eight);
11630
11631        let decoder = monad_nine.backend.call_trace_decoder();
11632        assert_eq!(
11633            decoder.hardfork(),
11634            Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into())
11635        );
11636        assert_eq!(
11637            decoder
11638                .labels
11639                .get(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
11640                .map(String::as_str),
11641            Some("ReserveBalance")
11642        );
11643    }
11644
11645    #[cfg(feature = "base")]
11646    #[tokio::test]
11647    async fn base_trace_decoder_follows_resolved_upgrade() {
11648        let (azul, _) =
11649            spawn(NodeConfig::test_base().with_hardfork(Some(BaseUpgrade::Azul.into()))).await;
11650        let stale_beryl =
11651            CallTraceDecoderBuilder::new().with_hardfork(Some(BaseUpgrade::Beryl.into())).build();
11652        *azul.backend.call_trace_decoder.write() = Arc::new(stale_beryl);
11653
11654        let decoder = azul.backend.call_trace_decoder();
11655        assert_eq!(decoder.hardfork(), Some(BaseUpgrade::Azul.into()));
11656        assert!(!decoder.labels.contains_key(&B20FactoryStorage::ADDRESS));
11657        assert!(!decoder.labels.contains_key(&ActivationRegistryStorage::ADDRESS));
11658
11659        let (beryl, _) =
11660            spawn(NodeConfig::test_base().with_hardfork(Some(BaseUpgrade::Beryl.into()))).await;
11661        let stale_azul =
11662            CallTraceDecoderBuilder::new().with_hardfork(Some(BaseUpgrade::Azul.into())).build();
11663        *beryl.backend.call_trace_decoder.write() = Arc::new(stale_azul);
11664
11665        let decoder = beryl.backend.call_trace_decoder();
11666        assert_eq!(decoder.hardfork(), Some(BaseUpgrade::Beryl.into()));
11667        assert_eq!(
11668            decoder.labels.get(&B20FactoryStorage::ADDRESS).map(String::as_str),
11669            Some("B20Factory")
11670        );
11671        assert_eq!(
11672            decoder.labels.get(&ActivationRegistryStorage::ADDRESS).map(String::as_str),
11673            Some("ActivationRegistry")
11674        );
11675    }
11676}