1use self::{in_memory_db::StateRootDb, state::trie_storage};
3
4use crate::{
5 ForkChoice, NodeConfig, PrecompileFactory,
6 config::{ForkTransactionReplay, PruneStateHistoryConfig},
7 eth::{
8 backend::{
9 cheats::{CheatEcrecover, CheatsManager},
10 db::{
11 AnvilCacheDB, BLOCKHASH_HISTORY, Db, MaybeFullDatabase, SerializableState, StateDb,
12 },
13 executor::{
14 AnvilBlockExecutor, BlockExecutionKind, EthereumBlockTransitions,
15 ExecutedPoolTransactions, FoundryReceiptBuilder, PoolTransactionHooks,
16 PoolTxGasConfig, apply_ethereum_post_execution_changes,
17 apply_ethereum_pre_execution_changes, block_blob_gas_limit,
18 build_tx_env_for_pending, execute_pool_transaction, execute_pool_transactions,
19 },
20 fork::{ClientFork, ForkEndpointIdentity},
21 genesis::GenesisConfig,
22 mem::{
23 state::{state_root, state_trie_witness, storage_root, trie_accounts},
24 storage::MinedTransactionReceipt,
25 },
26 notifications::{ChainNotification, ChainNotifications, NewBlockNotification},
27 replay::{
28 ExecutedHistoricalReplay, HistoricalReplayTransaction,
29 PreparedForkTransactionReplay, execute_historical_replay,
30 prepare_fork_transaction_replay,
31 },
32 tempo::AnvilStorageProvider,
33 time::{TimeManager, utc_from_secs},
34 validate::TransactionValidator,
35 },
36 error::{BlockchainError, ErrDetail, InvalidTransactionError},
37 fees::{FeeDetails, FeeManager, FeeSnapshot, MIN_SUGGESTED_PRIORITY_FEE},
38 macros::node_info,
39 pool::transactions::PoolTransaction,
40 preserve_simulation_request_fields,
41 },
42 mem::{
43 inspector::{AnvilInspector, InspectorTxConfig},
44 storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome},
45 },
46};
47use alloy_chains::NamedChain;
48use alloy_consensus::{
49 Blob, BlockHeader, EnvKzgSettings, Header, Signed, Transaction as TransactionTrait,
50 TransactionEnvelope, TrieAccount, TxEip4844Variant, TxEnvelope, TxReceipt, Typed2718,
51 constants::EMPTY_WITHDRAWALS,
52 proofs::{calculate_receipt_root, calculate_transaction_root},
53 transaction::Recovered,
54};
55use alloy_eips::{
56 BlockNumHash, Encodable2718, eip2935, eip4788,
57 eip4844::{DATA_GAS_PER_BLOB, kzg_to_versioned_hash},
58 eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS,
59 eip7002, eip7251,
60 eip7685::EMPTY_REQUESTS_HASH,
61 eip7840::BlobParams,
62 eip7910::SystemContract,
63};
64use alloy_evm::{
65 Database, EthEvmFactory, Evm, EvmEnv, EvmFactory, FromTxWithEncoded,
66 block::{BlockExecutionResult, BlockExecutor, StateDB},
67 eth::EthEvmContext,
68 overrides::{OverrideBlockHashes, apply_state_overrides},
69 precompiles::{DynPrecompile, MovePrecompileError, Precompile, PrecompilesMap},
70};
71use alloy_network::{
72 AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType,
73 BlockResponse, Network, NetworkTransactionBuilder, ReceiptResponse, UnknownTxEnvelope,
74 UnknownTypedTransaction,
75};
76#[cfg(feature = "optimism")]
77use alloy_op_evm::{OpEvmContext, OpEvmFactory, OpTx};
78use alloy_primitives::{
79 Address, B256, Bloom, Bytes, Signature, TxKind, U64, U256, address, hex, keccak256,
80 map::{AddressMap, B256Set, HashMap, HashSet},
81};
82use alloy_rlp::{Decodable, Encodable};
83use alloy_rpc_types::{
84 AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides,
85 BlockTransactions, EIP1186AccountProofResponse as AccountProof,
86 EIP1186StorageProof as StorageProof, Filter, Header as AlloyHeader, Index, Log, Transaction,
87 TransactionReceipt,
88 anvil::Forking,
89 debug::ExecutionWitness,
90 request::TransactionRequest,
91 serde_helpers::JsonStorageKey,
92 simulate::{
93 MAX_SIMULATE_BLOCKS, SimBlock, SimCallResult, SimulateError, SimulatePayload,
94 SimulatedBlock,
95 },
96 state::{EvmOverrides, StateOverride},
97 trace::{
98 filter::TraceFilter,
99 geth::{
100 CallConfig, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig,
101 GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace,
102 NoopFrame, TraceResult,
103 },
104 opcode::{BlockOpcodeGas, TransactionOpcodeGas},
105 parity::{
106 LocalizedTransactionTrace, TraceResults, TraceResultsWithTransactionHash, TraceType,
107 },
108 },
109};
110use alloy_rpc_types_eth::{AccountInfo as RpcAccountInfo, Bundle, EthCallResponse};
111use alloy_rpc_types_mev::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult};
112use alloy_serde::{OtherFields, WithOtherFields};
113use alloy_sol_types::SolCall;
114use alloy_trie::{HashBuilder, Nibbles, proof::ProofRetainer};
115use anvil_core::eth::{
116 block::{Block, BlockInfo, canonical_block, create_block},
117 transaction::{MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo},
118};
119use anvil_rpc::error::{ErrorCode, RpcError};
120use chrono::Datelike;
121use eyre::{Context, Result};
122use flate2::{Compression, read::GzDecoder, write::GzEncoder};
123#[cfg(feature = "optimism")]
124use foundry_evm::hardfork::OpHardfork;
125use foundry_evm::{
126 backend::{BlockchainDb, DatabaseError, DatabaseResult, RevertStateSnapshotAction},
127 constants::{DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE},
128 core::{
129 evm::{EvmEnvFor, TempoEvmNetwork},
130 precompiles::EC_RECOVER,
131 },
132 decode::RevertDecoder,
133 hardfork::{EthereumHardfork, FoundryHardfork},
134 inspectors::AccessListInspector,
135 traces::{
136 CallTraceDecoder, FourByteInspector, GethTraceBuilder, TracingInspector,
137 TracingInspectorConfig,
138 },
139 utils::{
140 apply_chain_specific_tx_replay_env_changes_for_chain, block_env_from_header,
141 get_blob_base_fee_update_fraction, get_blob_base_fee_update_fraction_by_spec_id,
142 get_blob_params_by_hardfork,
143 },
144};
145use foundry_evm_networks::{NetworkConfigs, arbitrum};
146#[cfg(feature = "optimism")]
147use foundry_primitives::get_deposit_tx_parts;
148use foundry_primitives::{
149 FoundryHeader, FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest,
150 FoundryTxEnvelope, FoundryTxReceipt, TempoTransactionRequest,
151};
152use futures::channel::mpsc::{UnboundedSender, unbounded};
153#[cfg(feature = "optimism")]
154use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, POST_EXEC_TX_TYPE_ID};
155#[cfg(feature = "optimism")]
156use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts};
157use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
158use revm::{
159 Database as RevmDatabase, DatabaseCommit, Inspector,
160 context::{Block as RevmBlock, BlockEnv, Cfg, CfgEnv, ContextSetters, ContextTr, TxEnv},
161 context_interface::{
162 JournalTr,
163 block::BlobExcessGasAndPrice,
164 result::{ExecutionResult, HaltReason, Output, ResultAndState},
165 transaction::TransactionType,
166 },
167 database::{AccountState, CacheDB, DbAccount, WrapDatabaseRef},
168 handler::{
169 EthFrame, EvmTr, EvmTrError, FrameResult, FrameTr, Handler as EvmHandler, validation,
170 },
171 inspector::{InspectorEvmTr, InspectorHandler},
172 interpreter::{InstructionResult, interpreter::EthInterpreter, interpreter_action::FrameInit},
173 precompile::{PrecompileSpecId, Precompiles},
174 primitives::{KECCAK_EMPTY, hardfork::SpecId},
175 state::{Account, AccountInfo, EvmState, EvmStorageSlot, TransactionId},
176};
177use revm_inspectors::opcode::OpcodeGasInspector;
178use std::{
179 collections::BTreeMap,
180 fmt::{self, Debug},
181 io::{Read, Write},
182 marker::PhantomData,
183 ops::Mul,
184 path::{Path, PathBuf},
185 sync::{
186 Arc,
187 atomic::{AtomicBool, Ordering},
188 },
189 time::Duration,
190};
191use storage::{Blockchain, DEFAULT_HISTORY_LIMIT, MinedTransaction};
192use tempo_evm::evm::TempoEvmFactory;
193use tempo_hardfork::TempoHardfork;
194use tempo_precompiles::{
195 NONCE_PRECOMPILE_ADDRESS, TIP_FEE_MANAGER_ADDRESS, extend_tempo_precompiles,
196 nonce::NonceManager,
197 storage::{Handler, StorageActions, StorageCtx},
198 tip_fee_manager::{IFeeManager, TipFeeManager},
199 tip20::{ISSUER_ROLE, ITIP20, TIP20Token},
200 tip20_factory::TIP20Factory,
201};
202use tempo_primitives::{
203 AASigned, SignatureType, TEMPO_TX_TYPE_ID, TempoSignature,
204 transaction::{
205 Call, KeychainSignature, PrimitiveSignature, RecoveredTempoAuthorization,
206 tt_signature::{P256SignatureWithPreHash, WebAuthnSignature},
207 },
208};
209use tempo_revm::{
210 ExecutionContext, TempoBatchCallEnv, TempoBlockEnv, TempoHaltReason, TempoTxEnv,
211 evm::TempoContext, gas_params::tempo_gas_params,
212};
213use tokio::{sync::RwLock as AsyncRwLock, task::JoinSet};
214
215#[cfg(feature = "optimism")]
222type OpCallDepositInfo = DepositTransactionParts;
223#[cfg(not(feature = "optimism"))]
224#[derive(Default, Clone, Debug)]
225struct OpCallDepositInfo;
226
227pub(crate) struct StagedForkReset {
229 node_config: NodeConfig,
230 db: Box<dyn Db>,
231 fees: FeeManager,
232 evm_env: EvmEnv,
233 fork: ClientFork,
234 timestamp: u64,
235 discard_old_cached_state: bool,
236 invalidated_cache_namespaces: Vec<ForkCacheNamespace>,
237 flush_old_cache: bool,
238 cache_lease: StagedForkCacheLease,
239}
240
241pub(crate) struct StagedMemoryReset<N: Network> {
243 node_config: NodeConfig,
244 db: Box<dyn Db>,
245 fees: FeeManager,
246 evm_env: EvmEnv,
247 hardfork: FoundryHardfork,
248 storage: BlockchainStorage<N>,
249 timestamp: u64,
250 flush_old_cache: bool,
251}
252
253#[derive(Clone, Debug, PartialEq, Eq)]
255struct ForkCacheSource {
256 rpc_url: String,
257 endpoint_identity: ForkEndpointIdentity,
258}
259
260impl ForkCacheSource {
261 fn from_fork(fork: &ClientFork) -> Option<Self> {
262 let config = fork.config.read();
263 Some(Self {
264 rpc_url: config.eth_rpc_url()?.to_string(),
265 endpoint_identity: config.endpoint_identity,
266 })
267 }
268
269 fn authoritative_identity_changed_at_same_url(
270 &self,
271 rpc_url: &str,
272 endpoint_identity: ForkEndpointIdentity,
273 ) -> bool {
274 let authoritative =
278 self.endpoint_identity.is_authoritative() || endpoint_identity.is_authoritative();
279 self.rpc_url == rpc_url && authoritative && self.endpoint_identity != endpoint_identity
280 }
281}
282
283#[derive(Clone, Debug, PartialEq, Eq)]
285struct ForkCacheNamespace {
286 chain_cache_dir: PathBuf,
287 file_name: String,
288}
289
290impl ForkCacheNamespace {
291 fn new(source_chain_id: u64, rpc_url: &str) -> Option<Self> {
292 Some(Self {
293 chain_cache_dir: foundry_config::Config::foundry_chain_cache_dir(source_chain_id)?,
294 file_name: format!("storage-{}.json", hex::encode(keccak256(rpc_url))),
295 })
296 }
297
298 fn invalidate(&self) -> Result<(), BlockchainError> {
299 let entries = match std::fs::read_dir(&self.chain_cache_dir) {
300 Ok(entries) => entries,
301 Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()),
302 Err(err) => {
303 return Err(BlockchainError::Internal(format!(
304 "failed to inspect fork cache at {}: {err}",
305 self.chain_cache_dir.display()
306 )));
307 }
308 };
309
310 for entry in entries {
311 let entry = entry.map_err(|err| {
312 BlockchainError::Internal(format!(
313 "failed to inspect fork cache at {}: {err}",
314 self.chain_cache_dir.display()
315 ))
316 })?;
317 let file_type = entry.file_type().map_err(|err| {
318 BlockchainError::Internal(format!(
319 "failed to inspect fork cache entry at {}: {err}",
320 entry.path().display()
321 ))
322 })?;
323 if !file_type.is_dir() {
324 continue;
325 }
326 let cache_path = entry.path().join(&self.file_name);
327 if let Err(err) = std::fs::remove_file(&cache_path)
328 && err.kind() != std::io::ErrorKind::NotFound
329 {
330 return Err(BlockchainError::Internal(format!(
331 "failed to invalidate fork cache at {}: {err}",
332 cache_path.display()
333 )));
334 }
335 }
336 Ok(())
337 }
338}
339
340#[derive(Clone, Debug, Default)]
342struct StagedForkCacheLease(Option<Arc<StagedForkCacheLeaseInner>>);
343
344#[derive(Debug)]
345struct StagedForkCacheLeaseInner {
346 db: BlockchainDb,
347 cache_path: PathBuf,
348 armed: AtomicBool,
349}
350
351impl StagedForkCacheLease {
352 fn new(db: BlockchainDb, cache_path: Option<PathBuf>) -> Self {
353 Self(cache_path.map(|cache_path| {
354 Arc::new(StagedForkCacheLeaseInner { db, cache_path, armed: AtomicBool::new(true) })
355 }))
356 }
357
358 fn for_db(db: &BlockchainDb) -> Self {
359 Self::new(db.clone(), db.cache().cache_path().map(Path::to_path_buf))
360 }
361
362 fn disarm(&self) {
363 if let Some(inner) = &self.0 {
364 inner.armed.store(false, Ordering::Release);
365 }
366 }
367
368 fn rollback(&self) -> Result<(), BlockchainError> {
369 let Some(inner) = &self.0 else { return Ok(()) };
370 if Arc::strong_count(inner) == 1 && inner.armed.load(Ordering::Acquire) {
373 inner.invalidate()?;
374 inner.armed.store(false, Ordering::Release);
375 }
376 Ok(())
377 }
378}
379
380impl StagedForkCacheLeaseInner {
381 fn invalidate(&self) -> Result<(), BlockchainError> {
382 self.db.db().clear();
383 self.db.cache().flush();
384 if let Err(err) = std::fs::remove_file(&self.cache_path)
385 && err.kind() != std::io::ErrorKind::NotFound
386 {
387 return Err(BlockchainError::Internal(format!(
388 "failed to invalidate fork cache at {}: {err}",
389 self.cache_path.display()
390 )));
391 }
392 Ok(())
393 }
394}
395
396impl Drop for StagedForkCacheLeaseInner {
397 fn drop(&mut self) {
398 if self.armed.swap(false, Ordering::AcqRel)
399 && let Err(err) = self.invalidate()
400 {
401 warn!(target: "backend", %err, "failed to roll back staged fork cache");
402 }
403 }
404}
405
406struct StagedForkDbUser<D> {
411 db: Option<Arc<AsyncRwLock<D>>>,
412 cache_lease: StagedForkCacheLease,
413}
414
415impl<D> Clone for StagedForkDbUser<D> {
416 fn clone(&self) -> Self {
417 Self { db: self.db.clone(), cache_lease: self.cache_lease.clone() }
418 }
419}
420
421impl<D> StagedForkDbUser<D> {
422 const fn db(&self) -> &Arc<AsyncRwLock<D>> {
423 self.db.as_ref().expect("staged fork database must be present until drop")
424 }
425}
426
427impl<D> Drop for StagedForkDbUser<D> {
428 fn drop(&mut self) {
429 drop(self.db.take());
431 }
432}
433
434#[cfg(feature = "monad")]
435pub(crate) type MonadReplayContext = monad_revm::MonadChainContext;
436#[cfg(not(feature = "monad"))]
438#[derive(Clone)]
439pub(crate) struct MonadReplayContext;
440
441#[cfg(feature = "monad")]
442enum MonadExecutionContext<'a> {
443 Exact(Box<MonadReplayContext>),
444 Next(&'a mut MonadReplayContext),
445}
446
447#[cfg(not(feature = "monad"))]
448struct MonadExecutionContext<'a> {
449 _marker: std::marker::PhantomData<&'a mut MonadReplayContext>,
450}
451
452#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
453enum EnvelopeExecutionKind {
454 #[default]
455 Transaction,
456 Replay,
457}
458
459#[cfg_attr(not(feature = "monad"), allow(dead_code))]
460struct EnvelopeExecution<'a> {
461 monad_context: Option<MonadExecutionContext<'a>>,
462 kind: EnvelopeExecutionKind,
463 hardfork: FoundryHardfork,
464}
465
466impl<'a> EnvelopeExecution<'a> {
467 const fn transaction(
468 monad_context: Option<MonadExecutionContext<'a>>,
469 hardfork: FoundryHardfork,
470 ) -> Self {
471 Self { monad_context, kind: EnvelopeExecutionKind::Transaction, hardfork }
472 }
473
474 const fn replay(
475 monad_context: Option<MonadExecutionContext<'a>>,
476 hardfork: FoundryHardfork,
477 ) -> Self {
478 Self { monad_context, kind: EnvelopeExecutionKind::Replay, hardfork }
479 }
480}
481
482#[cfg(feature = "monad")]
483fn monad_execution_context_at(
484 context: Option<&MonadReplayContext>,
485 current_tx_index: usize,
486) -> Option<MonadExecutionContext<'static>> {
487 context.map(|context| {
488 let mut context = context.clone();
489 context.current_tx_index = current_tx_index;
490 MonadExecutionContext::Exact(Box::new(context))
491 })
492}
493
494#[cfg(not(feature = "monad"))]
495const fn monad_execution_context_at(
496 _context: Option<&MonadReplayContext>,
497 _current_tx_index: usize,
498) -> Option<MonadExecutionContext<'static>> {
499 None
500}
501
502#[cfg(feature = "monad")]
503const fn next_monad_context(context: &mut MonadReplayContext) -> MonadExecutionContext<'_> {
504 MonadExecutionContext::Next(context)
505}
506
507#[cfg(not(feature = "monad"))]
508const fn next_monad_context(_context: &mut MonadReplayContext) -> MonadExecutionContext<'_> {
509 MonadExecutionContext { _marker: std::marker::PhantomData }
510}
511
512const fn noop_before_transaction<E, T>(_evm: &mut E, _tx: &T) {}
513
514const fn noop_on_execution_error<E>(_evm: &mut E) {}
515
516const SIMULATE_GAS_CAP: u64 = 50_000_000;
518const SEPOLIA_DEPOSIT_CONTRACT_ADDRESS: Address =
519 address!("7f02c3e3c98b133055b8b348b2ac625669ed295d");
520const HOLESKY_DEPOSIT_CONTRACT_ADDRESS: Address =
521 address!("4242424242424242424242424242424242424242");
522
523const TEMPO_RPC_SIMULATION_CONTEXT: B256 = B256::new(*b"TEMPO_RPC_SIMULATION_MPP_CONTEXT");
525
526struct SimulationHandler<EVM, ERROR, FRAME> {
528 _phantom: PhantomData<(EVM, ERROR, FRAME)>,
529}
530
531impl<EVM, ERROR, FRAME> Default for SimulationHandler<EVM, ERROR, FRAME> {
532 fn default() -> Self {
533 Self { _phantom: PhantomData }
534 }
535}
536
537impl<EVM, ERROR, FRAME> EvmHandler for SimulationHandler<EVM, ERROR, FRAME>
538where
539 EVM: EvmTr<
540 Context: ContextTr<
541 Block = BlockEnv,
542 Tx = TxEnv,
543 Journal: JournalTr<State = EvmState>,
544 > + ContextSetters,
545 Frame = FRAME,
546 >,
547 ERROR: EvmTrError<EVM>,
548 FRAME: FrameTr<FrameResult = FrameResult, FrameInit = FrameInit>,
549{
550 type Evm = EVM;
551 type Error = ERROR;
552 type HaltReason = HaltReason;
553
554 fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
555 let skip_blob_fee_check = evm.ctx_ref().cfg().is_base_fee_check_disabled()
556 && evm.ctx_ref().tx().tx_type == 3
557 && evm.ctx_ref().tx().max_fee_per_blob_gas == 0;
558 if !skip_blob_fee_check {
559 return validation::validate_env(evm.ctx());
560 }
561
562 let block = evm.ctx_ref().block().clone();
563 let mut validation_block = block.clone();
564 if let Some(blob_gas_and_price) = &mut validation_block.blob_excess_gas_and_price {
565 blob_gas_and_price.blob_gasprice = 0;
566 }
567 evm.ctx().set_block(validation_block);
568 let result = validation::validate_env(evm.ctx());
569 evm.ctx().set_block(block);
570 result
571 }
572}
573
574impl<EVM, ERROR> InspectorHandler for SimulationHandler<EVM, ERROR, EthFrame<EthInterpreter>>
575where
576 EVM: InspectorEvmTr<
577 Context: ContextTr<
578 Block = BlockEnv,
579 Tx = TxEnv,
580 Journal: JournalTr<State = EvmState>,
581 > + ContextSetters,
582 Frame = EthFrame<EthInterpreter>,
583 Inspector: Inspector<<EVM as EvmTr>::Context, EthInterpreter>,
584 >,
585 ERROR: EvmTrError<EVM>,
586{
587 type IT = EthInterpreter;
588}
589
590#[derive(Clone)]
591enum CallTxEnv {
592 Eth(TxEnv),
593 #[cfg(feature = "monad")]
594 Monad(TxEnv),
595 #[cfg(feature = "optimism")]
596 Op(OpTransaction<TxEnv>),
597 Tempo(TempoTxEnv),
598}
599
600impl CallTxEnv {
601 #[cfg_attr(not(feature = "js-tracer"), allow(dead_code))]
602 const fn base(&self) -> &TxEnv {
603 match self {
604 Self::Eth(tx) => tx,
605 #[cfg(feature = "monad")]
606 Self::Monad(tx) => tx,
607 #[cfg(feature = "optimism")]
608 Self::Op(tx) => &tx.base,
609 Self::Tempo(tx) => &tx.inner,
610 }
611 }
612
613 const fn base_mut(&mut self) -> &mut TxEnv {
614 match self {
615 Self::Eth(tx) => tx,
616 #[cfg(feature = "monad")]
617 Self::Monad(tx) => tx,
618 #[cfg(feature = "optimism")]
619 Self::Op(tx) => &mut tx.base,
620 Self::Tempo(tx) => &mut tx.inner,
621 }
622 }
623
624 fn into_base(self) -> TxEnv {
625 match self {
626 Self::Eth(tx) => tx,
627 #[cfg(feature = "monad")]
628 Self::Monad(tx) => tx,
629 #[cfg(feature = "optimism")]
630 Self::Op(tx) => tx.base,
631 Self::Tempo(tx) => tx.inner,
632 }
633 }
634
635 fn uses_protocol_call_nonce(&self) -> bool {
636 match self {
637 Self::Eth(tx) => matches!(tx.kind, TxKind::Call(_)),
638 #[cfg(feature = "monad")]
639 Self::Monad(tx) => matches!(tx.kind, TxKind::Call(_)),
640 #[cfg(feature = "optimism")]
641 Self::Op(tx) => matches!(tx.base.kind, TxKind::Call(_)),
642 Self::Tempo(tx) => tx.tempo_tx_env.as_ref().map_or_else(
643 || matches!(tx.inner.kind, TxKind::Call(_)),
644 |aa| {
645 aa.nonce_key.is_zero()
646 && aa
647 .aa_calls
648 .first()
649 .is_some_and(|call| matches!(call.to, TxKind::Call(_)))
650 },
651 ),
652 }
653 }
654}
655
656fn apply_tempo_envelope_identity(tx_env: &mut CallTxEnv, simulated_tx: Option<&AASigned>) {
657 if let (CallTxEnv::Tempo(tx_env), Some(simulated_tx)) = (tx_env, simulated_tx) {
658 tx_env.unique_tx_identifier = Some(simulated_tx.expiring_nonce_hash(tx_env.inner.caller));
659 if let Some(batch) = &mut tx_env.tempo_tx_env {
660 batch.tx_hash = *simulated_tx.hash();
661 }
662 }
663}
664
665struct PreparedCall {
666 evm_env: EvmEnv,
667 tx_env: CallTxEnv,
668 simulated_tempo_tx: Option<AASigned>,
669}
670
671#[derive(Default)]
672struct TypedCallOverrides {
673 gas_limit: Option<u64>,
674 access_list: Option<AccessList>,
675 disable_fee_charge: bool,
676}
677
678pub(crate) struct GasEstimateCallOptions {
679 gas_limit: u64,
680 disable_fee_charge: bool,
681 monad_context: Option<MonadReplayContext>,
682}
683
684impl GasEstimateCallOptions {
685 pub(crate) const fn new(
686 gas_limit: u64,
687 disable_fee_charge: bool,
688 monad_context: Option<MonadReplayContext>,
689 ) -> Self {
690 Self { gas_limit, disable_fee_charge, monad_context }
691 }
692}
693
694#[cfg(all(feature = "optimism", feature = "monad"))]
698pub trait BackendInspector<DB: Database>:
699 Inspector<EthEvmContext<DB>>
700 + Inspector<OpEvmContext<DB>>
701 + Inspector<TempoContext<DB>>
702 + Inspector<alloy_monad_evm::MonadContext<DB>>
703{
704}
705#[cfg(all(feature = "optimism", feature = "monad"))]
706impl<DB: Database, T> BackendInspector<DB> for T where
707 T: Inspector<EthEvmContext<DB>>
708 + Inspector<OpEvmContext<DB>>
709 + Inspector<TempoContext<DB>>
710 + Inspector<alloy_monad_evm::MonadContext<DB>>
711{
712}
713#[cfg(all(feature = "optimism", not(feature = "monad")))]
714pub trait BackendInspector<DB: Database>:
715 Inspector<EthEvmContext<DB>> + Inspector<OpEvmContext<DB>> + Inspector<TempoContext<DB>>
716{
717}
718#[cfg(all(feature = "optimism", not(feature = "monad")))]
719impl<DB: Database, T> BackendInspector<DB> for T where
720 T: Inspector<EthEvmContext<DB>> + Inspector<OpEvmContext<DB>> + Inspector<TempoContext<DB>>
721{
722}
723#[cfg(all(not(feature = "optimism"), feature = "monad"))]
724pub trait BackendInspector<DB: Database>:
725 Inspector<EthEvmContext<DB>>
726 + Inspector<TempoContext<DB>>
727 + Inspector<alloy_monad_evm::MonadContext<DB>>
728{
729}
730#[cfg(all(not(feature = "optimism"), feature = "monad"))]
731impl<DB: Database, T> BackendInspector<DB> for T where
732 T: Inspector<EthEvmContext<DB>>
733 + Inspector<TempoContext<DB>>
734 + Inspector<alloy_monad_evm::MonadContext<DB>>
735{
736}
737#[cfg(all(not(feature = "optimism"), not(feature = "monad")))]
738pub trait BackendInspector<DB: Database>:
739 Inspector<EthEvmContext<DB>> + Inspector<TempoContext<DB>>
740{
741}
742#[cfg(all(not(feature = "optimism"), not(feature = "monad")))]
743impl<DB: Database, T> BackendInspector<DB> for T where
744 T: Inspector<EthEvmContext<DB>> + Inspector<TempoContext<DB>>
745{
746}
747pub mod cache;
748pub mod fork_db;
749pub mod in_memory_db;
750pub mod inspector;
751#[cfg(feature = "monad")]
752mod monad;
753#[cfg(feature = "optimism")]
754pub mod optimism;
755pub mod state;
756pub mod storage;
757
758pub trait DatabaseRef: revm::DatabaseRef<Error = DatabaseError> + Debug {}
762impl<T> DatabaseRef for T where T: revm::DatabaseRef<Error = DatabaseError> + Debug {}
763impl DatabaseRef for dyn crate::eth::backend::db::Db {}
764
765pub const MIN_TRANSACTION_GAS: u128 = 21000;
767pub const MIN_CREATE_GAS: u128 = 53000;
769
770fn tempo_nonce(
771 state: &dyn DatabaseRef,
772 caller: Address,
773 nonce_key: U256,
774) -> Result<u64, BlockchainError> {
775 if nonce_key.is_zero() {
776 return Ok(state.basic_ref(caller)?.map(|account| account.nonce).unwrap_or_default());
777 }
778 if nonce_key == U256::MAX {
779 return Ok(0);
780 }
781 let slot = NonceManager::new().nonces[caller][nonce_key].slot();
782 Ok(state.storage_ref(NONCE_PRECOMPILE_ADDRESS, slot)?.saturating_to())
783}
784
785fn mock_tempo_signature(
786 key_type: SignatureType,
787 key_data: Option<Bytes>,
788 key_id: Option<Address>,
789 caller: Address,
790 is_t1c: bool,
791) -> TempoSignature {
792 let signature = match key_type {
793 SignatureType::Secp256k1 => {
794 PrimitiveSignature::Secp256k1(Signature::new(U256::ZERO, U256::ZERO, false))
795 }
796 SignatureType::P256 => PrimitiveSignature::P256(P256SignatureWithPreHash {
797 r: B256::ZERO,
798 s: B256::ZERO,
799 pub_key_x: B256::ZERO,
800 pub_key_y: B256::ZERO,
801 pre_hash: false,
802 }),
803 SignatureType::WebAuthn => {
804 const CLIENT_JSON: &str = r#"{"type":"webauthn.get","challenge":"","origin":""}"#;
805 const AUTH_DATA_SIZE: usize = 37;
806 const MIN_SIZE: usize = AUTH_DATA_SIZE + CLIENT_JSON.len();
807 const DEFAULT_SIZE: usize = 800;
808 const MAX_SIZE: usize = 8192;
809
810 let size = key_data
811 .as_deref()
812 .and_then(|data| match data.len() {
813 1 => Some(data[0] as usize),
814 2 => Some(u16::from_be_bytes([data[0], data[1]]) as usize),
815 4 => Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize),
816 _ => None,
817 })
818 .unwrap_or(DEFAULT_SIZE)
819 .clamp(MIN_SIZE, MAX_SIZE);
820 let mut webauthn_data = vec![0u8; AUTH_DATA_SIZE];
821 webauthn_data[32] = 0x01;
822 let padding = "x".repeat(size - MIN_SIZE);
823 webauthn_data.extend_from_slice(
824 format!(r#"{{"type":"webauthn.get","challenge":"","origin":"{padding}"}}"#)
825 .as_bytes(),
826 );
827 PrimitiveSignature::WebAuthn(WebAuthnSignature {
828 webauthn_data: webauthn_data.into(),
829 r: B256::ZERO,
830 s: B256::ZERO,
831 pub_key_x: B256::ZERO,
832 pub_key_y: B256::ZERO,
833 })
834 }
835 };
836
837 if key_id.is_some() {
838 let signature = if is_t1c {
839 KeychainSignature::new(caller, signature)
840 } else {
841 KeychainSignature::new_v1(caller, signature)
842 };
843 TempoSignature::Keychain(signature)
844 } else {
845 TempoSignature::Primitive(signature)
846 }
847}
848
849fn call_config_from_tracer_config(
850 tracer_config: GethDebugTracerConfig,
851) -> Result<CallConfig, serde_json::Error> {
852 let mut tracer_config = tracer_config.into_json();
853 if let Some(config) = tracer_config.as_object_mut()
854 && !config.contains_key("onlyTopCall")
855 && let Some(only_top_level_call) = config.remove("onlyTopLevelCall")
856 {
857 config.insert("onlyTopCall".to_string(), only_top_level_call);
858 }
859
860 GethDebugTracerConfig(tracer_config).into_call_config()
861}
862
863pub type State = foundry_evm::utils::StateChangeset;
864
865#[derive(Clone, Debug, Default)]
866struct SimulationPrecompileOverrides {
867 moves: Vec<(Address, Address)>,
868}
869
870pub enum BlockRequest<T> {
872 Pending(Vec<Arc<PoolTransaction<T>>>),
873 Number(u64),
874}
875
876impl<T> fmt::Debug for BlockRequest<T> {
877 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
878 match self {
879 Self::Pending(txs) => f.debug_tuple("Pending").field(&txs.len()).finish(),
880 Self::Number(n) => f.debug_tuple("Number").field(n).finish(),
881 }
882 }
883}
884
885impl<T> BlockRequest<T> {
886 pub const fn block_number(&self) -> BlockNumber {
887 match *self {
888 Self::Pending(_) => BlockNumber::Pending,
889 Self::Number(n) => BlockNumber::Number(n),
890 }
891 }
892}
893
894struct StateSnapshot {
895 block_number: u64,
896 block_hash: B256,
897 fees: FeeSnapshot,
898 time_offset: i128,
899}
900
901pub struct Backend<N: Network> {
903 db: Arc<AsyncRwLock<Box<dyn Db>>>,
923 blockchain: Blockchain<N>,
925 states: Arc<RwLock<InMemoryBlockStates>>,
927 evm_env: Arc<RwLock<EvmEnv>>,
929 networks: NetworkConfigs,
931 hardfork: Arc<RwLock<FoundryHardfork>>,
933 fork: Arc<RwLock<Option<ClientFork>>>,
935 last_fork_cache_source: Arc<RwLock<Option<ForkCacheSource>>>,
937 time: TimeManager,
939 cheats: CheatsManager,
941 fees: FeeManager,
943 genesis: GenesisConfig,
945 new_block_listeners: Arc<Mutex<Vec<UnboundedSender<ChainNotification>>>>,
948 active_state_snapshots: Arc<Mutex<HashMap<U256, StateSnapshot>>>,
950 enable_steps_tracing: bool,
951 print_logs: bool,
952 print_traces: bool,
953 call_trace_decoder: Arc<RwLock<Arc<CallTraceDecoder>>>,
955 prune_state_history_config: PruneStateHistoryConfig,
957 transaction_block_keeper: Option<usize>,
959 pub(crate) node_config: Arc<AsyncRwLock<NodeConfig>>,
960 slots_in_an_epoch: u64,
962 precompile_factory: Option<Arc<dyn PrecompileFactory>>,
964 mining: Arc<tokio::sync::Mutex<()>>,
966 disable_pool_balance_checks: bool,
968 startup_fork_cache_user: StagedForkDbUser<Box<dyn Db>>,
973}
974
975impl<N: Network> Clone for Backend<N> {
976 fn clone(&self) -> Self {
977 Self {
978 db: self.db.clone(),
979 blockchain: self.blockchain.clone(),
980 states: self.states.clone(),
981 evm_env: self.evm_env.clone(),
982 networks: self.networks,
983 hardfork: self.hardfork.clone(),
984 fork: self.fork.clone(),
985 last_fork_cache_source: self.last_fork_cache_source.clone(),
986 time: self.time.clone(),
987 cheats: self.cheats.clone(),
988 fees: self.fees.clone(),
989 genesis: self.genesis.clone(),
990 new_block_listeners: self.new_block_listeners.clone(),
991 active_state_snapshots: self.active_state_snapshots.clone(),
992 enable_steps_tracing: self.enable_steps_tracing,
993 print_logs: self.print_logs,
994 print_traces: self.print_traces,
995 call_trace_decoder: self.call_trace_decoder.clone(),
996 prune_state_history_config: self.prune_state_history_config,
997 transaction_block_keeper: self.transaction_block_keeper,
998 node_config: self.node_config.clone(),
999 slots_in_an_epoch: self.slots_in_an_epoch,
1000 precompile_factory: self.precompile_factory.clone(),
1001 mining: self.mining.clone(),
1002 disable_pool_balance_checks: self.disable_pool_balance_checks,
1003 startup_fork_cache_user: self.startup_fork_cache_user.clone(),
1004 }
1005 }
1006}
1007
1008impl<N: Network> fmt::Debug for Backend<N> {
1009 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1010 f.debug_struct("Backend").finish_non_exhaustive()
1011 }
1012}
1013
1014impl<N: Network> Backend<N> {
1016 pub fn impersonate(&self, addr: Address) -> bool {
1020 if self.cheats.impersonated_accounts().contains(&addr) {
1021 return true;
1022 }
1023 self.evm_env.write().cfg_env.disable_eip3607 = true;
1025 self.cheats.impersonate(addr)
1026 }
1027
1028 pub fn stop_impersonating(&self, addr: Address) {
1032 self.cheats.stop_impersonating(&addr);
1033 }
1034
1035 pub fn auto_impersonate_account(&self, enabled: bool) {
1037 self.cheats.set_auto_impersonate_account(enabled);
1038 }
1039
1040 pub fn get_fork(&self) -> Option<ClientFork> {
1042 self.fork.read().clone()
1043 }
1044
1045 pub(crate) fn commit_startup_fork_cache(&self) {
1047 self.startup_fork_cache_user.cache_lease.disarm();
1048 }
1049
1050 pub fn get_db(&self) -> &Arc<AsyncRwLock<Box<dyn Db>>> {
1052 &self.db
1053 }
1054
1055 pub(crate) async fn lock_mining(&self) -> tokio::sync::MutexGuard<'_, ()> {
1057 self.mining.lock().await
1058 }
1059
1060 pub async fn get_account(&self, address: Address) -> DatabaseResult<AccountInfo> {
1062 Ok(self.db.read().await.basic_ref(address)?.unwrap_or_default())
1063 }
1064
1065 pub fn is_fork(&self) -> bool {
1067 self.fork.read().is_some()
1068 }
1069
1070 pub async fn set_create2_deployer(&self, address: Address) -> DatabaseResult<()> {
1072 self.set_code(address, Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE)).await?;
1073 Ok(())
1074 }
1075
1076 pub(crate) fn update_interval_mine_block_time(&self, block_time: Duration) {
1078 self.states.write().update_interval_mine_block_time(block_time)
1079 }
1080
1081 pub const fn time(&self) -> &TimeManager {
1083 &self.time
1084 }
1085
1086 pub const fn cheats(&self) -> &CheatsManager {
1088 &self.cheats
1089 }
1090
1091 pub fn skip_blob_validation(&self, impersonator: Option<Address>) -> bool {
1093 self.cheats().auto_impersonate_accounts()
1094 || impersonator
1095 .is_some_and(|addr| self.cheats().impersonated_accounts().contains(&addr))
1096 }
1097
1098 pub const fn fees(&self) -> &FeeManager {
1100 &self.fees
1101 }
1102
1103 pub const fn evm_env(&self) -> &Arc<RwLock<EvmEnv>> {
1105 &self.evm_env
1106 }
1107
1108 pub fn best_hash(&self) -> B256 {
1110 self.blockchain.storage.read().best_hash
1111 }
1112
1113 pub fn best_number(&self) -> u64 {
1115 self.blockchain.storage.read().best_number
1116 }
1117
1118 pub fn set_block_number(&self, number: u64) {
1120 self.evm_env.write().block_env.number = U256::from(number);
1121 }
1122
1123 pub fn coinbase(&self) -> Address {
1125 self.evm_env.read().block_env.beneficiary
1126 }
1127
1128 pub fn chain_id(&self) -> U256 {
1130 U256::from(self.evm_env.read().cfg_env.chain_id)
1131 }
1132
1133 fn protocol_chain_id(&self) -> u64 {
1135 self.get_fork().map_or_else(|| self.evm_env.read().cfg_env.chain_id, |fork| fork.chain_id())
1136 }
1137
1138 pub fn set_chain_id(&self, chain_id: u64) {
1139 self.evm_env.write().cfg_env.chain_id = chain_id;
1140 }
1141
1142 pub const fn genesis_time(&self) -> u64 {
1144 self.genesis.timestamp
1145 }
1146
1147 pub const fn genesis_number(&self) -> u64 {
1149 self.genesis.number
1150 }
1151
1152 pub async fn current_balance(&self, address: Address) -> DatabaseResult<U256> {
1154 Ok(self.get_account(address).await?.balance)
1155 }
1156
1157 pub async fn current_nonce(&self, address: Address) -> DatabaseResult<u64> {
1159 Ok(self.get_account(address).await?.nonce)
1160 }
1161
1162 pub fn set_coinbase(&self, address: Address) {
1164 self.evm_env.write().block_env.beneficiary = address;
1165 }
1166
1167 pub fn set_next_block_prevrandao(&self, prevrandao: B256) {
1172 self.cheats.set_next_block_prevrandao(prevrandao);
1173 }
1174
1175 pub async fn set_nonce(&self, address: Address, nonce: U256) -> DatabaseResult<()> {
1177 self.db.write().await.set_nonce(address, nonce.try_into().unwrap_or(u64::MAX))
1178 }
1179
1180 pub async fn set_balance(&self, address: Address, balance: U256) -> DatabaseResult<()> {
1182 self.db.write().await.set_balance(address, balance)
1183 }
1184
1185 pub async fn set_code(&self, address: Address, code: Bytes) -> DatabaseResult<()> {
1187 self.db.write().await.set_code(address, code)
1188 }
1189
1190 pub async fn set_storage_at(
1192 &self,
1193 address: Address,
1194 slot: U256,
1195 val: B256,
1196 ) -> DatabaseResult<()> {
1197 self.db.write().await.set_storage_at(address, slot.into(), val)
1198 }
1199
1200 pub fn spec_id(&self) -> SpecId {
1202 *self.evm_env.read().spec_id()
1203 }
1204
1205 pub fn is_eip1559(&self) -> bool {
1207 (self.spec_id() as u8) >= (SpecId::LONDON as u8)
1208 }
1209
1210 pub fn is_eip3675(&self) -> bool {
1212 (self.spec_id() as u8) >= (SpecId::MERGE as u8)
1213 }
1214
1215 pub fn is_eip2930(&self) -> bool {
1217 (self.spec_id() as u8) >= (SpecId::BERLIN as u8)
1218 }
1219
1220 pub fn is_eip4844(&self) -> bool {
1222 (self.spec_id() as u8) >= (SpecId::CANCUN as u8)
1223 }
1224
1225 pub fn is_eip7702(&self) -> bool {
1227 (self.spec_id() as u8) >= (SpecId::PRAGUE as u8)
1228 }
1229
1230 pub const fn is_optimism(&self) -> bool {
1234 self.networks.is_optimism()
1235 }
1236
1237 pub const fn is_tempo(&self) -> bool {
1239 self.networks.is_tempo()
1240 }
1241
1242 pub const fn is_monad(&self) -> bool {
1244 self.networks.is_monad()
1245 }
1246
1247 pub const fn execution_profile_name(&self) -> &'static str {
1249 self.networks.execution_profile_name()
1250 }
1251
1252 fn pending_mined_transaction(
1254 &self,
1255 transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>,
1256 ) -> Result<PendingTransaction<FoundryTxEnvelope>, BlockchainError> {
1257 #[cfg(feature = "monad")]
1258 if self.is_monad() {
1259 return Self::monad_pending_mined_transaction_from_storage(
1260 &self.blockchain.storage.read(),
1261 transaction,
1262 );
1263 }
1264 Ok(PendingTransaction::from_maybe_impersonated(transaction)?)
1265 }
1266
1267 #[cfg(not(feature = "monad"))]
1268 fn active_monad_context_for_mined_block(
1269 &self,
1270 _block: &Block,
1271 ) -> Result<Option<MonadReplayContext>, BlockchainError> {
1272 Ok(None)
1273 }
1274
1275 #[cfg(not(feature = "monad"))]
1276 fn active_monad_context_before_mined_transaction(
1277 &self,
1278 _block: &Block,
1279 _current_tx_index: usize,
1280 ) -> Result<Option<MonadReplayContext>, BlockchainError> {
1281 Ok(None)
1282 }
1283
1284 pub fn hardfork(&self) -> FoundryHardfork {
1286 if let Some(hardfork) =
1287 self.fork.read().as_ref().and_then(|fork| fork.config.read().hardfork)
1288 {
1289 return hardfork;
1290 }
1291 *self.hardfork.read()
1292 }
1293
1294 fn ethereum_block_transitions(
1296 &self,
1297 hardfork: FoundryHardfork,
1298 parent_beacon_block_root: Option<B256>,
1299 execution_kind: BlockExecutionKind,
1300 ) -> Option<EthereumBlockTransitions> {
1301 if self.is_optimism() || self.is_tempo() {
1302 return None;
1303 }
1304 let FoundryHardfork::Ethereum(hardfork) = hardfork else { return None };
1305 Some(EthereumBlockTransitions {
1306 hardfork,
1307 deposit_contract_address: self.ethereum_deposit_contract_address(),
1308 parent_beacon_block_root,
1309 execution_kind,
1310 })
1311 }
1312
1313 fn ethereum_deposit_contract_address(&self) -> Address {
1315 if let Some(address) = self
1316 .genesis
1317 .genesis_init
1318 .as_ref()
1319 .and_then(|genesis| genesis.config.deposit_contract_address)
1320 {
1321 return address;
1322 }
1323
1324 match NamedChain::try_from(self.evm_env.read().cfg_env.chain_id) {
1325 Ok(NamedChain::Sepolia) => SEPOLIA_DEPOSIT_CONTRACT_ADDRESS,
1326 Ok(NamedChain::Holesky) => HOLESKY_DEPOSIT_CONTRACT_ADDRESS,
1327 _ => MAINNET_DEPOSIT_CONTRACT_ADDRESS,
1329 }
1330 }
1331
1332 pub fn tempo_hardfork(&self) -> TempoHardfork {
1334 TempoHardfork::from(self.hardfork())
1335 }
1336
1337 #[cfg(feature = "monad")]
1339 pub fn monad_hardfork(&self) -> monad_revm::MonadHardfork {
1340 monad_revm::MonadHardfork::from(self.hardfork())
1341 }
1342
1343 pub fn is_tempo_hardfork_active(&self, hardfork: TempoHardfork) -> bool {
1345 self.is_tempo() && self.tempo_hardfork() >= hardfork
1346 }
1347
1348 pub fn precompiles(&self) -> BTreeMap<String, Address> {
1350 let spec_id = self.spec_id();
1351 let mut precompiles =
1352 PrecompilesMap::from_static(Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)));
1353 let chain_id = self.protocol_chain_id();
1354 let timestamp = self.evm_env.read().block_env.timestamp.saturating_to();
1355 self.networks.inject_chain_precompiles(&mut precompiles, chain_id, timestamp);
1356
1357 let mut precompiles_map = BTreeMap::<String, Address>::default();
1358 for address in precompiles.addresses() {
1359 let precompile = precompiles.get(address).expect("precompile address must resolve");
1360 precompiles_map.insert(precompile.precompile_id().name().to_string(), *address);
1361 }
1362
1363 precompiles_map.extend(self.networks.precompiles(Some(self.hardfork())));
1365
1366 if let Some(factory) = &self.precompile_factory {
1367 for (address, precompile) in factory.precompiles() {
1368 precompiles_map.insert(precompile.precompile_id().to_string(), address);
1369 }
1370 }
1371
1372 precompiles_map
1373 }
1374
1375 pub fn system_contracts(&self) -> BTreeMap<SystemContract, Address> {
1377 let mut system_contracts = BTreeMap::<SystemContract, Address>::default();
1378
1379 let spec_id = self.spec_id();
1380
1381 if spec_id >= SpecId::CANCUN {
1382 system_contracts.extend(SystemContract::cancun());
1383 }
1384
1385 if spec_id >= SpecId::PRAGUE {
1386 system_contracts.extend(SystemContract::prague(None));
1387 }
1388
1389 system_contracts
1390 }
1391
1392 pub fn blob_params(&self) -> BlobParams {
1394 self.fees.blob_params()
1395 }
1396
1397 fn simulation_blob_params_at_timestamp(&self, timestamp: u64) -> BlobParams {
1398 let configured_hardfork = self.hardfork();
1399 if let FoundryHardfork::Ethereum(
1400 configured
1401 @ (EthereumHardfork::Osaka | EthereumHardfork::Bpo1 | EthereumHardfork::Bpo2),
1402 ) = configured_hardfork
1403 && let Some(hardfork) = FoundryHardfork::from_chain_and_timestamp(
1404 self.evm_env.read().cfg_env.chain_id,
1405 timestamp,
1406 )
1407 && let FoundryHardfork::Ethereum(
1408 scheduled @ (EthereumHardfork::Osaka
1409 | EthereumHardfork::Bpo1
1410 | EthereumHardfork::Bpo2),
1411 ) = hardfork
1412 {
1413 let hardfork = match (configured, scheduled) {
1414 (EthereumHardfork::Bpo2, _) | (_, EthereumHardfork::Bpo2) => EthereumHardfork::Bpo2,
1415 (EthereumHardfork::Bpo1, _) | (_, EthereumHardfork::Bpo1) => EthereumHardfork::Bpo1,
1416 _ => EthereumHardfork::Osaka,
1417 };
1418 return get_blob_params_by_hardfork(hardfork.into());
1419 }
1420 get_blob_params_by_hardfork(configured_hardfork)
1421 }
1422
1423 #[cfg(feature = "optimism")]
1424 fn is_optimism_jovian_at_header<H: BlockHeader>(
1425 &self,
1426 header: &H,
1427 decoded: Option<bool>,
1428 ) -> bool {
1429 if !self.is_optimism() {
1430 return false;
1431 }
1432 if let Some(jovian) = decoded {
1433 return jovian;
1434 }
1435 if !header.extra_data().is_empty() {
1436 return false;
1437 }
1438 let hardfork = if self.get_fork().is_some() {
1439 FoundryHardfork::from_chain_and_timestamp(self.protocol_chain_id(), header.timestamp())
1440 .unwrap_or_else(|| self.hardfork())
1441 } else {
1442 self.hardfork()
1443 };
1444 OpHardfork::from(hardfork) >= OpHardfork::Jovian
1445 }
1446
1447 #[cfg(not(feature = "optimism"))]
1448 fn is_optimism_jovian_at_header<H: BlockHeader>(
1449 &self,
1450 _header: &H,
1451 _decoded: Option<bool>,
1452 ) -> bool {
1453 false
1454 }
1455
1456 pub fn ensure_eip1559_active(&self) -> Result<(), BlockchainError> {
1458 if self.is_eip1559() {
1459 return Ok(());
1460 }
1461 Err(BlockchainError::EIP1559TransactionUnsupportedAtHardfork)
1462 }
1463
1464 pub fn ensure_eip2930_active(&self) -> Result<(), BlockchainError> {
1466 if self.is_eip2930() {
1467 return Ok(());
1468 }
1469 Err(BlockchainError::EIP2930TransactionUnsupportedAtHardfork)
1470 }
1471
1472 pub fn ensure_eip4844_active(&self) -> Result<(), BlockchainError> {
1473 if self.is_eip4844() {
1474 return Ok(());
1475 }
1476 Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork)
1477 }
1478
1479 pub fn ensure_eip7702_active(&self) -> Result<(), BlockchainError> {
1480 if self.is_eip7702() {
1481 return Ok(());
1482 }
1483 Err(BlockchainError::EIP7702TransactionUnsupportedAtHardfork)
1484 }
1485
1486 #[cfg(feature = "optimism")]
1488 pub const fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> {
1489 if self.is_optimism() {
1490 return Ok(());
1491 }
1492 Err(BlockchainError::DepositTransactionUnsupported)
1493 }
1494
1495 pub const fn ensure_tempo_active(&self) -> Result<(), BlockchainError> {
1497 if self.is_tempo() {
1498 return Ok(());
1499 }
1500 Err(BlockchainError::TempoTransactionUnsupported)
1501 }
1502
1503 fn inspector_tx_config(&self) -> InspectorTxConfig {
1505 InspectorTxConfig {
1506 print_traces: self.print_traces,
1507 print_logs: self.print_logs,
1508 enable_steps_tracing: self.enable_steps_tracing,
1509 call_trace_decoder: self.call_trace_decoder(),
1510 }
1511 }
1512
1513 fn call_trace_decoder(&self) -> Arc<CallTraceDecoder> {
1515 let hardfork = Some(self.networks.executed_hardfork(self.hardfork()));
1516 let decoder = self.call_trace_decoder.read();
1517 if decoder.hardfork() == hardfork {
1518 return Arc::clone(&decoder);
1519 }
1520 drop(decoder);
1521
1522 let mut decoder = self.call_trace_decoder.write();
1523 let mut updated = decoder.as_ref().clone();
1524 updated.set_hardfork(hardfork);
1525 *decoder = Arc::new(updated);
1526 Arc::clone(&decoder)
1527 }
1528
1529 fn pool_tx_gas_config(&self, evm_env: &EvmEnv) -> PoolTxGasConfig {
1531 let spec_id = *evm_env.spec_id();
1532 let is_cancun = spec_id >= SpecId::CANCUN;
1533 let blob_params = self.blob_params();
1534 PoolTxGasConfig {
1535 disable_block_gas_limit: evm_env.cfg_env.disable_block_gas_limit,
1536 tx_gas_limit_cap: evm_env.cfg_env.tx_gas_limit_cap,
1537 tx_gas_limit_cap_resolved: self.tx_gas_limit_cap(evm_env),
1538 max_blob_gas_per_block: blob_params.max_blob_gas_per_block(),
1539 is_cancun,
1540 }
1541 }
1542
1543 #[cfg(feature = "monad")]
1544 fn monad_cfg_env(&self, evm_env: &EvmEnv) -> Option<monad_revm::MonadCfgEnv> {
1545 if !self.is_monad() {
1546 return None;
1547 }
1548
1549 let hardfork = monad_revm::MonadHardfork::from(self.hardfork());
1550 Some(monad_revm::MonadCfgEnv::from(evm_env.cfg_env.clone().with_spec_and_gas_params(
1551 hardfork,
1552 monad_revm::instructions::monad_gas_params(hardfork),
1553 )))
1554 }
1555
1556 fn tx_gas_limit_cap(&self, evm_env: &EvmEnv) -> u64 {
1557 #[cfg(feature = "monad")]
1558 if let Some(cfg) = self.monad_cfg_env(evm_env) {
1559 return cfg.tx_gas_limit_cap();
1560 }
1561 evm_env.cfg_env.tx_gas_limit_cap()
1562 }
1563
1564 pub(crate) fn fallback_tx_gas_limit(&self, evm_env: &EvmEnv) -> u64 {
1565 let block_gas_limit = evm_env.block_env.gas_limit;
1566 if evm_env.cfg_env.tx_gas_limit_cap.is_none() {
1567 block_gas_limit.min(self.tx_gas_limit_cap(evm_env))
1568 } else {
1569 block_gas_limit
1570 }
1571 }
1572
1573 fn max_initcode_size(&self, evm_env: &EvmEnv) -> usize {
1574 #[cfg(feature = "monad")]
1575 if let Some(cfg) = self.monad_cfg_env(evm_env) {
1576 return cfg.max_initcode_size();
1577 }
1578 evm_env.cfg_env.max_initcode_size()
1579 }
1580
1581 pub fn gas_limit(&self) -> u64 {
1583 self.evm_env.read().block_env.gas_limit
1584 }
1585
1586 pub fn set_gas_limit(&self, gas_limit: u64) {
1588 self.evm_env.write().block_env.gas_limit = gas_limit;
1589 }
1590
1591 pub fn base_fee(&self) -> u64 {
1593 self.fees.base_fee()
1594 }
1595
1596 pub const fn is_min_priority_fee_enforced(&self) -> bool {
1598 self.fees.is_min_priority_fee_enforced()
1599 }
1600
1601 pub fn excess_blob_gas_and_price(&self) -> Option<BlobExcessGasAndPrice> {
1602 self.fees.excess_blob_gas_and_price()
1603 }
1604
1605 pub fn set_base_fee(&self, basefee: u64) {
1607 self.fees.set_base_fee(basefee)
1608 }
1609
1610 pub fn set_gas_price(&self, price: u128) {
1612 self.fees.set_gas_price(price)
1613 }
1614
1615 pub fn elasticity(&self) -> f64 {
1616 self.fees.elasticity()
1617 }
1618
1619 pub fn total_difficulty(&self) -> U256 {
1624 self.blockchain.storage.read().total_difficulty
1625 }
1626
1627 pub async fn create_state_snapshot(&self) -> U256 {
1631 let num = self.best_number();
1632 let hash = self.best_hash();
1633 let id = self.db.write().await.snapshot_state();
1634 trace!(target: "backend", "creating snapshot {} at {}", id, num);
1635 self.active_state_snapshots.lock().insert(
1636 id,
1637 StateSnapshot {
1638 block_number: num,
1639 block_hash: hash,
1640 fees: self.fees.snapshot(),
1641 time_offset: self.time.offset(),
1642 },
1643 );
1644 id
1645 }
1646
1647 pub fn list_state_snapshots(&self) -> BTreeMap<U256, (u64, B256)> {
1648 self.active_state_snapshots
1649 .lock()
1650 .iter()
1651 .map(|(&id, snapshot)| (id, (snapshot.block_number, snapshot.block_hash)))
1652 .collect()
1653 }
1654
1655 fn next_evm_env(&self) -> EvmEnv {
1657 let mut evm_env = self.evm_env.read().clone();
1658 evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1));
1660 evm_env.block_env.basefee = self.base_fee();
1661 evm_env.block_env.blob_excess_gas_and_price = self.excess_blob_gas_and_price();
1662 evm_env.block_env.timestamp = U256::from(self.time.current_call_timestamp());
1663 evm_env
1664 }
1665
1666 fn tx_replay_evm_env(&self, block: &Block) -> (EvmEnv, FoundryHardfork) {
1668 let mut evm_env = self.evm_env.read().clone();
1669 evm_env.block_env = block_env_from_header(&block.header);
1670 let hardfork = self.hardfork();
1671 #[cfg(feature = "monad")]
1672 let hardfork = if self.is_monad() {
1673 let block_hash = block.header.hash_slow();
1674 let fallback = crate::eth::backend::db::MonadBlockReplayProfile {
1675 execution_chain_id: evm_env.cfg_env.chain_id,
1676 hardfork: self.monad_hardfork(),
1677 };
1678 let profile = self
1679 .blockchain
1680 .storage
1681 .read()
1682 .monad_block_replay_profiles
1683 .get(&block_hash)
1684 .copied()
1685 .unwrap_or(fallback);
1686 evm_env.cfg_env.chain_id = profile.execution_chain_id;
1687 evm_env.cfg_env.spec = profile.hardfork.into();
1688 profile.hardfork.into()
1689 } else {
1690 hardfork
1691 };
1692 apply_chain_specific_tx_replay_env_changes_for_chain(
1693 &mut evm_env,
1694 self.protocol_chain_id(),
1695 );
1696 (evm_env, hardfork)
1697 }
1698
1699 fn prepare_block_replay<'a>(
1705 &self,
1706 block: &Block,
1707 parent_state: &'a StateDb,
1708 ) -> Result<(CacheDB<&'a StateDb>, EvmEnv, FoundryHardfork), BlockchainError> {
1709 self.prepare_block_replay_with_db(block, parent_state)
1710 }
1711
1712 fn prepare_block_replay_with_db<DB>(
1714 &self,
1715 block: &Block,
1716 db: DB,
1717 ) -> Result<(CacheDB<DB>, EvmEnv, FoundryHardfork), BlockchainError>
1718 where
1719 DB: DatabaseRef<Error = DatabaseError> + Debug,
1720 {
1721 let mut cache_db = AnvilCacheDB::new(db);
1722 let (evm_env, hardfork) = self.tx_replay_evm_env(block);
1723 let spec_id = *evm_env.spec_id();
1724 let inspector_tx_config = self.inspector_tx_config();
1725 let gas_config = self.pool_tx_gas_config(&evm_env);
1726
1727 self.execute_with_block_executor(
1728 &mut cache_db,
1729 &evm_env,
1730 block.header.parent_hash,
1731 spec_id,
1732 hardfork,
1733 block.header.parent_beacon_block_root,
1734 BlockExecutionKind::TransactionPrefix,
1735 &[],
1736 &gas_config,
1737 &inspector_tx_config,
1738 &|_, _| Ok(()),
1739 )?;
1740
1741 Ok((cache_db.0, evm_env, hardfork))
1742 }
1743
1744 fn replay_mined_transaction_prefix<DB>(
1746 &self,
1747 cache_db: &mut CacheDB<DB>,
1748 evm_env: &EvmEnv,
1749 hardfork: FoundryHardfork,
1750 block: &Block,
1751 end: usize,
1752 ) -> Result<(), BlockchainError>
1753 where
1754 DB: DatabaseRef<Error = DatabaseError> + Debug,
1755 {
1756 let monad_context = self.active_monad_context_for_mined_block(block)?;
1757 for (index, transaction) in block.body.transactions[..end].iter().enumerate() {
1758 let pending = self.pending_mined_transaction(transaction.clone())?;
1759 let mut inspector = AnvilInspector::default();
1760 let transaction_context = monad_execution_context_at(monad_context.as_ref(), index);
1761 let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
1762 cache_db,
1763 evm_env,
1764 &mut inspector,
1765 &pending,
1766 EnvelopeExecution::replay(transaction_context, hardfork),
1767 )?;
1768 cache_db.commit(result.state);
1769 }
1770 Ok(())
1771 }
1772
1773 fn build_inspector(&self) -> AnvilInspector {
1775 let mut inspector = AnvilInspector::default();
1776
1777 if self.print_logs {
1778 inspector = inspector.with_log_collector();
1779 }
1780 if self.print_traces {
1781 inspector = inspector.with_trace_printer();
1782 }
1783
1784 inspector
1785 }
1786
1787 fn build_mining_inspector(&self) -> AnvilInspector {
1789 let mut inspector = AnvilInspector::default().with_tracing();
1790 if self.enable_steps_tracing {
1791 inspector = inspector.with_steps_tracing();
1792 }
1793 if self.print_logs {
1794 inspector = inspector.with_log_collector();
1795 }
1796 if self.print_traces {
1797 inspector = inspector.with_trace_printer();
1798 }
1799 inspector
1800 }
1801
1802 pub fn new_block_notifications(&self) -> ChainNotifications {
1805 let (tx, rx) = unbounded();
1806 self.new_block_listeners.lock().push(tx);
1807 trace!(target: "backed", "added new block listener");
1808 rx
1809 }
1810
1811 pub fn new_block_listeners_count(&self) -> usize {
1814 self.new_block_listeners.lock().len()
1815 }
1816
1817 fn notify_on_new_block(&self, header: Header, hash: B256) {
1819 self.new_block_listeners.lock().retain(|tx| !tx.is_closed());
1822
1823 let notification =
1824 ChainNotification::Block(NewBlockNotification { hash, header: Arc::new(header) });
1825
1826 self.new_block_listeners
1827 .lock()
1828 .retain(|tx| tx.unbounded_send(notification.clone()).is_ok());
1829 }
1830
1831 fn notify_on_removed_logs(&self, logs: Vec<Log>) {
1834 self.new_block_listeners.lock().retain(|tx| !tx.is_closed());
1837
1838 let notification = ChainNotification::RemovedLogs(Arc::new(logs));
1839
1840 self.new_block_listeners
1841 .lock()
1842 .retain(|tx| tx.unbounded_send(notification.clone()).is_ok());
1843 }
1844
1845 pub fn convert_block_number(&self, block: Option<BlockNumber>) -> u64 {
1847 let current = self.best_number();
1848 match block.unwrap_or(BlockNumber::Latest) {
1849 BlockNumber::Latest | BlockNumber::Pending => current,
1850 BlockNumber::Earliest => 0,
1851 BlockNumber::Number(num) => num,
1852 BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch),
1853 BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2),
1854 }
1855 }
1856
1857 pub(crate) fn block_hash_by_number(&self, number: u64) -> Option<B256> {
1859 self.blockchain.hash(BlockNumber::Number(number).into(), self.slots_in_an_epoch)
1860 }
1861
1862 pub(crate) fn get_block_with_hash(&self, id: impl Into<BlockId>) -> Option<(Block, B256)> {
1864 let hash = self.blockchain.hash(id.into(), self.slots_in_an_epoch)?;
1865 let block = self.get_block_by_hash(hash)?;
1866 Some((block, hash))
1867 }
1868
1869 pub fn get_block(&self, id: impl Into<BlockId>) -> Option<Block> {
1870 self.get_block_with_hash(id).map(|(block, _)| block)
1871 }
1872
1873 pub fn get_block_by_hash(&self, hash: B256) -> Option<Block> {
1874 self.blockchain.get_block_by_hash(&hash)
1875 }
1876
1877 pub(crate) async fn fee_history_next_fees(&self, highest: u64) -> Option<(u128, u128)> {
1883 let _mining_guard = self.mining.lock().await;
1884 let next_number = highest.checked_add(1)?;
1885 if let Some(block) = self.get_block(next_number) {
1886 Some((
1887 block.header.base_fee_per_gas.unwrap_or_default() as u128,
1888 block.header.blob_fee(self.blob_params()).unwrap_or_default(),
1889 ))
1890 } else if highest == self.best_number() {
1891 Some((self.fees().base_fee() as u128, self.fees().base_fee_per_blob_gas()))
1892 } else {
1893 None
1894 }
1895 }
1896
1897 pub(crate) fn mined_parity_trace_transaction(
1899 &self,
1900 hash: B256,
1901 ) -> Option<Vec<LocalizedTransactionTrace>> {
1902 self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.parity_traces())
1903 }
1904
1905 pub(crate) fn mined_parity_trace_block(
1907 &self,
1908 block: u64,
1909 ) -> Option<Vec<LocalizedTransactionTrace>> {
1910 let block = self.get_block(block)?;
1911 let mut traces = vec![];
1912 let storage = self.blockchain.storage.read();
1913 for tx in block.body.transactions {
1914 if let Some(mined_tx) = storage.transactions.get(&tx.hash()) {
1915 traces.extend(mined_tx.parity_traces());
1916 }
1917 }
1918 Some(traces)
1919 }
1920
1921 pub(crate) fn mined_transaction(&self, hash: B256) -> Option<MinedTransaction<N>> {
1923 self.blockchain.storage.read().transactions.get(&hash).cloned()
1924 }
1925
1926 pub async fn impersonate_signature(
1928 &self,
1929 signature: Bytes,
1930 address: Address,
1931 ) -> Result<(), BlockchainError> {
1932 self.cheats.add_recover_override(signature, address);
1933 Ok(())
1934 }
1935
1936 pub async fn debug_code_by_hash(
1938 &self,
1939 code_hash: B256,
1940 block_id: Option<BlockId>,
1941 ) -> Result<Option<Bytes>, BlockchainError> {
1942 if let Ok(code) = self.db.read().await.code_by_hash_ref(code_hash) {
1943 return Ok(Some(code.original_bytes()));
1944 }
1945 if let Some(fork) = self.get_fork() {
1946 return Ok(fork.debug_code_by_hash(code_hash, block_id).await?);
1947 }
1948
1949 Ok(None)
1950 }
1951
1952 pub async fn debug_db_get(&self, key: String) -> Result<Option<Bytes>, BlockchainError> {
1960 let key_bytes = if key.starts_with("0x") {
1961 hex::decode(&key)
1962 .map_err(|_| BlockchainError::Message("Invalid hex key".to_string()))?
1963 } else {
1964 key.into_bytes()
1965 };
1966
1967 if key_bytes.len() != 33 {
1969 return Err(BlockchainError::Message(format!(
1970 "Invalid key length: expected 33 bytes, got {}",
1971 key_bytes.len()
1972 )));
1973 }
1974
1975 if key_bytes[0] != 0x63 {
1977 return Err(BlockchainError::Message(
1978 "Key prefix must be 0x63 for code hash lookups".to_string(),
1979 ));
1980 }
1981
1982 let code_hash = B256::from_slice(&key_bytes[1..33]);
1983
1984 self.debug_code_by_hash(code_hash, None).await
1986 }
1987
1988 fn mined_block_by_hash(&self, hash: B256) -> Option<AnyRpcBlock> {
1989 let block = self.blockchain.get_block_by_hash(&hash)?;
1990 Some(self.convert_block_with_hash(block, Some(hash)))
1991 }
1992
1993 pub(crate) async fn mined_transactions_by_block_number(
1994 &self,
1995 number: BlockNumber,
1996 ) -> Option<Vec<AnyRpcTransaction>> {
1997 if let Some(block) = self.get_block(number) {
1998 return self.mined_transactions_in_block(&block);
1999 }
2000 None
2001 }
2002
2003 pub(crate) fn mined_transactions_in_block(
2005 &self,
2006 block: &Block,
2007 ) -> Option<Vec<AnyRpcTransaction>> {
2008 let mut transactions = Vec::with_capacity(block.body.transactions.len());
2009 let base_fee = block.header.base_fee_per_gas();
2010 let storage = self.blockchain.storage.read();
2011 for hash in block.body.transactions.iter().map(|tx| tx.hash()) {
2012 let info = storage.transactions.get(&hash)?.info.clone();
2013 let tx = block.body.transactions.get(info.transaction_index as usize)?.clone();
2014
2015 let tx = transaction_build(Some(hash), tx, Some(block), Some(info), base_fee);
2016 transactions.push(tx);
2017 }
2018 Some(transactions)
2019 }
2020
2021 pub fn mined_block_by_number(&self, number: BlockNumber) -> Option<AnyRpcBlock> {
2022 let (block, hash) = self.get_block_with_hash(number)?;
2023 let mut block = self.convert_block_with_hash(block, Some(hash));
2024 block.transactions.convert_to_hashes();
2025 Some(block)
2026 }
2027
2028 pub fn get_full_block(&self, id: impl Into<BlockId>) -> Option<AnyRpcBlock> {
2029 let (block, hash) = self.get_block_with_hash(id)?;
2030 let transactions = self.mined_transactions_in_block(&block)?;
2031 let mut block = self.convert_block_with_hash(block, Some(hash));
2032 block.inner.transactions = BlockTransactions::Full(transactions);
2033 Some(block)
2034 }
2035
2036 pub fn convert_block(&self, block: Block) -> AnyRpcBlock {
2038 self.convert_block_with_hash(block, None)
2039 }
2040
2041 pub fn convert_block_with_hash(&self, block: Block, known_hash: Option<B256>) -> AnyRpcBlock {
2044 let transactions = block.body.transactions.iter().map(|tx| tx.hash()).collect();
2045 let block = canonical_block(block);
2046 let size = U256::from(block.length() as u32);
2047 let header = block.header;
2048
2049 let hash = known_hash.unwrap_or_else(|| header.hash_slow());
2050 let number = header.number();
2051 let withdrawals_root = header.withdrawals_root();
2052 let tempo_fields = header
2053 .as_tempo()
2054 .map(|header| {
2055 (
2056 header.timestamp_millis(),
2057 header.general_gas_limit,
2058 header.shared_gas_limit,
2059 header.timestamp_millis_part,
2060 )
2061 })
2062 .or_else(|| {
2063 self.is_tempo()
2064 .then(|| (header.timestamp().saturating_mul(1000), header.gas_limit(), 0, 0))
2065 });
2066
2067 let block = AlloyBlock {
2068 header: AlloyHeader {
2069 inner: AnyHeader::from(header.into_inner()),
2070 hash,
2071 total_difficulty: Some(self.total_difficulty()),
2072 size: Some(size),
2073 },
2074 transactions: alloy_rpc_types::BlockTransactions::Hashes(transactions),
2075 uncles: vec![],
2076 withdrawals: withdrawals_root.map(|_| Default::default()),
2077 };
2078
2079 let mut block = WithOtherFields::new(block);
2080
2081 if is_arbitrum(self.protocol_chain_id()) {
2083 block.other.insert("l1BlockNumber".to_string(), number.into());
2085 }
2086
2087 if let Some((
2088 timestamp_millis,
2089 general_gas_limit,
2090 shared_gas_limit,
2091 timestamp_millis_part,
2092 )) = tempo_fields
2093 {
2094 block.other.insert(
2095 "timestampMillis".to_string(),
2096 serde_json::Value::String(format!("0x{timestamp_millis:x}")),
2097 );
2098 block.other.insert(
2099 "mainBlockGeneralGasLimit".to_string(),
2100 serde_json::Value::String(format!("0x{general_gas_limit:x}")),
2101 );
2102 block.other.insert(
2103 "sharedGasLimit".to_string(),
2104 serde_json::Value::String(format!("0x{shared_gas_limit:x}")),
2105 );
2106 block.other.insert(
2107 "timestampMillisPart".to_string(),
2108 serde_json::Value::String(format!("0x{timestamp_millis_part:x}")),
2109 );
2110 }
2111
2112 AnyRpcBlock::from(block)
2113 }
2114
2115 pub async fn block_by_hash(&self, hash: B256) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2116 trace!(target: "backend", "get block by hash {:?}", hash);
2117 if let tx @ Some(_) = self.mined_block_by_hash(hash) {
2118 return Ok(tx);
2119 }
2120
2121 if let Some(fork) = self.get_fork() {
2122 return Ok(fork.block_by_hash(hash).await?);
2123 }
2124
2125 Ok(None)
2126 }
2127
2128 pub async fn block_by_hash_full(
2129 &self,
2130 hash: B256,
2131 ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2132 trace!(target: "backend", "get block by hash {:?}", hash);
2133 if let tx @ Some(_) = self.get_full_block(hash) {
2134 return Ok(tx);
2135 }
2136
2137 if let Some(fork) = self.get_fork() {
2138 return Ok(fork.block_by_hash_full(hash).await?);
2139 }
2140
2141 Ok(None)
2142 }
2143
2144 pub async fn block_by_number(
2145 &self,
2146 number: BlockNumber,
2147 ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2148 trace!(target: "backend", "get block by number {:?}", number);
2149 if let tx @ Some(_) = self.mined_block_by_number(number) {
2150 return Ok(tx);
2151 }
2152
2153 if let Some(fork) = self.get_fork() {
2154 let number = self.convert_block_number(Some(number));
2155 if fork.predates_fork_inclusive(number) {
2156 return Ok(fork.block_by_number(number).await?);
2157 }
2158 }
2159
2160 Ok(None)
2161 }
2162
2163 pub async fn block_by_number_full(
2164 &self,
2165 number: BlockNumber,
2166 ) -> Result<Option<AnyRpcBlock>, BlockchainError> {
2167 trace!(target: "backend", "get block by number {:?}", number);
2168 if let tx @ Some(_) = self.get_full_block(number) {
2169 return Ok(tx);
2170 }
2171
2172 if let Some(fork) = self.get_fork() {
2173 let number = self.convert_block_number(Some(number));
2174 if fork.predates_fork_inclusive(number) {
2175 return Ok(fork.block_by_number_full(number).await?);
2176 }
2177 }
2178
2179 Ok(None)
2180 }
2181
2182 pub async fn ensure_block_number<T: Into<BlockId>>(
2188 &self,
2189 block_id: Option<T>,
2190 ) -> Result<u64, BlockchainError> {
2191 let current = self.best_number();
2192 let requested =
2193 match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) {
2194 BlockId::Hash(hash) => {
2195 self.block_by_hash(hash.block_hash)
2196 .await?
2197 .ok_or(BlockchainError::BlockNotFound)?
2198 .header
2199 .number
2200 }
2201 BlockId::Number(num) => match num {
2202 BlockNumber::Latest | BlockNumber::Pending => current,
2203 BlockNumber::Earliest => U64::ZERO.to::<u64>(),
2204 BlockNumber::Number(num) => num,
2205 BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch),
2206 BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2),
2207 },
2208 };
2209
2210 if requested > current {
2211 Err(BlockchainError::BlockOutOfRange(current, requested))
2212 } else {
2213 Ok(requested)
2214 }
2215 }
2216
2217 fn inject_precompiles(&self, precompiles: &mut PrecompilesMap, evm_env: &EvmEnv) {
2226 self.inject_configured_precompiles(precompiles, evm_env);
2227
2228 if let Some(block_number) = self.arbitrum_block_number(evm_env) {
2229 self.inject_arbitrum_precompile_at_block(precompiles, block_number);
2230 }
2231 }
2232
2233 fn inject_configured_precompiles(&self, precompiles: &mut PrecompilesMap, evm_env: &EvmEnv) {
2234 self.networks.inject_precompiles(precompiles);
2235 self.networks.inject_chain_precompiles(
2236 precompiles,
2237 self.protocol_chain_id(),
2238 evm_env.block_env.timestamp.saturating_to(),
2239 );
2240
2241 if let Some(factory) = &self.precompile_factory {
2242 factory.install(precompiles);
2243 }
2244
2245 let cheats = Arc::new(self.cheats.clone());
2246 if cheats.has_recover_overrides() {
2247 let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats));
2248 precompiles.apply_precompile(&EC_RECOVER, move |_| {
2249 Some(DynPrecompile::new_stateful(
2250 cheat_ecrecover.precompile_id().clone(),
2251 move |input| cheat_ecrecover.call(input),
2252 ))
2253 });
2254 }
2255 }
2256
2257 fn inject_arbitrum_precompile_at_block(
2258 &self,
2259 precompiles: &mut PrecompilesMap,
2260 block_number: u64,
2261 ) {
2262 precompiles.apply_precompile(&arbitrum::ARB_SYS_ADDRESS, move |_| {
2263 Some(arbitrum::arb_sys_precompile(block_number))
2264 });
2265 }
2266
2267 fn simulation_precompile_overrides(
2268 &self,
2269 state_overrides: Option<&StateOverride>,
2270 evm_env: &EvmEnv,
2271 ) -> Result<SimulationPrecompileOverrides, BlockchainError> {
2272 let mut moves = state_overrides
2273 .into_iter()
2274 .flatten()
2275 .filter_map(|(source, account)| {
2276 account.move_precompile_to.map(|destination| (*source, destination))
2277 })
2278 .collect::<Vec<_>>();
2279 moves.sort_unstable();
2280 if moves.is_empty() {
2281 return Ok(SimulationPrecompileOverrides::default());
2282 }
2283 if self.is_optimism() || self.is_tempo() || self.is_monad() {
2284 return Err(simulate_rpc_error(
2285 -32000,
2286 "precompile moves are not supported on this network",
2287 ));
2288 }
2289
2290 let mut precompiles = PrecompilesMap::from_static(Precompiles::new(
2291 PrecompileSpecId::from_spec_id(*evm_env.spec_id()),
2292 ));
2293 self.inject_precompiles(&mut precompiles, evm_env);
2294 let precompile_addresses = precompiles.addresses().copied().collect::<HashSet<_>>();
2295
2296 for (source, _) in &moves {
2299 if !precompile_addresses.contains(source) {
2300 return Err(simulate_rpc_error(
2301 -32000,
2302 format!("account {source} is not a precompile"),
2303 ));
2304 }
2305 }
2306 for (source, destination) in &moves {
2307 if source == destination {
2308 return Err(simulate_rpc_error(
2309 -38022,
2310 format!("cannot move precompile {source} to itself"),
2311 ));
2312 }
2313 }
2314 let mut destinations = Vec::with_capacity(moves.len());
2315 for (_, destination) in &moves {
2316 if destinations.contains(destination) {
2317 return Err(simulate_rpc_error(
2318 -38023,
2319 format!("multiple precompiles moved to {destination}"),
2320 ));
2321 }
2322 destinations.push(*destination);
2323 }
2324
2325 Ok(SimulationPrecompileOverrides { moves })
2326 }
2327
2328 fn apply_simulation_precompile_overrides(
2329 &self,
2330 precompiles: &mut PrecompilesMap,
2331 overrides: &SimulationPrecompileOverrides,
2332 ) -> Result<alloy_primitives::map::AddressSet, BlockchainError> {
2333 let warm_addresses = precompiles.addresses().copied().collect();
2334 precompiles.move_precompiles(overrides.moves.iter().copied()).map_err(
2335 |MovePrecompileError::NotAPrecompile(address)| {
2336 simulate_rpc_error(-32000, format!("account {address} is not a precompile"))
2337 },
2338 )?;
2339
2340 let moved_sources =
2342 Arc::new(overrides.moves.iter().map(|(source, _)| *source).collect::<HashSet<_>>());
2343 precompiles.map_precompile_lookup(move |address, previous| {
2344 if moved_sources.contains(address) {
2345 None
2346 } else {
2347 previous.and_then(|lookup| lookup.lookup(address))
2348 }
2349 });
2350 Ok(warm_addresses)
2351 }
2352
2353 fn inject_tempo_precompiles<DB, I>(
2354 &self,
2355 evm: &mut tempo_evm::evm::TempoEvm<DB, I>,
2356 evm_env: &EvmEnv,
2357 ) where
2358 DB: Database,
2359 I: Inspector<TempoContext<DB>>,
2360 {
2361 self.inject_configured_precompiles(evm.precompiles_mut(), evm_env);
2362 let cfg = evm.ctx().cfg.clone();
2364 let non_creditable_slots = evm.non_creditable_slots();
2365 extend_tempo_precompiles(
2366 evm.precompiles_mut(),
2367 &cfg,
2368 StorageActions::disabled(),
2369 non_creditable_slots,
2370 );
2371 }
2372
2373 fn transact_eth_with_inspector_ref<'db, I, DB>(
2378 &self,
2379 db: &'db DB,
2380 evm_env: &EvmEnv,
2381 inspector: &mut I,
2382 tx_env: TxEnv,
2383 ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2384 where
2385 DB: DatabaseRef + ?Sized,
2386 I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
2387 WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2388 {
2389 self.transact_eth_with_inspector_ref_and_precompile_overrides(
2390 db,
2391 evm_env,
2392 inspector,
2393 tx_env,
2394 &SimulationPrecompileOverrides::default(),
2395 )
2396 }
2397
2398 fn transact_eth_with_inspector_ref_and_precompile_overrides<'db, I, DB>(
2399 &self,
2400 db: &'db DB,
2401 evm_env: &EvmEnv,
2402 inspector: &mut I,
2403 tx_env: TxEnv,
2404 overrides: &SimulationPrecompileOverrides,
2405 ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2406 where
2407 DB: DatabaseRef + ?Sized,
2408 I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
2409 WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2410 {
2411 let mut evm = EthEvmFactory::default().create_evm_with_inspector(
2412 WrapDatabaseRef(db),
2413 evm_env.clone(),
2414 inspector,
2415 );
2416 self.inject_precompiles(evm.precompiles_mut(), evm_env);
2417 if !overrides.moves.is_empty() {
2418 let warm_addresses =
2419 self.apply_simulation_precompile_overrides(evm.precompiles_mut(), overrides)?;
2420 evm.ctx_mut().journal_mut().warm_precompiles(&warm_addresses);
2422 }
2423 Ok(evm.transact(tx_env)?)
2424 }
2425
2426 fn transact_eth_simulation_with_inspector_ref<'db, I, DB>(
2427 &self,
2428 db: &'db DB,
2429 evm_env: &EvmEnv,
2430 inspector: &mut I,
2431 tx_env: TxEnv,
2432 overrides: &SimulationPrecompileOverrides,
2433 ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2434 where
2435 DB: DatabaseRef + ?Sized,
2436 I: Inspector<EthEvmContext<WrapDatabaseRef<&'db DB>>>,
2437 WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2438 {
2439 let mut evm = EthEvmFactory::default().create_evm_with_inspector(
2440 WrapDatabaseRef(db),
2441 evm_env.clone(),
2442 inspector,
2443 );
2444 self.inject_precompiles(evm.precompiles_mut(), evm_env);
2445 if !overrides.moves.is_empty() {
2446 let warm_addresses =
2447 self.apply_simulation_precompile_overrides(evm.precompiles_mut(), overrides)?;
2448 evm.ctx_mut().journal_mut().warm_precompiles(&warm_addresses);
2450 }
2451
2452 let mut evm = evm.into_inner();
2453 ContextSetters::set_tx(evm.ctx_mut(), tx_env);
2454 let mut handler = SimulationHandler::<
2455 _,
2456 revm::context::result::EVMError<DatabaseError>,
2457 EthFrame<EthInterpreter>,
2458 >::default();
2459 let result = handler.inspect_run(&mut evm)?;
2460 let state = evm.ctx_mut().journal_mut().finalize();
2461 Ok(ResultAndState { result, state })
2462 }
2463
2464 fn transact_envelope_with_inspector_ref_and_context<'db, I, DB>(
2468 &self,
2469 db: &'db DB,
2470 evm_env: &EvmEnv,
2471 inspector: &mut I,
2472 pending: &PendingTransaction<FoundryTxEnvelope>,
2473 #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
2474 MonadExecutionContext<'_>,
2475 >,
2476 ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
2477 where
2478 DB: DatabaseRef + ?Sized,
2479 I: BackendInspector<WrapDatabaseRef<&'db DB>>,
2480 WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2481 {
2482 self.transact_envelope_with_inspector_ref_and_context_kind(
2483 db,
2484 evm_env,
2485 inspector,
2486 pending,
2487 EnvelopeExecution::transaction(monad_context, self.hardfork()),
2488 )
2489 }
2490
2491 fn replay_envelope_with_inspector_ref_and_context<'db, I, DB>(
2493 &self,
2494 db: &'db DB,
2495 evm_env: &EvmEnv,
2496 inspector: &mut I,
2497 pending: &PendingTransaction<FoundryTxEnvelope>,
2498 execution: EnvelopeExecution<'_>,
2499 ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
2500 where
2501 DB: DatabaseRef + ?Sized,
2502 I: BackendInspector<WrapDatabaseRef<&'db DB>>,
2503 WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2504 {
2505 self.transact_envelope_with_inspector_ref_and_context_kind(
2506 db, evm_env, inspector, pending, execution,
2507 )
2508 }
2509
2510 fn transact_envelope_with_inspector_ref_and_context_kind<'db, I, DB>(
2511 &self,
2512 db: &'db DB,
2513 evm_env: &EvmEnv,
2514 inspector: &mut I,
2515 pending: &PendingTransaction<FoundryTxEnvelope>,
2516 #[cfg_attr(not(feature = "monad"), allow(unused_variables))] execution: EnvelopeExecution<
2517 '_,
2518 >,
2519 ) -> Result<(ResultAndState<HaltReason>, TxEnv), BlockchainError>
2520 where
2521 DB: DatabaseRef + ?Sized,
2522 I: BackendInspector<WrapDatabaseRef<&'db DB>>,
2523 WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2524 {
2525 let tx = pending.transaction.as_ref();
2526 let sender = *pending.sender();
2527 if tx.is_tempo() {
2528 let tx_env: TempoTxEnv =
2529 FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
2530 let base = tx_env.inner.clone();
2531 let result = self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)?;
2532 return Ok((result, base));
2533 }
2534 #[cfg(feature = "optimism")]
2535 if self.is_optimism() {
2536 let op_tx: OpTransaction<TxEnv> =
2537 FromTxWithEncoded::from_encoded_tx(tx, sender, tx.encoded_2718().into());
2538 let base = op_tx.base.clone();
2539 let result = self.transact_op_with_inspector_ref(db, evm_env, inspector, op_tx)?;
2540 return Ok((result, base));
2541 }
2542 let tx_env: TxEnv = build_tx_env_for_pending(pending, self.cheats());
2543 let base = tx_env.clone();
2544 #[cfg(feature = "monad")]
2545 let result = if self.is_monad() {
2546 let context = monad::resolve_execution_context(execution.monad_context, &tx_env);
2547 self.transact_monad_with_inspector_ref(
2548 db,
2549 evm_env,
2550 inspector,
2551 tx_env,
2552 monad::PreparedExecution {
2553 context,
2554 kind: execution.kind,
2555 hardfork: monad_revm::MonadHardfork::from(execution.hardfork),
2556 },
2557 )?
2558 } else {
2559 self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?
2560 };
2561 #[cfg(not(feature = "monad"))]
2562 let result = self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)?;
2563 Ok((result, base))
2564 }
2565
2566 fn build_tempo_evm_env(&self, evm_env: &EvmEnv) -> EvmEnvFor<TempoEvmNetwork> {
2569 let hardfork = self.tempo_hardfork();
2570 EvmEnv::new(
2571 evm_env.cfg_env.clone().with_spec_and_gas_params(hardfork, tempo_gas_params(hardfork)),
2572 TempoBlockEnv {
2573 inner: evm_env.block_env.clone(),
2574 timestamp_millis_part: 0,
2575 ..Default::default()
2576 },
2577 )
2578 }
2579
2580 fn transact_tempo_with_inspector_ref<'db, I, DB>(
2582 &self,
2583 db: &'db DB,
2584 evm_env: &EvmEnv,
2585 inspector: &mut I,
2586 tx_env: TempoTxEnv,
2587 ) -> Result<ResultAndState<HaltReason>, BlockchainError>
2588 where
2589 DB: DatabaseRef + ?Sized,
2590 I: Inspector<TempoContext<WrapDatabaseRef<&'db DB>>>,
2591 WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
2592 {
2593 let tempo_env = self.build_tempo_evm_env(evm_env);
2594 let mut evm = TempoEvmFactory::default().create_evm_with_inspector(
2595 WrapDatabaseRef(db),
2596 tempo_env,
2597 inspector,
2598 );
2599 self.inject_tempo_precompiles(&mut evm, evm_env);
2600 let result = evm.transact(tx_env)?;
2601 Ok(ResultAndState {
2602 result: result.result.map_haltreason(|h| match h {
2603 TempoHaltReason::Ethereum(eth) => eth,
2604 _ => HaltReason::PrecompileError,
2605 }),
2606 state: result.state,
2607 })
2608 }
2609
2610 #[allow(clippy::too_many_arguments, clippy::type_complexity)]
2613 fn execute_with_block_executor<DB>(
2614 &self,
2615 db: DB,
2616 evm_env: &EvmEnv,
2617 parent_hash: B256,
2618 spec_id: SpecId,
2619 hardfork: FoundryHardfork,
2620 parent_beacon_block_root: Option<B256>,
2621 execution_kind: BlockExecutionKind,
2622 pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
2623 gas_config: &PoolTxGasConfig,
2624 inspector_tx_config: &InspectorTxConfig,
2625 validator: &dyn Fn(
2626 &PoolTransaction<FoundryTxEnvelope>,
2627 &AccountInfo,
2628 ) -> Result<(), InvalidTransactionError>,
2629 ) -> Result<
2630 (ExecutedPoolTransactions<FoundryTxEnvelope>, BlockExecutionResult<FoundryReceiptEnvelope>),
2631 BlockchainError,
2632 >
2633 where
2634 DB: StateDB<Error = DatabaseError>,
2635 {
2636 #[cfg(feature = "monad")]
2637 if self.is_monad() {
2638 return self.execute_with_monad_block_executor(
2639 db,
2640 evm_env,
2641 parent_hash,
2642 spec_id,
2643 hardfork,
2644 pool_transactions,
2645 gas_config,
2646 inspector_tx_config,
2647 validator,
2648 );
2649 }
2650
2651 let inspector = self.build_mining_inspector();
2652 let ethereum_transitions =
2653 self.ethereum_block_transitions(hardfork, parent_beacon_block_root, execution_kind);
2654
2655 macro_rules! run {
2656 (
2657 $evm:expr,
2658 $before_transaction:expr,
2659 $execute_transaction:expr,
2660 $on_execution_error:expr
2661 ) => {{
2662 self.inject_precompiles($evm.precompiles_mut(), evm_env);
2663 let mut executor =
2664 AnvilBlockExecutor::new($evm, parent_hash, spec_id, ethereum_transitions)
2665 .with_max_blob_gas_per_block(gas_config.max_blob_gas_per_block);
2666 #[cfg(feature = "optimism")]
2667 if self.is_optimism() {
2668 executor.set_optimism_hardfork(hardfork);
2669 }
2670 executor
2671 .apply_pre_execution_changes()
2672 .map_err(|err| BlockchainError::Internal(err.to_string()))?;
2673 let mut hooks = PoolTransactionHooks {
2674 before_transaction: $before_transaction,
2675 execute_transaction: $execute_transaction,
2676 on_execution_error: $on_execution_error,
2677 };
2678 let pool_result = execute_pool_transactions(
2679 &mut executor,
2680 pool_transactions,
2681 gas_config,
2682 inspector_tx_config,
2683 self.cheats(),
2684 validator,
2685 &mut hooks,
2686 );
2687 let (evm, block_result) =
2688 executor.finish().map_err(|err| BlockchainError::Internal(err.to_string()))?;
2689 drop(evm);
2690 Ok((pool_result, block_result))
2691 }};
2692 }
2693
2694 #[cfg(feature = "optimism")]
2695 if self.is_optimism() {
2696 let op_env = EvmEnv::new(
2697 evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(hardfork.into()),
2698 evm_env.block_env.clone(),
2699 );
2700 let mut evm =
2701 OpEvmFactory::<OpTx>::default().create_evm_with_inspector(db, op_env, inspector);
2702 return run!(
2703 evm,
2704 noop_before_transaction,
2705 execute_pool_transaction,
2706 noop_on_execution_error
2707 );
2708 }
2709
2710 if self.is_tempo() {
2711 let tempo_env = self.build_tempo_evm_env(evm_env);
2712 let mut evm =
2713 TempoEvmFactory::default().create_evm_with_inspector(db, tempo_env, inspector);
2714 return run!(
2715 evm,
2716 noop_before_transaction,
2717 execute_pool_transaction,
2718 noop_on_execution_error
2719 );
2720 }
2721 let mut evm =
2722 EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
2723 run!(evm, noop_before_transaction, execute_pool_transaction, noop_on_execution_error)
2724 }
2725
2726 fn apply_simulation_pre_execution_changes<DB>(
2728 &self,
2729 db: DB,
2730 evm_env: &EvmEnv,
2731 parent_hash: B256,
2732 transitions: EthereumBlockTransitions,
2733 ) -> Result<(), BlockchainError>
2734 where
2735 DB: StateDB<Error = DatabaseError>,
2736 {
2737 let inspector = self.build_mining_inspector();
2738 let mut evm =
2739 EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
2740 self.inject_precompiles(evm.precompiles_mut(), evm_env);
2741 apply_ethereum_pre_execution_changes(&mut evm, parent_hash, transitions)
2742 .map_err(|err| BlockchainError::Internal(err.to_string()))
2743 }
2744
2745 fn apply_simulation_post_execution_changes<DB>(
2747 &self,
2748 db: DB,
2749 evm_env: &EvmEnv,
2750 transitions: EthereumBlockTransitions,
2751 receipts: &[FoundryReceiptEnvelope],
2752 ) -> Result<alloy_eips::eip7685::Requests, BlockchainError>
2753 where
2754 DB: StateDB<Error = DatabaseError>,
2755 {
2756 let inspector = self.build_mining_inspector();
2757 let mut evm =
2758 EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
2759 self.inject_precompiles(evm.precompiles_mut(), evm_env);
2760 apply_ethereum_post_execution_changes(&mut evm, transitions, receipts)
2761 .map_err(|err| BlockchainError::Internal(err.to_string()))
2762 }
2763
2764 fn build_call_env_with_base(
2773 &self,
2774 request: WithOtherFields<TransactionRequest>,
2775 fee_details: FeeDetails,
2776 block_env: BlockEnv,
2777 base_evm_env: Option<&EvmEnv>,
2778 ) -> (EvmEnv, TxEnv, OpCallDepositInfo) {
2779 let tx_type = request.minimal_tx_type() as u8;
2780
2781 let WithOtherFields::<TransactionRequest> {
2782 inner:
2783 TransactionRequest {
2784 from,
2785 to,
2786 gas,
2787 value,
2788 input,
2789 access_list,
2790 blob_versioned_hashes,
2791 authorization_list,
2792 nonce,
2793 sidecar: _,
2794 chain_id,
2795 .. },
2797 other,
2798 } = request;
2799
2800 let FeeDetails {
2801 gas_price,
2802 max_fee_per_gas,
2803 max_priority_fee_per_gas,
2804 max_fee_per_blob_gas,
2805 } = fee_details;
2806
2807 let gas_limit = gas.unwrap_or(block_env.gas_limit);
2808 let mut evm_env = base_evm_env.cloned().unwrap_or_else(|| self.evm_env.read().clone());
2809 evm_env.block_env = block_env;
2810 evm_env.cfg_env.disable_block_gas_limit = true;
2813 evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX);
2814
2815 evm_env.cfg_env.disable_base_fee = true;
2821
2822 evm_env.cfg_env.disable_nonce_check = true;
2824
2825 let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| {
2826 self.fees().raw_gas_price().saturating_add(MIN_SUGGESTED_PRIORITY_FEE)
2827 });
2828 let caller = from.unwrap_or_default();
2829 let to = to.as_ref().and_then(TxKind::to);
2830 let blob_hashes = blob_versioned_hashes.unwrap_or_default();
2831 let mut tx_env = TxEnv {
2832 caller,
2833 gas_limit,
2834 gas_price,
2835 gas_priority_fee: max_priority_fee_per_gas,
2836 max_fee_per_blob_gas: max_fee_per_blob_gas
2837 .or_else(|| {
2838 if blob_hashes.is_empty() { Some(0) } else { evm_env.block_env.blob_gasprice() }
2839 })
2840 .unwrap_or_default(),
2841 kind: match to {
2842 Some(addr) => TxKind::Call(*addr),
2843 None => TxKind::Create,
2844 },
2845 tx_type,
2846 value: value.unwrap_or_default(),
2847 data: input.into_input().unwrap_or_default(),
2848 chain_id: Some(chain_id.unwrap_or(evm_env.cfg_env.chain_id)),
2849 access_list: access_list.unwrap_or_default(),
2850 blob_hashes,
2851 ..Default::default()
2852 };
2853 tx_env.set_signed_authorization(authorization_list.unwrap_or_default());
2854
2855 if let Some(nonce) = nonce {
2856 tx_env.nonce = nonce;
2857 }
2858
2859 if evm_env.block_env.basefee == 0 {
2860 evm_env.cfg_env.disable_base_fee = true;
2863 }
2864
2865 #[cfg(feature = "optimism")]
2867 let op_deposit = if self.ensure_op_deposits_active().is_ok()
2868 && let Ok(deposit) = get_deposit_tx_parts(&other)
2869 {
2870 deposit
2871 } else {
2872 OpCallDepositInfo::default()
2873 };
2874 #[cfg(not(feature = "optimism"))]
2875 let op_deposit = {
2876 let _ = &other;
2878 OpCallDepositInfo
2879 };
2880
2881 (evm_env, tx_env, op_deposit)
2882 }
2883
2884 fn prepare_call_env(
2885 &self,
2886 state: &dyn DatabaseRef,
2887 request: WithOtherFields<TransactionRequest>,
2888 fee_details: FeeDetails,
2889 block_env: BlockEnv,
2890 ) -> Result<PreparedCall, BlockchainError> {
2891 self.prepare_call_env_from_base(state, request, fee_details, block_env, None)
2892 }
2893
2894 fn prepare_call_env_from_base(
2895 &self,
2896 state: &dyn DatabaseRef,
2897 request: WithOtherFields<TransactionRequest>,
2898 fee_details: FeeDetails,
2899 block_env: BlockEnv,
2900 base_evm_env: Option<&EvmEnv>,
2901 ) -> Result<PreparedCall, BlockchainError> {
2902 let request = self.parse_transaction_request(request)?;
2903 self.prepare_typed_call_env_with_base(state, request, fee_details, block_env, base_evm_env)
2904 }
2905
2906 const fn base_call_tx_env(&self, tx_env: TxEnv) -> CallTxEnv {
2907 #[cfg(feature = "monad")]
2908 if self.is_monad() {
2909 return CallTxEnv::Monad(tx_env);
2910 }
2911 CallTxEnv::Eth(tx_env)
2912 }
2913
2914 fn prepare_base_call_env_with_base(
2915 &self,
2916 request: WithOtherFields<TransactionRequest>,
2917 fee_details: FeeDetails,
2918 block_env: BlockEnv,
2919 base_evm_env: Option<&EvmEnv>,
2920 ) -> PreparedCall {
2921 let (evm_env, tx_env, op_deposit) =
2922 self.build_call_env_with_base(request, fee_details, block_env, base_evm_env);
2923 #[cfg(feature = "optimism")]
2924 let tx_env = if self.is_optimism() {
2925 CallTxEnv::Op(OpTransaction {
2926 base: tx_env,
2927 deposit: op_deposit,
2928 enveloped_tx: Some(Bytes::new()),
2929 })
2930 } else if self.is_tempo() {
2931 CallTxEnv::Tempo(TempoTxEnv::from(tx_env))
2932 } else {
2933 self.base_call_tx_env(tx_env)
2934 };
2935 #[cfg(not(feature = "optimism"))]
2936 let tx_env = {
2937 let _ = op_deposit;
2938 if self.is_tempo() {
2939 CallTxEnv::Tempo(TempoTxEnv::from(tx_env))
2940 } else {
2941 self.base_call_tx_env(tx_env)
2942 }
2943 };
2944 PreparedCall { evm_env, tx_env, simulated_tempo_tx: None }
2945 }
2946
2947 pub(crate) fn parse_transaction_request(
2949 &self,
2950 request: WithOtherFields<TransactionRequest>,
2951 ) -> Result<FoundryTransactionRequest, BlockchainError> {
2952 let transaction_type = request.transaction_type;
2953 if !self.is_tempo() && transaction_type != Some(TEMPO_TX_TYPE_ID) {
2954 #[cfg(feature = "optimism")]
2955 if transaction_type == Some(DEPOSIT_TX_TYPE_ID)
2956 || transaction_type == Some(POST_EXEC_TX_TYPE_ID)
2957 || get_deposit_tx_parts(&request.other).is_ok()
2958 {
2959 return Ok(FoundryTransactionRequest::Op(request));
2960 }
2961 return Ok(FoundryTransactionRequest::Ethereum(request.into_inner()));
2962 }
2963
2964 let parsed: FoundryTransactionRequest =
2965 request.try_into().map_err(|err: serde_json::Error| {
2966 BlockchainError::InvalidTransactionRequest(err.to_string())
2967 })?;
2968 if parsed.is_tempo() {
2969 self.ensure_tempo_active()?;
2970 }
2971 if parsed.is_tempo()
2972 && self.is_tempo()
2973 && transaction_type.is_some_and(|ty| ty != TEMPO_TX_TYPE_ID)
2974 {
2975 return Err(BlockchainError::FailedToDecodeTransaction);
2976 }
2977 Ok(parsed)
2978 }
2979
2980 fn build_tempo_request_env(
2981 &self,
2982 request: TempoTransactionRequest,
2983 mut base: TxEnv,
2984 ) -> Result<(TempoTxEnv, AASigned), BlockchainError> {
2985 let fee_payer = request.fee_payer_signature.map(|_| {
2986 request.clone().build_aa().ok().and_then(|tx| tx.recover_fee_payer(base.caller).ok())
2987 });
2988
2989 let mut response_request = request.clone();
2992 response_request.inner.from = Some(base.caller);
2993 response_request.inner.gas = Some(base.gas_limit);
2994 response_request.inner.nonce = Some(base.nonce);
2995 response_request.inner.chain_id = base.chain_id;
2996 response_request.inner.max_fee_per_gas = Some(base.gas_price);
2997 response_request.inner.max_priority_fee_per_gas =
2998 Some(base.gas_priority_fee.unwrap_or_default());
2999 response_request.inner.access_list = Some(base.access_list.clone());
3000 if response_request.calls.is_empty()
3001 && response_request.inner.to.is_none()
3002 && !base.data.is_empty()
3003 {
3004 response_request.inner.to = Some(base.kind);
3005 }
3006 let response_tx = response_request
3007 .build_aa()
3008 .map_err(|err| BlockchainError::InvalidTransactionRequest(err.to_string()))?;
3009 let response_tx = response_tx.into_signed(TempoSignature::default());
3010 let key_type = request.key_type.unwrap_or(SignatureType::Secp256k1);
3011 let key_data = request.key_data.clone();
3012 let key_id = request.key_id;
3013 let signature = mock_tempo_signature(
3014 key_type,
3015 key_data,
3016 key_id,
3017 base.caller,
3018 self.tempo_hardfork().is_t1c(),
3019 );
3020 let mut calls = request.calls;
3021 if let Some(to) = request.inner.to {
3022 calls.push(Call {
3023 to,
3024 value: request.inner.value.unwrap_or_default(),
3025 input: request.inner.input.into_input().unwrap_or_default(),
3026 });
3027 } else if calls.is_empty() && !base.data.is_empty() {
3028 calls.push(Call { to: base.kind, value: base.value, input: base.data.clone() });
3031 }
3032 if let Some(first_call) = calls.first() {
3033 base.kind = first_call.to;
3034 base.value = first_call.value;
3035 base.data = first_call.input.clone();
3036 }
3037 let tx_env = TempoTxEnv {
3038 fee_token: request.fee_token,
3039 is_system_tx: false,
3040 execution_context: ExecutionContext::Simulation,
3041 unique_tx_identifier: Some(TEMPO_RPC_SIMULATION_CONTEXT),
3042 fee_payer,
3043 tempo_tx_env: Some(Box::new(TempoBatchCallEnv {
3044 aa_calls: calls,
3045 signature,
3046 tempo_authorization_list: request
3047 .tempo_authorization_list
3048 .into_iter()
3049 .map(RecoveredTempoAuthorization::new)
3050 .collect(),
3051 nonce_key: request.nonce_key.unwrap_or_default(),
3052 key_authorization: request.key_authorization,
3053 signature_hash: B256::ZERO,
3054 tx_hash: B256::ZERO,
3055 valid_before: request.valid_before.map(|value| value.get()),
3056 valid_after: request.valid_after.map(|value| value.get()),
3057 subblock_transaction: false,
3058 override_key_id: key_id,
3059 expiring_nonce_idx: None,
3060 })),
3061 inner: base,
3062 };
3063 Ok((tx_env, response_tx))
3064 }
3065
3066 fn prepare_typed_call_env(
3067 &self,
3068 state: &dyn DatabaseRef,
3069 request: FoundryTransactionRequest,
3070 fee_details: FeeDetails,
3071 block_env: BlockEnv,
3072 ) -> Result<PreparedCall, BlockchainError> {
3073 self.prepare_typed_call_env_with_base(state, request, fee_details, block_env, None)
3074 }
3075
3076 fn prepare_typed_call_env_with_base(
3077 &self,
3078 state: &dyn DatabaseRef,
3079 request: FoundryTransactionRequest,
3080 fee_details: FeeDetails,
3081 block_env: BlockEnv,
3082 base_evm_env: Option<&EvmEnv>,
3083 ) -> Result<PreparedCall, BlockchainError> {
3084 match request {
3085 FoundryTransactionRequest::Tempo(tempo_request) => {
3086 self.ensure_tempo_active()?;
3087 let mut tempo_request = *tempo_request;
3088 if tempo_request.inner.nonce.is_none() {
3089 let caller = tempo_request.inner.from.unwrap_or_default();
3090 tempo_request.inner.nonce = Some(tempo_nonce(
3091 state,
3092 caller,
3093 tempo_request.nonce_key.unwrap_or_default(),
3094 )?);
3095 }
3096 let inner = WithOtherFields::new(tempo_request.inner.clone());
3097 let (evm_env, base, _) =
3098 self.build_call_env_with_base(inner, fee_details, block_env, base_evm_env);
3099 let (tx_env, simulated_tempo_tx) =
3100 self.build_tempo_request_env(tempo_request, base)?;
3101 Ok(PreparedCall {
3102 evm_env,
3103 tx_env: CallTxEnv::Tempo(tx_env),
3104 simulated_tempo_tx: Some(simulated_tempo_tx),
3105 })
3106 }
3107 FoundryTransactionRequest::Ethereum(request) => Ok(self
3108 .prepare_base_call_env_with_base(
3109 WithOtherFields::new(request),
3110 fee_details,
3111 block_env,
3112 base_evm_env,
3113 )),
3114 #[cfg(feature = "optimism")]
3115 FoundryTransactionRequest::Op(request) => Ok(self.prepare_base_call_env_with_base(
3116 request,
3117 fee_details,
3118 block_env,
3119 base_evm_env,
3120 )),
3121 }
3122 }
3123
3124 fn transact_call_with_inspector_ref<'db, I, DB>(
3125 &self,
3126 db: &'db DB,
3127 evm_env: &EvmEnv,
3128 inspector: &mut I,
3129 tx_env: CallTxEnv,
3130 #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
3131 MonadExecutionContext<'_>,
3132 >,
3133 ) -> Result<ResultAndState<HaltReason>, BlockchainError>
3134 where
3135 DB: DatabaseRef + ?Sized,
3136 I: BackendInspector<WrapDatabaseRef<&'db DB>>,
3137 WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
3138 {
3139 self.transact_call_with_inspector_ref_at_hardfork(
3140 db,
3141 evm_env,
3142 inspector,
3143 tx_env,
3144 monad_context,
3145 self.hardfork(),
3146 )
3147 }
3148
3149 fn transact_call_with_inspector_ref_at_hardfork<'db, I, DB>(
3150 &self,
3151 db: &'db DB,
3152 evm_env: &EvmEnv,
3153 inspector: &mut I,
3154 tx_env: CallTxEnv,
3155 #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
3156 MonadExecutionContext<'_>,
3157 >,
3158 #[cfg_attr(not(feature = "monad"), allow(unused_variables))] hardfork: FoundryHardfork,
3159 ) -> Result<ResultAndState<HaltReason>, BlockchainError>
3160 where
3161 DB: DatabaseRef + ?Sized,
3162 I: BackendInspector<WrapDatabaseRef<&'db DB>>,
3163 WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
3164 {
3165 match tx_env {
3166 CallTxEnv::Eth(tx_env) => {
3167 self.transact_eth_with_inspector_ref(db, evm_env, inspector, tx_env)
3168 }
3169 #[cfg(feature = "monad")]
3170 CallTxEnv::Monad(tx_env) => {
3171 let context = monad::resolve_execution_context(monad_context, &tx_env);
3172 self.transact_monad_with_inspector_ref(
3173 db,
3174 evm_env,
3175 inspector,
3176 tx_env,
3177 monad::PreparedExecution {
3178 context,
3179 kind: EnvelopeExecutionKind::Transaction,
3180 hardfork: monad_revm::MonadHardfork::from(hardfork),
3181 },
3182 )
3183 }
3184 #[cfg(feature = "optimism")]
3185 CallTxEnv::Op(tx_env) => {
3186 self.transact_op_with_inspector_ref(db, evm_env, inspector, tx_env)
3187 }
3188 CallTxEnv::Tempo(tx_env) => {
3189 self.transact_tempo_with_inspector_ref(db, evm_env, inspector, tx_env)
3190 }
3191 }
3192 }
3193
3194 pub fn call_with_state(
3195 &self,
3196 state: &dyn DatabaseRef,
3197 request: WithOtherFields<TransactionRequest>,
3198 fee_details: FeeDetails,
3199 block_env: BlockEnv,
3200 ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3201 self.call_with_state_and_context(state, request, fee_details, block_env, None)
3202 }
3203
3204 pub(crate) fn call_with_state_and_context(
3205 &self,
3206 state: &dyn DatabaseRef,
3207 request: WithOtherFields<TransactionRequest>,
3208 fee_details: FeeDetails,
3209 block_env: BlockEnv,
3210 mut monad_context: Option<MonadReplayContext>,
3211 ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3212 let mut inspector = self.build_inspector();
3213 let PreparedCall { evm_env, tx_env, .. } =
3214 self.prepare_call_env(state, request, fee_details, block_env)?;
3215 let ResultAndState { result, state } = self.transact_call_with_inspector_ref(
3216 state,
3217 &evm_env,
3218 &mut inspector,
3219 tx_env,
3220 monad_context.as_mut().map(next_monad_context),
3221 )?;
3222
3223 let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3224 inspector.print_logs();
3225
3226 if self.print_traces {
3227 inspector.into_print_traces(self.call_trace_decoder());
3228 }
3229
3230 Ok((exit_reason, out, gas_used as u128, state))
3231 }
3232
3233 pub(crate) fn call_with_state_typed_gas_limit(
3234 &self,
3235 state: &dyn DatabaseRef,
3236 request: FoundryTransactionRequest,
3237 fee_details: FeeDetails,
3238 block_env: BlockEnv,
3239 options: GasEstimateCallOptions,
3240 ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3241 let GasEstimateCallOptions { gas_limit, disable_fee_charge, monad_context } = options;
3242 self.call_with_state_typed_inner(
3243 state,
3244 request,
3245 fee_details,
3246 block_env,
3247 TypedCallOverrides {
3248 gas_limit: Some(gas_limit),
3249 disable_fee_charge,
3250 ..Default::default()
3251 },
3252 monad_context,
3253 )
3254 }
3255
3256 pub(crate) fn call_with_state_typed_access_list(
3257 &self,
3258 state: &dyn DatabaseRef,
3259 request: FoundryTransactionRequest,
3260 fee_details: FeeDetails,
3261 block_env: BlockEnv,
3262 access_list: AccessList,
3263 monad_context: Option<MonadReplayContext>,
3264 ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3265 self.call_with_state_typed_inner(
3266 state,
3267 request,
3268 fee_details,
3269 block_env,
3270 TypedCallOverrides { access_list: Some(access_list), ..Default::default() },
3271 monad_context,
3272 )
3273 }
3274
3275 fn call_with_state_typed_inner(
3276 &self,
3277 state: &dyn DatabaseRef,
3278 request: FoundryTransactionRequest,
3279 fee_details: FeeDetails,
3280 block_env: BlockEnv,
3281 overrides: TypedCallOverrides,
3282 mut monad_context: Option<MonadReplayContext>,
3283 ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
3284 let mut inspector = self.build_inspector();
3285 let PreparedCall { mut evm_env, mut tx_env, .. } =
3286 self.prepare_typed_call_env(state, request, fee_details, block_env)?;
3287 evm_env.cfg_env.disable_fee_charge = overrides.disable_fee_charge;
3288 if let Some(gas_limit) = overrides.gas_limit {
3289 tx_env.base_mut().gas_limit = gas_limit;
3290 }
3291 if let Some(access_list) = overrides.access_list {
3292 let tx_env = tx_env.base_mut();
3293 tx_env.access_list = access_list;
3294 if tx_env.tx_type == TransactionType::Legacy as u8 {
3295 tx_env.tx_type = TransactionType::Eip2930 as u8;
3296 }
3297 }
3298 let ResultAndState { result, state } = self.transact_call_with_inspector_ref(
3299 state,
3300 &evm_env,
3301 &mut inspector,
3302 tx_env,
3303 monad_context.as_mut().map(next_monad_context),
3304 )?;
3305 let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3306 inspector.print_logs();
3307 Ok((exit_reason, out, gas_used as u128, state))
3308 }
3309
3310 pub fn build_access_list_with_state(
3311 &self,
3312 state: &dyn DatabaseRef,
3313 request: WithOtherFields<TransactionRequest>,
3314 fee_details: FeeDetails,
3315 block_env: BlockEnv,
3316 ) -> Result<(InstructionResult, Option<Output>, u64, AccessList), BlockchainError> {
3317 self.build_access_list_with_state_and_context(state, request, fee_details, block_env, None)
3318 }
3319
3320 pub(crate) fn build_access_list_with_state_and_context(
3321 &self,
3322 state: &dyn DatabaseRef,
3323 request: WithOtherFields<TransactionRequest>,
3324 fee_details: FeeDetails,
3325 block_env: BlockEnv,
3326 mut monad_context: Option<MonadReplayContext>,
3327 ) -> Result<(InstructionResult, Option<Output>, u64, AccessList), BlockchainError> {
3328 let mut inspector =
3329 AccessListInspector::new(request.access_list.clone().unwrap_or_default());
3330
3331 let PreparedCall { evm_env, tx_env, .. } =
3332 self.prepare_call_env(state, request, fee_details, block_env)?;
3333 let ResultAndState { result, state: _ } = self.transact_call_with_inspector_ref(
3334 state,
3335 &evm_env,
3336 &mut inspector,
3337 tx_env,
3338 monad_context.as_mut().map(next_monad_context),
3339 )?;
3340 let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
3341 let access_list = inspector.access_list();
3342 #[cfg(feature = "monad")]
3343 let access_list = if self.is_monad() {
3344 monad::normalize_access_list(access_list, self.monad_hardfork())
3345 } else {
3346 access_list
3347 };
3348 Ok((exit_reason, out, gas_used, access_list))
3349 }
3350
3351 fn arbitrum_block_number(&self, evm_env: &EvmEnv) -> Option<u64> {
3352 if !arbitrum::is_arbitrum_chain(self.protocol_chain_id()) {
3353 return None;
3354 }
3355
3356 let env_block = evm_env.block_env.number.saturating_to();
3357 Some(self.get_fork().map_or(env_block, |fork| fork.block_number().max(env_block)))
3358 }
3359
3360 pub fn get_code_with_state(
3361 &self,
3362 state: &dyn DatabaseRef,
3363 address: Address,
3364 ) -> Result<Bytes, BlockchainError> {
3365 trace!(target: "backend", "get code for {:?}", address);
3366 let account = state.basic_ref(address)?.unwrap_or_default();
3367 if account.code_hash == KECCAK_EMPTY {
3368 return Ok(Default::default());
3370 }
3371 let code = if let Some(code) = account.code {
3372 code
3373 } else {
3374 state.code_by_hash_ref(account.code_hash)?
3375 };
3376 Ok(code.bytes()[..code.len()].to_vec().into())
3377 }
3378
3379 pub fn get_balance_with_state<D>(
3380 &self,
3381 state: D,
3382 address: Address,
3383 ) -> Result<U256, BlockchainError>
3384 where
3385 D: DatabaseRef,
3386 {
3387 trace!(target: "backend", "get balance for {:?}", address);
3388 Ok(state.basic_ref(address)?.unwrap_or_default().balance)
3389 }
3390
3391 pub async fn transaction_by_block_number_and_index(
3392 &self,
3393 number: BlockNumber,
3394 index: Index,
3395 ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
3396 if let Some(block) = self.mined_block_by_number(number) {
3397 return Ok(self.mined_transaction_by_block_hash_and_index(block.header.hash, index));
3398 }
3399
3400 if let Some(fork) = self.get_fork() {
3401 let number = self.convert_block_number(Some(number));
3402 if fork.predates_fork(number) {
3403 return Ok(fork
3404 .transaction_by_block_number_and_index(number, index.into())
3405 .await?);
3406 }
3407 }
3408
3409 Ok(None)
3410 }
3411
3412 pub async fn transaction_by_block_hash_and_index(
3413 &self,
3414 hash: B256,
3415 index: Index,
3416 ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
3417 if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) {
3418 return Ok(tx);
3419 }
3420
3421 if let Some(fork) = self.get_fork() {
3422 return Ok(fork.transaction_by_block_hash_and_index(hash, index.into()).await?);
3423 }
3424
3425 Ok(None)
3426 }
3427
3428 pub fn mined_transaction_by_block_hash_and_index(
3429 &self,
3430 block_hash: B256,
3431 index: Index,
3432 ) -> Option<AnyRpcTransaction> {
3433 let (info, block, tx) = {
3434 let storage = self.blockchain.storage.read();
3435 let block = storage.blocks.get(&block_hash).cloned()?;
3436 let index: usize = index.into();
3437 let tx = block.body.transactions.get(index)?.clone();
3438 let info = storage.transactions.get(&tx.hash())?.info.clone();
3439 (info, block, tx)
3440 };
3441
3442 Some(transaction_build(
3443 Some(info.transaction_hash),
3444 tx,
3445 Some(&block),
3446 Some(info),
3447 block.header.base_fee_per_gas(),
3448 ))
3449 }
3450
3451 pub async fn transaction_by_hash(
3452 &self,
3453 hash: B256,
3454 ) -> Result<Option<AnyRpcTransaction>, BlockchainError> {
3455 trace!(target: "backend", "transaction_by_hash={:?}", hash);
3456 if let tx @ Some(_) = self.mined_transaction_by_hash(hash) {
3457 return Ok(tx);
3458 }
3459
3460 if let Some(fork) = self.get_fork() {
3461 return fork
3462 .transaction_by_hash(hash)
3463 .await
3464 .map_err(BlockchainError::AlloyForkProvider);
3465 }
3466
3467 Ok(None)
3468 }
3469
3470 pub fn mined_transaction_by_hash(&self, hash: B256) -> Option<AnyRpcTransaction> {
3471 let (info, block) = {
3472 let storage = self.blockchain.storage.read();
3473 let MinedTransaction { info, block_hash, .. } =
3474 storage.transactions.get(&hash)?.clone();
3475 let block = storage.blocks.get(&block_hash).cloned()?;
3476 (info, block)
3477 };
3478 let tx = block.body.transactions.get(info.transaction_index as usize)?.clone();
3479
3480 Some(transaction_build(
3481 Some(info.transaction_hash),
3482 tx,
3483 Some(&block),
3484 Some(info),
3485 block.header.base_fee_per_gas(),
3486 ))
3487 }
3488
3489 pub async fn trace_transaction(
3491 &self,
3492 hash: B256,
3493 ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
3494 if let Some(traces) = self.mined_parity_trace_transaction(hash) {
3495 return Ok(traces);
3496 }
3497
3498 if let Some(fork) = self.get_fork() {
3499 return Ok(fork.trace_transaction(hash).await?);
3500 }
3501
3502 Ok(vec![])
3503 }
3504
3505 pub async fn trace_get(
3507 &self,
3508 hash: B256,
3509 indices: Vec<Index>,
3510 ) -> Result<Option<LocalizedTransactionTrace>, BlockchainError> {
3511 if indices.len() != 1 {
3512 return Ok(None);
3513 }
3514
3515 let index: usize = indices[0].into();
3516 if let Some(traces) = self.mined_parity_trace_transaction(hash) {
3517 return Ok(traces.into_iter().nth(index));
3518 }
3519
3520 if let Some(fork) = self.get_fork() {
3521 return Ok(fork.trace_get(hash, indices).await?);
3522 }
3523
3524 Ok(None)
3525 }
3526
3527 pub async fn trace_block(
3529 &self,
3530 block: BlockNumber,
3531 ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
3532 let number = self.convert_block_number(Some(block));
3533 if let Some(traces) = self.mined_parity_trace_block(number) {
3534 return Ok(traces);
3535 }
3536
3537 if let Some(fork) = self.get_fork()
3538 && fork.predates_fork(number)
3539 {
3540 return Ok(fork.trace_block(number).await?);
3541 }
3542
3543 Ok(vec![])
3544 }
3545
3546 pub async fn trace_call(
3548 &self,
3549 request: WithOtherFields<TransactionRequest>,
3550 fee_details: FeeDetails,
3551 trace_types: HashSet<TraceType>,
3552 block_request: BlockRequest<FoundryTxEnvelope>,
3553 block_id: BlockId,
3554 ) -> Result<TraceResults, BlockchainError>
3555 where
3556 Self: TransactionValidator<FoundryTxEnvelope>,
3557 N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
3558 {
3559 if let BlockRequest::Number(number) = &block_request
3560 && let Some(fork) = self.get_fork()
3561 && fork.predates_fork(*number)
3562 {
3563 return Ok(fork.trace_call(request, trace_types, block_id).await?);
3564 }
3565
3566 self.with_database_at_and_context(Some(block_request), |state, block, mut monad_context| {
3567 let cache_db = CacheDB::new(state);
3568 let mut inspector =
3569 TracingInspector::new(TracingInspectorConfig::from_parity_config(&trace_types));
3570 let PreparedCall { evm_env, tx_env, .. } =
3571 self.prepare_call_env(&cache_db, request, fee_details, block)?;
3572 let result = self.transact_call_with_inspector_ref(
3573 &cache_db,
3574 &evm_env,
3575 &mut inspector,
3576 tx_env,
3577 monad_context.as_mut().map(next_monad_context),
3578 )?;
3579
3580 inspector
3581 .into_parity_builder()
3582 .into_trace_results_with_state(&result, &trace_types, &cache_db)
3583 .map_err(Into::into)
3584 })
3585 .await
3586 }
3587
3588 pub async fn trace_replay_block_transactions(
3590 &self,
3591 block: BlockNumber,
3592 trace_types: HashSet<TraceType>,
3593 ) -> Result<Vec<TraceResultsWithTransactionHash>, BlockchainError> {
3594 let block_number = self.convert_block_number(Some(block));
3595
3596 if let Some(results) =
3598 self.mined_parity_trace_replay_block_transactions(block_number, &trace_types)?
3599 {
3600 return Ok(results);
3601 }
3602
3603 if let Some(fork) = self.get_fork()
3605 && fork.predates_fork(block_number)
3606 {
3607 return Ok(fork.trace_replay_block_transactions(block_number, trace_types).await?);
3608 }
3609
3610 Ok(vec![])
3611 }
3612
3613 pub async fn trace_replay_transaction(
3615 &self,
3616 hash: B256,
3617 trace_types: HashSet<TraceType>,
3618 ) -> Result<TraceResults, BlockchainError> {
3619 let block_number =
3620 self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.block_number);
3621
3622 if let Some(block_number) = block_number {
3626 let results = self
3627 .mined_parity_trace_replay_block_transactions(block_number, &trace_types)?
3628 .ok_or(BlockchainError::BlockNotFound)?;
3629
3630 return results
3631 .into_iter()
3632 .find(|result| result.transaction_hash == hash)
3633 .map(|result| result.full_trace)
3634 .ok_or_else(|| {
3635 BlockchainError::Internal(format!(
3636 "replayed block {block_number} for local transaction {hash:?}, \
3637 but its trace was missing"
3638 ))
3639 });
3640 }
3641
3642 if let Some(fork) = self.get_fork() {
3644 return Ok(fork.trace_replay_transaction(hash, trace_types).await?);
3645 }
3646
3647 Err(BlockchainError::TransactionNotFound)
3648 }
3649
3650 pub async fn trace_raw_transaction(
3652 &self,
3653 pending_transaction: PendingTransaction<FoundryTxEnvelope>,
3654 trace_types: HashSet<TraceType>,
3655 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3656 ) -> Result<TraceResults, BlockchainError>
3657 where
3658 N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
3659 {
3660 let trace_config = TracingInspectorConfig::from_parity_config(&trace_types);
3661
3662 self.with_database_at_and_context(block_request, |state, block_env, mut monad_context| {
3663 let cache_db = CacheDB::new(state);
3664 let mut evm_env = self.evm_env.read().clone();
3665 evm_env.block_env = block_env;
3666
3667 let mut inspector = TracingInspector::new(trace_config);
3668 let (result, _) = self.transact_envelope_with_inspector_ref_and_context(
3669 &cache_db,
3670 &evm_env,
3671 &mut inspector,
3672 &pending_transaction,
3673 monad_context.as_mut().map(next_monad_context),
3674 )?;
3675
3676 inspector
3677 .into_parity_builder()
3678 .into_trace_results_with_state(&result, &trace_types, &cache_db)
3679 .map_err(BlockchainError::from)
3680 })
3681 .await
3682 }
3683
3684 pub async fn trace_call_many(
3686 &self,
3687 calls: Vec<(WithOtherFields<TransactionRequest>, HashSet<TraceType>)>,
3688 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
3689 ) -> Result<Vec<TraceResults>, BlockchainError>
3690 where
3691 N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
3692 {
3693 self.with_database_at_and_context(block_request, |state, block_env, mut monad_context| {
3694 let mut cache_db = CacheDB::new(state);
3695 let mut results = Vec::with_capacity(calls.len());
3696 let mut calls = calls.into_iter().peekable();
3697
3698 while let Some((request, trace_types)) = calls.next() {
3699 let fee_details = FeeDetails::new(
3700 request.gas_price,
3701 request.max_fee_per_gas,
3702 request.max_priority_fee_per_gas,
3703 request.max_fee_per_blob_gas,
3704 )?
3705 .or_zero_fees();
3706 let PreparedCall { evm_env, mut tx_env, simulated_tempo_tx } =
3707 self.prepare_call_env(&cache_db, request, fee_details, block_env.clone())?;
3708 apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
3709
3710 let trace_config = TracingInspectorConfig::from_parity_config(&trace_types);
3711 let mut inspector = TracingInspector::new(trace_config);
3712 let result = self.transact_call_with_inspector_ref(
3713 &cache_db,
3714 &evm_env,
3715 &mut inspector,
3716 tx_env,
3717 monad_context.as_mut().map(next_monad_context),
3718 )?;
3719
3720 let trace_result = inspector
3721 .into_parity_builder()
3722 .into_trace_results_with_state(&result, &trace_types, &cache_db)
3723 .map_err(BlockchainError::from)?;
3724 results.push(trace_result);
3725
3726 if calls.peek().is_some() {
3727 cache_db.commit(result.state);
3728 }
3729 }
3730
3731 Ok(results)
3732 })
3733 .await
3734 }
3735
3736 fn mined_parity_trace_replay_block_transactions(
3738 &self,
3739 block_number: u64,
3740 trace_types: &HashSet<TraceType>,
3741 ) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, BlockchainError> {
3742 let Some(block) = self.get_block(block_number) else { return Ok(None) };
3743
3744 let parent_hash = block.header.parent_hash;
3746 let trace_config = TracingInspectorConfig::from_parity_config(trace_types);
3747
3748 let read_guard = self.states.upgradable_read();
3749 if let Some(state) = read_guard.get_state(&parent_hash) {
3750 self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types)
3751 .map(Some)
3752 } else {
3753 let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
3754 let Some(state) = write_guard.get_on_disk_state(&parent_hash) else {
3755 return Ok(None);
3756 };
3757 self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types)
3758 .map(Some)
3759 }
3760 }
3761
3762 fn replay_block_transactions_with_inspector(
3764 &self,
3765 block: &Block,
3766 parent_state: &StateDb,
3767 trace_config: TracingInspectorConfig,
3768 trace_types: &HashSet<TraceType>,
3769 ) -> Result<Vec<TraceResultsWithTransactionHash>, BlockchainError> {
3770 let (mut cache_db, evm_env, hardfork) = self.prepare_block_replay(block, parent_state)?;
3771 let mut results = Vec::new();
3772 let monad_context = self.active_monad_context_for_mined_block(block)?;
3773
3774 for tx_envelope in &block.body.transactions {
3776 let tx_hash = tx_envelope.hash();
3777
3778 let mut inspector = TracingInspector::new(trace_config);
3780
3781 let pending_tx = self.pending_mined_transaction(tx_envelope.clone())?;
3783 let transaction_context =
3784 monad_execution_context_at(monad_context.as_ref(), results.len());
3785 let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
3786 &cache_db,
3787 &evm_env,
3788 &mut inspector,
3789 &pending_tx,
3790 EnvelopeExecution::replay(transaction_context, hardfork),
3791 )?;
3792
3793 let full_trace = inspector
3795 .into_parity_builder()
3796 .into_trace_results_with_state(&result, trace_types, &cache_db)
3797 .map_err(BlockchainError::from)?;
3798
3799 results.push(TraceResultsWithTransactionHash { transaction_hash: tx_hash, full_trace });
3800
3801 cache_db.commit(result.state);
3803 }
3804
3805 Ok(results)
3806 }
3807
3808 pub async fn trace_filter(
3810 &self,
3811 filter: TraceFilter,
3812 ) -> Result<Vec<LocalizedTransactionTrace>, BlockchainError> {
3813 let matcher = filter.matcher();
3814 let start = filter.from_block.unwrap_or(0);
3815 let end = filter.to_block.unwrap_or_else(|| self.best_number());
3816
3817 if start > end {
3818 return Err(BlockchainError::RpcError(RpcError::invalid_params(
3819 "invalid block range, ensure that to block is greater than from block".to_string(),
3820 )));
3821 }
3822
3823 let dist = end - start;
3824 if dist > 300 {
3825 return Err(BlockchainError::RpcError(RpcError::invalid_params(
3826 "block range too large, currently limited to 300".to_string(),
3827 )));
3828 }
3829
3830 let mut trace_tasks = vec![];
3832 for num in start..=end {
3833 trace_tasks.push(self.trace_block(num.into()));
3834 }
3835
3836 let traces = futures::future::try_join_all(trace_tasks).await?;
3838 let filtered_traces =
3839 traces.into_iter().flatten().filter(|trace| matcher.matches(&trace.trace));
3840
3841 let filtered_traces: Vec<_> = if let Some(after) = filter.after {
3843 filtered_traces.skip(after as usize).collect()
3844 } else {
3845 filtered_traces.collect()
3846 };
3847
3848 let filtered_traces: Vec<_> = if let Some(count) = filter.count {
3849 filtered_traces.into_iter().take(count as usize).collect()
3850 } else {
3851 filtered_traces
3852 };
3853
3854 Ok(filtered_traces)
3855 }
3856
3857 pub fn get_blobs_by_block_id(
3858 &self,
3859 id: impl Into<BlockId>,
3860 versioned_hashes: Vec<B256>,
3861 ) -> Result<Option<Vec<alloy_consensus::Blob>>> {
3862 Ok(self.get_block(id).map(|block| {
3863 block
3864 .body
3865 .transactions
3866 .iter()
3867 .filter_map(|tx| tx.as_ref().sidecar())
3868 .flat_map(|sidecar| {
3869 sidecar.sidecar.blobs().iter().zip(sidecar.sidecar.commitments().iter())
3870 })
3871 .filter(|(_, commitment)| {
3872 versioned_hashes.is_empty()
3874 || versioned_hashes.contains(&kzg_to_versioned_hash(commitment.as_slice()))
3875 })
3876 .map(|(blob, _)| *blob)
3877 .collect()
3878 }))
3879 }
3880
3881 #[allow(clippy::large_stack_frames)]
3882 pub fn get_blob_by_versioned_hash(&self, hash: B256) -> Result<Option<Blob>> {
3883 let storage = self.blockchain.storage.read();
3884 for block in storage.blocks.values() {
3885 for tx in &block.body.transactions {
3886 let typed_tx = tx.as_ref();
3887 if let Some(sidecar) = typed_tx.sidecar() {
3888 for versioned_hash in sidecar.sidecar.versioned_hashes() {
3889 if versioned_hash == hash
3890 && let Some(index) =
3891 sidecar.sidecar.commitments().iter().position(|commitment| {
3892 kzg_to_versioned_hash(commitment.as_slice()) == *hash
3893 })
3894 && let Some(blob) = sidecar.sidecar.blobs().get(index)
3895 {
3896 return Ok(Some(*blob));
3897 }
3898 }
3899 }
3900 }
3901 }
3902 Ok(None)
3903 }
3904
3905 #[expect(clippy::too_many_arguments)]
3907 pub async fn with_genesis(
3908 db: Arc<AsyncRwLock<Box<dyn Db>>>,
3909 env: Arc<RwLock<EvmEnv>>,
3910 networks: NetworkConfigs,
3911 genesis: GenesisConfig,
3912 fees: FeeManager,
3913 fork: Arc<RwLock<Option<ClientFork>>>,
3914 enable_steps_tracing: bool,
3915 print_logs: bool,
3916 print_traces: bool,
3917 call_trace_decoder: Arc<CallTraceDecoder>,
3918 prune_state_history_config: PruneStateHistoryConfig,
3919 max_persisted_states: Option<usize>,
3920 transaction_block_keeper: Option<usize>,
3921 automine_block_time: Option<Duration>,
3922 cache_path: Option<PathBuf>,
3923 node_config: Arc<AsyncRwLock<NodeConfig>>,
3924 ) -> Result<Self> {
3925 let last_fork_cache_source = fork.read().as_ref().and_then(ForkCacheSource::from_fork);
3926 let blockchain = if let Some(fork) = fork.read().as_ref() {
3928 trace!(target: "backend", "using forked blockchain at {}", fork.block_number());
3929 Blockchain::forked(fork.block_number(), fork.block_hash(), fork.total_difficulty())
3930 } else {
3931 let header = genesis_header(
3932 &env.read(),
3933 fees.is_eip1559().then(|| fees.base_fee()),
3934 genesis.timestamp,
3935 genesis.number,
3936 );
3937 Blockchain::new(foundry_header(&networks, header))
3938 };
3939
3940 if fork.read().is_none() {
3943 env.write().block_env.number = U256::from(genesis.number);
3944
3945 if fees.tempo_hardfork().is_some() {
3949 let env = env.read();
3950 let next_base_fee = fees.get_next_block_base_fee_per_gas(
3951 0,
3952 env.block_env.gas_limit,
3953 env.block_env.basefee,
3954 );
3955 drop(env);
3956 fees.set_base_fee(next_base_fee);
3957 }
3958 }
3959
3960 let start_timestamp = if let Some(fork) = fork.read().as_ref() {
3961 fork.timestamp()
3962 } else {
3963 genesis.timestamp
3964 };
3965
3966 let mut states = if prune_state_history_config.is_config_enabled() {
3967 prune_state_history_config
3969 .max_memory_history
3970 .map(|limit| InMemoryBlockStates::new(limit, 0))
3971 .unwrap_or_default()
3972 .memory_only()
3973 } else if max_persisted_states.is_some() {
3974 max_persisted_states
3975 .map(|limit| InMemoryBlockStates::new(DEFAULT_HISTORY_LIMIT, limit))
3976 .unwrap_or_default()
3977 } else {
3978 Default::default()
3979 };
3980
3981 if let Some(cache_path) = cache_path {
3982 states = states.disk_path(cache_path);
3983 }
3984
3985 let (slots_in_an_epoch, precompile_factory, disable_pool_balance_checks, hardfork) = {
3986 let cfg = node_config.read().await;
3987 (
3988 cfg.slots_in_an_epoch,
3989 cfg.precompile_factory.clone(),
3990 cfg.disable_pool_balance_checks,
3991 cfg.get_hardfork(),
3992 )
3993 };
3994 let startup_cache_lease = if fork.read().is_some() {
3995 let db = db.read().await;
3996 let inner = db
3997 .maybe_inner()
3998 .map_err(|err| eyre::eyre!("fork database is missing its cache backend: {err}"))?;
3999 StagedForkCacheLease::for_db(inner)
4000 } else {
4001 StagedForkCacheLease::default()
4002 };
4003 let startup_fork_cache_user =
4004 StagedForkDbUser { db: Some(Arc::clone(&db)), cache_lease: startup_cache_lease };
4005
4006 let backend = Self {
4007 db,
4008 blockchain,
4009 states: Arc::new(RwLock::new(states)),
4010 evm_env: env,
4011 networks,
4012 hardfork: Arc::new(RwLock::new(hardfork)),
4013 fork,
4014 last_fork_cache_source: Arc::new(RwLock::new(last_fork_cache_source)),
4015 time: TimeManager::new(start_timestamp),
4016 cheats: Default::default(),
4017 new_block_listeners: Default::default(),
4018 fees,
4019 genesis,
4020 active_state_snapshots: Arc::new(Mutex::new(Default::default())),
4021 enable_steps_tracing,
4022 print_logs,
4023 print_traces,
4024 call_trace_decoder: Arc::new(RwLock::new(call_trace_decoder)),
4025 prune_state_history_config,
4026 transaction_block_keeper,
4027 node_config,
4028 slots_in_an_epoch,
4029 precompile_factory,
4030 mining: Arc::new(tokio::sync::Mutex::new(())),
4031 disable_pool_balance_checks,
4032 startup_fork_cache_user,
4033 };
4034
4035 #[cfg(feature = "monad")]
4036 let monad_fork =
4037 if backend.networks.is_monad() { backend.fork.read().clone() } else { None };
4038 #[cfg(feature = "monad")]
4039 if let Some(fork) = monad_fork {
4040 monad::cache_fork_context(&fork).await?;
4041 }
4042
4043 if let Some(interval_block_time) = automine_block_time {
4044 backend.update_interval_mine_block_time(interval_block_time);
4045 }
4046
4047 backend.apply_genesis().await.wrap_err("failed to create genesis")?;
4049 Ok(backend)
4050 }
4051
4052 async fn apply_genesis(&self) -> Result<(), DatabaseError> {
4056 trace!(target: "backend", "setting genesis balances");
4057
4058 if self.fork.read().is_some() {
4059 return self
4060 .apply_fork_genesis(
4061 Arc::clone(&self.db),
4062 self.startup_fork_cache_user.cache_lease.clone(),
4063 )
4064 .await;
4065 }
4066
4067 let mut db = self.db.write().await;
4068 for (account, info) in self.genesis.account_infos() {
4069 db.insert_account(account, info);
4070 }
4071
4072 db.insert_block_hash(U256::from(self.best_number()), self.best_hash());
4075
4076 if let Some(transitions) =
4077 self.ethereum_block_transitions(self.hardfork(), None, BlockExecutionKind::Complete)
4078 {
4079 if transitions.hardfork >= EthereumHardfork::Cancun {
4080 db.set_code(eip4788::BEACON_ROOTS_ADDRESS, eip4788::BEACON_ROOTS_CODE.clone())?;
4081 }
4082 if transitions.hardfork >= EthereumHardfork::Prague {
4083 db.set_code(
4084 eip2935::HISTORY_STORAGE_ADDRESS,
4085 eip2935::HISTORY_STORAGE_CODE.clone(),
4086 )?;
4087 db.set_code(
4088 eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
4089 eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone(),
4090 )?;
4091 db.set_code(
4092 eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
4093 eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_CODE.clone(),
4094 )?;
4095 }
4096 }
4097 self.genesis.apply_genesis_json_alloc(&mut **db)?;
4099 drop(db);
4100 self.apply_funded_accounts(&self.db).await?;
4101
4102 if self.networks.is_tempo() && !self.is_fork() {
4105 let chain_id = self.evm_env.read().cfg_env.chain_id;
4106 let timestamp = self.genesis.timestamp;
4107 let test_accounts: Vec<Address> = self.genesis.accounts.clone();
4108 let hardfork = self.tempo_hardfork();
4109 let mut db = self.db.write().await;
4110 crate::eth::backend::tempo::initialize_tempo_precompiles(
4111 &mut **db,
4112 chain_id,
4113 timestamp,
4114 &test_accounts,
4115 hardfork,
4116 )
4117 .map_err(|e| {
4118 tracing::error!(target: "backend", "failed to initialize Tempo precompiles: {e}");
4119 DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))
4120 })?;
4121 trace!(target: "backend", "initialized Tempo precompiles and fee tokens for {} accounts", test_accounts.len());
4122 }
4123
4124 trace!(target: "backend", "set genesis balances");
4125
4126 Ok(())
4127 }
4128
4129 async fn apply_fork_genesis(
4131 &self,
4132 db: Arc<AsyncRwLock<Box<dyn Db>>>,
4133 cache_lease: StagedForkCacheLease,
4134 ) -> Result<(), DatabaseError> {
4135 let user = StagedForkDbUser { db: Some(db), cache_lease };
4136 let mut genesis_accounts = JoinSet::new();
4137 for address in self.genesis.accounts.iter().copied() {
4138 let task_user = StagedForkDbUser {
4139 db: Some(Arc::clone(user.db())),
4140 cache_lease: user.cache_lease.clone(),
4141 };
4142
4143 genesis_accounts.spawn(async move {
4145 let db = task_user.db().read().await;
4146 let info = db.basic_ref(address)?.unwrap_or_default();
4147 Ok::<_, DatabaseError>((address, info))
4148 });
4149 }
4150
4151 let mut account_infos = Vec::with_capacity(self.genesis.accounts.len());
4152 while let Some(result) = genesis_accounts.join_next().await {
4153 match result {
4154 Ok(Ok(account)) => account_infos.push(account),
4155 Ok(Err(err)) => {
4156 genesis_accounts.shutdown().await;
4157 return Err(err);
4158 }
4159 Err(err) => {
4160 genesis_accounts.shutdown().await;
4161 return Err(DatabaseError::AnyRequest(Arc::new(eyre::eyre!(
4162 "fork genesis account task failed: {err}"
4163 ))));
4164 }
4165 }
4166 }
4167 let mut db_guard = user.db().write().await;
4168 for (address, mut info) in account_infos {
4169 info.balance = self.genesis.balance;
4170 db_guard.insert_account(address, info);
4171 }
4172 self.genesis.apply_genesis_json_alloc(&mut **db_guard)?;
4173 drop(db_guard);
4174 self.apply_funded_accounts(user.db()).await
4175 }
4176
4177 async fn apply_funded_accounts(
4179 &self,
4180 db: &Arc<AsyncRwLock<Box<dyn Db>>>,
4181 ) -> Result<(), DatabaseError> {
4182 let funded_accounts = self.node_config.read().await.funded_accounts.clone();
4183 let mut accounts = Vec::with_capacity(funded_accounts.len());
4184 for (address, balance) in funded_accounts {
4185 let mut info = db.read().await.basic_ref(address)?.unwrap_or_default();
4186 info.balance = balance;
4187 accounts.push((address, info));
4188 }
4189 let mut db = db.write().await;
4190 for (address, info) in accounts {
4191 db.insert_account(address, info);
4192 }
4193 Ok(())
4194 }
4195
4196 #[allow(clippy::too_many_arguments)]
4198 fn populate_memory_db(
4199 db: &mut dyn Db,
4200 genesis: &GenesisConfig,
4201 funded_accounts: &HashMap<Address, U256>,
4202 hardfork: FoundryHardfork,
4203 chain_id: u64,
4204 is_tempo: bool,
4205 tempo_hardfork: Option<TempoHardfork>,
4206 genesis_hash: B256,
4207 install_create2_deployer: bool,
4208 ) -> Result<(), DatabaseError> {
4209 for (account, info) in genesis.account_infos() {
4210 db.insert_account(account, info);
4211 }
4212 db.insert_block_hash(U256::from(genesis.number), genesis_hash);
4213
4214 if let FoundryHardfork::Ethereum(hardfork) = hardfork {
4215 if hardfork >= EthereumHardfork::Cancun {
4216 db.set_code(eip4788::BEACON_ROOTS_ADDRESS, eip4788::BEACON_ROOTS_CODE.clone())?;
4217 }
4218 if hardfork >= EthereumHardfork::Prague {
4219 db.set_code(
4220 eip2935::HISTORY_STORAGE_ADDRESS,
4221 eip2935::HISTORY_STORAGE_CODE.clone(),
4222 )?;
4223 db.set_code(
4224 eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
4225 eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone(),
4226 )?;
4227 db.set_code(
4228 eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
4229 eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_CODE.clone(),
4230 )?;
4231 }
4232 }
4233
4234 genesis.apply_genesis_json_alloc(db)?;
4235 for (&address, &balance) in funded_accounts {
4236 let mut info = db.basic_ref(address)?.unwrap_or_default();
4237 info.balance = balance;
4238 db.insert_account(address, info);
4239 }
4240
4241 if is_tempo {
4242 let hardfork = tempo_hardfork.ok_or_else(|| {
4243 DatabaseError::AnyRequest(Arc::new(eyre::eyre!(
4244 "missing Tempo hardfork during memory reset"
4245 )))
4246 })?;
4247 crate::eth::backend::tempo::initialize_tempo_precompiles(
4248 db,
4249 chain_id,
4250 genesis.timestamp,
4251 &genesis.accounts,
4252 hardfork,
4253 )
4254 .map_err(|err| DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{err}"))))?;
4255 }
4256
4257 if install_create2_deployer {
4258 db.set_code(
4259 DEFAULT_CREATE2_DEPLOYER,
4260 Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE),
4261 )?;
4262 }
4263
4264 Ok(())
4265 }
4266
4267 pub(crate) async fn prepare_fork_reset(
4269 &self,
4270 forking: Forking,
4271 serving_instance_id: B256,
4272 ) -> Result<StagedForkReset, BlockchainError> {
4273 let previous_fork = self.get_fork();
4274 let previous_source = self
4275 .last_fork_cache_source
4276 .read()
4277 .clone()
4278 .or_else(|| previous_fork.as_ref().and_then(ForkCacheSource::from_fork));
4279 let configured_rpc_urls = self.node_config.read().await.fork_urls.clone();
4280 let rpc_url_was_provided = forking.json_rpc_url.is_some();
4281 let target_rpc_urls = if let Some(url) = forking.json_rpc_url {
4282 vec![url]
4283 } else if !configured_rpc_urls.is_empty() {
4284 configured_rpc_urls
4285 } else {
4286 previous_fork
4287 .as_ref()
4288 .map(|fork| fork.config.read().fork_urls.clone())
4289 .filter(|urls| !urls.is_empty())
4290 .ok_or_else(|| {
4291 RpcError::invalid_params(
4292 "Forking not enabled and RPC URL not provided to start forking",
4293 )
4294 })?
4295 };
4296 let flush_old_cache = previous_fork.is_some();
4297 if flush_old_cache {
4298 self.db.write().await.maybe_flush_cache().map_err(BlockchainError::Internal)?;
4301 }
4302
4303 for _ in 0..3 {
4304 if let Some(staged) = self
4305 .stage_fork_reset(
4306 &target_rpc_urls,
4307 forking.block_number,
4308 serving_instance_id,
4309 previous_source.clone(),
4310 flush_old_cache,
4311 rpc_url_was_provided,
4312 )
4313 .await?
4314 {
4315 return Ok(staged);
4316 }
4317 }
4318 Err(BlockchainError::Internal(
4319 "fork endpoint changed while the replacement was being staged".to_string(),
4320 ))
4321 }
4322
4323 async fn stage_fork_reset(
4325 &self,
4326 target_rpc_urls: &[String],
4327 block_number: Option<u64>,
4328 serving_instance_id: B256,
4329 previous_source: Option<ForkCacheSource>,
4330 flush_old_cache: bool,
4331 rpc_url_was_provided: bool,
4332 ) -> Result<Option<StagedForkReset>, BlockchainError> {
4333 let target_rpc_url = target_rpc_urls.first().ok_or_else(|| {
4334 BlockchainError::Internal("at least one fork URL is required".to_string())
4335 })?;
4336 let mut staged_config = self.node_config.read().await.clone();
4337 if rpc_url_was_provided {
4338 staged_config.fork_chain_id = None;
4339 }
4340 let configured_endpoint_is_anvil = staged_config.fork_endpoint_is_anvil
4341 && staged_config.fork_urls.contains(target_rpc_url);
4342 let cached_endpoint_is_anvil = previous_source.as_ref().is_some_and(|source| {
4343 source.rpc_url == *target_rpc_url && source.endpoint_identity.is_authoritative()
4344 });
4345 staged_config.fork_endpoint_is_anvil =
4346 configured_endpoint_is_anvil || cached_endpoint_is_anvil;
4347 staged_config.fork_urls = target_rpc_urls.to_vec();
4348 staged_config.fork_choice = block_number.map(|number| ForkChoice::Block(number as i128));
4349 let mut staged_env = self.evm_env.read().clone();
4350 staged_config.apply_tempo_fork_beneficiary_default(&mut staged_env);
4351 let staged_fees = self.fees.detached();
4352 let (mut staged_db, staged_client_config) = staged_config
4353 .setup_fork_db_config(target_rpc_url.clone(), &mut staged_env, &staged_fees)
4354 .await?;
4355 let cache_lease = StagedForkCacheLease::for_db(staged_db.inner());
4356 let cache_identity_changed = previous_source.as_ref().is_some_and(|source| {
4357 source.authoritative_identity_changed_at_same_url(
4358 target_rpc_url,
4359 staged_client_config.endpoint_identity,
4360 )
4361 });
4362 if cache_identity_changed {
4363 staged_db.clear_into_state_snapshot();
4364 staged_db.insert_block_hash(
4365 U256::from(staged_client_config.block_number),
4366 staged_client_config.block_hash,
4367 );
4368 }
4369 let mut invalidated_cache_namespaces = Vec::new();
4370 if cache_identity_changed && !staged_config.no_storage_caching {
4371 if let Some(source) = &previous_source
4372 && let Some(namespace) = ForkCacheNamespace::new(
4373 source.endpoint_identity.source_chain_id,
4374 target_rpc_url,
4375 )
4376 {
4377 invalidated_cache_namespaces.push(namespace);
4378 }
4379 if let Some(namespace) =
4380 ForkCacheNamespace::new(staged_client_config.chain_id, target_rpc_url)
4381 && !invalidated_cache_namespaces.contains(&namespace)
4382 {
4383 invalidated_cache_namespaces.push(namespace);
4384 }
4385 }
4386 let discard_old_cached_state =
4387 cache_identity_changed && flush_old_cache && !invalidated_cache_namespaces.is_empty();
4388 let staged_db: Arc<AsyncRwLock<Box<dyn Db>>> =
4389 Arc::new(AsyncRwLock::new(Box::new(staged_db)));
4390 let staged_fork = ClientFork::new(staged_client_config.clone(), Arc::clone(&staged_db));
4391 let attempt = async {
4392 let target_networks =
4393 staged_client_config.endpoint_identity.network_profile.unwrap_or_default();
4394 if !staged_config.has_explicit_network_selection()
4395 && !self.networks.supports_fork_source(&target_networks)
4396 {
4397 return Err(RpcError::invalid_params(format!(
4398 "cannot reset Anvil across network families ({} -> {}); start a new \
4399 instance with matching network configuration",
4400 self.execution_profile_name(),
4401 target_networks.execution_profile_name()
4402 ))
4403 .into());
4404 }
4405 if staged_client_config.endpoint_identity.instance_id == Some(serving_instance_id) {
4406 return Err(
4407 RpcError::invalid_params("cannot reset Anvil to its own RPC endpoint").into()
4408 );
4409 }
4410 let fork_block = staged_fork
4411 .block_by_number(staged_fork.block_number())
4412 .await?
4413 .ok_or(BlockchainError::BlockNotFound)?;
4414 if fork_block.header.hash != staged_client_config.block_hash {
4415 return Ok(None);
4416 }
4417 self.apply_fork_genesis(Arc::clone(&staged_db), cache_lease.clone()).await?;
4418
4419 #[cfg(feature = "monad")]
4420 if self.is_monad() {
4421 monad::cache_fork_context(&staged_fork).await?;
4422 }
4423
4424 if !staged_config
4425 .fork_urls_match_context(
4426 target_rpc_urls,
4427 staged_client_config.endpoint_identity,
4428 staged_client_config.block_number,
4429 staged_client_config.block_hash,
4430 )
4431 .await?
4432 {
4433 return Ok(None);
4434 }
4435
4436 Ok(Some((fork_block, staged_fork.storage.read().clone())))
4437 }
4438 .await;
4439 drop(staged_fork);
4440 let (fork_block, staged_storage) = match attempt {
4441 Ok(Some(staged)) => staged,
4442 Ok(None) => {
4443 drop(staged_db);
4444 self.rollback_staged_fork_cache(cache_lease, flush_old_cache).await?;
4445 return Ok(None);
4446 }
4447 Err(err) => {
4448 drop(staged_db);
4449 self.rollback_staged_fork_cache(cache_lease, flush_old_cache).await?;
4450 return Err(err);
4451 }
4452 };
4453 let staged_db = match Arc::try_unwrap(staged_db) {
4454 Ok(staged_db) => staged_db.into_inner(),
4455 Err(staged_db) => {
4456 drop(staged_db);
4457 self.rollback_staged_fork_cache(cache_lease, flush_old_cache).await?;
4458 return Err(BlockchainError::Internal(
4459 "staged fork database still has active references".to_string(),
4460 ));
4461 }
4462 };
4463
4464 let fork = ClientFork::new(staged_client_config, Arc::clone(&self.db));
4465 *fork.storage.write() = staged_storage;
4466 Ok(Some(StagedForkReset {
4467 node_config: staged_config,
4468 db: staged_db,
4469 fees: staged_fees,
4470 evm_env: staged_env,
4471 fork,
4472 timestamp: fork_block.header.timestamp(),
4473 discard_old_cached_state,
4474 invalidated_cache_namespaces,
4475 flush_old_cache,
4476 cache_lease,
4477 }))
4478 }
4479
4480 async fn rollback_staged_fork_cache(
4481 &self,
4482 cache_lease: StagedForkCacheLease,
4483 restore_live_cache: bool,
4484 ) -> Result<(), BlockchainError> {
4485 let rollback_err = cache_lease.rollback().err();
4486 drop(cache_lease);
4489 let restore_err =
4490 if restore_live_cache { self.db.read().await.maybe_flush_cache().err() } else { None };
4491 match (rollback_err, restore_err) {
4492 (None, None) => Ok(()),
4493 (Some(err), None) => Err(err),
4494 (None, Some(err)) => Err(BlockchainError::Internal(format!(
4495 "failed to restore the live fork cache after staged reset rollback: {err}"
4496 ))),
4497 (Some(rollback), Some(restore)) => Err(BlockchainError::Internal(format!(
4498 "{rollback}; restoring the live fork cache also failed: {restore}"
4499 ))),
4500 }
4501 }
4502
4503 pub(crate) async fn commit_fork_reset(
4505 &self,
4506 staged: StagedForkReset,
4507 ) -> Result<(), BlockchainError> {
4508 let fork_block_number = staged.fork.block_number();
4509 let fork_block_hash = staged.fork.block_hash();
4510 let fork_total_difficulty = staged.fork.total_difficulty();
4511 let fork_cache_source = ForkCacheSource::from_fork(&staged.fork);
4512
4513 let mut node_config = self.node_config.write().await;
4516 let mut db = self.db.write().await;
4517 if staged.flush_old_cache {
4518 db.maybe_flush_cache().map_err(BlockchainError::Internal)?;
4519 }
4520 if let Err(err) =
4521 staged.invalidated_cache_namespaces.iter().try_for_each(ForkCacheNamespace::invalidate)
4522 {
4523 let StagedForkReset { db: staged_db, cache_lease, flush_old_cache, .. } = staged;
4526 drop(staged_db);
4527 let rollback_err = cache_lease.rollback().err();
4528 drop(cache_lease);
4529 let restore_err = if flush_old_cache { db.maybe_flush_cache().err() } else { None };
4530 let mut message = err.to_string();
4531 if let Some(err) = rollback_err {
4532 message.push_str(&format!("; staged cache rollback also failed: {err}"));
4533 }
4534 if let Some(err) = restore_err {
4535 message.push_str(&format!("; restoring the live fork cache also failed: {err}"));
4536 }
4537 return Err(BlockchainError::Internal(message));
4538 }
4539 if staged.discard_old_cached_state {
4540 db.clear_into_state_snapshot();
4541 }
4542 staged.cache_lease.disarm();
4543 let StagedForkReset {
4544 node_config: staged_node_config,
4545 db: staged_db,
4546 fees,
4547 evm_env,
4548 fork,
4549 timestamp,
4550 ..
4551 } = staged;
4552 *node_config = staged_node_config;
4553 *db = staged_db;
4554 self.fees.replace_from(&fees);
4555 *self.evm_env.write() = evm_env;
4556 *self.fork.write() = Some(fork);
4557 *self.last_fork_cache_source.write() = fork_cache_source;
4558 *self.blockchain.storage.write() =
4559 BlockchainStorage::forked(fork_block_number, fork_block_hash, fork_total_difficulty);
4560 self.states.write().clear();
4561 self.active_state_snapshots.lock().clear();
4562 self.time.reset(timestamp);
4563 self.cheats.clear_next_block_prevrandao();
4564
4565 trace!(target: "backend", "reset fork");
4566 Ok(())
4567 }
4568
4569 pub(crate) async fn prepare_memory_reset(
4571 &self,
4572 ) -> Result<StagedMemoryReset<N>, BlockchainError> {
4573 let reset_from_fork = self.is_fork();
4574 let genesis_timestamp = self.genesis.timestamp;
4575 let genesis_number = self.genesis.number;
4576 let mut staged_config = self.node_config.read().await.clone();
4577 staged_config.fork_source_chain_id = None;
4578 staged_config.fork_execution_chain_id = None;
4579 staged_config.fork_endpoint_is_anvil = false;
4580 staged_config.restore_fork_overrides();
4581 let local_chain_id = staged_config.get_chain_id();
4582 staged_config.update_wallet_chain_id(local_chain_id);
4583 let (
4584 local_gas_limit,
4585 local_base_fee,
4586 local_base_fee_is_explicit,
4587 local_gas_price,
4588 local_blob_params,
4589 local_blob_excess_gas_and_price,
4590 local_beneficiary,
4591 install_create2_deployer,
4592 ) = {
4593 (
4594 staged_config.gas_limit(),
4595 staged_config.get_base_fee(),
4596 staged_config.base_fee.is_some()
4597 || staged_config
4598 .genesis
4599 .as_ref()
4600 .is_some_and(|genesis| genesis.base_fee_per_gas.is_some()),
4601 staged_config.get_gas_price(),
4602 staged_config.get_blob_params(),
4603 staged_config.get_blob_excess_gas_and_price(),
4604 staged_config.genesis.as_ref().map(|genesis| genesis.coinbase).unwrap_or_default(),
4605 !staged_config.disable_default_create2_deployer,
4606 )
4607 };
4608
4609 let local_hardfork = staged_config.get_hardfork();
4610 let local_spec = SpecId::from(local_hardfork);
4611 let local_tempo_hardfork =
4612 self.networks.is_tempo().then(|| TempoHardfork::from(local_hardfork));
4613 let staged_fees = self.fees.detached();
4614 staged_fees.set_execution_rules(
4615 local_spec,
4616 self.networks.base_fee_params(genesis_timestamp),
4617 local_tempo_hardfork,
4618 );
4619 #[cfg(feature = "optimism")]
4620 if self.networks.is_optimism() {
4621 staged_fees.set_optimism_hardfork(local_hardfork);
4622 }
4623 staged_fees.set_blob_params(local_blob_params);
4624 staged_fees.set_blob_excess_gas_and_price(local_blob_excess_gas_and_price);
4625
4626 let preserve_live_base_fee =
4632 !reset_from_fork && !local_base_fee_is_explicit && local_tempo_hardfork.is_none();
4633 let genesis_base_fee =
4634 if preserve_live_base_fee { staged_fees.base_fee() } else { local_base_fee };
4635
4636 let mut staged_cfg = CfgEnv::default();
4637 staged_cfg.set_spec_and_mainnet_gas_params(local_spec);
4638 staged_cfg.chain_id = local_chain_id;
4639 staged_cfg.limit_contract_code_size = staged_config.code_size_limit;
4640 staged_cfg.disable_eip3607 = true;
4641 staged_cfg.disable_block_gas_limit = staged_config.disable_block_gas_limit;
4642 if !staged_config.enable_tx_gas_limit {
4643 staged_cfg.tx_gas_limit_cap = Some(u64::MAX);
4644 }
4645 if let Some(memory_limit) = staged_config.memory_limit {
4646 staged_cfg.memory_limit = memory_limit;
4647 }
4648 let staged_env = EvmEnv::new(
4649 staged_cfg,
4650 BlockEnv {
4651 number: U256::from(genesis_number),
4652 beneficiary: local_beneficiary,
4653 timestamp: U256::from(genesis_timestamp),
4654 gas_limit: local_gas_limit,
4655 basefee: genesis_base_fee,
4656 prevrandao: Some(B256::ZERO),
4657 ..Default::default()
4658 },
4659 );
4660
4661 let base_fee = staged_fees.is_eip1559().then_some(genesis_base_fee);
4662 let header = genesis_header(&staged_env, base_fee, genesis_timestamp, genesis_number);
4663 let staged_storage = BlockchainStorage::new(foundry_header(&self.networks, header));
4664
4665 staged_fees.set_base_fee(genesis_base_fee);
4669 if staged_fees.is_eip1559() {
4670 let next_base_fee = if staged_fees.tempo_hardfork().is_some() {
4671 staged_fees.get_next_block_base_fee_per_gas(
4672 0,
4673 staged_env.block_env.gas_limit,
4674 genesis_base_fee,
4675 )
4676 } else if preserve_live_base_fee {
4677 crate::eth::fees::INITIAL_BASE_FEE
4678 } else {
4679 genesis_base_fee
4680 };
4681 staged_fees.set_base_fee(next_base_fee);
4682 }
4683 staged_fees.set_gas_price(local_gas_price);
4684
4685 let mut staged_db: Box<dyn Db> = Box::new(StateRootDb::new(
4686 self.prune_state_history_config.is_state_history_supported(),
4687 ));
4688 Self::populate_memory_db(
4689 &mut *staged_db,
4690 &self.genesis,
4691 &staged_config.funded_accounts,
4692 local_hardfork,
4693 local_chain_id,
4694 self.networks.is_tempo(),
4695 local_tempo_hardfork,
4696 staged_storage.genesis_hash,
4697 install_create2_deployer,
4698 )?;
4699
4700 Ok(StagedMemoryReset {
4701 node_config: staged_config,
4702 db: staged_db,
4703 fees: staged_fees,
4704 evm_env: staged_env,
4705 hardfork: local_hardfork,
4706 storage: staged_storage,
4707 timestamp: genesis_timestamp,
4708 flush_old_cache: reset_from_fork,
4709 })
4710 }
4711
4712 pub(crate) async fn commit_memory_reset(
4714 &self,
4715 staged: StagedMemoryReset<N>,
4716 ) -> Result<(), BlockchainError> {
4717 let StagedMemoryReset {
4718 node_config,
4719 db,
4720 fees,
4721 evm_env,
4722 hardfork,
4723 storage,
4724 timestamp,
4725 flush_old_cache,
4726 } = staged;
4727 let mut live_config = self.node_config.write().await;
4728 let mut live_db = self.db.write().await;
4729 if flush_old_cache {
4730 live_db.maybe_flush_cache().map_err(BlockchainError::Internal)?;
4731 }
4732
4733 *live_config = node_config;
4734 *live_db = db;
4735 self.fees.replace_from(&fees);
4736 *self.evm_env.write() = evm_env;
4737 *self.hardfork.write() = hardfork;
4738 *self.fork.write() = None;
4739 *self.blockchain.storage.write() = storage;
4740 self.states.write().clear();
4741 self.active_state_snapshots.lock().clear();
4742 self.time.reset(timestamp);
4743 self.cheats.clear_next_block_prevrandao();
4744 trace!(target: "backend", "reset to fresh in-memory state");
4745 Ok(())
4746 }
4747
4748 pub async fn revert_state_snapshot(&self, id: U256) -> Result<bool, BlockchainError> {
4750 let Some((num, hash, fees, time_offset)) =
4751 self.active_state_snapshots.lock().get(&id).map(|snapshot| {
4752 (snapshot.block_number, snapshot.block_hash, snapshot.fees, snapshot.time_offset)
4753 })
4754 else {
4755 return Ok(false);
4756 };
4757 let block = self.block_by_hash(hash).await?.ok_or(BlockchainError::BlockNotFound)?;
4758 if !self.db.write().await.revert_state(id, RevertStateSnapshotAction::RevertRemove) {
4759 return Ok(false);
4760 }
4761 {
4762 let mut snapshots = self.active_state_snapshots.lock();
4763 snapshots.remove(&id);
4764 snapshots.retain(|snapshot_id, _| *snapshot_id < id);
4765 }
4766 self.blockchain.storage.write().unwind_to(num, hash);
4768
4769 let reset_time = block.header.timestamp();
4770 self.time.reset_with_offset(reset_time, time_offset);
4771 self.cheats.clear_next_block_prevrandao();
4773
4774 {
4775 let mut env = self.evm_env.write();
4776 env.block_env = BlockEnv {
4777 number: U256::from(num),
4778 timestamp: U256::from(block.header.timestamp()),
4779 difficulty: block.header.difficulty(),
4780 prevrandao: Some(block.header.mix_hash().unwrap_or_default()),
4782 gas_limit: block.header.gas_limit(),
4783 beneficiary: env.block_env.beneficiary,
4785 basefee: env.block_env.basefee,
4786 ..Default::default()
4787 };
4788 }
4789 self.fees.restore(fees);
4790 Ok(true)
4791 }
4792
4793 pub async fn inspect_tx(
4795 &self,
4796 tx: Arc<PoolTransaction<FoundryTxEnvelope>>,
4797 ) -> Result<
4798 (InstructionResult, Option<Output>, u64, State, Vec<revm::primitives::Log>),
4799 BlockchainError,
4800 > {
4801 let evm_env = self.next_evm_env();
4802 let db = self.db.read().await;
4803 let mut inspector = self.build_inspector();
4804 #[cfg(feature = "monad")]
4805 let mut monad_context = self
4806 .is_monad()
4807 .then(|| self.monad_context_for_child_of(self.best_hash()))
4808 .transpose()?;
4809 #[cfg(not(feature = "monad"))]
4810 let mut monad_context = None;
4811 let (ResultAndState { result, state }, _) = self
4812 .transact_envelope_with_inspector_ref_and_context(
4813 &**db,
4814 &evm_env,
4815 &mut inspector,
4816 &tx.pending_transaction,
4817 monad_context.as_mut().map(next_monad_context),
4818 )?;
4819 let (exit_reason, gas_used, out, logs) = unpack_execution_result(result);
4820
4821 inspector.print_logs();
4822
4823 if self.print_traces {
4824 inspector.print_traces(self.call_trace_decoder());
4825 }
4826
4827 Ok((exit_reason, out, gas_used, state, logs))
4828 }
4829}
4830
4831impl<N: Network> Backend<N>
4832where
4833 N::ReceiptEnvelope: TxReceipt<Log = alloy_primitives::Log>,
4834{
4835 fn mined_logs_for_block(&self, filter: Filter, block: Block, block_hash: B256) -> Vec<Log> {
4837 let mut all_logs = Vec::new();
4838 let mut block_log_index = 0u32;
4839
4840 let storage = self.blockchain.storage.read();
4841
4842 for tx in block.body.transactions {
4843 let Some(tx) = storage.transactions.get(&tx.hash()) else {
4844 continue;
4845 };
4846
4847 let logs = tx.receipt.logs();
4848 let transaction_hash = tx.info.transaction_hash;
4849
4850 for log in logs {
4851 if filter.matches(log) {
4852 all_logs.push(Log {
4853 inner: log.clone(),
4854 block_hash: Some(block_hash),
4855 block_number: Some(block.header.number()),
4856 block_timestamp: Some(block.header.timestamp()),
4857 transaction_hash: Some(transaction_hash),
4858 transaction_index: Some(tx.info.transaction_index),
4859 log_index: Some(block_log_index as u64),
4860 removed: false,
4861 });
4862 }
4863 block_log_index += 1;
4864 }
4865 }
4866 all_logs
4867 }
4868
4869 fn removed_logs_since(&self, block_number: u64) -> Vec<Log> {
4876 let storage = self.blockchain.storage.read();
4877 let mut all_logs = Vec::new();
4878
4879 for num in (block_number + 1)..=storage.best_number {
4880 if let Some(hash) = storage.hashes.get(&num)
4881 && let Some(block) = storage.blocks.get(hash)
4882 {
4883 let mut block_log_index = 0u64;
4884 for tx in &block.body.transactions {
4885 if let Some(tx) = storage.transactions.get(&tx.hash()) {
4886 for log in tx.receipt.logs() {
4887 all_logs.push(Log {
4888 inner: log.clone(),
4889 block_hash: Some(*hash),
4890 block_number: Some(num),
4891 block_timestamp: Some(block.header.timestamp()),
4892 transaction_hash: Some(tx.info.transaction_hash),
4893 transaction_index: Some(tx.info.transaction_index),
4894 log_index: Some(block_log_index),
4895 removed: true,
4896 });
4897 block_log_index += 1;
4898 }
4899 }
4900 }
4901 }
4902 }
4903
4904 all_logs
4905 }
4906
4907 async fn logs_for_block(
4909 &self,
4910 filter: Filter,
4911 hash: B256,
4912 ) -> Result<Vec<Log>, BlockchainError> {
4913 if let Some(block) = self.blockchain.get_block_by_hash(&hash) {
4914 return Ok(self.mined_logs_for_block(filter, block, hash));
4915 }
4916
4917 if let Some(fork) = self.get_fork() {
4918 return Ok(fork.logs(&filter).await?);
4919 }
4920
4921 Err(BlockchainError::UnknownBlock)
4922 }
4923
4924 async fn logs_for_range(
4926 &self,
4927 filter: &Filter,
4928 mut from: u64,
4929 to: u64,
4930 ) -> Result<Vec<Log>, BlockchainError> {
4931 let mut all_logs = Vec::new();
4932
4933 if let Some(fork) = self.get_fork() {
4935 let to_on_fork = if fork.predates_fork(to) {
4936 to
4937 } else {
4938 fork.block_number()
4940 };
4941
4942 if fork.predates_fork_inclusive(from) {
4943 let filter = filter.clone().from_block(from).to_block(to_on_fork);
4945 all_logs = fork.logs(&filter).await?;
4946
4947 from = fork.block_number() + 1;
4949 }
4950 }
4951
4952 for number in from..=to {
4953 if let Some((block, hash)) = self.get_block_with_hash(number) {
4954 all_logs.extend(self.mined_logs_for_block(filter.clone(), block, hash));
4955 }
4956 }
4957
4958 Ok(all_logs)
4959 }
4960
4961 pub async fn logs(&self, filter: Filter) -> Result<Vec<Log>, BlockchainError> {
4963 trace!(target: "backend", "get logs [{:?}]", filter);
4964 if let Some(hash) = filter.get_block_hash() {
4965 self.logs_for_block(filter, hash).await
4966 } else {
4967 let best = self.best_number();
4968 let to_block =
4969 self.convert_block_number(filter.block_option.get_to_block().copied()).min(best);
4970 let from_block =
4971 self.convert_block_number(filter.block_option.get_from_block().copied());
4972 if from_block > best {
4973 return Err(BlockchainError::BlockOutOfRange(best, from_block));
4974 }
4975
4976 self.logs_for_range(&filter, from_block, to_block).await
4977 }
4978 }
4979
4980 pub fn mined_receipts(&self, hash: B256) -> Option<Vec<N::ReceiptEnvelope>> {
4982 let storage = self.blockchain.storage.read();
4983 let block = storage.blocks.get(&hash)?;
4984 block
4985 .body
4986 .transactions
4987 .iter()
4988 .map(|transaction| {
4989 storage.transactions.get(&transaction.hash()).map(|tx| tx.receipt.clone())
4990 })
4991 .collect()
4992 }
4993}
4994
4995impl<N: Network> Backend<N>
4997where
4998 Self: TransactionValidator<FoundryTxEnvelope>,
4999 N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
5000{
5001 pub async fn mine_block(
5006 &self,
5007 pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
5008 ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
5009 self.do_mine_block(pool_transactions).await
5010 }
5011
5012 pub(crate) async fn apply_fork_transaction_replay(
5014 &self,
5015 replay: ForkTransactionReplay,
5016 ) -> Result<()> {
5017 let source_chain_id = self.protocol_chain_id();
5018 let arbitrum_block_numbers = is_arbitrum(source_chain_id).then(|| {
5019 (
5020 replay.source_block.header().number(),
5021 arbitrum_replay_block_number(&replay.source_block),
5022 )
5023 });
5024 let prepared = prepare_fork_transaction_replay(replay, self.is_monad())?;
5025 let fallback_execution_chain_id = self
5026 .get_fork()
5027 .map(|fork| fork.execution_chain_id())
5028 .unwrap_or_else(|| self.chain_id().to());
5029 let execution_chain_id = prepared.execution_chain_id(fallback_execution_chain_id)?;
5030 let PreparedForkTransactionReplay { transactions, timestamp, parent_beacon_block_root } =
5031 prepared;
5032 eyre::ensure!(!transactions.is_empty(), "fork transaction replay prefix is empty");
5033 let next_timestamp = timestamp.checked_add(1).ok_or_else(|| {
5034 eyre::eyre!("fork transaction replay timestamp cannot be incremented")
5035 })?;
5036
5037 let _mining_guard = self.mining.lock().await;
5038 let current_base_fee = self.base_fee();
5039 let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price();
5040 let mut evm_env = self.evm_env.read().clone();
5041 if evm_env.block_env.basefee == 0 {
5042 evm_env.cfg_env.disable_base_fee = true;
5043 }
5044
5045 let best_number = self.blockchain.storage.read().best_number;
5046 let block_number = best_number.saturating_add(1);
5047 if arbitrum_block_numbers.is_some() {
5048 evm_env.block_env.number = U256::from(block_number);
5049 } else {
5050 evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1));
5051 }
5052 evm_env.block_env.basefee = current_base_fee;
5053 evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price;
5054 evm_env.block_env.timestamp = U256::from(timestamp);
5055
5056 let best_hash = self.blockchain.storage.read().best_hash;
5057 let mut prevrandao_input = [0u8; 40];
5058 prevrandao_input[..32].copy_from_slice(best_hash.as_slice());
5059 prevrandao_input[32..].copy_from_slice(&block_number.to_le_bytes());
5060 evm_env.block_env.prevrandao = Some(
5061 self.cheats.take_next_block_prevrandao().unwrap_or_else(|| keccak256(prevrandao_input)),
5062 );
5063
5064 let mut replay_env = evm_env.clone();
5065 let arbitrum_rpc_block_number = arbitrum_block_numbers.map(|(rpc, evm)| {
5066 replay_env.block_env.number = evm;
5067 rpc
5068 });
5069 replay_env.cfg_env.chain_id = execution_chain_id;
5070 apply_chain_specific_tx_replay_env_changes_for_chain(&mut replay_env, source_chain_id);
5071 let inspector_tx_config = self.inspector_tx_config();
5072
5073 let scheduled_hardfork =
5074 FoundryHardfork::from_chain_and_timestamp(source_chain_id, timestamp);
5075 #[cfg(feature = "monad")]
5076 let mut monad_replay = self
5077 .prepare_monad_fork_replay(
5078 source_chain_id,
5079 execution_chain_id,
5080 timestamp,
5081 best_hash,
5082 &transactions,
5083 )
5084 .await?;
5085 #[cfg(feature = "monad")]
5086 let hardfork = monad_replay
5087 .as_ref()
5088 .map(monad::ForkReplay::hardfork)
5089 .unwrap_or_else(|| scheduled_hardfork.unwrap_or_else(|| self.hardfork()));
5090 #[cfg(not(feature = "monad"))]
5091 let hardfork = scheduled_hardfork.unwrap_or_else(|| self.hardfork());
5092 if !self.is_optimism() && !self.is_tempo() {
5093 replay_env.cfg_env.spec = SpecId::from(hardfork);
5094 if replay_env.cfg_env.spec >= SpecId::CANCUN
5096 && replay_env.block_env.blob_excess_gas_and_price.is_none()
5097 {
5098 replay_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(
5099 0,
5100 get_blob_base_fee_update_fraction_by_spec_id(replay_env.cfg_env.spec),
5101 ));
5102 }
5103 }
5104
5105 #[cfg(feature = "monad")]
5106 let monad_context = monad_replay.as_mut().and_then(monad::ForkReplay::take_context);
5107 #[cfg(not(feature = "monad"))]
5108 let monad_context = None;
5109
5110 let (block_info, state_changes, block_hash) = {
5111 let db = self.db.read().await;
5112 let mut overlay = AnvilCacheDB::new(&**db);
5113 let ExecutedHistoricalReplay {
5114 block_result,
5115 transactions,
5116 transaction_infos,
5117 state_changes,
5118 } = self.execute_with_replay_block_executor(
5119 &mut overlay,
5120 &replay_env,
5121 best_hash,
5122 arbitrum_rpc_block_number,
5123 hardfork,
5124 parent_beacon_block_root,
5125 &transactions,
5126 &inspector_tx_config,
5127 monad_context,
5128 )?;
5129 let state_root = overlay.maybe_state_root().unwrap_or_default();
5130 let block_info = self.build_block_info(
5131 &replay_env,
5132 best_hash,
5133 block_number,
5134 state_root,
5135 block_result,
5136 transactions,
5137 transaction_infos,
5138 parent_beacon_block_root,
5139 );
5140 let block_hash = block_info.block.header.hash_slow();
5141 (block_info, state_changes, block_hash)
5142 };
5143
5144 if self.prune_state_history_config.is_state_history_supported() {
5145 let state = self.db.read().await.current_state();
5146 self.states.write().insert(best_hash, state);
5147 }
5148
5149 {
5150 let mut db = self.db.write().await;
5151 for state in state_changes {
5152 db.commit(state);
5153 }
5154 db.insert_block_hash(U256::from(block_number), block_hash);
5155 }
5156
5157 let BlockInfo { block, transactions, receipts } = block_info;
5158 let header = block.header.clone();
5159 {
5160 let mut storage = self.blockchain.storage.write();
5161 storage.best_number = block_number;
5162 storage.best_hash = block_hash;
5163 if !self.is_eip3675() {
5164 storage.total_difficulty =
5165 storage.total_difficulty.saturating_add(header.difficulty);
5166 }
5167 storage.blocks.insert(block_hash, block);
5168 storage.hashes.insert(block_number, block_hash);
5169 #[cfg(feature = "monad")]
5170 if let Some(replay) = &mut monad_replay {
5171 replay.store_metadata(&mut storage, block_hash);
5172 }
5173 for (info, receipt) in transactions.into_iter().zip(receipts) {
5174 let mined_tx = MinedTransaction { info, receipt, block_hash, block_number };
5175 storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx);
5176 }
5177
5178 if let Some(transaction_block_keeper) = self.transaction_block_keeper
5179 && storage.blocks.len() > transaction_block_keeper
5180 {
5181 let to_clear = block_number
5182 .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX));
5183 storage.remove_block_transactions_by_number(to_clear)
5184 }
5185 }
5186
5187 #[cfg(feature = "monad")]
5188 if let Some(replay) = &monad_replay {
5189 self.finalize_monad_fork_replay(replay, &mut evm_env);
5190 }
5191
5192 evm_env.block_env.difficulty = U256::ZERO;
5193 *self.evm_env.write() = evm_env;
5194 self.time.reset(timestamp);
5195 self.time.set_next_block_timestamp(next_timestamp)?;
5196
5197 #[cfg(feature = "optimism")]
5198 if self.is_optimism() {
5199 self.fees.set_optimism_base_fee_rules(header.extra_data());
5200 }
5201 let next_block_base_fee = self.fees.get_next_block_base_fee_from_header(&header);
5202 let next_block_excess_blob_gas = self.networks.next_block_blob_excess_gas(
5203 self.fees.blob_params(),
5204 header.excess_blob_gas.unwrap_or_default(),
5205 header.blob_gas_used.unwrap_or_default(),
5206 header.base_fee_per_gas.unwrap_or_default(),
5207 );
5208 self.fees.set_base_fee(next_block_base_fee);
5209 self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
5210 next_block_excess_blob_gas,
5211 get_blob_base_fee_update_fraction_by_spec_id(*self.evm_env.read().spec_id()),
5212 ));
5213 self.notify_on_new_block(header.into_inner(), block_hash);
5214
5215 Ok(())
5216 }
5217
5218 #[allow(clippy::too_many_arguments)]
5219 fn execute_with_replay_block_executor<DB>(
5220 &self,
5221 db: DB,
5222 evm_env: &EvmEnv,
5223 parent_hash: B256,
5224 arbitrum_rpc_block_number: Option<u64>,
5225 hardfork: FoundryHardfork,
5226 parent_beacon_block_root: Option<B256>,
5227 transactions: &[HistoricalReplayTransaction],
5228 inspector_tx_config: &InspectorTxConfig,
5229 #[cfg_attr(not(feature = "monad"), allow(unused_variables))] monad_context: Option<
5230 MonadReplayContext,
5231 >,
5232 ) -> Result<ExecutedHistoricalReplay>
5233 where
5234 DB: StateDB<Error = DatabaseError>,
5235 {
5236 #[cfg(feature = "monad")]
5237 if self.is_monad() {
5238 return self.execute_with_monad_replay_block_executor(
5239 db,
5240 evm_env,
5241 parent_hash,
5242 hardfork,
5243 transactions,
5244 inspector_tx_config,
5245 monad_context,
5246 );
5247 }
5248
5249 let inspector = self.build_mining_inspector();
5250 let ethereum_transitions = self.ethereum_block_transitions(
5251 hardfork,
5252 parent_beacon_block_root,
5253 BlockExecutionKind::TransactionPrefix,
5254 );
5255
5256 macro_rules! run {
5257 ($evm:expr) => {{
5258 run!($evm, |executor| execute_historical_replay(
5259 executor,
5260 transactions,
5261 inspector_tx_config,
5262 ))
5263 }};
5264 ($evm:expr, $execute:expr) => {{
5265 self.inject_precompiles($evm.precompiles_mut(), evm_env);
5266 if let Some(block_number) = arbitrum_rpc_block_number {
5267 self.inject_arbitrum_precompile_at_block($evm.precompiles_mut(), block_number);
5268 }
5269 let mut executor = AnvilBlockExecutor::new(
5273 $evm,
5274 parent_hash,
5275 *evm_env.spec_id(),
5276 ethereum_transitions,
5277 )
5278 .with_state_changes();
5279 #[cfg(feature = "optimism")]
5280 if self.is_optimism() {
5281 executor.set_optimism_hardfork(hardfork);
5282 }
5283 executor
5284 .apply_pre_execution_changes()
5285 .wrap_err("failed to apply replay block-start transitions")?;
5286 let (stored_transactions, transaction_infos) = $execute(&mut executor)?;
5287 let state_changes = executor.take_state_changes();
5288 let (evm, block_result) =
5289 executor.finish().wrap_err("failed to finish replay block execution")?;
5290 drop(evm);
5291 Ok(ExecutedHistoricalReplay {
5292 block_result,
5293 transactions: stored_transactions,
5294 transaction_infos,
5295 state_changes,
5296 })
5297 }};
5298 }
5299
5300 #[cfg(feature = "optimism")]
5301 if self.is_optimism() {
5302 let op_env = EvmEnv::new(
5303 evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(hardfork.into()),
5304 evm_env.block_env.clone(),
5305 );
5306 let mut evm =
5307 OpEvmFactory::<OpTx>::default().create_evm_with_inspector(db, op_env, inspector);
5308 return run!(evm);
5309 }
5310
5311 if self.is_tempo() {
5312 let tempo_env = self.build_tempo_evm_env(evm_env);
5313 let mut evm =
5314 TempoEvmFactory::default().create_evm_with_inspector(db, tempo_env, inspector);
5315 return run!(evm);
5316 }
5317
5318 let mut evm =
5319 EthEvmFactory::default().create_evm_with_inspector(db, evm_env.clone(), inspector);
5320 run!(evm)
5321 }
5322
5323 #[allow(clippy::too_many_arguments)]
5325 fn build_block_info(
5326 &self,
5327 evm_env: &EvmEnv,
5328 parent_hash: B256,
5329 number: u64,
5330 state_root: B256,
5331 block_result: BlockExecutionResult<FoundryReceiptEnvelope>,
5332 transactions: Vec<MaybeImpersonatedTransaction<FoundryTxEnvelope>>,
5333 transaction_infos: Vec<TransactionInfo>,
5334 parent_beacon_block_root: Option<B256>,
5335 ) -> BlockInfo<N> {
5336 let spec_id = *evm_env.spec_id();
5337 let is_shanghai = spec_id >= SpecId::SHANGHAI;
5338 let is_cancun = spec_id >= SpecId::CANCUN;
5339 let is_prague = spec_id >= SpecId::PRAGUE;
5340
5341 let receipts_root = calculate_receipt_root(&block_result.receipts);
5342 let cumulative_blob_gas_used = is_cancun.then_some(block_result.blob_gas_used);
5343 let bloom = block_result.receipts.iter().fold(Bloom::default(), |mut b, r| {
5344 b.accrue_bloom(r.logs_bloom());
5345 b
5346 });
5347
5348 let header = Header {
5349 parent_hash,
5350 ommers_hash: Default::default(),
5351 beneficiary: evm_env.block_env.beneficiary,
5352 state_root,
5353 transactions_root: Default::default(),
5354 receipts_root,
5355 logs_bloom: bloom,
5356 difficulty: evm_env.block_env.difficulty,
5357 number,
5358 gas_limit: evm_env.block_env.gas_limit,
5359 gas_used: block_result.gas_used,
5360 timestamp: evm_env.block_env.timestamp.saturating_to(),
5361 extra_data: self.fees.base_fee_extra_data(),
5362 mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(),
5363 nonce: Default::default(),
5364 base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(evm_env.block_env.basefee),
5365 parent_beacon_block_root: is_cancun
5366 .then(|| parent_beacon_block_root.unwrap_or_default()),
5367 blob_gas_used: cumulative_blob_gas_used,
5368 excess_blob_gas: if is_cancun { evm_env.block_env.blob_excess_gas() } else { None },
5369 withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
5370 requests_hash: is_prague.then(|| block_result.requests.requests_hash()),
5371 block_access_list_hash: None,
5372 slot_number: None,
5373 };
5374
5375 let block = create_block(foundry_header(&self.networks, header), transactions);
5376 BlockInfo { block, transactions: transaction_infos, receipts: block_result.receipts }
5377 }
5378
5379 async fn do_mine_block(
5380 &self,
5381 pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
5382 ) -> Result<MinedBlockOutcome<FoundryTxEnvelope>, BlockchainError> {
5383 let _mining_guard = self.mining.lock().await;
5384 trace!(target: "backend", "creating new block with {} transactions", pool_transactions.len());
5385
5386 let (outcome, header, block_hash) = {
5387 let current_base_fee = self.base_fee();
5388 let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price();
5389
5390 let mut evm_env = self.evm_env.read().clone();
5391 let hardfork = self.hardfork();
5392
5393 if evm_env.block_env.basefee == 0 {
5394 evm_env.cfg_env.disable_base_fee = true;
5397 }
5398
5399 let block_number = self.blockchain.storage.read().best_number.saturating_add(1);
5400
5401 if is_arbitrum(self.protocol_chain_id()) {
5403 evm_env.block_env.number = U256::from(block_number);
5405 } else {
5406 evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1));
5407 }
5408
5409 evm_env.block_env.basefee = current_base_fee;
5410 evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price;
5411
5412 let best_hash = self.blockchain.storage.read().best_hash;
5413
5414 let mut input = [0u8; 40];
5415 input[..32].copy_from_slice(best_hash.as_slice());
5416 input[32..].copy_from_slice(&block_number.to_le_bytes());
5417 let next_prevrandao = self.cheats.prepare_next_block_prevrandao();
5421 evm_env.block_env.prevrandao =
5422 Some(next_prevrandao.map_or_else(|| keccak256(input), |pending| pending.value));
5423
5424 let (block_info, included, invalid, not_yet_valid, block_hash, parent_state) = {
5425 let mut db = self.db.write().await;
5426
5427 let pending_timestamp = self.time.prepare_next_timestamp();
5431 evm_env.block_env.timestamp = U256::from(pending_timestamp.timestamp);
5432
5433 let mut mining_evm_env = evm_env.clone();
5436 if pool_transactions.iter().any(|tx| tx.is_replay) {
5437 apply_chain_specific_tx_replay_env_changes_for_chain(
5438 &mut mining_evm_env,
5439 self.protocol_chain_id(),
5440 );
5441 }
5442
5443 let spec_id = *mining_evm_env.spec_id();
5444
5445 let inspector_tx_config = self.inspector_tx_config();
5446 let gas_config = self.pool_tx_gas_config(&mining_evm_env);
5447
5448 let mut candidate_db = AnvilCacheDB::new(&**db);
5449 let (pool_result, block_result) = self.execute_with_block_executor(
5450 &mut candidate_db,
5451 &mining_evm_env,
5452 best_hash,
5453 spec_id,
5454 hardfork,
5455 Some(B256::ZERO),
5456 BlockExecutionKind::Complete,
5457 &pool_transactions,
5458 &gas_config,
5459 &inspector_tx_config,
5460 &|pool_tx, account| {
5461 let validation_env =
5462 if pool_tx.is_replay { &mining_evm_env } else { &evm_env };
5463 self.validate_mining_pool_transaction_for(pool_tx, account, validation_env)
5464 },
5465 )?;
5466
5467 let included = pool_result.included;
5468 let invalid = pool_result.invalid;
5469 let not_yet_valid = pool_result.not_yet_valid;
5470
5471 let CacheDB { cache, db: _ } = candidate_db.0;
5472 let parent_state = self
5473 .prune_state_history_config
5474 .is_state_history_supported()
5475 .then(|| db.current_state());
5476 commit_cache(&mut **db, cache)?;
5477 let state_root = db.maybe_state_root().unwrap_or_default();
5478 let block_info = self.build_block_info(
5479 &mining_evm_env,
5480 best_hash,
5481 block_number,
5482 state_root,
5483 block_result,
5484 pool_result.txs,
5485 pool_result.tx_info,
5486 Some(B256::ZERO),
5487 );
5488
5489 let block_hash = block_info.block.header.hash_slow();
5491 db.insert_block_hash(U256::from(block_info.block.header.number()), block_hash);
5492 self.time.commit_next_timestamp(pending_timestamp);
5493 if let Some(pending) = next_prevrandao {
5494 self.cheats.consume_next_block_prevrandao(pending);
5495 }
5496
5497 (block_info, included, invalid, not_yet_valid, block_hash, parent_state)
5498 };
5499
5500 let BlockInfo { block, transactions, receipts } = block_info;
5502
5503 let header = block.header.clone();
5504 #[cfg(feature = "monad")]
5505 let monad_participants = self.is_monad().then(|| {
5506 let tx_envs = included
5507 .iter()
5508 .map(|pool_tx| {
5509 build_tx_env_for_pending::<FoundryTxEnvelope, TxEnv>(
5510 &pool_tx.pending_transaction,
5511 self.cheats(),
5512 )
5513 })
5514 .collect::<Vec<_>>();
5515 foundry_evm::core::evm::monad_block_participants(&tx_envs)
5516 });
5517
5518 if let Some(parent_state) = parent_state {
5519 self.states.write().insert(best_hash, parent_state);
5520 }
5521
5522 trace!(
5523 target: "backend",
5524 "Mined block {} with {} tx {:?}",
5525 block_number,
5526 transactions.len(),
5527 transactions.iter().map(|tx| tx.transaction_hash).collect::<Vec<_>>()
5528 );
5529 let mut storage = self.blockchain.storage.write();
5530 storage.best_number = block_number;
5532 storage.best_hash = block_hash;
5533 if !self.is_eip3675() {
5536 storage.total_difficulty =
5537 storage.total_difficulty.saturating_add(header.difficulty);
5538 }
5539
5540 storage.blocks.insert(block_hash, block);
5541 storage.hashes.insert(block_number, block_hash);
5542 #[cfg(feature = "monad")]
5543 if let Some(participants) = monad_participants {
5544 monad::store_block_metadata(
5545 &mut storage,
5546 block_hash,
5547 participants,
5548 evm_env.cfg_env.chain_id,
5549 hardfork,
5550 );
5551 }
5552
5553 node_info!("");
5554 for (info, receipt) in transactions.into_iter().zip(receipts) {
5556 node_info!(" Transaction: {:?}", info.transaction_hash);
5558 if let Some(contract) = &info.contract_address {
5559 node_info!(" Contract created: {contract}");
5560 }
5561 node_info!(" Gas used: {}", info.gas_used);
5562 if !info.exit.is_ok() {
5563 let r = RevertDecoder::new().decode(
5564 info.out.as_ref().map(|b| &b[..]).unwrap_or_default(),
5565 Some(info.exit),
5566 );
5567 node_info!(" Error: reverted with: {r}");
5568 }
5569 node_info!("");
5570
5571 let mined_tx = MinedTransaction { info, receipt, block_hash, block_number };
5572 storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx);
5573 }
5574
5575 if let Some(transaction_block_keeper) = self.transaction_block_keeper
5577 && storage.blocks.len() > transaction_block_keeper
5578 {
5579 let to_clear = block_number
5580 .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX));
5581 storage.remove_block_transactions_by_number(to_clear)
5582 }
5583
5584 self.time.mark_block_created();
5585
5586 evm_env.block_env.difficulty = U256::from(0);
5588
5589 *self.evm_env.write() = evm_env;
5591
5592 let timestamp = utc_from_secs(header.timestamp);
5593
5594 node_info!(" Block Number: {}", block_number);
5595 node_info!(" Block Hash: {:?}", block_hash);
5596 if timestamp.year() > 9999 {
5597 node_info!(" Block Time: {:?}\n", timestamp.to_rfc3339());
5599 } else {
5600 node_info!(" Block Time: {:?}\n", timestamp.to_rfc2822());
5601 }
5602
5603 let outcome = MinedBlockOutcome { block_number, included, invalid, not_yet_valid };
5604
5605 (outcome, header, block_hash)
5606 };
5607 let next_block_base_fee = self.fees.get_next_block_base_fee_from_header(&header);
5608 let next_block_excess_blob_gas = self.networks.next_block_blob_excess_gas(
5609 self.fees.blob_params(),
5610 header.excess_blob_gas.unwrap_or_default(),
5611 header.blob_gas_used.unwrap_or_default(),
5612 header.base_fee_per_gas.unwrap_or_default(),
5613 );
5614
5615 self.fees.set_base_fee(next_block_base_fee);
5617
5618 self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
5619 next_block_excess_blob_gas,
5620 self.fees.blob_params().update_fraction as u64,
5621 ));
5622
5623 self.notify_on_new_block(header.into_inner(), block_hash);
5625
5626 Ok(outcome)
5627 }
5628
5629 pub async fn reorg(
5636 &self,
5637 depth: u64,
5638 tx_pairs: HashMap<u64, Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>>,
5639 common_block: Block,
5640 ) -> Result<(), BlockchainError> {
5641 self.rollback(common_block).await?;
5642 for i in 0..depth {
5644 let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new);
5645 let outcome = self.do_mine_block(to_be_mined).await?;
5646 node_info!(
5647 " Mined reorg block number {}. With {} valid txs and with invalid {} txs",
5648 outcome.block_number,
5649 outcome.included.len(),
5650 outcome.invalid.len()
5651 );
5652 }
5653
5654 Ok(())
5655 }
5656
5657 pub async fn pending_block(
5661 &self,
5662 pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
5663 ) -> BlockInfo<N> {
5664 self.with_pending_block(pool_transactions, |_, block| block).await
5665 }
5666
5667 pub async fn with_pending_block<F, T>(
5671 &self,
5672 pool_transactions: Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>,
5673 f: F,
5674 ) -> T
5675 where
5676 F: FnOnce(Box<dyn MaybeFullDatabase + '_>, BlockInfo<N>) -> T,
5677 {
5678 let db = self.db.read().await;
5679 let evm_env = self.next_evm_env();
5680
5681 let mut cache_db = AnvilCacheDB::new(&*db);
5682
5683 let parent_hash = self.blockchain.storage.read().best_hash;
5684
5685 let spec_id = *evm_env.spec_id();
5686
5687 let inspector_tx_config = self.inspector_tx_config();
5688 let gas_config = self.pool_tx_gas_config(&evm_env);
5689
5690 let (pool_result, block_result) = self
5691 .execute_with_block_executor(
5692 &mut cache_db,
5693 &evm_env,
5694 parent_hash,
5695 spec_id,
5696 self.hardfork(),
5697 Some(B256::ZERO),
5698 BlockExecutionKind::Complete,
5699 &pool_transactions,
5700 &gas_config,
5701 &inspector_tx_config,
5702 &|pool_tx, account| {
5703 self.validate_pool_transaction_for(
5704 &pool_tx.pending_transaction,
5705 account,
5706 &evm_env,
5707 )
5708 },
5709 )
5710 .expect("pending block execution failed");
5711
5712 let cache_db = cache_db.0;
5714
5715 let state_root = cache_db.maybe_state_root().unwrap_or_default();
5716 let block_number = evm_env.block_env.number.saturating_to();
5717 let block_info = self.build_block_info(
5718 &evm_env,
5719 parent_hash,
5720 block_number,
5721 state_root,
5722 block_result,
5723 pool_result.txs,
5724 pool_result.tx_info,
5725 Some(B256::ZERO),
5726 );
5727
5728 f(Box::new(cache_db), block_info)
5729 }
5730
5731 pub async fn get_fee_token_balance(
5736 &self,
5737 token: Address,
5738 account: Address,
5739 ) -> Result<U256, BlockchainError> {
5740 let mut calldata = vec![0x70, 0xa0, 0x82, 0x31];
5742 calldata.extend_from_slice(&[0u8; 12]);
5744 calldata.extend_from_slice(account.as_slice());
5745
5746 let request = WithOtherFields::new(TransactionRequest {
5747 from: Some(Address::ZERO),
5748 to: Some(TxKind::Call(token)),
5749 input: calldata.into(),
5750 ..Default::default()
5751 });
5752
5753 let fee_details = FeeDetails::zero();
5754 let (exit, out, _, _) = self.call(request, fee_details, None, Default::default()).await?;
5755
5756 if exit != InstructionResult::Return && exit != InstructionResult::Stop {
5758 return Ok(U256::ZERO);
5760 }
5761
5762 match out {
5764 Some(Output::Call(data)) if data.len() >= 32 => Ok(U256::from_be_slice(&data[..32])),
5765 _ => Ok(U256::ZERO),
5766 }
5767 }
5768
5769 pub async fn tempo_fee_payer(&self) -> Option<Address> {
5773 if !self.is_tempo() {
5774 return None;
5775 }
5776 self.node_config.read().await.tempo_fee_payer_address()
5777 }
5778
5779 pub async fn tempo_user_fee_token(&self, account: Address) -> Result<Address, BlockchainError> {
5783 let calldata = IFeeManager::userTokensCall { user: account }.abi_encode();
5784
5785 let request = WithOtherFields::new(TransactionRequest {
5786 from: Some(Address::ZERO),
5787 to: Some(TxKind::Call(TIP_FEE_MANAGER_ADDRESS)),
5788 input: calldata.into(),
5789 ..Default::default()
5790 });
5791
5792 let (exit, out, _, _) =
5793 self.call(request, FeeDetails::zero(), None, Default::default()).await?;
5794
5795 let token = if exit == InstructionResult::Return
5796 && let Some(Output::Call(data)) = out
5797 {
5798 IFeeManager::userTokensCall::abi_decode_returns(&data).unwrap_or(Address::ZERO)
5799 } else {
5800 Address::ZERO
5801 };
5802
5803 if token.is_zero() {
5804 return Ok(foundry_evm::core::tempo::PATH_USD_ADDRESS);
5805 }
5806 Ok(token)
5807 }
5808
5809 pub async fn call(
5815 &self,
5816 request: WithOtherFields<TransactionRequest>,
5817 fee_details: FeeDetails,
5818 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
5819 overrides: EvmOverrides,
5820 ) -> Result<(InstructionResult, Option<Output>, u128, State), BlockchainError> {
5821 self.with_database_at_and_context(block_request, |state, mut block, monad_context| {
5822 let block_number = block.number;
5823 let (exit, out, gas, state) = {
5824 let mut cache_db = CacheDB::new(state);
5825 if let Some(state_overrides) = overrides.state {
5826 apply_state_overrides(state_overrides.into_iter().collect(), &mut cache_db)?;
5827 }
5828 if let Some(block_overrides) = overrides.block {
5829 cache_db.apply_block_overrides(*block_overrides, &mut block);
5830 }
5831 self.call_with_state_and_context(
5832 &cache_db,
5833 request,
5834 fee_details,
5835 block,
5836 monad_context,
5837 )
5838 }?;
5839 trace!(target: "backend", "call return {:?} out: {:?} gas {} on block {}", exit, out, gas, block_number);
5840 Ok((exit, out, gas, state))
5841 })
5842 .await
5843 }
5844
5845 pub async fn call_with_tracing(
5846 &self,
5847 request: WithOtherFields<TransactionRequest>,
5848 fee_details: FeeDetails,
5849 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
5850 opts: GethDebugTracingCallOptions,
5851 ) -> Result<GethTrace, BlockchainError> {
5852 let GethDebugTracingCallOptions {
5853 tracing_options,
5854 block_overrides,
5855 state_overrides,
5856 tx_index,
5857 } = opts;
5858
5859 if let Some(tx_index) = tx_index {
5860 return self
5861 .call_with_tracing_at_tx_index(
5862 request,
5863 fee_details,
5864 block_request,
5865 tx_index,
5866 tracing_options,
5867 state_overrides,
5868 block_overrides,
5869 )
5870 .await;
5871 }
5872
5873 self.with_database_at_and_context(block_request, |state, block, monad_context| {
5874 let cache_db = CacheDB::new(state);
5875 self.trace_call_with_state(
5876 request,
5877 fee_details,
5878 block,
5879 cache_db,
5880 tracing_options,
5881 state_overrides,
5882 block_overrides,
5883 monad_context,
5884 None,
5885 )
5886 })
5887 .await
5888 }
5889
5890 #[allow(clippy::too_many_arguments)]
5891 async fn call_with_tracing_at_tx_index(
5892 &self,
5893 request: WithOtherFields<TransactionRequest>,
5894 fee_details: FeeDetails,
5895 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
5896 tx_index: u64,
5897 tracing_options: GethDebugTracingOptions,
5898 state_overrides: Option<StateOverride>,
5899 block_overrides: Option<BlockOverrides>,
5900 ) -> Result<GethTrace, BlockchainError> {
5901 let tx_index = usize::try_from(tx_index).map_err(|_| {
5902 BlockchainError::RpcError(RpcError::invalid_params(format!(
5903 "tx_index {tx_index} does not fit in usize"
5904 )))
5905 })?;
5906 let block_number = match block_request {
5907 Some(BlockRequest::Pending(_)) => {
5908 return Err(BlockchainError::RpcError(RpcError::invalid_params(
5909 "tx_index is not supported for pending blocks".to_string(),
5910 )));
5911 }
5912 Some(BlockRequest::Number(number)) => number,
5913 None => self.best_number(),
5914 };
5915 let block_id = BlockId::Number(BlockNumber::Number(block_number));
5916
5917 if let Some(block) = self.get_block(block_id) {
5918 return self.mined_trace_call_at_tx_index(
5919 request,
5920 fee_details,
5921 &block,
5922 tx_index,
5923 tracing_options,
5924 state_overrides,
5925 block_overrides,
5926 );
5927 }
5928
5929 if let Some(fork) = self.get_fork()
5930 && fork.predates_fork_inclusive(block_number)
5931 {
5932 let opts = GethDebugTracingCallOptions {
5933 tracing_options,
5934 state_overrides,
5935 block_overrides,
5936 tx_index: Some(tx_index as u64),
5937 };
5938 return Ok(fork.debug_trace_call(request, block_id, opts).await?);
5939 }
5940
5941 Err(BlockchainError::BlockNotFound)
5942 }
5943
5944 #[allow(clippy::too_many_arguments)]
5945 fn mined_trace_call_at_tx_index(
5946 &self,
5947 request: WithOtherFields<TransactionRequest>,
5948 fee_details: FeeDetails,
5949 block: &Block,
5950 tx_index: usize,
5951 tracing_options: GethDebugTracingOptions,
5952 state_overrides: Option<StateOverride>,
5953 block_overrides: Option<BlockOverrides>,
5954 ) -> Result<GethTrace, BlockchainError> {
5955 let transaction_count = block.body.transactions.len();
5956 if tx_index >= transaction_count {
5957 return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
5958 "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
5959 ))));
5960 }
5961
5962 let trace = |parent_state: &StateDb| -> Result<GethTrace, BlockchainError> {
5963 let db = Box::new(parent_state) as Box<dyn MaybeFullDatabase + '_>;
5964 let (mut cache_db, evm_env, hardfork) = self.prepare_block_replay_with_db(block, db)?;
5965 self.replay_mined_transaction_prefix(
5966 &mut cache_db,
5967 &evm_env,
5968 hardfork,
5969 block,
5970 tx_index,
5971 )?;
5972 self.trace_call_with_state(
5973 request,
5974 fee_details,
5975 evm_env.block_env.clone(),
5976 cache_db,
5977 tracing_options,
5978 state_overrides,
5979 block_overrides,
5980 self.active_monad_context_before_mined_transaction(block, tx_index)?,
5981 Some((evm_env, hardfork)),
5982 )
5983 };
5984
5985 let read_guard = self.states.upgradable_read();
5986 if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
5987 trace(state)
5988 } else {
5989 let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
5990 let state = write_guard
5991 .get_on_disk_state(&block.header.parent_hash)
5992 .ok_or(BlockchainError::BlockNotFound)?;
5993 trace(state)
5994 }
5995 }
5996
5997 #[allow(clippy::too_many_arguments)]
5998 fn trace_call_with_state(
5999 &self,
6000 request: WithOtherFields<TransactionRequest>,
6001 fee_details: FeeDetails,
6002 mut block: BlockEnv,
6003 mut cache_db: CacheDB<Box<dyn MaybeFullDatabase + '_>>,
6004 tracing_options: GethDebugTracingOptions,
6005 state_overrides: Option<StateOverride>,
6006 block_overrides: Option<BlockOverrides>,
6007 mut monad_context: Option<MonadReplayContext>,
6008 historical_execution: Option<(EvmEnv, FoundryHardfork)>,
6009 ) -> Result<GethTrace, BlockchainError> {
6010 let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options;
6011 let block_number = block.number;
6012 let base_evm_env = historical_execution.as_ref().map(|(evm_env, _)| evm_env);
6013 let hardfork = historical_execution
6014 .as_ref()
6015 .map(|(_, hardfork)| *hardfork)
6016 .unwrap_or_else(|| self.hardfork());
6017
6018 if let Some(state_overrides) = state_overrides {
6019 apply_state_overrides(state_overrides, &mut cache_db)?;
6020 }
6021 if let Some(block_overrides) = block_overrides {
6022 cache_db.apply_block_overrides(block_overrides, &mut block);
6023 }
6024
6025 if let Some(tracer) = tracer {
6026 return match tracer {
6027 GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
6028 GethDebugBuiltInTracerType::CallTracer => {
6029 let call_config = call_config_from_tracer_config(tracer_config)
6030 .map_err(|e| RpcError::invalid_params(e.to_string()))?;
6031
6032 let mut inspector = self.build_inspector().with_tracing_config(
6033 TracingInspectorConfig::from_geth_call_config(&call_config),
6034 );
6035
6036 let PreparedCall { evm_env, tx_env, .. } = self
6037 .prepare_call_env_from_base(
6038 &cache_db,
6039 request,
6040 fee_details,
6041 block,
6042 base_evm_env,
6043 )?;
6044 let ResultAndState { result, state: _ } = self
6045 .transact_call_with_inspector_ref_at_hardfork(
6046 &cache_db,
6047 &evm_env,
6048 &mut inspector,
6049 tx_env,
6050 monad_context.as_mut().map(next_monad_context),
6051 hardfork,
6052 )?;
6053
6054 inspector.print_logs();
6055 if self.print_traces {
6056 inspector.print_traces(self.call_trace_decoder());
6057 }
6058
6059 let tracing_inspector = inspector.tracer.expect("tracer disappeared");
6060
6061 Ok(tracing_inspector
6062 .into_geth_builder()
6063 .geth_call_traces(call_config, result.tx_gas_used())
6064 .into())
6065 }
6066 GethDebugBuiltInTracerType::PreStateTracer => {
6067 let pre_state_config = tracer_config
6068 .into_pre_state_config()
6069 .map_err(|e| RpcError::invalid_params(e.to_string()))?;
6070
6071 let mut inspector = TracingInspector::new(
6072 TracingInspectorConfig::from_geth_prestate_config(&pre_state_config),
6073 );
6074
6075 let PreparedCall { evm_env, tx_env, .. } = self
6076 .prepare_call_env_from_base(
6077 &cache_db,
6078 request,
6079 fee_details,
6080 block,
6081 base_evm_env,
6082 )?;
6083 let result = self.transact_call_with_inspector_ref_at_hardfork(
6084 &cache_db,
6085 &evm_env,
6086 &mut inspector,
6087 tx_env,
6088 monad_context.as_mut().map(next_monad_context),
6089 hardfork,
6090 )?;
6091
6092 Ok(inspector
6093 .into_geth_builder()
6094 .geth_prestate_traces(&result, &pre_state_config, cache_db)?
6095 .into())
6096 }
6097 GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()),
6098 GethDebugBuiltInTracerType::FourByteTracer
6099 | GethDebugBuiltInTracerType::MuxTracer
6100 | GethDebugBuiltInTracerType::FlatCallTracer
6101 | GethDebugBuiltInTracerType::Erc7562Tracer
6102 | GethDebugBuiltInTracerType::StateGasTracer => {
6103 Err(RpcError::invalid_params("unsupported tracer type").into())
6104 }
6105 },
6106 #[cfg(not(feature = "js-tracer"))]
6107 GethDebugTracerType::JsTracer(_) => {
6108 Err(RpcError::invalid_params("unsupported tracer type").into())
6109 }
6110 #[cfg(feature = "js-tracer")]
6111 GethDebugTracerType::JsTracer(code) => {
6112 let config = tracer_config.into_json();
6113 let mut inspector =
6114 revm_inspectors::tracing::js::JsInspector::new(code, config)
6115 .map_err(|err| BlockchainError::Message(err.to_string()))?;
6116
6117 let PreparedCall { evm_env, tx_env, .. } = self.prepare_call_env_from_base(
6118 &cache_db,
6119 request,
6120 fee_details,
6121 block.clone(),
6122 base_evm_env,
6123 )?;
6124 let result = self.transact_call_with_inspector_ref_at_hardfork(
6125 &cache_db,
6126 &evm_env,
6127 &mut inspector,
6128 tx_env.clone(),
6129 monad_context.as_mut().map(next_monad_context),
6130 hardfork,
6131 )?;
6132 let res = inspector
6133 .json_result(result, tx_env.base(), &block, &cache_db)
6134 .map_err(|err| BlockchainError::Message(err.to_string()))?;
6135
6136 Ok(GethTrace::JS(res))
6137 }
6138 };
6139 }
6140
6141 let mut inspector = self
6143 .build_inspector()
6144 .with_tracing_config(TracingInspectorConfig::from_geth_config(&config));
6145
6146 let PreparedCall { evm_env, tx_env, .. } =
6147 self.prepare_call_env_from_base(&cache_db, request, fee_details, block, base_evm_env)?;
6148 let ResultAndState { result, state: _ } = self
6149 .transact_call_with_inspector_ref_at_hardfork(
6150 &cache_db,
6151 &evm_env,
6152 &mut inspector,
6153 tx_env,
6154 monad_context.as_mut().map(next_monad_context),
6155 hardfork,
6156 )?;
6157
6158 let (exit_reason, gas_used, out, _logs) = unpack_execution_result(result);
6159
6160 let tracing_inspector = inspector.tracer.expect("tracer disappeared");
6161 let return_value = out.as_ref().map(|o| o.data()).cloned().unwrap_or_default();
6162
6163 trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call");
6164
6165 let res = tracing_inspector
6166 .into_geth_builder()
6167 .geth_traces(gas_used, return_value, config)
6168 .into();
6169
6170 Ok(res)
6171 }
6172
6173 pub async fn with_database_at<F, T>(
6175 &self,
6176 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6177 f: F,
6178 ) -> Result<T, BlockchainError>
6179 where
6180 F: FnOnce(Box<dyn MaybeFullDatabase + '_>, BlockEnv) -> T,
6181 {
6182 let block_number = match block_request {
6183 Some(BlockRequest::Pending(pool_transactions)) => {
6184 let result = self
6185 .with_pending_block(pool_transactions, |state, block| {
6186 let block = block.block;
6187 f(state, block_env_from_header(&block.header))
6188 })
6189 .await;
6190 return Ok(result);
6191 }
6192 Some(BlockRequest::Number(bn)) => Some(BlockNumber::Number(bn)),
6193 None => None,
6194 };
6195 let block_number = self.convert_block_number(block_number);
6196 let current_number = self.best_number();
6197
6198 if block_number > current_number {
6200 return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
6201 }
6202
6203 if block_number < current_number {
6204 if let Some((block_hash, block)) = self
6205 .block_by_number(BlockNumber::Number(block_number))
6206 .await?
6207 .map(|block| (block.header.hash, block))
6208 {
6209 let read_guard = self.states.upgradable_read();
6210 if let Some(state_db) = read_guard.get_state(&block_hash) {
6211 return Ok(f(Box::new(state_db), block_env_from_header(&block.header)));
6212 }
6213
6214 let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6215 if let Some(state) = write_guard.get_on_disk_state(&block_hash) {
6216 return Ok(f(Box::new(state), block_env_from_header(&block.header)));
6217 }
6218 }
6219
6220 warn!(target: "backend", "Not historic state found for block={}", block_number);
6221 return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
6222 }
6223
6224 let db = self.db.read().await;
6225 let block = self.evm_env.read().block_env.clone();
6226 Ok(f(Box::new(&**db), block))
6227 }
6228
6229 pub(crate) async fn with_database_at_and_context<F, T>(
6231 &self,
6232 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6233 f: F,
6234 ) -> Result<T, BlockchainError>
6235 where
6236 F: FnOnce(
6237 Box<dyn MaybeFullDatabase + '_>,
6238 BlockEnv,
6239 Option<MonadReplayContext>,
6240 ) -> Result<T, BlockchainError>,
6241 {
6242 let block_number = match block_request {
6243 Some(BlockRequest::Pending(pool_transactions)) => {
6244 return self
6245 .with_pending_block(pool_transactions, |state, block_info| {
6246 let context = self.active_monad_context_before_mined_transaction(
6247 &block_info.block,
6248 block_info.block.body.transactions.len(),
6249 )?;
6250 let block_env = block_env_from_header(&block_info.block.header);
6251 f(state, block_env, context)
6252 })
6253 .await;
6254 }
6255 Some(BlockRequest::Number(number)) => Some(BlockNumber::Number(number)),
6256 None => None,
6257 };
6258 let block_number = self.convert_block_number(block_number);
6259 let current_number = self.best_number();
6260
6261 if block_number > current_number {
6262 return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
6263 }
6264
6265 #[cfg(feature = "monad")]
6266 let context = if self.is_monad() {
6267 Some(self.monad_context_for_child_of_block_number(block_number).await?)
6268 } else {
6269 None
6270 };
6271 #[cfg(not(feature = "monad"))]
6272 let context = None;
6273
6274 if block_number < current_number {
6275 if let Some((block_hash, block)) = self
6276 .block_by_number(BlockNumber::Number(block_number))
6277 .await?
6278 .map(|block| (block.header.hash, block))
6279 {
6280 let read_guard = self.states.upgradable_read();
6281 if let Some(state_db) = read_guard.get_state(&block_hash) {
6282 return f(Box::new(state_db), block_env_from_header(&block.header), context);
6283 }
6284
6285 let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6286 if let Some(state) = write_guard.get_on_disk_state(&block_hash) {
6287 return f(Box::new(state), block_env_from_header(&block.header), context);
6288 }
6289 }
6290
6291 warn!(target: "backend", "Not historic state found for block={}", block_number);
6292 return Err(BlockchainError::BlockOutOfRange(current_number, block_number));
6293 }
6294
6295 let db = self.db.read().await;
6296 let block = self.evm_env.read().block_env.clone();
6297 f(Box::new(&**db), block, context)
6298 }
6299
6300 pub async fn storage_at(
6301 &self,
6302 address: Address,
6303 index: U256,
6304 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6305 ) -> Result<B256, BlockchainError> {
6306 self.with_database_at(block_request, |db, _| {
6307 trace!(target: "backend", "get storage for {:?} at {:?}", address, index);
6308 let val = db.storage_ref(address, index)?;
6309 Ok(val.into())
6310 })
6311 .await?
6312 }
6313
6314 pub async fn tempo_nonce(
6315 &self,
6316 caller: Address,
6317 nonce_key: U256,
6318 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6319 ) -> Result<u64, BlockchainError> {
6320 self.with_database_at(block_request, |state, _| tempo_nonce(&state, caller, nonce_key))
6321 .await?
6322 }
6323
6324 pub async fn storage_values(
6326 &self,
6327 requests: HashMap<Address, Vec<B256>>,
6328 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6329 ) -> Result<HashMap<Address, Vec<B256>>, BlockchainError> {
6330 self.with_database_at(block_request, |db, _| {
6331 trace!(target: "backend", "get storage values for {} addresses", requests.len());
6332 let mut result: HashMap<Address, Vec<B256>> = HashMap::default();
6333 for (address, slots) in &requests {
6334 let mut values = Vec::with_capacity(slots.len());
6335 for slot in slots {
6336 let val = db.storage_ref(*address, (*slot).into())?;
6337 values.push(val.into());
6338 }
6339 result.insert(*address, values);
6340 }
6341 Ok(result)
6342 })
6343 .await?
6344 }
6345
6346 pub async fn get_code(
6351 &self,
6352 address: Address,
6353 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6354 ) -> Result<Bytes, BlockchainError> {
6355 self.with_database_at(block_request, |db, _| self.get_code_with_state(&db, address)).await?
6356 }
6357
6358 pub async fn get_balance(
6362 &self,
6363 address: Address,
6364 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6365 ) -> Result<U256, BlockchainError> {
6366 self.with_database_at(block_request, |db, _| self.get_balance_with_state(db, address))
6367 .await?
6368 }
6369
6370 pub async fn get_account_at_block(
6371 &self,
6372 address: Address,
6373 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6374 ) -> Result<TrieAccount, BlockchainError> {
6375 self.with_database_at(block_request, |block_db, _| {
6376 let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
6377 let account = db.get(&address).cloned().unwrap_or_default();
6378 let storage_root = storage_root(&account.storage);
6379 let code_hash = account.info.code_hash;
6380 let balance = account.info.balance;
6381 let nonce = account.info.nonce;
6382 Ok(TrieAccount { balance, nonce, code_hash, storage_root })
6383 })
6384 .await?
6385 }
6386
6387 pub async fn get_nonce(
6391 &self,
6392 address: Address,
6393 block_request: BlockRequest<FoundryTxEnvelope>,
6394 ) -> Result<u64, BlockchainError> {
6395 if let BlockRequest::Pending(pool_transactions) = &block_request
6396 && let Some(value) = get_pool_transactions_nonce(pool_transactions, address)
6397 {
6398 return Ok(value);
6399 }
6400 let final_block_request = match block_request {
6401 BlockRequest::Pending(_) => BlockRequest::Number(self.best_number()),
6402 BlockRequest::Number(bn) => BlockRequest::Number(bn),
6403 };
6404
6405 self.with_database_at(Some(final_block_request), |db, _| {
6406 trace!(target: "backend", "get nonce for {:?}", address);
6407 Ok(db.basic_ref(address)?.unwrap_or_default().nonce)
6408 })
6409 .await?
6410 }
6411
6412 fn replay_tx_with_inspector<I, F, T>(
6413 &self,
6414 hash: B256,
6415 mut inspector: I,
6416 f: F,
6417 ) -> Result<T, BlockchainError>
6418 where
6419 for<'a> I: BackendInspector<WrapDatabaseRef<&'a CacheDB<Box<&'a StateDb>>>> + 'a,
6420 for<'a> F:
6421 FnOnce(ResultAndState<HaltReason>, CacheDB<Box<&'a StateDb>>, I, TxEnv, EvmEnv) -> T,
6422 {
6423 let block = {
6424 let storage = self.blockchain.storage.read();
6425 let MinedTransaction { block_hash, .. } = storage
6426 .transactions
6427 .get(&hash)
6428 .cloned()
6429 .ok_or(BlockchainError::TransactionNotFound)?;
6430
6431 storage.blocks.get(&block_hash).cloned().ok_or(BlockchainError::BlockNotFound)?
6432 };
6433
6434 let index = block
6435 .body
6436 .transactions
6437 .iter()
6438 .position(|tx| tx.hash() == hash)
6439 .expect("transaction not found in block");
6440
6441 let trace = |parent_state: &StateDb| -> Result<T, BlockchainError> {
6442 let (mut cache_db, evm_env, hardfork) =
6443 self.prepare_block_replay_with_db(&block, Box::new(parent_state))?;
6444 self.replay_mined_transaction_prefix(&mut cache_db, &evm_env, hardfork, &block, index)?;
6445
6446 let target_tx = block.body.transactions[index].clone();
6447 let target_tx = self.pending_mined_transaction(target_tx)?;
6448 let monad_context = self.active_monad_context_for_mined_block(&block)?;
6449 let transaction_context = monad_execution_context_at(monad_context.as_ref(), index);
6450 let (result, base_tx_env) = self.replay_envelope_with_inspector_ref_and_context(
6451 &cache_db,
6452 &evm_env,
6453 &mut inspector,
6454 &target_tx,
6455 EnvelopeExecution::replay(transaction_context, hardfork),
6456 )?;
6457
6458 Ok(f(result, cache_db, inspector, base_tx_env, evm_env))
6459 };
6460
6461 let read_guard = self.states.upgradable_read();
6462 if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
6463 trace(state)
6464 } else {
6465 let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6466 let state = write_guard
6467 .get_on_disk_state(&block.header.parent_hash)
6468 .ok_or(BlockchainError::BlockNotFound)?;
6469 trace(state)
6470 }
6471 }
6472
6473 #[cfg(feature = "js-tracer")]
6475 pub async fn trace_tx_with_js_tracer(
6476 &self,
6477 hash: B256,
6478 code: String,
6479 opts: GethDebugTracingOptions,
6480 ) -> Result<GethTrace, BlockchainError> {
6481 let GethDebugTracingOptions { tracer_config, .. } = opts;
6482 let config = tracer_config.into_json();
6483 let inspector = revm_inspectors::tracing::js::JsInspector::new(code, config)
6484 .map_err(|err| BlockchainError::Message(err.to_string()))?;
6485 let trace = self.replay_tx_with_inspector(
6486 hash,
6487 inspector,
6488 |result, cache_db, mut inspector, tx_env, evm_env| {
6489 inspector
6490 .json_result(
6491 result,
6492 &alloy_evm::IntoTxEnv::into_tx_env(tx_env),
6493 &evm_env.block_env,
6494 &cache_db,
6495 )
6496 .map_err(|e| BlockchainError::Message(e.to_string()))
6497 },
6498 )??;
6499 Ok(GethTrace::JS(trace))
6500 }
6501
6502 pub async fn prove_account_at(
6506 &self,
6507 address: Address,
6508 keys: Vec<B256>,
6509 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
6510 ) -> Result<AccountProof, BlockchainError> {
6511 let block_number = block_request.as_ref().map(|r| r.block_number());
6512
6513 self.with_database_at(block_request, |block_db, _| {
6514 trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number);
6515 let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
6516 let account = db.get(&address).cloned().unwrap_or_default();
6517
6518 let mut builder = HashBuilder::default()
6519 .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))]));
6520
6521 for (key, account) in trie_accounts(db) {
6522 builder.add_leaf(key, &account);
6523 }
6524
6525 let _ = builder.root();
6526
6527 let proof = builder
6528 .take_proof_nodes()
6529 .into_nodes_sorted()
6530 .into_iter()
6531 .map(|(_, v)| v)
6532 .collect();
6533 let (storage_hash, storage_proofs) = prove_storage(&account.storage, &keys);
6534
6535 let account_proof = AccountProof {
6536 address,
6537 balance: account.info.balance,
6538 nonce: account.info.nonce,
6539 code_hash: account.info.code_hash,
6540 storage_hash,
6541 account_proof: proof,
6542 storage_proof: keys
6543 .into_iter()
6544 .zip(storage_proofs)
6545 .map(|(key, proof)| {
6546 let storage_key: U256 = key.into();
6547 let value = account.storage.get(&storage_key).copied().unwrap_or_default();
6548 StorageProof { key: JsonStorageKey::Hash(key), value, proof }
6549 })
6550 .collect(),
6551 };
6552
6553 Ok(account_proof)
6554 })
6555 .await?
6556 }
6557}
6558
6559impl<N: Network> Backend<N>
6560where
6561 N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
6562{
6563 pub async fn trace_transaction_opcode_gas(
6565 &self,
6566 hash: B256,
6567 ) -> Result<Option<TransactionOpcodeGas>, BlockchainError> {
6568 match self.replay_tx_with_inspector(
6569 hash,
6570 OpcodeGasInspector::default(),
6571 move |_, _, inspector, _, _| TransactionOpcodeGas {
6572 transaction_hash: hash,
6573 opcode_gas: inspector.opcode_gas_iter().collect(),
6574 },
6575 ) {
6576 Ok(trace) => Ok(Some(trace)),
6577 Err(BlockchainError::TransactionNotFound) => {
6578 if let Some(fork) = self.get_fork() {
6579 return Ok(fork.trace_transaction_opcode_gas(hash).await?);
6580 }
6581
6582 Ok(None)
6583 }
6584 Err(err) => Err(err),
6585 }
6586 }
6587
6588 pub async fn trace_block_opcode_gas(
6590 &self,
6591 block_id: BlockId,
6592 ) -> Result<Option<BlockOpcodeGas>, BlockchainError> {
6593 if let Some((block, block_hash)) = self.get_block_with_hash(block_id) {
6594 return self.mined_block_opcode_gas(&block, block_hash).map(Some);
6595 }
6596
6597 if let Some(fork) = self.get_fork() {
6598 let number = self.ensure_block_number(Some(block_id)).await?;
6599 if fork.predates_fork_inclusive(number) {
6600 return Ok(fork.trace_block_opcode_gas(block_id).await?);
6601 }
6602 }
6603
6604 Err(BlockchainError::BlockNotFound)
6605 }
6606
6607 fn mined_block_opcode_gas(
6608 &self,
6609 block: &Block,
6610 block_hash: B256,
6611 ) -> Result<BlockOpcodeGas, BlockchainError> {
6612 if block.header.number() == self.genesis_number() {
6614 return Ok(BlockOpcodeGas {
6615 block_hash,
6616 block_number: block.header.number(),
6617 transactions: Vec::new(),
6618 });
6619 }
6620
6621 let parent_hash = block.header.parent_hash;
6622
6623 let trace = |parent_state: &StateDb| -> Result<Vec<TransactionOpcodeGas>, BlockchainError> {
6624 let (mut cache_db, evm_env, hardfork) =
6625 self.prepare_block_replay(block, parent_state)?;
6626 let mut transactions = Vec::with_capacity(block.body.transactions.len());
6627 let monad_context = self.active_monad_context_for_mined_block(block)?;
6628
6629 for tx_envelope in &block.body.transactions {
6630 let mut inspector = OpcodeGasInspector::default();
6631 let pending_tx = self.pending_mined_transaction(tx_envelope.clone())?;
6632 let transaction_context =
6633 monad_execution_context_at(monad_context.as_ref(), transactions.len());
6634 let (result, _) = self.replay_envelope_with_inspector_ref_and_context(
6635 &cache_db,
6636 &evm_env,
6637 &mut inspector,
6638 &pending_tx,
6639 EnvelopeExecution::replay(transaction_context, hardfork),
6640 )?;
6641
6642 transactions.push(TransactionOpcodeGas {
6643 transaction_hash: tx_envelope.hash(),
6644 opcode_gas: inspector.opcode_gas_iter().collect(),
6645 });
6646
6647 cache_db.commit(result.state);
6648 }
6649
6650 Ok(transactions)
6651 };
6652
6653 let read_guard = self.states.upgradable_read();
6654 let transactions = if let Some(state) = read_guard.get_state(&parent_hash) {
6655 trace(state)?
6656 } else {
6657 let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6658 let state = write_guard
6659 .get_on_disk_state(&parent_hash)
6660 .ok_or(BlockchainError::BlockNotFound)?;
6661 trace(state)?
6662 };
6663
6664 Ok(BlockOpcodeGas { block_hash, block_number: block.header.number(), transactions })
6665 }
6666
6667 pub async fn debug_execution_witness(
6686 &self,
6687 block: BlockNumber,
6688 ) -> Result<ExecutionWitness, BlockchainError> {
6689 let number = self.convert_block_number(Some(block));
6690 let best = self.best_number();
6691 if number > best {
6692 return Err(BlockchainError::BlockOutOfRange(best, number));
6693 }
6694 let Some(parent) = number.checked_sub(1) else {
6695 return Err(BlockchainError::Message(
6696 "genesis block has no parent state to build a witness from".to_string(),
6697 ));
6698 };
6699
6700 let mut headers = Vec::new();
6701 for ancestor in (number.saturating_sub(BLOCKHASH_HISTORY)..number).rev() {
6702 let Some(block) = self.get_block(ancestor) else { break };
6703 headers.push(alloy_rlp::encode(&block.header).into());
6704 }
6705
6706 self.with_database_at(Some(BlockRequest::Number(parent)), |state, _| {
6707 let Some(accounts) = state.maybe_full_db() else {
6708 return Err(BlockchainError::Message(
6709 "debug_executionWitness is not supported while forking".to_string(),
6710 ));
6711 };
6712
6713 let (_, nodes) = state_trie_witness(&accounts);
6714 let mut codes = Vec::new();
6715 let mut seen_codes = B256Set::default();
6716 let mut keys = Vec::new();
6717 for (address, account) in &accounts {
6718 keys.push(Bytes::copy_from_slice(address.as_slice()));
6719 for slot in account.storage.keys() {
6720 keys.push(Bytes::copy_from_slice(&slot.to_be_bytes::<32>()));
6721 }
6722 if account.info.code_hash != KECCAK_EMPTY
6723 && seen_codes.insert(account.info.code_hash)
6724 {
6725 let code = match &account.info.code {
6726 Some(code) => code.original_bytes(),
6727 None => state.code_by_hash_ref(account.info.code_hash)?.original_bytes(),
6728 };
6729 codes.push(code);
6730 }
6731 }
6732 keys.sort_unstable();
6733 keys.dedup();
6734
6735 Ok(ExecutionWitness { state: nodes, codes, keys, headers })
6736 })
6737 .await?
6738 }
6739
6740 pub async fn debug_account_info_at(
6742 &self,
6743 block_id: BlockId,
6744 tx_index: Index,
6745 address: Address,
6746 ) -> Result<Option<RpcAccountInfo>, BlockchainError> {
6747 if let Some((block, _)) = self.get_block_with_hash(block_id) {
6748 return self.mined_debug_account_info_at(&block, tx_index, address).map(Some);
6749 }
6750
6751 if let Some(fork) = self.get_fork() {
6752 let number = self.ensure_block_number(Some(block_id)).await?;
6753 if fork.predates_fork_inclusive(number) {
6754 let resolved = match block_id {
6758 BlockId::Hash(_) => block_id,
6759 _ => BlockId::number(number),
6760 };
6761 return Ok(fork.debug_account_info_at(resolved, tx_index, address).await?);
6762 }
6763 }
6764
6765 Err(BlockchainError::BlockNotFound)
6766 }
6767
6768 fn mined_debug_account_info_at(
6769 &self,
6770 block: &Block,
6771 tx_index: Index,
6772 address: Address,
6773 ) -> Result<RpcAccountInfo, BlockchainError> {
6774 let tx_index = tx_index.0;
6775 let transaction_count = block.body.transactions.len();
6776 if tx_index >= transaction_count {
6777 return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
6778 "tx_index {tx_index} out of bounds for block with {transaction_count} transactions"
6779 ))));
6780 }
6781
6782 let trace = |parent_state: &StateDb| -> Result<RpcAccountInfo, BlockchainError> {
6783 let (mut cache_db, evm_env, hardfork) =
6784 self.prepare_block_replay_with_db(block, Box::new(parent_state))?;
6785 self.replay_mined_transaction_prefix(
6786 &mut cache_db,
6787 &evm_env,
6788 hardfork,
6789 block,
6790 tx_index + 1,
6791 )?;
6792 let account = revm::DatabaseRef::basic_ref(&cache_db, address)?.unwrap_or_default();
6793 let code = self.get_code_with_state(&cache_db, address)?;
6794 Ok(RpcAccountInfo { balance: account.balance, nonce: account.nonce, code })
6795 };
6796
6797 let read_guard = self.states.upgradable_read();
6798 if let Some(state) = read_guard.get_state(&block.header.parent_hash) {
6799 trace(state)
6800 } else {
6801 let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6802 let state = write_guard
6803 .get_on_disk_state(&block.header.parent_hash)
6804 .ok_or(BlockchainError::BlockNotFound)?;
6805 trace(state)
6806 }
6807 }
6808
6809 pub async fn rollback(&self, common_block: Block) -> Result<(), BlockchainError> {
6814 let hash = common_block.header.hash_slow();
6815
6816 let common_state = {
6818 let return_state_or_throw_err =
6819 |db: Option<&StateDb>| -> Result<AddressMap<DbAccount>, BlockchainError> {
6820 let state_db = db.ok_or(BlockchainError::DataUnavailable)?;
6821 let db_full =
6822 state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?;
6823 Ok(db_full.clone())
6824 };
6825
6826 let read_guard = self.states.upgradable_read();
6827 if let Some(db) = read_guard.get_state(&hash) {
6828 return_state_or_throw_err(Some(db))?
6829 } else {
6830 let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard);
6831 return_state_or_throw_err(write_guard.get_on_disk_state(&hash))?
6832 }
6833 };
6834
6835 {
6836 let removed_logs = self.removed_logs_since(common_block.header.number());
6839
6840 let removed_blocks =
6842 self.blockchain.storage.write().unwind_to(common_block.header.number(), hash);
6843
6844 let removed_hashes: Vec<_> =
6846 removed_blocks.iter().map(|b| b.header.hash_slow()).collect();
6847 self.states.write().remove_block_states(&removed_hashes);
6848
6849 if !removed_logs.is_empty() {
6852 self.notify_on_removed_logs(removed_logs);
6853 }
6854
6855 let mut env = self.evm_env.write();
6857 env.block_env.number = U256::from(common_block.header.number());
6858 env.block_env.timestamp = U256::from(common_block.header.timestamp());
6859 env.block_env.gas_limit = common_block.header.gas_limit();
6860 env.block_env.difficulty = common_block.header.difficulty();
6861 env.block_env.prevrandao = common_block.header.mix_hash();
6862
6863 self.time.reset(env.block_env.timestamp.saturating_to());
6864 self.cheats.clear_next_block_prevrandao();
6866 }
6867
6868 {
6869 let block_hashes: Vec<_> = {
6873 let storage = self.blockchain.storage.read();
6874 let min_block = common_block.header.number().saturating_sub(256);
6875 storage
6876 .hashes
6877 .iter()
6878 .filter(|(num, _)| **num >= min_block)
6879 .map(|(&num, &hash)| (num, hash))
6880 .collect()
6881 };
6882
6883 let mut db = self.db.write().await;
6885 db.clear();
6886
6887 for (address, acc) in common_state {
6889 db.insert_account(address, acc.info);
6890 for (key, value) in acc.storage {
6891 db.set_storage_at(address, key.into(), value.into())?;
6892 }
6893 }
6894
6895 for (block_num, hash) in block_hashes {
6898 db.insert_block_hash(U256::from(block_num), hash);
6899 }
6900 }
6901
6902 Ok(())
6903 }
6904
6905 pub async fn debug_trace_transaction(
6907 &self,
6908 hash: B256,
6909 opts: GethDebugTracingOptions,
6910 ) -> Result<GethTrace, BlockchainError> {
6911 #[cfg(feature = "js-tracer")]
6912 if let Some(tracer_type) = opts.tracer.as_ref()
6913 && tracer_type.is_js()
6914 {
6915 return self
6916 .trace_tx_with_js_tracer(hash, tracer_type.as_str().to_string(), opts.clone())
6917 .await;
6918 }
6919
6920 if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()).await {
6921 return trace;
6922 }
6923
6924 if let Some(fork) = self.get_fork() {
6925 return Ok(fork.debug_trace_transaction(hash, opts).await?);
6926 }
6927
6928 Err(BlockchainError::TransactionNotFound)
6929 }
6930
6931 pub async fn debug_trace_block(
6933 &self,
6934 rlp_block: Bytes,
6935 opts: GethDebugTracingOptions,
6936 ) -> Result<Vec<TraceResult>, BlockchainError> {
6937 let mut rlp = rlp_block.as_ref();
6938 let block = Block::<FoundryTxEnvelope>::decode(&mut rlp).map_err(|err| {
6939 BlockchainError::RpcError(RpcError::invalid_params(format!(
6940 "failed to decode block: {err}"
6941 )))
6942 })?;
6943 if !rlp.is_empty() {
6944 return Err(BlockchainError::RpcError(RpcError::invalid_params(
6945 "failed to decode block: trailing bytes".to_string(),
6946 )));
6947 }
6948
6949 self.debug_trace_block_by_hash(block.header.hash_slow(), opts).await
6950 }
6951
6952 pub async fn debug_trace_block_by_hash(
6954 &self,
6955 block_hash: B256,
6956 opts: GethDebugTracingOptions,
6957 ) -> Result<Vec<TraceResult>, BlockchainError> {
6958 if let Some(block) = self.blockchain.get_block_by_hash(&block_hash) {
6959 let mut traces = Vec::new();
6960 for tx in &block.body.transactions {
6961 let tx_hash = tx.hash();
6962 match self.debug_trace_transaction(tx_hash, opts.clone()).await {
6963 Ok(trace) => {
6964 traces.push(TraceResult::Success { result: trace, tx_hash: Some(tx_hash) });
6965 }
6966 Err(error) => {
6967 traces.push(TraceResult::Error {
6968 error: error.to_string(),
6969 tx_hash: Some(tx_hash),
6970 });
6971 }
6972 }
6973 }
6974 return Ok(traces);
6975 }
6976
6977 if let Some(fork) = self.get_fork() {
6978 return Ok(fork.debug_trace_block_by_hash(block_hash, opts).await?);
6979 }
6980
6981 Err(BlockchainError::BlockNotFound)
6982 }
6983
6984 pub async fn debug_trace_block_by_number(
6986 &self,
6987 block_number: BlockNumber,
6988 opts: GethDebugTracingOptions,
6989 ) -> Result<Vec<TraceResult>, BlockchainError> {
6990 let number = self.convert_block_number(Some(block_number));
6991
6992 if let Some(block) = self.get_block(BlockId::Number(BlockNumber::Number(number))) {
6993 let mut traces = Vec::new();
6994 for tx in &block.body.transactions {
6995 let tx_hash = tx.hash();
6996 match self.debug_trace_transaction(tx_hash, opts.clone()).await {
6997 Ok(trace) => {
6998 traces.push(TraceResult::Success { result: trace, tx_hash: Some(tx_hash) });
6999 }
7000 Err(error) => {
7001 traces.push(TraceResult::Error {
7002 error: error.to_string(),
7003 tx_hash: Some(tx_hash),
7004 });
7005 }
7006 }
7007 }
7008 return Ok(traces);
7009 }
7010
7011 if let Some(fork) = self.get_fork() {
7012 return Ok(fork.debug_trace_block_by_number(number, opts).await?);
7013 }
7014
7015 Err(BlockchainError::BlockNotFound)
7016 }
7017
7018 fn geth_trace(
7019 &self,
7020 tx: &MinedTransaction<N>,
7021 opts: GethDebugTracingOptions,
7022 ) -> Result<GethTrace, BlockchainError> {
7023 let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts;
7024
7025 if let Some(tracer) = tracer {
7026 match tracer {
7027 GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
7028 GethDebugBuiltInTracerType::FourByteTracer => {
7029 let inspector = FourByteInspector::default();
7030 let res = self.replay_tx_with_inspector(
7031 tx.info.transaction_hash,
7032 inspector,
7033 |_, _, inspector, _, _| FourByteFrame::from(inspector).into(),
7034 )?;
7035 return Ok(res);
7036 }
7037 GethDebugBuiltInTracerType::CallTracer => {
7038 return match call_config_from_tracer_config(tracer_config) {
7039 Ok(call_config) => {
7040 let inspector = TracingInspector::new(
7041 TracingInspectorConfig::from_geth_call_config(&call_config),
7042 );
7043 let frame = self.replay_tx_with_inspector(
7044 tx.info.transaction_hash,
7045 inspector,
7046 |_, _, inspector, _, _| {
7047 inspector
7048 .geth_builder()
7049 .geth_call_traces(call_config, tx.info.gas_used)
7050 .into()
7051 },
7052 )?;
7053 Ok(frame)
7054 }
7055 Err(e) => Err(RpcError::invalid_params(e.to_string()).into()),
7056 };
7057 }
7058 GethDebugBuiltInTracerType::PreStateTracer => {
7059 return match tracer_config.into_pre_state_config() {
7060 Ok(pre_state_config) => {
7061 let inspector = TracingInspector::new(
7062 TracingInspectorConfig::from_geth_prestate_config(
7063 &pre_state_config,
7064 ),
7065 );
7066 let frame = self.replay_tx_with_inspector(
7067 tx.info.transaction_hash,
7068 inspector,
7069 |state, db, inspector, _, _| {
7070 inspector.geth_builder().geth_prestate_traces(
7071 &state,
7072 &pre_state_config,
7073 db,
7074 )
7075 },
7076 )??;
7077 Ok(frame.into())
7078 }
7079 Err(e) => Err(RpcError::invalid_params(e.to_string()).into()),
7080 };
7081 }
7082 GethDebugBuiltInTracerType::NoopTracer
7083 | GethDebugBuiltInTracerType::MuxTracer
7084 | GethDebugBuiltInTracerType::Erc7562Tracer
7085 | GethDebugBuiltInTracerType::FlatCallTracer
7086 | GethDebugBuiltInTracerType::StateGasTracer => {}
7087 },
7088 GethDebugTracerType::JsTracer(_code) => {}
7089 }
7090
7091 return Ok(NoopFrame::default().into());
7092 }
7093
7094 Ok(GethTraceBuilder::new(tx.info.traces.clone())
7096 .geth_traces(tx.info.gas_used, tx.info.out.clone().unwrap_or_default(), config)
7097 .into())
7098 }
7099
7100 async fn mined_geth_trace_transaction(
7101 &self,
7102 hash: B256,
7103 opts: GethDebugTracingOptions,
7104 ) -> Option<Result<GethTrace, BlockchainError>> {
7105 self.blockchain.storage.read().transactions.get(&hash).map(|tx| self.geth_trace(tx, opts))
7106 }
7107
7108 pub async fn transaction_receipt(
7109 &self,
7110 hash: B256,
7111 ) -> Result<Option<FoundryTxReceipt>, BlockchainError> {
7112 if let Some(receipt) = self.mined_transaction_receipt(hash) {
7113 return Ok(Some(receipt.inner));
7114 }
7115
7116 if let Some(fork) = self.get_fork() {
7117 let receipt = fork.transaction_receipt(hash).await?;
7118 let number = self.convert_block_number(
7119 receipt.clone().and_then(|r| r.block_number()).map(BlockNumber::from),
7120 );
7121
7122 if fork.predates_fork_inclusive(number) {
7123 return Ok(receipt);
7124 }
7125 }
7126
7127 Ok(None)
7128 }
7129
7130 pub fn mined_block_receipts(&self, id: impl Into<BlockId>) -> Option<Vec<FoundryTxReceipt>> {
7132 let storage = self.blockchain.storage.read();
7133 let hash = match id.into() {
7134 BlockId::Hash(hash) => hash.block_hash,
7135 BlockId::Number(number) => storage.hash(number, self.slots_in_an_epoch)?,
7136 };
7137 let block = storage.blocks.get(&hash)?.clone();
7138
7139 if block.body.transactions.iter().enumerate().any(|(index, transaction)| {
7140 storage.transactions.get(&transaction.hash()).is_none_or(|transaction| {
7141 transaction.block_hash != hash
7142 || transaction.info.transaction_index as usize != index
7143 })
7144 }) {
7145 drop(storage);
7146 return block
7147 .body
7148 .transactions
7149 .into_iter()
7150 .map(|transaction| {
7151 self.mined_transaction_receipt(transaction.hash()).map(|receipt| receipt.inner)
7152 })
7153 .collect();
7154 }
7155
7156 let mut receipts = Vec::with_capacity(block.body.transactions.len());
7157 let mut next_log_index = 0;
7158
7159 for block_transaction in &block.body.transactions {
7160 let transaction = storage.transactions.get(&block_transaction.hash())?;
7161 let log_count = transaction.receipt.logs().len();
7162 let receipt = self.build_mined_transaction_receipt(
7163 &transaction.info,
7164 transaction.receipt.clone(),
7165 transaction.block_hash,
7166 &block,
7167 next_log_index,
7168 );
7169 receipts.push(receipt.inner);
7170 next_log_index += log_count;
7171 }
7172
7173 Some(receipts)
7174 }
7175
7176 pub(crate) fn mined_transaction_receipt(
7178 &self,
7179 hash: B256,
7180 ) -> Option<MinedTransactionReceipt<FoundryNetwork>> {
7181 let storage = self.blockchain.storage.read();
7182 let transaction = storage.transactions.get(&hash)?;
7183
7184 let index = transaction.info.transaction_index as usize;
7185 let block = storage.blocks.get(&transaction.block_hash)?;
7186 let mut next_log_index = 0;
7187 for block_transaction in &block.body.transactions[..index] {
7188 next_log_index +=
7189 storage.transactions.get(&block_transaction.hash())?.receipt.logs().len();
7190 }
7191
7192 Some(self.build_mined_transaction_receipt(
7193 &transaction.info,
7194 transaction.receipt.clone(),
7195 transaction.block_hash,
7196 block,
7197 next_log_index,
7198 ))
7199 }
7200
7201 fn build_mined_transaction_receipt(
7202 &self,
7203 info: &TransactionInfo,
7204 tx_receipt: FoundryReceiptEnvelope,
7205 block_hash: B256,
7206 block: &Block,
7207 next_log_index: usize,
7208 ) -> MinedTransactionReceipt<FoundryNetwork> {
7209 let transaction = block.body.transactions[info.transaction_index as usize].clone();
7210
7211 let excess_blob_gas = block.header.excess_blob_gas();
7213 let blob_gas_price =
7214 alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas.unwrap_or_default());
7215 let blob_gas_used = transaction.blob_gas_used();
7216
7217 let effective_gas_price = transaction.effective_gas_price(block.header.base_fee_per_gas());
7218
7219 let tx_receipt = tx_receipt.convert_logs_rpc(
7220 BlockNumHash::new(block.header.number(), block_hash),
7221 block.header.timestamp(),
7222 info.transaction_hash,
7223 info.transaction_index,
7224 next_log_index,
7225 );
7226
7227 let receipt = TransactionReceipt {
7228 inner: tx_receipt,
7229 transaction_hash: info.transaction_hash,
7230 transaction_index: Some(info.transaction_index),
7231 block_number: Some(block.header.number()),
7232 gas_used: info.gas_used,
7233 contract_address: info.contract_address,
7234 effective_gas_price,
7235 block_hash: Some(block_hash),
7236 from: info.from,
7237 to: info.to,
7238 blob_gas_price: Some(blob_gas_price),
7239 blob_gas_used,
7240 };
7241
7242 let mut inner = FoundryTxReceipt::with_timestamp(receipt, block.header.timestamp());
7244 if self.is_tempo() {
7245 let fee_payer = match &*transaction {
7246 FoundryTxEnvelope::Tempo(tx) => match tx.tx().recover_fee_payer(info.from) {
7247 Ok(fee_payer) => fee_payer,
7248 Err(error) => {
7249 warn!(
7250 target: "backend",
7251 %error,
7252 tx_hash = ?info.transaction_hash,
7253 "failed to recover Tempo fee payer for mined receipt"
7254 );
7255 info.from
7256 }
7257 },
7258 _ => info.from,
7259 };
7260 inner = inner.with_fee_payer(fee_payer);
7261
7262 if inner.effective_gas_price() > 0
7265 && inner.gas_used() > 0
7266 && let Some(fee_token) = inner.0.inner.logs().last().map(|log| log.address())
7267 {
7268 inner = inner.with_fee_token(fee_token);
7269 }
7270 }
7271 MinedTransactionReceipt { inner, out: info.out.clone() }
7272 }
7273
7274 pub async fn block_receipts(
7276 &self,
7277 number: BlockId,
7278 ) -> Result<Option<Vec<FoundryTxReceipt>>, BlockchainError> {
7279 if let Some(receipts) = self.mined_block_receipts(number) {
7280 return Ok(Some(receipts));
7281 }
7282
7283 if let Some(fork) = self.get_fork() {
7284 let number = match self.ensure_block_number(Some(number)).await {
7285 Err(_) => return Ok(None),
7286 Ok(n) => n,
7287 };
7288
7289 if fork.predates_fork_inclusive(number) {
7290 let receipts = fork.block_receipts(number).await?;
7291
7292 return Ok(receipts);
7293 }
7294 }
7295
7296 Ok(None)
7297 }
7298}
7299
7300impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> Backend<N> {
7301 pub async fn serialized_state(
7303 &self,
7304 preserve_historical_states: bool,
7305 ) -> Result<SerializableState, BlockchainError> {
7306 let at = self.evm_env.read().block_env.clone();
7307 #[cfg(feature = "monad")]
7308 let mut monad_block_participants = BTreeMap::new();
7309 #[cfg(feature = "monad")]
7310 let mut monad_block_replay_profiles = BTreeMap::new();
7311 let (best_number, blocks, transactions) = {
7312 let storage = self.blockchain.storage.read();
7313 #[cfg(feature = "monad")]
7314 if self.is_monad() {
7315 monad_block_participants = storage
7316 .monad_block_participants
7317 .iter()
7318 .filter(|(hash, _)| storage.blocks.contains_key(*hash))
7319 .map(|(hash, participants)| (*hash, participants.iter().copied().collect()))
7320 .collect();
7321 monad_block_replay_profiles = storage
7322 .monad_block_replay_profiles
7323 .iter()
7324 .filter(|(hash, _)| storage.blocks.contains_key(*hash))
7325 .map(|(hash, profile)| (*hash, *profile))
7326 .collect();
7327 }
7328 (storage.best_number, storage.serialized_blocks(), storage.serialized_transactions())
7329 };
7330 let historical_states =
7331 preserve_historical_states.then(|| self.states.write().serialized_states());
7332
7333 let state = self
7334 .db
7335 .read()
7336 .await
7337 .dump_state(at, best_number, blocks, transactions, historical_states)?
7338 .ok_or_else(|| {
7339 BlockchainError::RpcError(RpcError::invalid_params(
7340 "Dumping state not supported with the current configuration",
7341 ))
7342 })?;
7343 #[cfg(feature = "monad")]
7344 let state = {
7345 let mut state = state;
7346 state.monad_block_participants = monad_block_participants;
7347 state.monad_block_replay_profiles = monad_block_replay_profiles;
7348 state
7349 };
7350 Ok(state)
7351 }
7352
7353 pub async fn dump_state(
7355 &self,
7356 preserve_historical_states: bool,
7357 ) -> Result<Bytes, BlockchainError> {
7358 let state = self.serialized_state(preserve_historical_states).await?;
7359 let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
7360 encoder
7361 .write_all(&serde_json::to_vec(&state).unwrap_or_default())
7362 .map_err(|_| BlockchainError::DataUnavailable)?;
7363 Ok(encoder.finish().unwrap_or_default().into())
7364 }
7365
7366 pub async fn load_state(&self, mut state: SerializableState) -> Result<bool, BlockchainError> {
7368 let _mining_guard = self.mining.lock().await;
7369 let mut block_env = state.block.take();
7370 let mut selected_head = None;
7371 let mut selected_header = None;
7372 let mut checkpoint = None;
7373 let fork_head = self.get_fork().map(|f| (f.block_number(), f.block_hash(), f.timestamp()));
7374 if let Some(block) = &mut block_env {
7375 if self.is_tempo() && self.is_fork() && block.beneficiary.is_zero() {
7376 block.beneficiary = TIP_FEE_MANAGER_ADDRESS;
7377 }
7378 let best_number = state.best_block_number.unwrap_or(block.number.saturating_to());
7381 let (selected_best_number, selected_best_hash) = if let Some((number, hash, _)) =
7382 fork_head
7383 {
7384 trace!(target: "backend", state_block_number=?best_number, fork_block_number=?number);
7385 if best_number > number {
7389 (best_number, None)
7390 } else {
7391 (number, Some(hash))
7394 }
7395 } else {
7396 (best_number, None)
7397 };
7398
7399 let best_hash = if let Some(hash) = selected_best_hash {
7400 selected_header = state
7401 .blocks
7402 .iter()
7403 .rev()
7404 .find(|block| block.header.hash_slow() == hash)
7405 .map(|block| block.header.clone());
7406 hash
7407 } else if state.blocks.is_empty() {
7408 let spec_id = self.spec_id();
7409 let is_cancun = spec_id >= SpecId::CANCUN;
7410 let parent_hash = selected_best_number
7411 .checked_sub(1)
7412 .and_then(|number| self.blockchain.storage.read().hashes.get(&number).copied())
7413 .unwrap_or_default();
7414 let header = Header {
7415 parent_hash,
7416 beneficiary: block.beneficiary,
7417 difficulty: block.difficulty,
7418 number: selected_best_number,
7419 gas_limit: block.gas_limit,
7420 timestamp: block.timestamp.saturating_to(),
7421 mix_hash: block.prevrandao.unwrap_or_default(),
7422 base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(block.basefee),
7423 parent_beacon_block_root: is_cancun.then_some(Default::default()),
7424 blob_gas_used: is_cancun.then_some(0),
7425 excess_blob_gas: if is_cancun { block.blob_excess_gas() } else { None },
7426 withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
7427 requests_hash: (spec_id >= SpecId::PRAGUE).then_some(EMPTY_REQUESTS_HASH),
7428 ..Default::default()
7429 };
7430 let header = foundry_header(&self.networks, header);
7431 let best_hash = header.hash_slow();
7432 selected_header = Some(header.clone());
7433 checkpoint = Some(create_block(
7434 header,
7435 Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
7436 ));
7437 warn!(
7438 target: "backend",
7439 block_number = selected_best_number,
7440 "state dump has no block history; created a synthetic checkpoint block"
7441 );
7442 best_hash
7443 } else if let Some(header) = state
7444 .blocks
7445 .iter()
7446 .rev()
7447 .find(|block| block.header.number() == selected_best_number)
7448 .map(|block| block.header.clone())
7449 {
7450 let best_hash = header.hash_slow();
7451 selected_header = Some(header);
7452 best_hash
7453 } else {
7454 return Err(BlockchainError::RpcError(RpcError::internal_error_with(format!(
7455 "Best hash not found for best number {selected_best_number}",
7456 ))));
7457 };
7458
7459 selected_head = Some((selected_best_number, best_hash));
7460 }
7461
7462 let blocks = std::mem::take(&mut state.blocks);
7466 let transactions = std::mem::take(&mut state.transactions);
7467 let mut storage = self.blockchain.storage.read().clone();
7468 storage.load_blocks(blocks);
7469 storage.load_transactions(transactions);
7470 if let Some(checkpoint) = checkpoint {
7471 storage.insert_block(checkpoint);
7472 }
7473 if let Some((number, hash)) = selected_head {
7474 storage.hashes.insert(number, hash);
7475 storage.best_number = number;
7476 storage.best_hash = hash;
7477 }
7478
7479 #[cfg(feature = "monad")]
7480 if self.is_monad() {
7481 for (hash, profile) in &state.monad_block_replay_profiles {
7482 if storage.blocks.contains_key(hash) {
7483 storage.monad_block_replay_profiles.insert(*hash, *profile);
7484 }
7485 }
7486 for (hash, participants) in &state.monad_block_participants {
7487 if storage.blocks.contains_key(hash) {
7488 storage
7489 .monad_block_participants
7490 .insert(*hash, participants.iter().copied().collect());
7491 }
7492 }
7493 self.rebuild_monad_block_participant_cache(&mut storage)?;
7494 self.monad_context_for_child_of_in_storage(&storage, storage.best_hash)?;
7497 }
7498
7499 let canonical_timestamp = match fork_head {
7504 Some((_, fork_hash, fork_timestamp)) if storage.best_hash == fork_hash => {
7505 Some(fork_timestamp)
7506 }
7507 _ => storage.blocks.get(&storage.best_hash).map(|block| block.header.timestamp),
7508 };
7509
7510 if let Some(block) = block_env.as_mut() {
7511 if !is_arbitrum(self.chain_id().to())
7514 && let Some((number, _)) = selected_head
7515 {
7516 block.number = U256::from(number);
7517 }
7518 }
7519
7520 let next_fees = selected_header.as_ref().map(|header| {
7521 let parent_fees = self.fees.get_parent_header_fees(header);
7522 let next_block_excess_blob_gas = self.networks.next_block_blob_excess_gas(
7523 self.fees.blob_params(),
7524 header.excess_blob_gas().unwrap_or_default(),
7525 header.blob_gas_used().unwrap_or_default(),
7526 header.base_fee_per_gas().unwrap_or_default(),
7527 );
7528 let blob_excess_gas_and_price = BlobExcessGasAndPrice::new(
7529 next_block_excess_blob_gas,
7530 get_blob_base_fee_update_fraction(
7531 self.evm_env.read().cfg_env.chain_id,
7532 header.timestamp,
7533 ),
7534 );
7535 (parent_fees, blob_excess_gas_and_price)
7536 });
7537
7538 let historical_states = state.historical_states.take();
7539 if !self.db.write().await.load_state(state)? {
7540 return Err(RpcError::invalid_params(
7541 "Loading state not supported with the current configuration",
7542 )
7543 .into());
7544 }
7545
7546 let block_hashes = {
7551 let min_block = storage.best_number.saturating_sub(256);
7552 storage
7553 .hashes
7554 .iter()
7555 .filter(|(num, _)| (min_block..=storage.best_number).contains(*num))
7556 .map(|(&num, &hash)| (U256::from(num), hash))
7557 .collect()
7558 };
7559
7560 *self.blockchain.storage.write() = storage;
7561 if let Some(timestamp) = canonical_timestamp {
7562 self.time.reset(timestamp);
7563 }
7564 if let Some(block_env) = block_env {
7565 self.evm_env.write().block_env = block_env;
7566 }
7567 if let Some((parent_fees, blob_excess_gas_and_price)) = next_fees {
7568 #[cfg(feature = "optimism")]
7569 if self.is_optimism() {
7570 self.fees.set_optimism_base_fee_rules(&parent_fees.extra_data);
7571 }
7572 self.fees.set_base_fee(parent_fees.base_fee);
7573 self.fees.set_blob_excess_gas_and_price(blob_excess_gas_and_price);
7574 }
7575
7576 self.db.write().await.set_block_hashes(block_hashes);
7577
7578 if let Some(historical_states) = historical_states {
7579 self.states.write().load_states(historical_states);
7580 }
7581
7582 Ok(true)
7583 }
7584
7585 pub async fn load_state_bytes(&self, buf: Bytes) -> Result<bool, BlockchainError> {
7587 let orig_buf = &buf.0[..];
7588 let mut decoder = GzDecoder::new(orig_buf);
7589 let mut decoded_data = Vec::new();
7590
7591 let state: SerializableState = serde_json::from_slice(if decoder.header().is_some() {
7592 decoder
7593 .read_to_end(decoded_data.as_mut())
7594 .map_err(|_| BlockchainError::FailedToDecodeStateDump)?;
7595 &decoded_data
7596 } else {
7597 &buf.0
7598 })
7599 .map_err(|_| BlockchainError::FailedToDecodeStateDump)?;
7600
7601 self.load_state(state).await
7602 }
7603}
7604
7605impl Backend<FoundryNetwork> {
7606 pub async fn call_bundle(
7608 &self,
7609 bundle: EthCallBundle,
7610 transactions: Vec<PendingTransaction<FoundryTxEnvelope>>,
7611 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7612 ) -> Result<EthCallBundleResponse, BlockchainError> {
7613 let EthCallBundle {
7614 block_number,
7615 coinbase,
7616 timestamp,
7617 gas_limit,
7618 difficulty,
7619 base_fee,
7620 ..
7621 } = bundle;
7622
7623 let blob_gas_used = transactions
7624 .iter()
7625 .filter_map(|transaction| transaction.transaction.blob_gas_used())
7626 .sum::<u64>();
7627 let max_blob_gas = self.blob_params().max_blob_gas_per_block();
7628 if blob_gas_used > max_blob_gas {
7629 return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
7630 "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
7631 ))));
7632 }
7633
7634 self.with_database_at_and_context(
7635 block_request,
7636 |state, mut block_env, mut monad_context| {
7637 let state_block_number = block_env.number.to::<u64>();
7638 block_env.number = U256::from(block_number);
7639 block_env.timestamp = timestamp
7640 .map(U256::from)
7641 .unwrap_or_else(|| block_env.timestamp.saturating_add(U256::from(12)));
7642 if let Some(coinbase) = coinbase {
7643 block_env.beneficiary = coinbase;
7644 }
7645 if let Some(gas_limit) = gas_limit {
7646 block_env.gas_limit = gas_limit;
7647 }
7648 if let Some(difficulty) = difficulty {
7649 block_env.difficulty = difficulty;
7650 }
7651 if let Some(base_fee) = base_fee {
7652 block_env.basefee = base_fee.try_into().unwrap_or(u64::MAX);
7653 }
7654
7655 let mut evm_env = self.evm_env.read().clone();
7656 evm_env.block_env = block_env;
7657 let coinbase = evm_env.block_env.beneficiary;
7658 let base_fee = evm_env.block_env.basefee;
7659 let mut cache_db = CacheDB::new(state);
7660 let initial_coinbase = revm::DatabaseRef::basic_ref(&cache_db, coinbase)?
7661 .map(|account| account.balance)
7662 .unwrap_or_default();
7663 let mut coinbase_balance_before_tx = initial_coinbase;
7664 let mut coinbase_balance_after_tx = initial_coinbase;
7665 let mut total_gas_used = 0u64;
7666 let mut total_gas_fees = U256::ZERO;
7667 let mut bundle_hash = alloy_primitives::Keccak256::new();
7668 let mut results = Vec::with_capacity(transactions.len());
7669
7670 for transaction in transactions {
7671 let sender = *transaction.sender();
7672 let tx = transaction.transaction.as_ref();
7673 let tx_hash = tx.hash();
7674 bundle_hash.update(tx_hash);
7675
7676 let mut inspector = self.build_inspector();
7677 let (ResultAndState { result, state }, _) = self
7678 .transact_envelope_with_inspector_ref_and_context(
7679 &cache_db,
7680 &evm_env,
7681 &mut inspector,
7682 &transaction,
7683 monad_context.as_mut().map(next_monad_context),
7684 )?;
7685
7686 let gas_price = tx.effective_tip_per_gas(base_fee).unwrap_or_default();
7687 let gas_used = result.tx_gas_used();
7688 let gas_fees = U256::from(gas_used) * U256::from(gas_price);
7689 total_gas_used += gas_used;
7690 total_gas_fees += gas_fees;
7691
7692 coinbase_balance_after_tx = state
7693 .get(&coinbase)
7694 .map(|account| account.info.balance)
7695 .unwrap_or(coinbase_balance_before_tx);
7696 let coinbase_diff =
7697 coinbase_balance_after_tx.saturating_sub(coinbase_balance_before_tx);
7698 let eth_sent_to_coinbase = coinbase_diff.saturating_sub(gas_fees);
7699 coinbase_balance_before_tx = coinbase_balance_after_tx;
7700
7701 let output = result.output().cloned().unwrap_or_default();
7702 let (value, revert) = if result.is_success() {
7703 (Some(output), None)
7704 } else {
7705 (None, Some(output))
7706 };
7707
7708 results.push(EthCallBundleTransactionResult {
7709 coinbase_diff,
7710 eth_sent_to_coinbase,
7711 from_address: sender,
7712 gas_fees,
7713 gas_price: U256::from(gas_price),
7714 gas_used,
7715 to_address: tx.to(),
7716 tx_hash,
7717 value,
7718 revert,
7719 });
7720 cache_db.commit(state);
7721 }
7722
7723 let coinbase_diff = coinbase_balance_after_tx.saturating_sub(initial_coinbase);
7724 let eth_sent_to_coinbase = coinbase_diff.saturating_sub(total_gas_fees);
7725 let bundle_gas_price =
7726 coinbase_diff.checked_div(U256::from(total_gas_used)).unwrap_or_default();
7727
7728 Ok(EthCallBundleResponse {
7729 bundle_hash: bundle_hash.finalize(),
7730 bundle_gas_price,
7731 coinbase_diff,
7732 eth_sent_to_coinbase,
7733 gas_fees: total_gas_fees,
7734 results,
7735 state_block_number,
7736 total_gas_used,
7737 })
7738 },
7739 )
7740 .await
7741 }
7742
7743 pub async fn call_many(
7745 &self,
7746 bundles: Vec<Bundle<WithOtherFields<TransactionRequest>>>,
7747 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7748 state_override: Option<alloy_rpc_types::state::StateOverride>,
7749 ) -> Result<Vec<Vec<EthCallResponse>>, BlockchainError> {
7750 if bundles.is_empty() {
7751 return Err(BlockchainError::RpcError(RpcError::invalid_params(
7752 "bundles are empty.".to_string(),
7753 )));
7754 }
7755
7756 self.with_database_at_and_context(
7757 block_request,
7758 |state, mut block_env, mut monad_context| {
7759 let mut cache_db = CacheDB::new(state);
7760 if let Some(state_override) = state_override {
7761 apply_state_overrides(state_override, &mut cache_db)?;
7762 }
7763
7764 let mut results = Vec::with_capacity(bundles.len());
7765 for bundle in bundles {
7766 let Bundle { transactions, block_override } = bundle;
7767 if let Some(block_override) = block_override {
7768 cache_db.apply_block_overrides(block_override, &mut block_env);
7769 }
7770
7771 let mut bundle_results = Vec::with_capacity(transactions.len());
7772 for request in transactions {
7773 let fee_details = FeeDetails::new(
7774 request.gas_price,
7775 request.max_fee_per_gas,
7776 request.max_priority_fee_per_gas,
7777 request.max_fee_per_blob_gas,
7778 )?
7779 .or_zero_fees();
7780 let PreparedCall { evm_env, mut tx_env, simulated_tempo_tx } = self
7781 .prepare_call_env(&cache_db, request, fee_details, block_env.clone())?;
7782 apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
7783
7784 let mut inspector = self.build_inspector();
7785 let ResultAndState { result, state } = self
7786 .transact_call_with_inspector_ref(
7787 &cache_db,
7788 &evm_env,
7789 &mut inspector,
7790 tx_env,
7791 monad_context.as_mut().map(next_monad_context),
7792 )?;
7793
7794 let output = result.output().cloned().unwrap_or_default();
7795 let response = if result.is_success() {
7796 EthCallResponse { value: Some(output), error: None }
7797 } else {
7798 let error = RevertDecoder::new()
7799 .maybe_decode(&output, None)
7800 .unwrap_or_else(|| "execution failed".to_string());
7801 EthCallResponse { value: None, error: Some(error) }
7802 };
7803
7804 cache_db.commit(state);
7805 bundle_results.push(response);
7806 }
7807
7808 results.push(bundle_results);
7809 block_env.number = block_env.number.saturating_add(U256::ONE);
7810 block_env.timestamp = block_env.timestamp.saturating_add(U256::ONE);
7811 #[cfg(feature = "monad")]
7812 self::monad::advance_block_context(&mut monad_context);
7813 }
7814
7815 Ok(results)
7816 },
7817 )
7818 .await
7819 }
7820
7821 pub async fn simulate(
7823 &self,
7824 request: SimulatePayload,
7825 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7826 block_interval: u64,
7827 ) -> Result<Vec<SimulatedBlock<AnyRpcBlock>>, BlockchainError> {
7828 self.simulate_raw(
7829 preserve_simulation_request_fields(request),
7830 block_request,
7831 block_interval,
7832 )
7833 .await
7834 }
7835
7836 pub(crate) async fn simulate_raw(
7838 &self,
7839 request: SimulatePayload<WithOtherFields<TransactionRequest>>,
7840 block_request: Option<BlockRequest<FoundryTxEnvelope>>,
7841 block_interval: u64,
7842 ) -> Result<Vec<SimulatedBlock<AnyRpcBlock>>, BlockchainError> {
7843 let simulate_at = |state: Box<dyn MaybeFullDatabase + '_>,
7844 base_block_env: BlockEnv,
7845 base_number,
7846 base_timestamp,
7847 base_hash,
7848 base_fee,
7849 mut monad_context: Option<MonadReplayContext>,
7850 base_base_fee_per_gas,
7851 base_excess_blob_gas,
7852 base_blob_gas_used,
7853 base_fee_extra_data: Bytes,
7854 optimism_jovian: bool| {
7855 let SimulatePayload {
7856 block_state_calls,
7857 trace_transfers,
7858 validation,
7859 return_full_transactions,
7860 } = request;
7861 let block_state_calls = sanitize_simulation_blocks(
7862 block_state_calls,
7863 base_number,
7864 base_timestamp,
7865 block_interval,
7866 )?;
7867 let mut cache_db = CacheDB::new(state);
7868 cache_db.cache.block_hashes.insert(U256::from(base_number), base_hash);
7869 let mut block_res = Vec::with_capacity(block_state_calls.len());
7870 let mut parent_hash = base_hash;
7871 let mut next_base_fee = base_fee;
7872 let mut inherited_block_env = base_block_env;
7873 let (is_merge, is_cancun, is_amsterdam, tx_gas_limit_cap) = {
7874 let cfg_env = &self.evm_env.read().cfg_env;
7875 (
7876 cfg_env.spec >= SpecId::MERGE,
7877 cfg_env.spec >= SpecId::CANCUN,
7878 cfg_env.spec >= SpecId::AMSTERDAM,
7879 cfg_env.tx_gas_limit_cap(),
7880 )
7881 };
7882 let mut parent_base_fee_per_gas = base_base_fee_per_gas;
7883 let mut parent_excess_blob_gas = base_excess_blob_gas;
7884 let mut parent_blob_gas_used = base_blob_gas_used;
7885 let mut rpc_gas_budget = SIMULATE_GAS_CAP;
7886
7887 for block in block_state_calls {
7889 let SimBlock { block_overrides, state_overrides, calls } = block;
7890 let mut block_env = inherited_block_env.clone();
7891 let overridden_beacon_root =
7892 block_overrides.as_ref().and_then(|overrides| overrides.beacon_root);
7893 let block_timestamp = block_overrides
7894 .as_ref()
7895 .and_then(|overrides| overrides.time)
7896 .unwrap_or_else(|| block_env.timestamp.saturating_to());
7897 let blob_params = self.simulation_blob_params_at_timestamp(block_timestamp);
7898 if is_cancun {
7899 let excess_blob_gas = self.networks.next_block_blob_excess_gas(
7900 blob_params,
7901 parent_excess_blob_gas,
7902 parent_blob_gas_used,
7903 parent_base_fee_per_gas,
7904 );
7905 block_env.set_blob_excess_gas_and_price(
7906 excess_blob_gas,
7907 blob_params.update_fraction as u64,
7908 );
7909 } else {
7910 block_env.blob_excess_gas_and_price = None;
7911 }
7912 block_env.basefee = if validation { next_base_fee } else { 0 };
7913 block_env.prevrandao = Some(B256::ZERO);
7914 if is_merge && !is_arbitrum(self.protocol_chain_id()) {
7915 block_env.difficulty = U256::ZERO;
7916 }
7917 let mut call_res = Vec::with_capacity(calls.len());
7918 let mut log_index = 0;
7919 let mut cumulative_gas_used = 0;
7920 let mut block_regular_gas_used = 0;
7921 let mut block_state_gas_used = 0;
7922 let mut block_blob_gas_used = 0u64;
7923 let mut transactions = Vec::with_capacity(calls.len());
7924 let mut transaction_envelopes = Vec::with_capacity(calls.len());
7925 let mut receipts = Vec::with_capacity(calls.len());
7926 let overridden_block_hashes = block_overrides
7927 .as_ref()
7928 .and_then(|overrides| overrides.block_hash.as_ref())
7929 .map(|overrides| {
7930 overrides
7931 .keys()
7932 .map(|number| {
7933 let number = U256::from(*number);
7934 (number, cache_db.cache.block_hashes.get(&number).copied())
7935 })
7936 .collect::<Vec<_>>()
7937 })
7938 .unwrap_or_default();
7939
7940 if let Some(block_overrides) = block_overrides {
7941 cache_db.apply_block_overrides(block_overrides, &mut block_env);
7942 }
7943 let simulation_evm_env =
7944 EvmEnv::new(self.evm_env.read().cfg_env.clone(), block_env.clone());
7945 let spec_id = *simulation_evm_env.spec_id();
7946 let ethereum_transitions = self
7947 .ethereum_block_transitions(self.hardfork(), None, BlockExecutionKind::Complete)
7948 .map(|mut transitions| {
7949 transitions.parent_beacon_block_root = (transitions.hardfork
7950 >= EthereumHardfork::Cancun)
7951 .then_some(overridden_beacon_root.unwrap_or_default());
7952 transitions
7953 });
7954 let precompile_overrides = self.simulation_precompile_overrides(
7955 state_overrides.as_ref(),
7956 &simulation_evm_env,
7957 )?;
7958
7959 if let Some(mut state_overrides) = state_overrides {
7962 state_overrides.retain(|_, account| {
7963 account.balance.is_some()
7964 || account.nonce.is_some()
7965 || account.code.is_some()
7966 || account.state.is_some()
7967 || account
7968 .state_diff
7969 .as_ref()
7970 .is_some_and(|state_diff| !state_diff.is_empty())
7971 });
7972 let previously_deleted = previously_deleted_accounts(
7973 &cache_db.cache.accounts,
7974 state_overrides.keys().copied(),
7975 );
7976 apply_state_overrides(state_overrides, &mut cache_db)?;
7977 preserve_deleted_storage(&mut cache_db.cache.accounts, previously_deleted);
7978 }
7979
7980 if let Some(transitions) = ethereum_transitions {
7981 self.apply_simulation_pre_execution_changes(
7982 &mut cache_db,
7983 &simulation_evm_env,
7984 parent_hash,
7985 transitions,
7986 )?;
7987 }
7988
7989 for (req_idx, mut request) in calls.into_iter().enumerate() {
7991 let classified_request = self.parse_transaction_request(request.clone())?;
7992 let is_ethereum_request = classified_request.is_ethereum();
7993 let mut parsed_request = self.is_tempo().then_some(classified_request);
7994 if is_ethereum_request {
7995 request.populate_blob_hashes();
7996 let preferred_type = request.preferred_type();
7997 request.transaction_type = Some(preferred_type as u8);
7998 request.trim_conflicting_keys();
7999 request.populate_blob_hashes();
8000 }
8001 let request_blob_gas_used = if is_ethereum_request && !optimism_jovian {
8002 u64::try_from(request.blob_versioned_hashes.as_ref().map_or(0, Vec::len))
8003 .unwrap_or(u64::MAX)
8004 .saturating_mul(DATA_GAS_PER_BLOB)
8005 } else {
8006 0
8007 };
8008 let max_blob_gas = block_blob_gas_limit(
8009 optimism_jovian,
8010 block_env.gas_limit,
8011 blob_params.max_blob_gas_per_block(),
8012 );
8013 if !optimism_jovian
8014 && block_blob_gas_used.saturating_add(request_blob_gas_used) > max_blob_gas
8015 {
8016 return Err(BlockchainError::RpcError(RpcError::invalid_params(format!(
8017 "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
8018 ))));
8019 }
8020 block_blob_gas_used = block_blob_gas_used.saturating_add(request_blob_gas_used);
8021
8022 let inner = request.as_ref();
8023 let remaining_regular_gas =
8024 block_env.gas_limit.saturating_sub(block_regular_gas_used);
8025 let remaining_state_gas =
8026 block_env.gas_limit.saturating_sub(block_state_gas_used);
8027 let remaining_gas = if is_amsterdam {
8028 remaining_regular_gas.min(remaining_state_gas)
8029 } else {
8030 block_env.gas_limit.saturating_sub(cumulative_gas_used)
8031 };
8032 let requested_gas = inner.gas.unwrap_or(remaining_gas);
8033 let exceeds_gas_limit = if is_amsterdam {
8034 let requested_regular_gas = requested_gas.min(tx_gas_limit_cap);
8035 requested_regular_gas > remaining_regular_gas
8036 || requested_gas > remaining_state_gas
8037 } else {
8038 requested_gas > remaining_gas
8039 };
8040 if exceeds_gas_limit {
8041 return Err(BlockchainError::RpcError(RpcError {
8042 code: ErrorCode::ServerError(-38015),
8043 message: format!(
8044 "block gas limit exceeded: remaining {remaining_gas}, requested {requested_gas}"
8045 )
8046 .into(),
8047 data: None,
8048 }));
8049 }
8050 let execution_gas_limit = requested_gas.min(rpc_gas_budget);
8051 let preserve_signed_gas = matches!(
8052 &parsed_request,
8053 Some(FoundryTransactionRequest::Tempo(request))
8054 if request.fee_payer_signature.is_some()
8055 );
8056 request.gas = Some(execution_gas_limit);
8057 if !preserve_signed_gas && let Some(parsed_request) = &mut parsed_request {
8058 parsed_request.as_mut().gas = Some(execution_gas_limit);
8059 }
8060
8061 let caller = request.from.unwrap_or_default();
8062 let caller_nonce = RevmDatabase::basic(&mut cache_db, caller)?
8063 .map(|account| account.nonce)
8064 .unwrap_or_default();
8065 let tempo_nonce_key =
8066 parsed_request.as_ref().and_then(|request| match request {
8067 FoundryTransactionRequest::Tempo(request) => request.nonce_key,
8068 _ => None,
8069 });
8070 if request.nonce.is_none() {
8071 let nonce = tempo_nonce_key.map_or(Ok(caller_nonce), |nonce_key| {
8072 tempo_nonce(&cache_db, caller, nonce_key)
8073 })?;
8074 request.nonce = Some(nonce);
8075 if let Some(parsed_request) = &mut parsed_request {
8076 parsed_request.as_mut().nonce = Some(nonce);
8077 }
8078 }
8079
8080 if is_ethereum_request {
8081 let mut canonical_request =
8082 FoundryTransactionRequest::Ethereum(request.inner.clone());
8083 canonical_request.prep_for_submission();
8084 request.inner = canonical_request.as_ref().clone();
8085 if let Some(parsed_request) = &mut parsed_request {
8086 *parsed_request = canonical_request;
8087 }
8088 }
8089
8090 let fee_details = FeeDetails::new(
8091 request.gas_price,
8092 request.max_fee_per_gas,
8093 request.max_priority_fee_per_gas,
8094 request.max_fee_per_blob_gas,
8095 )?
8096 .or_zero_fees();
8097
8098 let PreparedCall { mut evm_env, mut tx_env, simulated_tempo_tx } =
8099 if let Some(parsed_request) = parsed_request {
8100 self.prepare_typed_call_env(
8101 &cache_db,
8102 parsed_request,
8103 fee_details,
8104 block_env.clone(),
8105 )?
8106 } else {
8107 self.prepare_call_env(
8108 &cache_db,
8109 request.clone(),
8110 fee_details,
8111 block_env.clone(),
8112 )?
8113 };
8114 tx_env.base_mut().gas_limit = execution_gas_limit;
8115 apply_tempo_envelope_identity(&mut tx_env, simulated_tempo_tx.as_ref());
8116 if !validation
8117 && tempo_nonce_key.is_none_or(|key| key.is_zero())
8118 && request.nonce == Some(u64::MAX)
8119 {
8120 tx_env.base_mut().nonce = 0;
8121 }
8122 let uses_protocol_call_nonce = tx_env.uses_protocol_call_nonce();
8123 let simulated_envelope = simulated_tempo_tx.map(FoundryTxEnvelope::Tempo);
8124
8125 if is_amsterdam {
8126 let spec = evm_env.cfg_env.spec;
8128 evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec);
8129 }
8130
8131 evm_env.cfg_env.disable_eip3607 = true;
8133
8134 if validation {
8135 evm_env.cfg_env.disable_nonce_check = false;
8136 evm_env.cfg_env.disable_base_fee = false;
8137 evm_env.cfg_env.disable_block_gas_limit = false;
8138 }
8139
8140 let mut inspector = self.build_inspector();
8141
8142 inspector = inspector.with_simulation_logs(trace_transfers);
8144 trace!(target: "backend", env=?evm_env, spec=?evm_env.spec_id(),"simulate evm env");
8145 let execution_result = match tx_env {
8146 CallTxEnv::Eth(tx_env)
8147 if !validation
8148 && tx_env.tx_type == 3
8149 && tx_env.max_fee_per_blob_gas == 0 =>
8150 {
8151 self.transact_eth_simulation_with_inspector_ref(
8152 &cache_db,
8153 &evm_env,
8154 &mut inspector,
8155 tx_env,
8156 &precompile_overrides,
8157 )
8158 }
8159 tx_env if precompile_overrides.moves.is_empty() => self
8160 .transact_call_with_inspector_ref(
8161 &cache_db,
8162 &evm_env,
8163 &mut inspector,
8164 tx_env,
8165 monad_context.as_mut().map(next_monad_context),
8166 ),
8167 tx_env => self.transact_eth_with_inspector_ref_and_precompile_overrides(
8168 &cache_db,
8169 &evm_env,
8170 &mut inspector,
8171 tx_env.into_base(),
8172 &precompile_overrides,
8173 ),
8174 };
8175 let ResultAndState { result, mut state } = match execution_result {
8176 Err(BlockchainError::InvalidTransaction(error)) => {
8177 return Err(simulate_transaction_error(error));
8178 }
8179 result => result?,
8180 };
8181 if !validation
8182 && caller_nonce == u64::MAX
8183 && uses_protocol_call_nonce
8184 && let Some(account) = state.get_mut(&caller)
8185 {
8186 account.info.nonce = 0;
8187 }
8188 trace!(target: "backend", ?result, ?request, "simulate call");
8189
8190 let canonical_logs = result.clone().into_logs();
8191 let (response_logs, attempted_log_count) = inspector
8192 .take_simulation_logs(&canonical_logs, result.is_success())
8193 .expect("simulation log collector is installed");
8194 inspector.print_logs();
8195 if self.print_traces {
8196 inspector.into_print_traces(self.call_trace_decoder());
8197 }
8198
8199 let previously_deleted = previously_deleted_accounts(
8203 &cache_db.cache.accounts,
8204 state.keys().copied(),
8205 );
8206
8207 rpc_gas_budget = rpc_gas_budget.saturating_sub(result.tx_gas_used());
8208 cumulative_gas_used = cumulative_gas_used.saturating_add(result.tx_gas_used());
8209 block_regular_gas_used = block_regular_gas_used
8210 .saturating_add(result.gas().block_regular_gas_used());
8211 block_state_gas_used =
8212 block_state_gas_used.saturating_add(result.gas().block_state_gas_used());
8213
8214 let from = caller;
8216 request.sidecar = None;
8217 let tx = if let Some(envelope) = simulated_envelope {
8218 MaybeImpersonatedTransaction::impersonated(envelope, from)
8219 } else {
8220 if request.to.is_none() {
8221 request.to = Some(TxKind::Create);
8222 }
8223 let mut request = self.parse_transaction_request(request)?;
8224 request.prep_for_submission();
8225 let typed_tx = request.build_unsigned().map_err(|e| {
8226 BlockchainError::InvalidTransactionRequest(e.to_string())
8227 })?;
8228 MaybeImpersonatedTransaction::impersonated(
8229 typed_tx.into_impersonated(),
8230 from,
8231 )
8232 };
8233 let tx_hash = tx.as_ref().hash();
8234 #[cfg(feature = "optimism")]
8235 if optimism_jovian {
8236 let tx_blob_gas = crate::eth::backend::executor::optimism::blob_gas_used(
8237 &mut cache_db,
8238 tx.as_ref(),
8239 true,
8240 )
8241 .map_err(|err| BlockchainError::Internal(err.to_string()))?;
8242 if block_blob_gas_used.saturating_add(tx_blob_gas) > max_blob_gas {
8243 return Err(BlockchainError::RpcError(RpcError::invalid_params(
8244 format!(
8245 "blob gas usage exceeds the limit of {max_blob_gas} gas per block."
8246 ),
8247 )));
8248 }
8249 block_blob_gas_used = block_blob_gas_used.saturating_add(tx_blob_gas);
8250 }
8251
8252 cache_db.commit(state);
8255 preserve_deleted_storage(&mut cache_db.cache.accounts, previously_deleted);
8256 #[cfg(feature = "optimism")]
8257 let receipt = if matches!(tx.as_ref(), FoundryTxEnvelope::Deposit(_)) {
8258 crate::eth::backend::executor::optimism::build_simulated_deposit_receipt(
8259 self.hardfork(),
8260 caller_nonce,
8261 &result,
8262 canonical_logs.clone(),
8263 cumulative_gas_used,
8264 )
8265 } else {
8266 FoundryReceiptBuilder::build_simulated_receipt(
8267 tx.as_ref().tx_type(),
8268 &result,
8269 canonical_logs.clone(),
8270 cumulative_gas_used,
8271 )
8272 };
8273 #[cfg(not(feature = "optimism"))]
8274 let receipt = FoundryReceiptBuilder::build_simulated_receipt(
8275 tx.as_ref().tx_type(),
8276 &result,
8277 canonical_logs.clone(),
8278 cumulative_gas_used,
8279 );
8280 receipts.push(receipt);
8281 transaction_envelopes.push(tx.as_ref().clone());
8282 let rpc_tx =
8283 transaction_build(Some(tx_hash), tx, None, None, Some(block_env.basefee));
8284 transactions.push(rpc_tx);
8285
8286 let return_data = if result.is_success() {
8287 result.output().cloned().unwrap_or_default()
8288 } else {
8289 Bytes::new()
8290 };
8291 let sim_res = SimCallResult {
8292 return_data,
8293 gas_used: result.tx_gas_used(),
8294 max_used_gas: Some(
8295 result.gas().total_gas_spent().max(result.gas().floor_gas()),
8296 ),
8297 status: result.is_success(),
8298 error: match &result {
8299 ExecutionResult::Success { .. } => None,
8300 ExecutionResult::Revert { output, .. } => {
8301 let message = RevertDecoder::new()
8302 .maybe_decode(output, None)
8303 .map(|reason| format!("execution reverted: {reason}"))
8304 .unwrap_or_else(|| "execution reverted".to_string());
8305 Some(SimulateError {
8306 code: SimulateError::EXECUTION_REVERTED_CODE,
8307 message,
8308 data: Some(output.clone()),
8309 })
8310 }
8311 ExecutionResult::Halt { reason, .. } => Some(SimulateError {
8312 code: SimulateError::VM_EXECUTION_ERROR_CODE,
8313 message: if matches!(reason, HaltReason::OutOfGas(_)) {
8314 "out of gas".to_string()
8315 } else {
8316 format!("vm execution error: {reason}")
8317 },
8318 data: None,
8319 }),
8320 },
8321 logs: response_logs
8322 .into_iter()
8323 .map(|(idx, log)| Log {
8324 inner: log,
8325 block_number: Some(block_env.number.saturating_to()),
8326 block_timestamp: Some(block_env.timestamp.saturating_to()),
8327 transaction_index: Some(req_idx as u64),
8328 log_index: Some(idx + log_index),
8329 removed: false,
8330
8331 block_hash: None,
8332 transaction_hash: Some(tx_hash),
8333 })
8334 .collect(),
8335 };
8336 log_index += attempted_log_count;
8337 call_res.push(sim_res);
8338 }
8339
8340 for (number, hash) in overridden_block_hashes {
8341 if let Some(hash) = hash {
8342 cache_db.cache.block_hashes.insert(number, hash);
8343 } else {
8344 cache_db.cache.block_hashes.remove(&number);
8345 }
8346 }
8347
8348 let gas_used = if is_amsterdam {
8349 block_regular_gas_used.max(block_state_gas_used)
8350 } else {
8351 cumulative_gas_used
8352 };
8353 let requests = if let Some(transitions) = ethereum_transitions {
8354 self.apply_simulation_post_execution_changes(
8355 &mut cache_db,
8356 &simulation_evm_env,
8357 transitions,
8358 &receipts,
8359 )?
8360 } else {
8361 Default::default()
8362 };
8363
8364 let state_root = cache_db
8366 .maybe_full_db()
8367 .map(|accounts| state_root(&accounts))
8368 .unwrap_or_default();
8369 let header = Header {
8370 logs_bloom: receipts.iter().fold(Bloom::ZERO, |mut bloom, receipt| {
8371 bloom.accrue_bloom(receipt.logs_bloom());
8372 bloom
8373 }),
8374 transactions_root: calculate_transaction_root(&transaction_envelopes),
8375 receipts_root: calculate_receipt_root(&receipts),
8376 parent_hash,
8377 beneficiary: block_env.beneficiary,
8378 state_root,
8379 difficulty: block_env.difficulty,
8380 number: block_env.number.saturating_to(),
8381 gas_limit: block_env.gas_limit,
8382 gas_used,
8383 timestamp: block_env.timestamp.saturating_to(),
8384 extra_data: base_fee_extra_data.clone(),
8385 mix_hash: block_env.prevrandao.unwrap_or_default(),
8386 nonce: Default::default(),
8387 base_fee_per_gas: (spec_id >= SpecId::LONDON).then_some(block_env.basefee),
8388 withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
8389 blob_gas_used: is_cancun.then_some(block_blob_gas_used),
8390 excess_blob_gas: if is_cancun { block_env.blob_excess_gas() } else { None },
8391 parent_beacon_block_root: ethereum_transitions.and_then(|transitions| {
8392 (transitions.hardfork >= EthereumHardfork::Cancun)
8393 .then_some(transitions.parent_beacon_block_root.unwrap_or_default())
8394 }),
8395 requests_hash: ethereum_transitions.and_then(|transitions| {
8396 (transitions.hardfork >= EthereumHardfork::Prague)
8397 .then(|| requests.requests_hash())
8398 }),
8399 ..Default::default()
8400 };
8401 let block_hash = header.hash_slow();
8402 for (transaction_index, transaction) in transactions.iter_mut().enumerate() {
8403 transaction.block_hash = Some(block_hash);
8404 transaction.block_number = Some(header.number);
8405 transaction.transaction_index = Some(transaction_index as u64);
8406 transaction.block_timestamp = Some(header.timestamp);
8407 }
8408 let mut block = alloy_rpc_types::Block {
8409 header: AnyRpcHeader {
8410 hash: block_hash,
8411 inner: header.into(),
8412 total_difficulty: None,
8413 size: None,
8414 },
8415 uncles: vec![],
8416 transactions: BlockTransactions::Full(transactions),
8417 withdrawals: (spec_id >= SpecId::SHANGHAI).then_some(Default::default()),
8418 };
8419
8420 if !return_full_transactions {
8421 block.transactions.convert_to_hashes();
8422 }
8423
8424 for res in &mut call_res {
8425 res.logs.iter_mut().for_each(|log| {
8426 log.block_hash = Some(block.header.hash);
8427 });
8428 }
8429
8430 let simulated_block = SimulatedBlock {
8431 inner: AnyRpcBlock::new(WithOtherFields::new(block)),
8432 calls: call_res,
8433 };
8434
8435 parent_hash = block_hash;
8436 cache_db.cache.block_hashes.insert(block_env.number, block_hash);
8437 inherited_block_env.beneficiary = block_env.beneficiary;
8438 inherited_block_env.difficulty = block_env.difficulty;
8439 inherited_block_env.gas_limit = block_env.gas_limit;
8440 let header = &simulated_block.inner.header;
8442 next_base_fee = self.fees.calculate_next_block_base_fee_from_header(&header.inner);
8443 parent_base_fee_per_gas = header.base_fee_per_gas().unwrap_or_default();
8444 parent_excess_blob_gas = header.excess_blob_gas().unwrap_or_default();
8445 parent_blob_gas_used = header.blob_gas_used().unwrap_or_default();
8446
8447 block_res.push(simulated_block);
8448 #[cfg(feature = "monad")]
8449 self::monad::advance_block_context(&mut monad_context);
8450 }
8451
8452 Ok(block_res)
8453 };
8454
8455 match block_request {
8456 Some(BlockRequest::Pending(pool_transactions)) => {
8457 self.with_pending_block(pool_transactions, |state, block| {
8458 let header = &block.block.header;
8459 let parent_fees = self.fees.calculate_parent_header_fees(header);
8460 let optimism_jovian =
8461 self.is_optimism_jovian_at_header(header, parent_fees.optimism_jovian);
8462 let monad_context = self.active_monad_context_before_mined_transaction(
8463 &block.block,
8464 block.block.body.transactions.len(),
8465 )?;
8466 #[cfg(feature = "monad")]
8467 let monad_context = {
8468 let mut monad_context = monad_context;
8469 self::monad::advance_block_context(&mut monad_context);
8470 monad_context
8471 };
8472 simulate_at(
8473 state,
8474 block_env_from_header(header),
8475 header.number(),
8476 header.timestamp(),
8477 header.hash_slow(),
8478 parent_fees.base_fee,
8479 monad_context,
8480 header.base_fee_per_gas().unwrap_or_default(),
8481 header.excess_blob_gas().unwrap_or_default(),
8482 header.blob_gas_used().unwrap_or_default(),
8483 parent_fees.extra_data,
8484 optimism_jovian,
8485 )
8486 })
8487 .await
8488 }
8489 block_request => {
8490 let base_block_number = match block_request.as_ref() {
8491 Some(BlockRequest::Number(number)) => BlockNumber::Number(*number),
8492 Some(BlockRequest::Pending(_)) => unreachable!(),
8493 None => BlockNumber::Latest,
8494 };
8495 let base_block = self
8496 .block_by_number(base_block_number)
8497 .await?
8498 .ok_or(BlockchainError::BlockNotFound)?;
8499 let base_number = base_block.header.number();
8500 let base_timestamp = base_block.header.timestamp();
8501 let base_hash = base_block.header.hash;
8502 let parent_fees = self.fees.calculate_parent_header_fees(&base_block.header.inner);
8503 let optimism_jovian = self.is_optimism_jovian_at_header(
8504 &base_block.header.inner,
8505 parent_fees.optimism_jovian,
8506 );
8507
8508 #[cfg(feature = "monad")]
8509 let monad_context = if self.is_monad() {
8510 Some(self.monad_context_for_child_of_block_number(base_number).await?)
8511 } else {
8512 None
8513 };
8514 #[cfg(not(feature = "monad"))]
8515 let monad_context = None;
8516
8517 self.with_database_at(block_request, |state, block_env| {
8518 simulate_at(
8519 state,
8520 block_env,
8521 base_number,
8522 base_timestamp,
8523 base_hash,
8524 parent_fees.base_fee,
8525 monad_context,
8526 base_block.header.base_fee_per_gas().unwrap_or_default(),
8527 base_block.header.excess_blob_gas().unwrap_or_default(),
8528 base_block.header.blob_gas_used().unwrap_or_default(),
8529 parent_fees.extra_data,
8530 optimism_jovian,
8531 )
8532 })
8533 .await?
8534 }
8535 }
8536 }
8537
8538 pub fn get_blob_by_tx_hash(&self, hash: B256) -> Result<Option<Vec<alloy_consensus::Blob>>> {
8539 let storage = self.blockchain.storage.read();
8540 Ok(storage.transactions.get(&hash).and_then(|mined| {
8541 storage
8542 .blocks
8543 .get(&mined.block_hash)?
8544 .body
8545 .transactions
8546 .get(mined.info.transaction_index as usize)?
8547 .as_ref()
8548 .sidecar()
8549 .map(|sidecar| sidecar.sidecar.blobs().to_vec())
8550 }))
8551 }
8552
8553 pub async fn set_fee_token(&self, user: Address, token: Address) -> DatabaseResult<()> {
8555 self.with_tempo_storage(|| {
8556 let mut fee_manager = TipFeeManager::new();
8557 fee_manager
8558 .set_user_token(user, IFeeManager::setUserTokenCall { token })
8559 .map_err(tempo_db_err)
8560 })
8561 .await
8562 }
8563
8564 pub async fn set_validator_fee_token(
8566 &self,
8567 validator: Address,
8568 token: Address,
8569 ) -> DatabaseResult<()> {
8570 self.with_tempo_storage(|| {
8571 let mut fee_manager = TipFeeManager::new();
8572 fee_manager
8574 .set_validator_token(
8575 validator,
8576 IFeeManager::setValidatorTokenCall { token },
8577 Address::ZERO,
8578 )
8579 .map_err(tempo_db_err)
8580 })
8581 .await
8582 }
8583
8584 pub async fn set_fee_amm_liquidity(
8586 &self,
8587 user_token: Address,
8588 validator_token: Address,
8589 amount: U256,
8590 ) -> DatabaseResult<()> {
8591 let admin = Address::repeat_byte(0x11);
8593 self.with_tempo_storage(|| {
8594 for &token_address in &[user_token, validator_token] {
8597 let mut token = TIP20Token::from_address(token_address).map_err(tempo_db_err)?;
8598 token.grant_role_internal(admin, *ISSUER_ROLE).map_err(tempo_db_err)?;
8599 token.mint(admin, ITIP20::mintCall { to: admin, amount }).map_err(tempo_db_err)?;
8600 }
8601 let mut fee_manager = TipFeeManager::new();
8602 fee_manager
8603 .mint(admin, user_token, validator_token, amount, admin)
8604 .map_err(tempo_db_err)?;
8605 Ok(())
8606 })
8607 .await
8608 }
8609
8610 pub async fn set_tip20_balance(
8612 &self,
8613 address: Address,
8614 token_address: Address,
8615 balance: U256,
8616 ) -> DatabaseResult<()> {
8617 if self.try_set_tip20_balance(address, token_address, balance).await? {
8618 return Ok(());
8619 }
8620
8621 Err(tempo_db_err(format!("address {token_address} is not a deployed TIP-20 token")))
8622 }
8623
8624 pub async fn try_set_tip20_balance(
8626 &self,
8627 address: Address,
8628 token_address: Address,
8629 balance: U256,
8630 ) -> DatabaseResult<bool> {
8631 self.with_tempo_storage(|| {
8632 if !TIP20Factory::new().is_tip20(token_address).map_err(tempo_db_err)? {
8633 return Ok(false);
8634 }
8635
8636 let mut token = TIP20Token::from_address(token_address).map_err(tempo_db_err)?;
8637 token.balances[address].write(balance).map_err(tempo_db_err)?;
8638 Ok(true)
8639 })
8640 .await
8641 }
8642
8643 async fn with_tempo_storage<R>(&self, f: impl FnOnce() -> R) -> R {
8646 let hardfork = self.hardfork();
8647 let (chain_id, timestamp, block_number) = {
8649 let env = self.evm_env.read();
8650 (
8651 env.cfg_env.chain_id,
8652 U256::from(env.block_env.timestamp),
8653 env.block_env.number.to::<u64>(),
8654 )
8655 };
8656 let mut db = self.db.write().await;
8657 let mut storage = AnvilStorageProvider::new(
8658 &mut **db,
8659 chain_id,
8660 timestamp,
8661 block_number,
8662 hardfork.into(),
8663 );
8664 StorageCtx::enter(&mut storage, f)
8665 }
8666}
8667
8668fn tempo_db_err<E: std::fmt::Display>(e: E) -> DatabaseError {
8670 DatabaseError::AnyRequest(Arc::new(eyre::eyre!("{e}")))
8671}
8672
8673fn get_pool_transactions_nonce(
8675 pool_transactions: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
8676 address: Address,
8677) -> Option<u64> {
8678 if let Some(highest_nonce) = pool_transactions
8679 .iter()
8680 .filter(|tx| {
8681 *tx.pending_transaction.sender() == address
8682 && !tx.pending_transaction.transaction.as_ref().has_nonzero_tempo_nonce_key()
8683 })
8684 .map(|tx| tx.pending_transaction.nonce())
8685 .max()
8686 {
8687 let tx_count = highest_nonce.saturating_add(1);
8688 return Some(tx_count);
8689 }
8690 None
8691}
8692
8693impl<N: Network> Backend<N>
8694where
8695 N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
8696{
8697 fn validate_mining_pool_transaction_for(
8699 &self,
8700 pool_tx: &PoolTransaction<FoundryTxEnvelope>,
8701 account: &AccountInfo,
8702 evm_env: &EvmEnv,
8703 ) -> Result<(), InvalidTransactionError> {
8704 #[cfg(feature = "monad")]
8705 if self.validate_monad_mining_pool_transaction_for(pool_tx, account, evm_env)? {
8706 return Ok(());
8707 }
8708
8709 self.validate_pool_transaction_for(&pool_tx.pending_transaction, account, evm_env)
8710 }
8711}
8712
8713#[async_trait::async_trait]
8714impl<N: Network> TransactionValidator<FoundryTxEnvelope> for Backend<N>
8715where
8716 N: Network<TxEnvelope = FoundryTxEnvelope, ReceiptEnvelope = FoundryReceiptEnvelope>,
8717{
8718 async fn validate_pool_transaction(
8719 &self,
8720 tx: &PendingTransaction<FoundryTxEnvelope>,
8721 ) -> Result<(), BlockchainError> {
8722 let address = *tx.sender();
8723 let account = self.get_account(address).await?;
8724 let evm_env = self.next_evm_env();
8725
8726 if let FoundryTxEnvelope::Tempo(aa_tx) = tx.transaction.as_ref() {
8728 let tempo_tx = aa_tx.tx();
8729 let current_time = evm_env.block_env.timestamp.saturating_to::<u64>();
8730
8731 const AA_VALID_BEFORE_MIN_SECS: u64 = 3;
8733 if let Some(valid_before) = tempo_tx.valid_before.map(|v| v.get()) {
8734 let min_allowed = current_time.saturating_add(AA_VALID_BEFORE_MIN_SECS);
8735 if valid_before <= min_allowed {
8736 return Err(InvalidTransactionError::TempoValidBeforeExpired {
8737 valid_before,
8738 min_allowed,
8739 }
8740 .into());
8741 }
8742 }
8743
8744 const AA_VALID_AFTER_MAX_SECS: u64 = 3600;
8746 if let Some(valid_after) = tempo_tx.valid_after.map(|v| v.get()) {
8747 let max_allowed = current_time.saturating_add(AA_VALID_AFTER_MAX_SECS);
8748 if valid_after > max_allowed {
8749 return Err(InvalidTransactionError::TempoValidAfterTooFar {
8750 valid_after,
8751 max_allowed,
8752 }
8753 .into());
8754 }
8755 }
8756
8757 let fee_payer = tempo_tx.recover_fee_payer(address).unwrap_or(address);
8759 let fee_token =
8760 tempo_tx.fee_token.unwrap_or(foundry_evm::core::tempo::PATH_USD_ADDRESS);
8761
8762 let required_wei =
8764 U256::from(tempo_tx.gas_limit).saturating_mul(U256::from(tempo_tx.max_fee_per_gas));
8765 let required = required_wei / U256::from(10u64.pow(12));
8766
8767 let balance = self.get_fee_token_balance(fee_token, fee_payer).await?;
8768 if balance < required {
8769 return Err(InvalidTransactionError::TempoInsufficientFeeTokenBalance {
8770 balance,
8771 required,
8772 }
8773 .into());
8774 }
8775 }
8776
8777 Ok(self.validate_pool_transaction_for(tx, &account, &evm_env)?)
8778 }
8779
8780 fn validate_pool_transaction_for(
8781 &self,
8782 pending: &PendingTransaction<FoundryTxEnvelope>,
8783 account: &AccountInfo,
8784 evm_env: &EvmEnv,
8785 ) -> Result<(), InvalidTransactionError> {
8786 let tx = &pending.transaction;
8787
8788 if let Some(tx_chain_id) = tx.chain_id() {
8789 let chain_id = self.chain_id();
8790 if chain_id.to::<u64>() != tx_chain_id {
8791 if let FoundryTxEnvelope::Legacy(tx) = tx.as_ref() {
8792 if evm_env.cfg_env.spec >= SpecId::SPURIOUS_DRAGON && tx.chain_id().is_none() {
8794 debug!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V");
8795 return Err(InvalidTransactionError::IncompatibleEIP155);
8796 }
8797 } else {
8798 debug!(target: "backend", ?chain_id, ?tx_chain_id, "invalid chain id");
8799 return Err(InvalidTransactionError::InvalidChainId);
8800 }
8801 }
8802 }
8803
8804 if self.is_tempo() && !tx.value().is_zero() {
8806 warn!(target: "backend", "[{:?}] native value transfer not allowed in Tempo mode", tx.hash());
8807 return Err(InvalidTransactionError::TempoNativeValueTransfer);
8808 }
8809
8810 if self.is_tempo_hardfork_active(TempoHardfork::T5)
8812 && let FoundryTxEnvelope::Tempo(aa_tx) = tx.as_ref()
8813 {
8814 const MAX_TEMPO_AUTHORIZATIONS: usize = 16;
8815 let auth_count = aa_tx.tx().tempo_authorization_list.len();
8816 if auth_count > MAX_TEMPO_AUTHORIZATIONS {
8817 warn!(target: "backend", "[{:?}] Tempo tx has too many authorizations: {}", tx.hash(), auth_count);
8818 return Err(InvalidTransactionError::TempoTooManyAuthorizations {
8819 count: auth_count,
8820 max: MAX_TEMPO_AUTHORIZATIONS,
8821 });
8822 }
8823 }
8824
8825 #[cfg(feature = "optimism")]
8827 let is_deposit_tx = pending.transaction.as_ref().is_deposit();
8828 #[cfg(not(feature = "optimism"))]
8829 let is_deposit_tx = false;
8830 let is_tempo_tx = pending.transaction.as_ref().is_tempo();
8831 let nonce = tx.nonce();
8832 if nonce < account.nonce && !is_deposit_tx && !is_tempo_tx {
8833 debug!(target: "backend", "[{:?}] nonce too low", tx.hash());
8834 return Err(InvalidTransactionError::NonceTooLow);
8835 }
8836
8837 #[cfg(feature = "monad")]
8838 self.validate_monad_transaction_type(tx)?;
8839
8840 if evm_env.cfg_env.spec >= SpecId::CANCUN && tx.is_eip4844() {
8842 let blob_tx = match tx.as_ref() {
8844 FoundryTxEnvelope::Eip4844(tx) => tx.tx(),
8845 _ => unreachable!(),
8846 };
8847
8848 let blob_count = blob_tx.tx().blob_versioned_hashes.len();
8849
8850 if blob_count == 0 {
8852 return Err(InvalidTransactionError::NoBlobHashes);
8853 }
8854
8855 let max_blobs_per_tx = self.blob_params().max_blobs_per_tx as usize;
8857 if blob_count > max_blobs_per_tx {
8858 return Err(InvalidTransactionError::TooManyBlobs(blob_count, max_blobs_per_tx));
8859 }
8860
8861 if !self.skip_blob_validation(Some(*pending.sender()))
8863 && let Err(err) = blob_tx.validate(EnvKzgSettings::default().get())
8864 {
8865 return Err(InvalidTransactionError::BlobTransactionValidationError(err));
8866 }
8867 }
8868
8869 if evm_env.cfg_env.spec >= SpecId::SHANGHAI && tx.kind() == TxKind::Create {
8871 let max_initcode_size = evm_env
8872 .cfg_env
8873 .limit_contract_code_size
8874 .map(|limit| limit.saturating_mul(2))
8875 .unwrap_or_else(|| self.max_initcode_size(evm_env));
8876 if tx.input().len() > max_initcode_size {
8877 return Err(InvalidTransactionError::MaxInitCodeSizeExceeded);
8878 }
8879 }
8880
8881 if !self.disable_pool_balance_checks {
8883 if tx.gas_limit() < MIN_TRANSACTION_GAS as u64 {
8885 debug!(target: "backend", "[{:?}] gas too low", tx.hash());
8886 return Err(InvalidTransactionError::GasTooLow);
8887 }
8888
8889 if !evm_env.cfg_env.disable_block_gas_limit
8891 && tx.gas_limit() > evm_env.block_env.gas_limit
8892 {
8893 debug!(target: "backend", "[{:?}] gas too high", tx.hash());
8894 return Err(InvalidTransactionError::GasTooHigh(ErrDetail {
8895 detail: String::from("tx.gas_limit > env.block.gas_limit"),
8896 }));
8897 }
8898
8899 if evm_env.cfg_env.tx_gas_limit_cap.is_none()
8901 && tx.gas_limit() > self.tx_gas_limit_cap(evm_env)
8902 {
8903 debug!(target: "backend", "[{:?}] gas too high", tx.hash());
8904 return Err(InvalidTransactionError::GasTooHigh(ErrDetail {
8905 detail: String::from("tx.gas_limit > resolved tx gas limit cap"),
8906 }));
8907 }
8908
8909 if evm_env.cfg_env.spec >= SpecId::LONDON {
8911 if tx.max_fee_per_gas() < evm_env.block_env.basefee.into() && !is_deposit_tx {
8912 debug!(target: "backend", "max fee per gas={}, too low, block basefee={}", tx.max_fee_per_gas(), evm_env.block_env.basefee);
8913 return Err(InvalidTransactionError::FeeCapTooLow);
8914 }
8915
8916 if !evm_env.cfg_env.disable_priority_fee_check
8917 && let (Some(max_priority_fee_per_gas), max_fee_per_gas) =
8918 (tx.as_ref().max_priority_fee_per_gas(), tx.as_ref().max_fee_per_gas())
8919 && max_priority_fee_per_gas > max_fee_per_gas
8920 {
8921 debug!(target: "backend", "max priority fee per gas={}, too high, max fee per gas={}", max_priority_fee_per_gas, max_fee_per_gas);
8922 return Err(InvalidTransactionError::TipAboveFeeCap);
8923 }
8924 }
8925
8926 if evm_env.cfg_env.spec >= SpecId::CANCUN
8928 && tx.is_eip4844()
8929 && let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas()
8930 && let Some(blob_gas_and_price) = &evm_env.block_env.blob_excess_gas_and_price
8931 && max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice
8932 {
8933 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);
8934 return Err(InvalidTransactionError::BlobFeeCapTooLow(
8935 max_fee_per_blob_gas,
8936 blob_gas_and_price.blob_gasprice,
8937 ));
8938 }
8939
8940 let value = tx.value();
8941 match tx.as_ref() {
8942 #[cfg(feature = "optimism")]
8943 FoundryTxEnvelope::Deposit(deposit_tx) => {
8944 if value > account.balance + U256::from(deposit_tx.mint) {
8950 debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + U256::from(deposit_tx.mint), value, *pending.sender());
8951 return Err(InvalidTransactionError::InsufficientFunds);
8952 }
8953 }
8954 FoundryTxEnvelope::Tempo(_) => {
8955 }
8958 #[cfg(feature = "monad")]
8959 _ if self.validate_monad_transaction_funds(pending, account, evm_env)? => {}
8960 _ => {
8961 let max_cost = (tx.gas_limit() as u128)
8962 .saturating_mul(tx.max_fee_per_gas())
8963 .saturating_add(
8964 tx.blob_gas_used()
8965 .map(|g| g as u128)
8966 .unwrap_or(0)
8967 .mul(tx.max_fee_per_blob_gas().unwrap_or(0)),
8968 );
8969 let req_funds =
8971 max_cost.checked_add(value.saturating_to()).ok_or_else(|| {
8972 debug!(target: "backend", "[{:?}] cost too high", tx.hash());
8973 InvalidTransactionError::InsufficientFunds
8974 })?;
8975 if account.balance < U256::from(req_funds) {
8976 debug!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender());
8977 return Err(InvalidTransactionError::InsufficientFunds);
8978 }
8979 }
8980 }
8981 }
8982 Ok(())
8983 }
8984
8985 fn validate_for(
8986 &self,
8987 tx: &PendingTransaction<FoundryTxEnvelope>,
8988 account: &AccountInfo,
8989 evm_env: &EvmEnv,
8990 ) -> Result<(), InvalidTransactionError> {
8991 self.validate_pool_transaction_for(tx, account, evm_env)?;
8992 if tx.nonce() > account.nonce {
8993 return Err(InvalidTransactionError::NonceTooHigh);
8994 }
8995 Ok(())
8996 }
8997}
8998
8999fn rehash<T>(signed: Signed<T>, hash: B256) -> Signed<T>
9001where
9002 T: alloy_consensus::transaction::RlpEcdsaEncodableTx,
9003{
9004 let (t, sig, _) = signed.into_parts();
9005 Signed::new_unchecked(t, sig, hash)
9006}
9007
9008fn build_rpc_transaction(
9009 envelope: AnyTxEnvelope,
9010 from: Address,
9011 block: Option<&Block>,
9012 info: Option<&TransactionInfo>,
9013 effective_gas_price: Option<u128>,
9014) -> AnyRpcTransaction {
9015 let tx = Transaction {
9016 inner: Recovered::new_unchecked(envelope, from),
9017 block_hash: block.map(|block| block.header.hash_slow()),
9018 block_number: block.map(|block| block.header.number()),
9019 transaction_index: info.map(|info| info.transaction_index),
9020 effective_gas_price,
9021 block_timestamp: block.map(|block| block.header.timestamp()),
9022 };
9023 AnyRpcTransaction::from(WithOtherFields::new(tx))
9024}
9025
9026pub fn transaction_build(
9028 tx_hash: Option<B256>,
9029 eth_transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>,
9030 block: Option<&Block>,
9031 info: Option<TransactionInfo>,
9032 base_fee: Option<u64>,
9033) -> AnyRpcTransaction {
9034 let mined_from = info.as_ref().map(|info| info.from);
9035
9036 #[cfg(feature = "optimism")]
9037 if let FoundryTxEnvelope::Deposit(deposit_tx) = eth_transaction.as_ref() {
9038 let dep_tx = deposit_tx;
9039
9040 let ser = serde_json::to_value(dep_tx).expect("could not serialize TxDeposit");
9041 let maybe_deposit_fields = OtherFields::try_from(ser);
9042
9043 match maybe_deposit_fields {
9044 Ok(mut fields) => {
9045 fields.insert("v".to_string(), serde_json::to_value("0x0").unwrap());
9048 fields.insert("r".to_string(), serde_json::to_value(B256::ZERO).unwrap());
9049 fields.insert(String::from("s"), serde_json::to_value(B256::ZERO).unwrap());
9050 fields.insert(String::from("nonce"), serde_json::to_value("0x0").unwrap());
9051
9052 let inner = UnknownTypedTransaction {
9053 ty: AnyTxType(DEPOSIT_TX_TYPE_ID),
9054 fields,
9055 memo: Default::default(),
9056 };
9057
9058 let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
9059 hash: tx_hash.unwrap_or_else(|| eth_transaction.hash()),
9060 inner,
9061 });
9062
9063 return build_rpc_transaction(
9064 envelope,
9065 mined_from.unwrap_or(deposit_tx.from),
9066 block,
9067 info.as_ref(),
9068 None,
9069 );
9070 }
9071 Err(_) => {
9072 error!(target: "backend", "failed to serialize deposit transaction");
9073 }
9074 }
9075 }
9076
9077 if let FoundryTxEnvelope::Tempo(tempo_tx) = eth_transaction.as_ref() {
9078 let from = mined_from.unwrap_or_else(|| eth_transaction.recover().unwrap_or_default());
9079 let ser = serde_json::to_value(tempo_tx).expect("could not serialize Tempo transaction");
9080 let maybe_tempo_fields = OtherFields::try_from(ser);
9081
9082 match maybe_tempo_fields {
9083 Ok(fields) => {
9084 let inner = UnknownTypedTransaction {
9085 ty: AnyTxType(TEMPO_TX_TYPE_ID),
9086 fields,
9087 memo: Default::default(),
9088 };
9089
9090 let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope {
9091 hash: tx_hash.unwrap_or_else(|| eth_transaction.hash()),
9092 inner,
9093 });
9094
9095 return build_rpc_transaction(envelope, from, block, info.as_ref(), None);
9096 }
9097 Err(_) => {
9098 error!(target: "backend", "failed to serialize tempo transaction");
9099 }
9100 }
9101 }
9102
9103 let from = mined_from.unwrap_or_else(|| eth_transaction.recover().unwrap_or_default());
9104 let effective_gas_price = eth_transaction.effective_gas_price(base_fee);
9105
9106 let hash = tx_hash.unwrap_or_else(|| eth_transaction.hash());
9112
9113 let eth_envelope = FoundryTxEnvelope::from(eth_transaction)
9114 .try_into_eth()
9115 .expect("non-standard transactions are handled above");
9116
9117 let envelope = match eth_envelope {
9118 TxEnvelope::Legacy(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Legacy(rehash(s, hash))),
9119 TxEnvelope::Eip1559(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip1559(rehash(s, hash))),
9120 TxEnvelope::Eip2930(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip2930(rehash(s, hash))),
9121 TxEnvelope::Eip4844(s) => {
9122 let s = if block.is_some() { s.map(TxEip4844Variant::drop_sidecar) } else { s };
9123 AnyTxEnvelope::Ethereum(TxEnvelope::Eip4844(rehash(s, hash)))
9124 }
9125 TxEnvelope::Eip7702(s) => AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(rehash(s, hash))),
9126 };
9127
9128 build_rpc_transaction(envelope, from, block, info.as_ref(), Some(effective_gas_price))
9129}
9130
9131pub fn prove_storage(
9137 storage: &alloy_primitives::map::U256Map<U256>,
9138 keys: &[B256],
9139) -> (B256, Vec<Vec<Bytes>>) {
9140 let keys: Vec<_> = keys.iter().map(|key| Nibbles::unpack(keccak256(key))).collect();
9141
9142 let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(keys.clone()));
9143
9144 for (key, value) in trie_storage(storage) {
9145 builder.add_leaf(key, &value);
9146 }
9147
9148 let root = builder.root();
9149
9150 let mut proofs = Vec::new();
9151 let all_proof_nodes = builder.take_proof_nodes();
9152
9153 for proof_key in keys {
9154 let matching_proof_nodes =
9157 all_proof_nodes.matching_nodes_sorted(&proof_key).into_iter().map(|(_, node)| node);
9158 proofs.push(matching_proof_nodes.collect());
9159 }
9160
9161 (root, proofs)
9162}
9163
9164pub fn is_arbitrum(chain_id: u64) -> bool {
9165 if let Ok(chain) = NamedChain::try_from(chain_id) {
9166 return chain.is_arbitrum();
9167 }
9168 false
9169}
9170
9171fn commit_cache(db: &mut dyn Db, cache: revm::database::Cache) -> Result<(), BlockchainError> {
9173 let revm::database::Cache { accounts, contracts, .. } = cache;
9174 let mut changes = EvmState::default();
9175 for (address, db_account) in accounts {
9176 if db_account.account_state == AccountState::None {
9177 continue;
9178 }
9179
9180 let DbAccount { mut info, account_state, storage } = db_account;
9181 if account_state == AccountState::NotExisting && db.basic(address)?.is_none() {
9184 continue;
9185 }
9186 if info.code.is_none() {
9187 info.code = contracts.get(&info.code_hash).cloned();
9188 }
9189 let mut account = Account::from(info);
9190 account.mark_touch();
9191 match account_state {
9192 AccountState::NotExisting => account.mark_selfdestruct(),
9193 AccountState::StorageCleared => account.mark_created(),
9194 AccountState::Touched => {}
9195 AccountState::None => unreachable!(),
9196 }
9197 for (slot, value) in storage {
9198 let original = if account_state == AccountState::StorageCleared {
9199 U256::ZERO
9200 } else {
9201 db.storage(address, slot)?
9202 };
9203 account
9204 .storage
9205 .insert(slot, EvmStorageSlot::new_changed(original, value, TransactionId::ZERO));
9206 }
9207 changes.insert(address, account);
9208 }
9209 db.commit(changes);
9210 Ok(())
9211}
9212
9213fn simulate_rpc_error(code: i64, message: impl Into<String>) -> BlockchainError {
9214 BlockchainError::RpcError(RpcError {
9215 code: ErrorCode::from(code),
9216 message: message.into().into(),
9217 data: None,
9218 })
9219}
9220
9221fn previously_deleted_accounts(
9222 accounts: &AddressMap<DbAccount>,
9223 addresses: impl IntoIterator<Item = Address>,
9224) -> Vec<Address> {
9225 addresses
9226 .into_iter()
9227 .filter(|address| {
9228 accounts
9229 .get(address)
9230 .is_some_and(|account| account.account_state == AccountState::NotExisting)
9231 })
9232 .collect()
9233}
9234
9235fn preserve_deleted_storage(
9236 accounts: &mut AddressMap<DbAccount>,
9237 previously_deleted: Vec<Address>,
9238) {
9239 for address in previously_deleted {
9240 if let Some(account) = accounts.get_mut(&address)
9241 && account.account_state != AccountState::NotExisting
9242 {
9243 account.account_state = AccountState::StorageCleared;
9244 }
9245 }
9246}
9247
9248fn simulate_transaction_error(error: InvalidTransactionError) -> BlockchainError {
9249 let code = match &error {
9250 InvalidTransactionError::NonceTooLow => -38010,
9251 InvalidTransactionError::NonceTooHigh => -38011,
9252 InvalidTransactionError::NonceMaxValue => -32603,
9253 InvalidTransactionError::FeeCapTooLow => -38012,
9254 InvalidTransactionError::GasTooLow | InvalidTransactionError::GasTooHigh(_) => -38013,
9255 InvalidTransactionError::InsufficientFunds
9256 | InvalidTransactionError::InsufficientFundsForTransfer => -38014,
9257 _ => return BlockchainError::InvalidTransaction(error),
9258 };
9259
9260 simulate_rpc_error(code, format!("err: {error}"))
9261}
9262
9263pub(in crate::eth) fn sanitize_simulation_blocks<T>(
9264 blocks: Vec<SimBlock<T>>,
9265 base_number: u64,
9266 base_timestamp: u64,
9267 block_interval: u64,
9268) -> Result<Vec<SimBlock<T>>, BlockchainError> {
9269 let block_interval = block_interval.max(1);
9270 let mut sanitized = Vec::with_capacity(blocks.len());
9271 let mut previous_number = base_number;
9272 let mut previous_timestamp = base_timestamp;
9273
9274 for mut block in blocks {
9275 let mut overrides = block.block_overrides.take().unwrap_or_default();
9276 let default_number = previous_number.checked_add(1).ok_or_else(|| {
9277 simulate_rpc_error(-38020, "block number overflow while constructing sequence")
9278 })?;
9279 let number =
9280 overrides.number.map(|number| number.saturating_to()).unwrap_or(default_number);
9281
9282 if number <= previous_number {
9283 return Err(simulate_rpc_error(
9284 -38020,
9285 format!("block numbers must be in order: {number} <= {previous_number}"),
9286 ));
9287 }
9288
9289 let gap = number - previous_number - 1;
9290 let remaining = MAX_SIMULATE_BLOCKS as usize - sanitized.len();
9291 if gap as usize >= remaining {
9292 return Err(simulate_rpc_error(-38026, "too many blocks"));
9293 }
9294
9295 for offset in 0..gap {
9296 let timestamp = previous_timestamp.checked_add(block_interval).ok_or_else(|| {
9297 simulate_rpc_error(-38021, "block timestamp overflow while filling number gap")
9298 })?;
9299 sanitized.push(SimBlock {
9300 block_overrides: Some(BlockOverrides {
9301 number: Some(U256::from(default_number + offset)),
9302 time: Some(timestamp),
9303 ..Default::default()
9304 }),
9305 state_overrides: None,
9306 calls: Vec::new(),
9307 });
9308 previous_timestamp = timestamp;
9309 }
9310
9311 let timestamp = match overrides.time {
9312 Some(timestamp) => timestamp,
9313 None => previous_timestamp.checked_add(block_interval).ok_or_else(|| {
9314 simulate_rpc_error(-38021, "block timestamp overflow while constructing sequence")
9315 })?,
9316 };
9317 if timestamp <= previous_timestamp {
9318 return Err(simulate_rpc_error(
9319 -38021,
9320 format!("block timestamps must be in order: {timestamp} <= {previous_timestamp}"),
9321 ));
9322 }
9323
9324 overrides.number = Some(U256::from(number));
9325 overrides.time = Some(timestamp);
9326 block.block_overrides = Some(overrides);
9327 sanitized.push(block);
9328 previous_number = number;
9329 previous_timestamp = timestamp;
9330 }
9331
9332 Ok(sanitized)
9333}
9334
9335fn unpack_execution_result<H: IntoInstructionResult>(
9337 result: ExecutionResult<H>,
9338) -> (InstructionResult, u64, Option<Output>, Vec<revm::primitives::Log>) {
9339 match result {
9340 ExecutionResult::Success { reason, gas, output, logs, .. } => {
9341 (reason.into(), gas.tx_gas_used(), Some(output), logs)
9342 }
9343 ExecutionResult::Revert { gas, output, logs, .. } => {
9344 (InstructionResult::Revert, gas.tx_gas_used(), Some(Output::Call(output)), logs)
9345 }
9346 ExecutionResult::Halt { reason, gas, logs, .. } => {
9347 (reason.into_instruction_result(), gas.tx_gas_used(), None, logs)
9348 }
9349 }
9350}
9351
9352fn arbitrum_replay_block_number(block: &AnyRpcBlock) -> U256 {
9353 block
9354 .other
9355 .get("l1BlockNumber")
9356 .cloned()
9357 .and_then(|number| serde_json::from_value(number).ok())
9358 .unwrap_or_else(|| U256::from(block.header().number()))
9359}
9360
9361pub use foundry_evm::core::evm::IntoInstructionResult;
9366
9367fn genesis_header(
9369 evm_env: &EvmEnv,
9370 base_fee: Option<u64>,
9371 timestamp: u64,
9372 genesis_number: u64,
9373) -> Header {
9374 let spec_id = *evm_env.spec_id();
9375 Header {
9376 timestamp,
9377 base_fee_per_gas: base_fee,
9378 gas_limit: evm_env.block_env.gas_limit,
9379 beneficiary: evm_env.block_env.beneficiary,
9380 difficulty: evm_env.block_env.difficulty,
9381 blob_gas_used: evm_env.block_env.blob_excess_gas_and_price.as_ref().map(|_| 0),
9382 excess_blob_gas: evm_env.block_env.blob_excess_gas(),
9383 number: genesis_number,
9384 parent_beacon_block_root: (spec_id >= SpecId::CANCUN).then_some(Default::default()),
9385 withdrawals_root: (spec_id >= SpecId::SHANGHAI).then_some(EMPTY_WITHDRAWALS),
9386 requests_hash: (spec_id >= SpecId::PRAGUE).then_some(EMPTY_REQUESTS_HASH),
9387 ..Default::default()
9388 }
9389}
9390
9391fn foundry_header(networks: &NetworkConfigs, header: Header) -> FoundryHeader {
9393 if networks.is_tempo() { FoundryHeader::tempo(header) } else { header.into() }
9394}
9395
9396#[cfg(test)]
9397mod tests {
9398 use super::{
9399 ForkCacheNamespace, ForkCacheSource, StagedForkCacheLease, StagedForkDbUser,
9400 arbitrum_replay_block_number,
9401 };
9402 use crate::{NodeConfig, config::ForkTransactionReplay, spawn};
9403 use alloy_network::{AnyHeader, AnyRpcBlock, AnyRpcHeader, TransactionBuilder};
9404 use alloy_primitives::{B256, Bytes, U256};
9405 use alloy_provider::Provider;
9406 use alloy_rpc_types::{Block, BlockTransactions, TransactionRequest, state::EvmOverrides};
9407 use alloy_serde::WithOtherFields;
9408 use foundry_config::NamedChain;
9409 use foundry_evm::{
9410 backend::{BlockchainDb, BlockchainDbMeta},
9411 hardfork::{EthereumHardfork, FoundryHardfork},
9412 };
9413 use foundry_evm_networks::arbitrum;
9414 use std::sync::Arc;
9415 use tempfile::tempdir;
9416
9417 fn test_cache_db(cache_path: std::path::PathBuf) -> BlockchainDb {
9418 let db = BlockchainDb::new(BlockchainDbMeta::default(), Some(cache_path));
9419 db.block_hashes().write().insert(U256::ZERO, B256::repeat_byte(0x11));
9420 db.cache().flush();
9421 db
9422 }
9423
9424 #[test]
9425 fn arbitrum_transaction_replay_uses_l1_block_number() {
9426 let header = AnyHeader { number: 75_219_831, ..Default::default() };
9427 let mut block = AnyRpcBlock::new(
9428 Block::new(
9429 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
9430 BlockTransactions::Full(Vec::new()),
9431 )
9432 .into(),
9433 );
9434 block.other.insert("l1BlockNumber".to_string(), serde_json::json!("0x10276d3"));
9435
9436 assert_eq!(arbitrum_replay_block_number(&block), U256::from(16_938_707));
9437 }
9438
9439 #[tokio::test]
9440 async fn fork_arbitrum_transaction_replay_preserves_rpc_block_number() {
9441 const GENESIS_BLOCK: u64 = 101;
9442 const L1_BLOCK: u64 = 10;
9443 const REPLAY_BLOCK: u64 = GENESIS_BLOCK + 1;
9444
9445 let config = || {
9446 NodeConfig::test()
9447 .with_chain_id(Some(NamedChain::Arbitrum as u64))
9448 .with_genesis_block_number(Some(GENESIS_BLOCK))
9449 };
9450 let (_source_api, source_handle) = spawn(config()).await;
9451 let source_provider = source_handle.http_provider();
9452 let sender = source_provider.get_accounts().await.unwrap()[0];
9453 let receipt = source_provider
9454 .send_transaction(WithOtherFields::new(
9455 TransactionRequest::default()
9456 .with_from(sender)
9457 .with_to(arbitrum::ARB_SYS_ADDRESS)
9458 .with_input(Bytes::copy_from_slice(&arbitrum::ARB_BLOCK_NUMBER_SELECTOR)),
9459 ))
9460 .await
9461 .unwrap()
9462 .get_receipt()
9463 .await
9464 .unwrap();
9465 let mut source_block = source_provider
9466 .get_block_by_hash(receipt.block_hash.unwrap())
9467 .full()
9468 .await
9469 .unwrap()
9470 .unwrap();
9471 source_block
9472 .other
9473 .insert("l1BlockNumber".to_string(), serde_json::json!(format!("0x{L1_BLOCK:x}")));
9474
9475 let (replay_api, _replay_handle) = spawn(config()).await;
9476 replay_api
9477 .backend
9478 .apply_fork_transaction_replay(ForkTransactionReplay { source_block, target_index: 0 })
9479 .await
9480 .unwrap();
9481
9482 let expected = arbitrum::arb_block_number_output(REPLAY_BLOCK);
9483 let replayed =
9484 replay_api.backend.mined_transaction_receipt(receipt.transaction_hash).unwrap();
9485 assert_eq!(replayed.out.unwrap(), expected);
9486 assert_eq!(replay_api.block_number().unwrap(), U256::from(REPLAY_BLOCK));
9487
9488 let output = replay_api
9489 .call(
9490 WithOtherFields::new(
9491 TransactionRequest::default()
9492 .with_to(arbitrum::ARB_SYS_ADDRESS)
9493 .with_input(Bytes::copy_from_slice(&arbitrum::ARB_BLOCK_NUMBER_SELECTOR)),
9494 ),
9495 None,
9496 EvmOverrides::default(),
9497 )
9498 .await
9499 .unwrap();
9500 assert_eq!(output, expected);
9501 }
9502
9503 fn test_endpoint_identity(
9504 hardfork: Option<FoundryHardfork>,
9505 instance_id: Option<B256>,
9506 ) -> super::ForkEndpointIdentity {
9507 super::ForkEndpointIdentity {
9508 execution_chain_id: 1,
9509 source_chain_id: 1,
9510 network: None,
9511 network_profile: None,
9512 hardfork,
9513 instance_id,
9514 source_fork_block_number: None,
9515 source_fork_block_hash: None,
9516 }
9517 }
9518
9519 #[tokio::test]
9520 async fn missing_snapshot_block_does_not_change_head() {
9521 let (api, _handle) = spawn(NodeConfig::test()).await;
9522 let snapshot_hash = api.backend.best_hash();
9523 let snapshot = api.backend.create_state_snapshot().await;
9524 api.mine_one().await.unwrap();
9525 let best_number = api.backend.best_number();
9526 let best_hash = api.backend.best_hash();
9527 let head_wall_time = api.backend.time().last_block_wall_time();
9528
9529 api.backend.blockchain.storage.write().blocks.remove(&snapshot_hash);
9530 let err = api.backend.revert_state_snapshot(snapshot).await.unwrap_err();
9531
9532 assert!(matches!(err, super::BlockchainError::BlockNotFound));
9533 assert_eq!(api.backend.best_number(), best_number);
9534 assert_eq!(api.backend.best_hash(), best_hash);
9535 assert_eq!(api.backend.time().last_block_wall_time(), head_wall_time);
9536 }
9537
9538 struct CacheFlushingDb(BlockchainDb);
9539
9540 impl Drop for CacheFlushingDb {
9541 fn drop(&mut self) {
9542 self.0.block_hashes().write().insert(U256::from(1), B256::repeat_byte(0x22));
9543 self.0.cache().flush();
9544 }
9545 }
9546
9547 #[test]
9548 fn staged_fork_cache_lease_waits_for_last_owner() {
9549 let root = tempdir().unwrap();
9550 let block_cache_dir = root.path().join("1");
9551 let cache_path = block_cache_dir.join("storage.json");
9552 let sibling_cache_path = block_cache_dir.join("storage-sibling.json");
9553 let db = test_cache_db(cache_path.clone());
9554 std::fs::write(&sibling_cache_path, b"sibling").unwrap();
9555 let lease = StagedForkCacheLease::new(db.clone(), Some(cache_path.clone()));
9556 let last_user = lease.clone();
9557
9558 lease.rollback().unwrap();
9559 assert!(!db.block_hashes().read().is_empty());
9560 drop(lease);
9561 assert!(!db.block_hashes().read().is_empty());
9562
9563 drop(last_user);
9564 assert!(db.block_hashes().read().is_empty());
9565 assert!(!cache_path.exists());
9566 assert!(sibling_cache_path.exists());
9567 }
9568
9569 #[tokio::test]
9570 async fn staged_fork_cache_lease_survives_task_cancellation() {
9571 let root = tempdir().unwrap();
9572 let block_cache_dir = root.path().join("1");
9573 let cache_path = block_cache_dir.join("storage.json");
9574 let cache_db = test_cache_db(cache_path.clone());
9575 let lease = StagedForkCacheLease::new(cache_db.clone(), Some(cache_path.clone()));
9576 let db = Arc::new(tokio::sync::RwLock::new(CacheFlushingDb(cache_db.clone())));
9577 let task_user = StagedForkDbUser { db: Some(Arc::clone(&db)), cache_lease: lease.clone() };
9578 drop(db);
9579 let task = tokio::spawn(async move {
9580 let _db = task_user.db().read().await;
9581 std::future::pending::<()>().await;
9582 });
9583 tokio::task::yield_now().await;
9584
9585 assert_eq!(Arc::strong_count(lease.0.as_ref().unwrap()), 2);
9587 lease.rollback().unwrap();
9588 drop(lease);
9589
9590 task.abort();
9591 assert!(task.await.unwrap_err().is_cancelled());
9592
9593 assert!(cache_db.block_hashes().read().is_empty());
9594 assert!(!cache_path.exists());
9595 }
9596
9597 #[test]
9598 fn staged_fork_cache_lease_disarm_preserves_committed_cache() {
9599 let root = tempdir().unwrap();
9600 let block_cache_dir = root.path().join("1");
9601 let cache_path = block_cache_dir.join("storage.json");
9602 let db = test_cache_db(cache_path.clone());
9603 let lease = StagedForkCacheLease::new(db.clone(), Some(cache_path.clone()));
9604
9605 lease.disarm();
9606 drop(lease);
9607
9608 assert!(!db.block_hashes().read().is_empty());
9609 assert!(cache_path.exists());
9610 }
9611
9612 #[test]
9613 fn fork_cache_namespace_preserves_sibling_endpoints() {
9614 let root = tempdir().unwrap();
9615 let target_file = "storage-target.json";
9616 let sibling_file = "storage-sibling.json";
9617 let regular_cache_entry = root.path().join("latest.json");
9618 std::fs::write(®ular_cache_entry, b"ordinary RPC cache entry").unwrap();
9619 for block in ["1", "2"] {
9620 let block_dir = root.path().join(block);
9621 std::fs::create_dir_all(&block_dir).unwrap();
9622 std::fs::write(block_dir.join(target_file), b"target").unwrap();
9623 std::fs::write(block_dir.join(sibling_file), b"sibling").unwrap();
9624 }
9625 let namespace = ForkCacheNamespace {
9626 chain_cache_dir: root.path().to_path_buf(),
9627 file_name: target_file.to_string(),
9628 };
9629
9630 namespace.invalidate().unwrap();
9631
9632 for block in ["1", "2"] {
9633 let block_dir = root.path().join(block);
9634 assert!(!block_dir.join(target_file).exists());
9635 assert!(block_dir.join(sibling_file).exists());
9636 }
9637 assert!(regular_cache_entry.exists());
9638 }
9639
9640 #[test]
9641 fn startup_fork_cache_user_rolls_back_after_database_flush() {
9642 let root = tempdir().unwrap();
9643 let block_cache_dir = root.path().join("1");
9644 let cache_path = block_cache_dir.join("storage.json");
9645 let cache_db = test_cache_db(cache_path.clone());
9646 let user = StagedForkDbUser {
9647 db: Some(Arc::new(tokio::sync::RwLock::new(CacheFlushingDb(cache_db.clone())))),
9648 cache_lease: StagedForkCacheLease::new(cache_db.clone(), Some(cache_path.clone())),
9649 };
9650
9651 drop(user);
9652
9653 assert!(cache_db.block_hashes().read().is_empty());
9654 assert!(!cache_path.exists());
9655 }
9656
9657 #[test]
9658 fn startup_fork_cache_user_preserves_cache_after_commit() {
9659 let root = tempdir().unwrap();
9660 let block_cache_dir = root.path().join("1");
9661 let cache_path = block_cache_dir.join("storage.json");
9662 let cache_db = test_cache_db(cache_path.clone());
9663 let user = StagedForkDbUser {
9664 db: Some(Arc::new(tokio::sync::RwLock::new(CacheFlushingDb(cache_db.clone())))),
9665 cache_lease: StagedForkCacheLease::new(cache_db.clone(), Some(cache_path.clone())),
9666 };
9667 user.cache_lease.disarm();
9668
9669 drop(user);
9670
9671 assert_eq!(
9672 cache_db.block_hashes().read().get(&U256::from(1)),
9673 Some(&B256::repeat_byte(0x22))
9674 );
9675 assert!(cache_path.exists());
9676 }
9677
9678 #[test]
9679 fn fork_cache_source_invalidates_only_same_url_authoritative_replacements() {
9680 let anonymous = test_endpoint_identity(None, None);
9681 let anonymous_source = ForkCacheSource {
9682 rpc_url: "http://localhost".to_string(),
9683 endpoint_identity: anonymous,
9684 };
9685 let mut changed_anonymous = anonymous;
9686 changed_anonymous.source_chain_id = 2;
9687
9688 assert!(
9689 !anonymous_source
9690 .authoritative_identity_changed_at_same_url("http://localhost", changed_anonymous)
9691 );
9692 assert!(
9693 !anonymous_source
9694 .authoritative_identity_changed_at_same_url("http://mirror", anonymous)
9695 );
9696
9697 let hardfork = Some(FoundryHardfork::Ethereum(EthereumHardfork::Prague));
9698 let authoritative = test_endpoint_identity(hardfork, Some(B256::repeat_byte(0x22)));
9699 let authoritative_source = ForkCacheSource {
9700 rpc_url: "http://localhost".to_string(),
9701 endpoint_identity: authoritative,
9702 };
9703 let replacement = test_endpoint_identity(hardfork, Some(B256::repeat_byte(0x33)));
9704
9705 assert!(
9706 !authoritative_source
9707 .authoritative_identity_changed_at_same_url("http://localhost", authoritative)
9708 );
9709 assert!(
9710 authoritative_source
9711 .authoritative_identity_changed_at_same_url("http://localhost", replacement)
9712 );
9713 assert!(
9714 anonymous_source
9715 .authoritative_identity_changed_at_same_url("http://localhost", authoritative)
9716 );
9717 assert!(
9718 authoritative_source
9719 .authoritative_identity_changed_at_same_url("http://localhost", anonymous)
9720 );
9721 }
9722
9723 #[tokio::test]
9724 async fn test_deterministic_block_mining() {
9725 let genesis_timestamp = 1743944919u64;
9727
9728 let config_a = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into());
9730 let config_b = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into());
9731
9732 let (api_a, _handle_a) = spawn(config_a).await;
9733 let (api_b, _handle_b) = spawn(config_b).await;
9734
9735 let outcome_a_1 = api_a.backend.mine_block(vec![]).await.unwrap();
9737 let outcome_b_1 = api_b.backend.mine_block(vec![]).await.unwrap();
9738
9739 assert_eq!(outcome_a_1.block_number, outcome_b_1.block_number);
9741
9742 let block_a_1 =
9744 api_a.block_by_number(outcome_a_1.block_number.into()).await.unwrap().unwrap();
9745 let block_b_1 =
9746 api_b.block_by_number(outcome_b_1.block_number.into()).await.unwrap().unwrap();
9747
9748 assert_eq!(
9750 block_a_1.header.hash, block_b_1.header.hash,
9751 "Block hashes should be deterministic. Got {} vs {}",
9752 block_a_1.header.hash, block_b_1.header.hash
9753 );
9754
9755 let outcome_a_2 = api_a.backend.mine_block(vec![]).await.unwrap();
9757 let outcome_b_2 = api_b.backend.mine_block(vec![]).await.unwrap();
9758
9759 let block_a_2 =
9760 api_a.block_by_number(outcome_a_2.block_number.into()).await.unwrap().unwrap();
9761 let block_b_2 =
9762 api_b.block_by_number(outcome_b_2.block_number.into()).await.unwrap().unwrap();
9763
9764 assert_eq!(
9765 block_a_2.header.hash, block_b_2.header.hash,
9766 "Second block hashes should also be deterministic. Got {} vs {}",
9767 block_a_2.header.hash, block_b_2.header.hash
9768 );
9769
9770 assert_ne!(
9772 block_a_1.header.hash, block_a_2.header.hash,
9773 "Different blocks should have different hashes"
9774 );
9775 }
9776
9777 #[cfg(feature = "monad")]
9778 #[tokio::test]
9779 async fn monad_load_state_rebuilds_participant_cache() {
9780 use alloy_network::TransactionBuilder as _;
9781 use alloy_provider::Provider as _;
9782
9783 let config = || {
9784 NodeConfig::test_monad()
9785 .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
9786 };
9787 let (api, handle) = spawn(config()).await;
9788 let provider = handle.http_provider();
9789 let accounts = provider.get_accounts().await.unwrap();
9790 let sender = accounts[0];
9791
9792 let receipt = provider
9793 .send_transaction(
9794 alloy_rpc_types::TransactionRequest::default()
9795 .with_from(sender)
9796 .with_to(accounts[1])
9797 .with_value(U256::from(1))
9798 .into(),
9799 )
9800 .await
9801 .unwrap()
9802 .get_receipt()
9803 .await
9804 .unwrap();
9805 let block_hash = receipt.block_hash.unwrap();
9806 let state = api.serialized_state(false).await.unwrap();
9807
9808 let (loaded_api, _handle) = spawn(config()).await;
9809 loaded_api.backend.load_state(state).await.unwrap();
9810
9811 let storage = loaded_api.backend.blockchain.storage.read();
9812 let participants = storage.monad_block_participants.get(&block_hash).unwrap();
9813 assert!(participants.contains(&sender));
9814 }
9815
9816 #[cfg(feature = "monad")]
9817 #[tokio::test]
9818 async fn monad_load_state_restores_pruned_participant_cache() {
9819 use alloy_consensus::BlockHeader as _;
9820 use alloy_network::TransactionBuilder as _;
9821 use alloy_provider::Provider as _;
9822
9823 let config = || {
9824 NodeConfig::test_monad()
9825 .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
9826 .with_transaction_block_keeper(Some(1usize))
9827 };
9828 let (api, handle) = spawn(config()).await;
9829 let provider = handle.http_provider();
9830 let accounts = provider.get_accounts().await.unwrap();
9831 let sender = accounts[0];
9832
9833 let first_receipt = provider
9834 .send_transaction(
9835 alloy_rpc_types::TransactionRequest::default()
9836 .with_from(sender)
9837 .with_to(accounts[1])
9838 .with_value(U256::from(1))
9839 .into(),
9840 )
9841 .await
9842 .unwrap()
9843 .get_receipt()
9844 .await
9845 .unwrap();
9846 let second_receipt = provider
9847 .send_transaction(
9848 alloy_rpc_types::TransactionRequest::default()
9849 .with_from(sender)
9850 .with_to(accounts[1])
9851 .with_value(U256::from(1))
9852 .into(),
9853 )
9854 .await
9855 .unwrap()
9856 .get_receipt()
9857 .await
9858 .unwrap();
9859 let first_block_hash = first_receipt.block_hash.unwrap();
9860 let second_block_hash = second_receipt.block_hash.unwrap();
9861
9862 {
9863 let storage = api.backend.blockchain.storage.read();
9864 let first_block = storage.blocks.get(&first_block_hash).unwrap();
9865 assert!(first_block.body.transactions.is_empty());
9866 assert_ne!(
9867 first_block.header.transactions_root(),
9868 alloy_consensus::constants::EMPTY_ROOT_HASH
9869 );
9870 assert!(storage.monad_block_participants[&first_block_hash].contains(&sender));
9871 assert!(!storage.blocks[&second_block_hash].body.transactions.is_empty());
9872 }
9873
9874 let state = api.serialized_state(false).await.unwrap();
9875 assert!(state.monad_block_participants[&first_block_hash].contains(&sender));
9876 assert_eq!(state.monad_block_participants.len(), 2);
9877 assert!(state.monad_block_participants[&second_block_hash].contains(&sender));
9878
9879 let (loaded_api, _handle) = spawn(config()).await;
9880 loaded_api.backend.load_state(state).await.unwrap();
9881
9882 {
9883 let storage = loaded_api.backend.blockchain.storage.read();
9884 assert!(storage.monad_block_participants[&first_block_hash].contains(&sender));
9885 assert!(storage.monad_block_participants[&second_block_hash].contains(&sender));
9886 }
9887
9888 let outcome = loaded_api.backend.mine_block(vec![]).await.unwrap();
9889 assert_eq!(outcome.block_number, 3);
9890 }
9891
9892 #[cfg(feature = "monad")]
9893 #[tokio::test]
9894 async fn monad_load_state_rejection_is_atomic() {
9895 use alloy_consensus::BlockHeader as _;
9896 use alloy_network::TransactionBuilder as _;
9897 use alloy_provider::Provider as _;
9898
9899 let config = || {
9900 NodeConfig::test_monad()
9901 .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
9902 .with_transaction_block_keeper(Some(1usize))
9903 };
9904 let (source_api, source_handle) = spawn(config()).await;
9905 let source_provider = source_handle.http_provider();
9906 let accounts = source_provider.get_accounts().await.unwrap();
9907 let sentinel = alloy_primitives::Address::repeat_byte(0x77);
9908
9909 source_api.anvil_set_balance(sentinel, U256::from(123)).await.unwrap();
9910 for nonce in 0..2 {
9911 source_provider
9912 .send_transaction(
9913 alloy_rpc_types::TransactionRequest::default()
9914 .with_from(accounts[0])
9915 .with_to(accounts[1])
9916 .with_nonce(nonce)
9917 .with_value(U256::from(1))
9918 .into(),
9919 )
9920 .await
9921 .unwrap()
9922 .get_receipt()
9923 .await
9924 .unwrap();
9925 }
9926
9927 let mut invalid_state = source_api.serialized_state(false).await.unwrap();
9928 let pruned_block_hash = invalid_state
9929 .blocks
9930 .iter()
9931 .find(|block| {
9932 block.transactions.is_empty()
9933 && block.header.transactions_root()
9934 != alloy_consensus::constants::EMPTY_ROOT_HASH
9935 })
9936 .unwrap()
9937 .header
9938 .hash_slow();
9939 assert!(invalid_state.monad_block_participants.remove(&pruned_block_hash).is_some());
9940
9941 let (target_api, _handle) = spawn(config()).await;
9942 target_api.anvil_set_balance(sentinel, U256::from(7)).await.unwrap();
9943 target_api.mine_one().await.unwrap();
9944 let original_best_hash = target_api.backend.best_hash();
9945 let original_best_number = target_api.backend.best_number();
9946 let original_block_env = target_api.backend.evm_env.read().block_env.clone();
9947 let original_balance = target_api.backend.current_balance(sentinel).await.unwrap();
9948
9949 let err = target_api.backend.load_state(invalid_state).await.unwrap_err();
9950 assert!(matches!(err, super::BlockchainError::DataUnavailable));
9951 assert_eq!(target_api.backend.best_hash(), original_best_hash);
9952 assert_eq!(target_api.backend.best_number(), original_best_number);
9953 assert_eq!(target_api.backend.evm_env.read().block_env, original_block_env);
9954 assert_eq!(target_api.backend.current_balance(sentinel).await.unwrap(), original_balance);
9955
9956 let outcome = target_api.backend.mine_block(vec![]).await.unwrap();
9957 assert_eq!(outcome.block_number, original_best_number + 1);
9958 }
9959
9960 #[tokio::test]
9961 async fn trace_decoder_follows_executed_hardfork_for_cross_namespace_override() {
9962 let (api, _) = spawn(
9963 NodeConfig::test_tempo()
9964 .with_hardfork(Some(FoundryHardfork::Ethereum(EthereumHardfork::Prague))),
9965 )
9966 .await;
9967
9968 let decoder = api.backend.call_trace_decoder();
9969 assert_eq!(decoder.hardfork(), Some(FoundryHardfork::Tempo(api.backend.tempo_hardfork())));
9970 assert!(Arc::ptr_eq(&decoder, &api.backend.call_trace_decoder()));
9972 }
9973
9974 #[cfg(feature = "monad")]
9975 #[tokio::test]
9976 async fn monad_trace_decoder_follows_resolved_hardfork() {
9977 let (monad_eight, _) = spawn(
9978 NodeConfig::test_monad()
9979 .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadEight.into())),
9980 )
9981 .await;
9982 let stale_monad_nine = foundry_evm::traces::CallTraceDecoderBuilder::new()
9983 .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into()))
9984 .build();
9985 *monad_eight.backend.call_trace_decoder.write() = Arc::new(stale_monad_nine);
9986
9987 let decoder = monad_eight.backend.call_trace_decoder();
9988 assert_eq!(
9989 decoder.hardfork(),
9990 Some(foundry_evm::hardfork::MonadHardfork::MonadEight.into())
9991 );
9992 assert!(
9993 !decoder
9994 .labels
9995 .contains_key(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
9996 );
9997
9998 let (monad_nine, _) = spawn(
9999 NodeConfig::test_monad()
10000 .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into())),
10001 )
10002 .await;
10003 let stale_monad_eight = foundry_evm::traces::CallTraceDecoderBuilder::new()
10004 .with_hardfork(Some(foundry_evm::hardfork::MonadHardfork::MonadEight.into()))
10005 .build();
10006 *monad_nine.backend.call_trace_decoder.write() = Arc::new(stale_monad_eight);
10007
10008 let decoder = monad_nine.backend.call_trace_decoder();
10009 assert_eq!(
10010 decoder.hardfork(),
10011 Some(foundry_evm::hardfork::MonadHardfork::MonadNine.into())
10012 );
10013 assert_eq!(
10014 decoder
10015 .labels
10016 .get(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
10017 .map(String::as_str),
10018 Some("ReserveBalance")
10019 );
10020 }
10021}