Skip to main content

anvil/eth/backend/mem/
storage.rs

1//! In-memory blockchain storage
2use crate::eth::{
3    backend::{
4        db::{
5            MaybeFullDatabase, SerializableBlock, SerializableHistoricalStates,
6            SerializableTransaction, StateDb,
7        },
8        mem::{cache::DiskStateCache, tempo::TempoChain},
9    },
10    pool::transactions::PoolTransaction,
11};
12use alloy_consensus::BlockHeader;
13use alloy_eips::eip7928::BlockAccessList;
14use alloy_network::Network;
15use alloy_primitives::{
16    B256, Bytes, U256,
17    map::{B256HashMap, HashMap},
18};
19use alloy_rpc_types::{
20    BlockId, BlockNumberOrTag, TransactionInfo as RethTransactionInfo,
21    trace::{
22        otterscan::{InternalOperation, OperationType},
23        parity::LocalizedTransactionTrace,
24    },
25};
26use anvil_core::eth::{
27    block::{Block, create_block},
28    transaction::{MaybeImpersonatedTransaction, TransactionInfo},
29};
30use foundry_evm::traces::{
31    CallKind, CallTraceNode, ParityTraceBuilder, TraceMemberOrder, TracingInspectorConfig,
32};
33use foundry_primitives::{FoundryHeader, FoundryReceiptEnvelope, FoundryTxEnvelope};
34use parking_lot::RwLock;
35use std::{collections::VecDeque, fmt, path::PathBuf, sync::Arc, time::Duration};
36
37// use yansi::Paint;
38
39// === various limits in number of blocks ===
40
41pub const DEFAULT_HISTORY_LIMIT: usize = 500;
42const MIN_HISTORY_LIMIT: usize = 10;
43// 1hr of up-time at lowest 1s interval
44const MAX_ON_DISK_HISTORY_LIMIT: usize = 3_600;
45
46/// Represents the complete state of single block
47pub struct InMemoryBlockStates {
48    /// The states at a certain block
49    states: B256HashMap<StateDb>,
50    /// Older states in the secondary history tier.
51    ///
52    /// Structurally shared states remain here directly; other database types move their data to
53    /// disk and keep an empty state object here for loading it.
54    on_disk_states: B256HashMap<StateDb>,
55    /// How many states to store at most
56    in_memory_limit: usize,
57    /// minimum amount of states we keep in memory
58    min_in_memory_limit: usize,
59    /// Whether the memory limit includes an additional slot for the current state.
60    current_state_slot: bool,
61    /// maximum amount of states we keep on disk
62    ///
63    /// Limiting the states will prevent disk blow up, especially in interval mining mode
64    max_on_disk_limit: usize,
65    /// the oldest states written to disk
66    oldest_on_disk: VecDeque<B256>,
67    /// all states present, used to enforce `in_memory_limit`
68    present: VecDeque<B256>,
69    /// Stores old states on disk
70    disk_cache: DiskStateCache,
71}
72
73impl InMemoryBlockStates {
74    /// Creates a new instance with limited slots
75    pub fn new(in_memory_limit: usize, on_disk_limit: usize) -> Self {
76        let in_memory_limit = in_memory_limit.max(1);
77        Self {
78            states: Default::default(),
79            on_disk_states: Default::default(),
80            in_memory_limit,
81            min_in_memory_limit: in_memory_limit.min(MIN_HISTORY_LIMIT),
82            current_state_slot: false,
83            max_on_disk_limit: on_disk_limit,
84            oldest_on_disk: Default::default(),
85            present: Default::default(),
86            disk_cache: Default::default(),
87        }
88    }
89
90    /// Configures no disk caching
91    pub const fn memory_only(mut self) -> Self {
92        self.max_on_disk_limit = 0;
93        self
94    }
95
96    /// Configures the path on disk where the states will cached.
97    pub fn disk_path(mut self, path: PathBuf) -> Self {
98        self.disk_cache = self.disk_cache.with_path(path);
99        self
100    }
101
102    /// This modifies the `limit` what to keep stored in memory.
103    ///
104    /// This will ensure the new limit adjusts based on the block time.
105    /// The lowest blocktime is 1s which should increase the limit slightly.
106    /// Memory-only caches retain their configured limit.
107    pub fn update_interval_mine_block_time(&mut self, block_time: Duration) {
108        let block_time = block_time.as_secs();
109        // for block times lower than 2s we increase the mem limit since we're mining _small_ blocks
110        // very fast
111        // this will gradually be decreased once the max limit was reached
112        if block_time <= 2 && !self.is_memory_only() {
113            self.in_memory_limit = DEFAULT_HISTORY_LIMIT * 3 + self.current_state_slot as usize;
114            self.enforce_limits();
115        }
116    }
117
118    /// Returns true if only memory caching is supported.
119    const fn is_memory_only(&self) -> bool {
120        self.max_on_disk_limit == 0
121    }
122
123    /// Inserts a new (hash -> state) pair
124    ///
125    /// When the configured limit for the number of states that can be stored in memory is reached,
126    /// the oldest state is removed.
127    ///
128    /// Database types without structural sharing gradually move snapshots to disk as the chain
129    /// grows. Structurally shared snapshots move into the secondary tier without serialization.
130    ///
131    /// When a state that was previously written to disk is requested, it is simply read from disk.
132    pub fn insert(&mut self, hash: B256, state: StateDb) {
133        if !self.is_memory_only() && self.present.len() >= self.in_memory_limit {
134            // once we hit the max limit we gradually decrease it
135            self.in_memory_limit =
136                self.in_memory_limit.saturating_sub(1).max(self.min_in_memory_limit);
137        }
138
139        self.enforce_limits();
140
141        self.states.insert(hash, state);
142        self.present.push_back(hash);
143    }
144
145    /// Enforces configured limits
146    fn enforce_limits(&mut self) {
147        // enforce memory limits
148        while self.present.len() >= self.in_memory_limit {
149            // evict the oldest block
150            if let Some((hash, mut state)) = self
151                .present
152                .pop_front()
153                .and_then(|hash| self.states.remove(&hash).map(|state| (hash, state)))
154            {
155                // only write to disk if supported
156                if !self.is_memory_only() {
157                    if state.is_persistent() {
158                        self.on_disk_states.insert(hash, state);
159                        self.oldest_on_disk.push_back(hash);
160                        continue;
161                    }
162
163                    let state_snapshot = state.serialize_state();
164                    if self.disk_cache.write(hash, &state_snapshot) {
165                        state.clear();
166                        // Write succeeded, move state to on-disk tracking
167                        self.on_disk_states.insert(hash, state);
168                        self.oldest_on_disk.push_back(hash);
169                    } else {
170                        // Write failed, keep state in memory to avoid data loss
171                        self.states.insert(hash, state);
172                        self.present.push_front(hash);
173                        // Increase limit temporarily to prevent infinite retry loop
174                        self.in_memory_limit = self.in_memory_limit.saturating_add(1);
175                        break;
176                    }
177                }
178            }
179        }
180
181        // enforce on disk limit and purge the oldest state cached on disk
182        while !self.is_memory_only() && self.oldest_on_disk.len() >= self.max_on_disk_limit {
183            // evict the oldest block
184            if let Some(hash) = self.oldest_on_disk.pop_front()
185                && self.on_disk_states.remove(&hash).is_some_and(|state| !state.is_persistent())
186            {
187                self.disk_cache.remove(hash);
188            }
189        }
190    }
191
192    /// Returns the in-memory state for the given `hash` if present
193    pub fn get_state(&self, hash: &B256) -> Option<&StateDb> {
194        self.states.get(hash)
195    }
196
197    /// Returns on-disk state for the given `hash` if present
198    pub fn get_on_disk_state(&mut self, hash: &B256) -> Option<&StateDb> {
199        if let Some(state) = self.on_disk_states.get_mut(hash) {
200            if state.is_persistent() {
201                return Some(state);
202            }
203
204            let cached = self.disk_cache.read(*hash)?;
205            state.init_from_state_snapshot(cached);
206            return Some(state);
207        }
208
209        None
210    }
211
212    /// Sets the maximum number of stats we keep in memory
213    pub const fn set_cache_limit(&mut self, limit: usize) {
214        let limit = if limit == 0 { 1 } else { limit };
215        self.in_memory_limit = limit.saturating_add(self.current_state_slot as usize);
216        self.min_in_memory_limit =
217            (if limit < MIN_HISTORY_LIMIT { limit } else { MIN_HISTORY_LIMIT })
218                .saturating_add(self.current_state_slot as usize);
219    }
220
221    /// Clears all entries
222    pub fn clear(&mut self) {
223        self.states.clear();
224        self.present.clear();
225        self.oldest_on_disk.clear();
226        for (hash, state) in std::mem::take(&mut self.on_disk_states) {
227            if !state.is_persistent() {
228                self.disk_cache.remove(hash);
229            }
230        }
231    }
232
233    /// Removes states for the given block hashes.
234    ///
235    /// This is used during chain rollback to clean up states for blocks that are no longer part
236    /// of the canonical chain.
237    pub fn remove_block_states(&mut self, hashes: &[B256]) {
238        for hash in hashes {
239            self.states.remove(hash);
240            if self.on_disk_states.remove(hash).is_some_and(|state| !state.is_persistent()) {
241                self.disk_cache.remove(*hash);
242            }
243        }
244        self.present.retain(|h| !hashes.contains(h));
245        self.oldest_on_disk.retain(|h| !hashes.contains(h));
246    }
247
248    /// Serialize all states to a list of serializable historical states
249    pub fn serialized_states(&mut self) -> SerializableHistoricalStates {
250        // Get in-memory states
251        let mut states = self
252            .states
253            .iter_mut()
254            .map(|(hash, state)| (*hash, state.serialize_state()))
255            .collect::<Vec<_>>();
256
257        // Get on-disk state snapshots
258        for (hash, state) in &mut self.on_disk_states {
259            if state.is_persistent() {
260                states.push((*hash, state.serialize_state()));
261            } else if let Some(state_snapshot) = self.disk_cache.read(*hash) {
262                states.push((*hash, state_snapshot));
263            }
264        }
265        states.sort_unstable_by_key(|(hash, _)| *hash);
266
267        SerializableHistoricalStates::new(states)
268    }
269
270    /// Retains the current state in addition to the configured historical states.
271    pub(crate) const fn with_current_state(mut self) -> Self {
272        if !self.current_state_slot {
273            self.in_memory_limit = self.in_memory_limit.saturating_add(1);
274            self.min_in_memory_limit = self.min_in_memory_limit.saturating_add(1);
275            self.current_state_slot = true;
276        }
277        self
278    }
279}
280
281impl fmt::Debug for InMemoryBlockStates {
282    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283        f.debug_struct("InMemoryBlockStates")
284            .field("in_memory_limit", &self.in_memory_limit)
285            .field("min_in_memory_limit", &self.min_in_memory_limit)
286            .field("current_state_slot", &self.current_state_slot)
287            .field("max_on_disk_limit", &self.max_on_disk_limit)
288            .field("oldest_on_disk", &self.oldest_on_disk)
289            .field("present", &self.present)
290            .finish_non_exhaustive()
291    }
292}
293
294impl Default for InMemoryBlockStates {
295    fn default() -> Self {
296        // enough in memory to store `DEFAULT_HISTORY_LIMIT` blocks in memory
297        Self::new(DEFAULT_HISTORY_LIMIT, MAX_ON_DISK_HISTORY_LIMIT)
298    }
299}
300
301/// Stores the blockchain data (blocks, transactions)
302#[derive(Clone, Debug)]
303pub struct BlockchainStorage<N: Network> {
304    /// all stored blocks (block hash -> block)
305    pub blocks: B256HashMap<Block>,
306    /// EIP-7928 block access lists for locally mined blocks.
307    pub block_access_lists: B256HashMap<BlockAccessList>,
308    /// mapping from block number -> block hash
309    pub hashes: HashMap<u64, B256>,
310    /// The current best hash
311    pub best_hash: B256,
312    /// The current best block number
313    pub best_number: u64,
314    /// genesis hash of the chain
315    pub genesis_hash: B256,
316    /// genesis block number of the chain
317    pub genesis_number: u64,
318    /// Mapping from the transaction hash to a tuple containing the transaction as well as the
319    /// transaction receipt
320    pub transactions: B256HashMap<MinedTransaction<N>>,
321    /// The total difficulty of the chain until this block
322    pub total_difficulty: U256,
323    /// Tempo certificates and identity lifecycle, published with this canonical chain.
324    pub(crate) tempo: Option<TempoChain>,
325    /// Monad senders and authorities retained even when old transaction bodies are pruned.
326    #[cfg(feature = "monad")]
327    pub monad_block_participants: B256HashMap<foundry_evm::core::evm::MonadBlockParticipants>,
328    /// Execution profile used when each locally stored Monad block was created.
329    #[cfg(feature = "monad")]
330    pub monad_block_replay_profiles: B256HashMap<crate::eth::backend::db::MonadBlockReplayProfile>,
331}
332
333impl<N: Network> BlockchainStorage<N> {
334    /// Removes all metadata associated with a locally stored Monad block.
335    #[cfg(feature = "monad")]
336    fn remove_monad_block_metadata(&mut self, block_hash: &B256) {
337        self.monad_block_participants.remove(block_hash);
338        self.monad_block_replay_profiles.remove(block_hash);
339    }
340
341    /// Creates a new storage with a genesis block.
342    pub fn new(header: FoundryHeader) -> Self {
343        let block =
344            create_block(header, Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new());
345        let genesis_hash = block.header.hash_slow();
346        let best_hash = genesis_hash;
347        let best_number = block.header.number();
348
349        let mut blocks = B256HashMap::default();
350        blocks.insert(genesis_hash, block);
351
352        let mut hashes = HashMap::default();
353        hashes.insert(best_number, genesis_hash);
354        Self {
355            blocks,
356            block_access_lists: Default::default(),
357            hashes,
358            best_hash,
359            best_number,
360            genesis_hash,
361            genesis_number: best_number,
362            transactions: Default::default(),
363            total_difficulty: Default::default(),
364            tempo: None,
365            #[cfg(feature = "monad")]
366            monad_block_participants: Default::default(),
367            #[cfg(feature = "monad")]
368            monad_block_replay_profiles: Default::default(),
369        }
370    }
371
372    pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self {
373        let mut hashes = HashMap::default();
374        hashes.insert(block_number, block_hash);
375
376        Self {
377            blocks: B256HashMap::default(),
378            block_access_lists: Default::default(),
379            hashes,
380            best_hash: block_hash,
381            best_number: block_number,
382            genesis_hash: Default::default(),
383            genesis_number: 0,
384            transactions: Default::default(),
385            total_difficulty,
386            tempo: None,
387            #[cfg(feature = "monad")]
388            monad_block_participants: Default::default(),
389            #[cfg(feature = "monad")]
390            monad_block_replay_profiles: Default::default(),
391        }
392    }
393
394    /// Unwind the chain state back to the given block in storage.
395    ///
396    /// The block identified by `block_number` and `block_hash` is __non-inclusive__, i.e. it will
397    /// remain in the state.
398    pub fn unwind_to(&mut self, block_number: u64, block_hash: B256) -> Vec<Block> {
399        let mut removed = vec![];
400        let best_num: u64 = self.best_number;
401        for i in (block_number + 1)..=best_num {
402            if let Some(hash) = self.hashes.get(&i).copied() {
403                // First remove the block's transactions while the mappings still exist
404                self.remove_block_transactions_by_number(i);
405
406                // Now remove the block from storage (may already be empty of txs) and drop mapping
407                if let Some(block) = self.blocks.remove(&hash) {
408                    removed.push(block);
409                }
410                self.block_access_lists.remove(&hash);
411                #[cfg(feature = "monad")]
412                self.remove_monad_block_metadata(&hash);
413                self.hashes.remove(&i);
414            }
415        }
416        self.best_hash = block_hash;
417        self.best_number = block_number;
418        removed
419    }
420
421    pub fn empty() -> Self {
422        Self {
423            blocks: Default::default(),
424            block_access_lists: Default::default(),
425            hashes: Default::default(),
426            best_hash: Default::default(),
427            best_number: Default::default(),
428            genesis_hash: Default::default(),
429            genesis_number: Default::default(),
430            transactions: Default::default(),
431            total_difficulty: Default::default(),
432            tempo: None,
433            #[cfg(feature = "monad")]
434            monad_block_participants: Default::default(),
435            #[cfg(feature = "monad")]
436            monad_block_replay_profiles: Default::default(),
437        }
438    }
439
440    /// Removes all stored transactions for the given block number
441    pub fn remove_block_transactions_by_number(&mut self, num: u64) {
442        if let Some(hash) = self.hashes.get(&num).copied() {
443            self.remove_block_transactions(hash);
444        }
445    }
446
447    /// Removes all stored transactions for the given block hash
448    pub fn remove_block_transactions(&mut self, block_hash: B256) {
449        self.block_access_lists.remove(&block_hash);
450        if let Some(block) = self.blocks.get_mut(&block_hash) {
451            for tx in &block.body.transactions {
452                self.transactions.remove(&tx.hash());
453            }
454            block.body.transactions.clear();
455        }
456    }
457
458    /// Serialize all blocks in storage
459    pub fn serialized_blocks(&self) -> Vec<SerializableBlock> {
460        let mut blocks = self.blocks.iter().collect::<Vec<_>>();
461        blocks.sort_unstable_by_key(|(hash, block)| {
462            let hash = **hash;
463            let number = block.header.number();
464            let is_canonical = self.hashes.get(&number).is_some_and(|canonical| *canonical == hash);
465            (number, is_canonical, hash)
466        });
467        blocks.into_iter().map(|(_, block)| block.clone().into()).collect()
468    }
469
470    /// Adds a block to storage and returns its hash.
471    pub fn insert_block(&mut self, block: Block) -> B256 {
472        let block_hash = block.header.hash_slow();
473        let block_number = block.header.number();
474        self.blocks.insert(block_hash, block);
475        self.hashes.insert(block_number, block_hash);
476
477        // Update genesis_hash if we are loading the genesis block, so that
478        // Finalized/Safe/Earliest block tag lookups return the correct hash. The genesis
479        // number can be non-zero when configured via `--block-number`.
480        // See: https://github.com/foundry-rs/foundry/issues/12645
481        if block_number == self.genesis_number {
482            self.genesis_hash = block_hash;
483        }
484
485        block_hash
486    }
487
488    /// Deserialize and add blocks above the fork boundary to the backend storage.
489    pub fn load_blocks(
490        &mut self,
491        serializable_blocks: Vec<SerializableBlock>,
492        fork_boundary: Option<u64>,
493    ) {
494        for serializable_block in serializable_blocks {
495            let block: Block = serializable_block.into();
496            if fork_boundary.is_some_and(|boundary| block.header.number() <= boundary) {
497                continue;
498            }
499            self.insert_block(block);
500        }
501    }
502
503    /// Returns the hash for [BlockNumberOrTag]
504    pub fn hash(&self, number: BlockNumberOrTag, slots_in_an_epoch: u64) -> Option<B256> {
505        match number {
506            BlockNumberOrTag::Latest => Some(self.best_hash),
507            BlockNumberOrTag::Earliest => Some(self.genesis_hash),
508            BlockNumberOrTag::Pending => None,
509            BlockNumberOrTag::Number(num) => self.hashes.get(&num).copied(),
510            BlockNumberOrTag::Safe => {
511                if self.best_number.saturating_sub(slots_in_an_epoch) > self.genesis_number {
512                    self.hashes.get(&(self.best_number - slots_in_an_epoch)).copied()
513                } else {
514                    Some(self.genesis_hash)
515                }
516            }
517            BlockNumberOrTag::Finalized => {
518                if self.best_number.saturating_sub(slots_in_an_epoch * 2) > self.genesis_number {
519                    self.hashes.get(&(self.best_number - slots_in_an_epoch * 2)).copied()
520                } else {
521                    Some(self.genesis_hash)
522                }
523            }
524        }
525    }
526}
527
528impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> BlockchainStorage<N> {
529    pub fn serialized_transactions(&self) -> Vec<SerializableTransaction> {
530        let mut transactions = self
531            .transactions
532            .values()
533            .map(|tx: &MinedTransaction<N>| SerializableTransaction::from(tx.clone()))
534            .collect::<Vec<_>>();
535        transactions.sort_unstable_by_key(|tx| {
536            (tx.block_number, tx.info.transaction_index, tx.info.transaction_hash)
537        });
538        transactions
539    }
540
541    /// Deserialize and add transactions above the fork boundary to the backend storage.
542    pub fn load_transactions(
543        &mut self,
544        serializable_transactions: Vec<SerializableTransaction>,
545        fork_boundary: Option<u64>,
546    ) {
547        for serializable_transaction in serializable_transactions {
548            if fork_boundary
549                .is_some_and(|boundary| serializable_transaction.block_number <= boundary)
550            {
551                continue;
552            }
553            let transaction: MinedTransaction<N> = serializable_transaction.into();
554            self.transactions.insert(transaction.info.transaction_hash, transaction);
555        }
556    }
557}
558
559/// A simple in-memory blockchain
560#[derive(Clone, Debug)]
561pub struct Blockchain<N: Network> {
562    /// underlying storage that supports concurrent reads
563    pub storage: Arc<RwLock<BlockchainStorage<N>>>,
564}
565
566impl<N: Network> Blockchain<N> {
567    /// Creates a new storage with a genesis block.
568    pub fn new(header: FoundryHeader) -> Self {
569        Self { storage: Arc::new(RwLock::new(BlockchainStorage::new(header))) }
570    }
571
572    pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self {
573        Self {
574            storage: Arc::new(RwLock::new(BlockchainStorage::forked(
575                block_number,
576                block_hash,
577                total_difficulty,
578            ))),
579        }
580    }
581
582    /// returns the header hash of given block
583    pub fn hash(&self, id: BlockId, slots_in_an_epoch: u64) -> Option<B256> {
584        match id {
585            BlockId::Hash(h) => Some(h.block_hash),
586            BlockId::Number(num) => self.storage.read().hash(num, slots_in_an_epoch),
587        }
588    }
589
590    pub fn get_block_by_hash(&self, hash: &B256) -> Option<Block> {
591        self.storage.read().blocks.get(hash).cloned()
592    }
593
594    pub fn get_transaction_by_hash(&self, hash: &B256) -> Option<MinedTransaction<N>> {
595        self.storage.read().transactions.get(hash).cloned()
596    }
597}
598
599/// Represents the outcome of mining a new block
600pub struct MinedBlockOutcome<T> {
601    /// The block that was mined
602    pub block_number: u64,
603    /// All transactions included in the block
604    pub included: Vec<Arc<PoolTransaction<T>>>,
605    /// Transactions whose nonce was already consumed by the current state.
606    pub stale: Vec<Arc<PoolTransaction<T>>>,
607    /// All transactions that were attempted to be included but were invalid at the time of
608    /// execution
609    pub invalid: Vec<Arc<PoolTransaction<T>>>,
610    /// Transactions skipped because they're not yet valid (e.g., valid_after in the future).
611    /// These remain in the pool and should be retried later.
612    pub not_yet_valid: Vec<Arc<PoolTransaction<T>>>,
613}
614
615impl<T> Clone for MinedBlockOutcome<T> {
616    fn clone(&self) -> Self {
617        Self {
618            block_number: self.block_number,
619            included: self.included.clone(),
620            stale: self.stale.clone(),
621            invalid: self.invalid.clone(),
622            not_yet_valid: self.not_yet_valid.clone(),
623        }
624    }
625}
626
627impl<T> fmt::Debug for MinedBlockOutcome<T> {
628    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
629        f.debug_struct("MinedBlockOutcome")
630            .field("block_number", &self.block_number)
631            .field("included", &self.included.len())
632            .field("stale", &self.stale.len())
633            .field("invalid", &self.invalid.len())
634            .field("not_yet_valid", &self.not_yet_valid.len())
635            .finish()
636    }
637}
638
639/// Container type for a mined transaction
640#[derive(Clone, Debug)]
641pub struct MinedTransaction<N: Network> {
642    pub info: TransactionInfo,
643    pub receipt: N::ReceiptEnvelope,
644    pub block_hash: B256,
645    pub block_number: u64,
646}
647
648impl<N: Network> MinedTransaction<N> {
649    /// Returns the traces of the transaction for `trace_transaction`.
650    ///
651    /// Like simulated traces, these omit nested zero-value precompile calls identified during
652    /// execution, regardless of subsequent changes to the node configuration.
653    pub fn parity_traces(&self) -> Vec<LocalizedTransactionTrace> {
654        ParityTraceBuilder::new(
655            exclude_precompile_calls(self.info.traces.clone()),
656            None,
657            TracingInspectorConfig::default_parity(),
658        )
659        .into_localized_transaction_traces(RethTransactionInfo {
660            hash: Some(self.info.transaction_hash),
661            index: Some(self.info.transaction_index),
662            block_hash: Some(self.block_hash),
663            block_number: Some(self.block_number),
664            base_fee: None,
665            block_timestamp: None,
666        })
667    }
668
669    pub fn ots_internal_operations(&self) -> Vec<InternalOperation> {
670        self.info
671            .traces
672            .iter()
673            .filter_map(|node| {
674                let r#type = match node.trace.kind {
675                    _ if node.is_selfdestruct() => OperationType::OpSelfDestruct,
676                    CallKind::Call if !node.trace.value.is_zero() => OperationType::OpTransfer,
677                    CallKind::Create => OperationType::OpCreate,
678                    CallKind::Create2 => OperationType::OpCreate2,
679                    _ => return None,
680                };
681                let (from, to, value) = if node.is_selfdestruct() {
682                    (
683                        node.trace.address,
684                        node.trace.selfdestruct_refund_target.unwrap_or_default(),
685                        node.trace.selfdestruct_transferred_value.unwrap_or_default(),
686                    )
687                } else {
688                    (node.trace.caller, node.trace.address, node.trace.value)
689                };
690                Some(InternalOperation { r#type, from, to, value })
691            })
692            .collect()
693    }
694}
695
696/// Detaches precompile calls identified during execution from the call graph, as the tracing
697/// inspector does when configured to exclude precompile calls.
698///
699/// Mined transactions keep these calls for the Geth-style traces, which include them.
700fn exclude_precompile_calls(mut nodes: Vec<CallTraceNode>) -> Vec<CallTraceNode> {
701    for idx in 0..nodes.len() {
702        if nodes[idx].is_precompile()
703            && let Some(parent) = nodes[idx].parent
704            && let Some(position) = nodes[parent].children.iter().position(|&child| child == idx)
705        {
706            let parent = &mut nodes[parent];
707            parent.children.remove(position);
708            parent.ordering.retain_mut(|member| match member {
709                TraceMemberOrder::Call(child) if *child == position => false,
710                TraceMemberOrder::Call(child) => {
711                    if *child > position {
712                        *child -= 1;
713                    }
714                    true
715                }
716                _ => true,
717            });
718        }
719    }
720    nodes
721}
722
723/// Intermediary Anvil representation of a receipt
724#[derive(Clone, Debug)]
725pub struct MinedTransactionReceipt<N: Network> {
726    /// The actual json rpc receipt object
727    pub inner: N::ReceiptResponse,
728    /// Output data for the transaction
729    pub out: Option<Bytes>,
730}
731
732#[cfg(test)]
733mod tests {
734    use super::*;
735    use crate::eth::backend::{db::Db, mem::in_memory_db::StateRootDb};
736    use alloy_consensus::Header;
737    use alloy_primitives::{Address, hex};
738    use alloy_rlp::Decodable;
739    use foundry_evm::backend::MemDb;
740    use foundry_primitives::FoundryNetwork;
741    use revm::{database::DatabaseRef, interpreter::InstructionResult, state::AccountInfo};
742    use tempo_primitives::TempoHeader;
743
744    #[test]
745    fn test_interval_update() {
746        let mut storage = InMemoryBlockStates::default();
747        storage.update_interval_mine_block_time(Duration::from_secs(1));
748        assert_eq!(storage.in_memory_limit, DEFAULT_HISTORY_LIMIT * 3);
749    }
750
751    #[test]
752    fn test_interval_update_preserves_memory_only_limit() {
753        for limit in [1, 8, DEFAULT_HISTORY_LIMIT * 4] {
754            let mut storage = InMemoryBlockStates::new(limit, 0);
755            for number in 0..limit + 2 {
756                storage.insert(B256::from(U256::from(number)), StateDb::new(MemDb::default()));
757            }
758
759            for seconds in [3, 1, 2] {
760                storage.update_interval_mine_block_time(Duration::from_secs(seconds));
761                assert_eq!(storage.in_memory_limit, limit);
762                assert_eq!(storage.states.len(), limit);
763                assert!(storage.on_disk_states.is_empty());
764                assert!(storage.get_state(&B256::ZERO).is_none());
765                assert!(storage.get_state(&B256::with_last_byte(2)).is_some());
766            }
767        }
768    }
769
770    #[test]
771    fn test_init_state_limits() {
772        let mut storage = InMemoryBlockStates::default();
773        assert_eq!(storage.in_memory_limit, DEFAULT_HISTORY_LIMIT);
774        assert_eq!(storage.min_in_memory_limit, MIN_HISTORY_LIMIT);
775        assert_eq!(storage.max_on_disk_limit, MAX_ON_DISK_HISTORY_LIMIT);
776
777        storage = storage.memory_only();
778        assert!(storage.is_memory_only());
779
780        storage = InMemoryBlockStates::new(1, 0);
781        assert!(storage.is_memory_only());
782        assert_eq!(storage.in_memory_limit, 1);
783        assert_eq!(storage.min_in_memory_limit, 1);
784        assert_eq!(storage.max_on_disk_limit, 0);
785
786        storage = InMemoryBlockStates::new(1, 2);
787        assert!(!storage.is_memory_only());
788        assert_eq!(storage.in_memory_limit, 1);
789        assert_eq!(storage.min_in_memory_limit, 1);
790        assert_eq!(storage.max_on_disk_limit, 2);
791
792        storage = InMemoryBlockStates::new(0, 0);
793        assert!(storage.is_memory_only());
794        assert_eq!(storage.in_memory_limit, 1);
795        assert_eq!(storage.min_in_memory_limit, 1);
796        assert_eq!(storage.max_on_disk_limit, 0);
797
798        storage.set_cache_limit(0);
799        assert_eq!(storage.in_memory_limit, 1);
800        assert_eq!(storage.min_in_memory_limit, 1);
801    }
802
803    #[tokio::test(flavor = "multi_thread")]
804    async fn can_read_write_cached_state() {
805        let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT);
806        let one = B256::with_last_byte(1);
807        let two = B256::with_last_byte(2);
808
809        let mut state = MemDb::default();
810        let addr = Address::random();
811        let info = AccountInfo::from_balance(U256::from(1337));
812        state.insert_account(addr, info);
813        storage.insert(one, StateDb::new(state));
814        storage.insert(two, StateDb::new(MemDb::default()));
815
816        // wait for files to be flushed
817        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
818
819        assert_eq!(storage.on_disk_states.len(), 1);
820        assert!(storage.on_disk_states.contains_key(&one));
821
822        let loaded = storage.get_on_disk_state(&one).unwrap();
823
824        let acc = loaded.basic_ref(addr).unwrap().unwrap();
825        assert_eq!(acc.balance, U256::from(1337u64));
826    }
827
828    #[test]
829    fn persistent_states_do_not_use_disk_cache() {
830        let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT);
831        let one = B256::with_last_byte(1);
832        let two = B256::with_last_byte(2);
833        let address = Address::random();
834        let mut db = StateRootDb::default();
835
836        db.insert_account(address, AccountInfo::from_balance(U256::ONE));
837        storage.insert(one, db.current_state());
838        db.set_balance(address, U256::from(2)).unwrap();
839        storage.insert(two, db.current_state());
840
841        assert!(storage.disk_cache.temp_dir.is_none());
842        assert!(storage.on_disk_states.get(&one).unwrap().is_persistent());
843        assert_eq!(
844            storage.get_on_disk_state(&one).unwrap().basic_ref(address).unwrap().unwrap().balance,
845            U256::ONE
846        );
847        storage.remove_block_states(&[one]);
848        assert!(storage.disk_cache.temp_dir.is_none());
849    }
850
851    #[tokio::test(flavor = "multi_thread")]
852    async fn can_decrease_state_cache_size() {
853        let limit = 15;
854        let mut storage = InMemoryBlockStates::new(limit, MAX_ON_DISK_HISTORY_LIMIT);
855
856        let num_states = 30;
857        for idx in 0..num_states {
858            let mut state = MemDb::default();
859            let hash = B256::from(U256::from(idx));
860            let addr = Address::from_word(hash);
861            let balance = (idx * 2) as u64;
862            let info = AccountInfo::from_balance(U256::from(balance));
863            state.insert_account(addr, info);
864            storage.insert(hash, StateDb::new(state));
865        }
866
867        // wait for files to be flushed
868        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
869
870        let on_disk_states_len = num_states - storage.min_in_memory_limit;
871
872        assert_eq!(storage.on_disk_states.len(), on_disk_states_len);
873        assert_eq!(storage.present.len(), storage.min_in_memory_limit);
874
875        for idx in 0..num_states {
876            let hash = B256::from(U256::from(idx));
877            let addr = Address::from_word(hash);
878
879            let loaded = if idx < on_disk_states_len {
880                storage.get_on_disk_state(&hash).unwrap()
881            } else {
882                storage.get_state(&hash).unwrap()
883            };
884
885            let acc = loaded.basic_ref(addr).unwrap().unwrap();
886            let balance = (idx * 2) as u64;
887            assert_eq!(acc.balance, U256::from(balance));
888        }
889    }
890
891    #[test]
892    fn test_remove_block_states_on_rollback() {
893        let mut storage = InMemoryBlockStates::new(10, MAX_ON_DISK_HISTORY_LIMIT);
894
895        // Insert 5 states
896        let hashes: Vec<B256> = (0..5)
897            .map(|i| {
898                let hash = B256::from(U256::from(i));
899                let mut state = MemDb::default();
900                let addr = Address::from_word(hash);
901                state.insert_account(addr, AccountInfo::from_balance(U256::from(i * 100)));
902                storage.insert(hash, StateDb::new(state));
903                hash
904            })
905            .collect();
906
907        assert_eq!(storage.present.len(), 5);
908
909        // Simulate rollback: remove the last 3 blocks
910        let removed_hashes = &hashes[2..];
911        storage.remove_block_states(removed_hashes);
912
913        // Only the first 2 states should remain
914        assert_eq!(storage.present.len(), 2);
915        assert!(storage.get_state(&hashes[0]).is_some());
916        assert!(storage.get_state(&hashes[1]).is_some());
917        for h in removed_hashes {
918            assert!(storage.get_state(h).is_none());
919            assert!(!storage.present.contains(h));
920        }
921    }
922
923    #[tokio::test(flavor = "multi_thread")]
924    async fn test_remove_block_states_cleans_disk_cache() {
925        // Use limit=1 to force states to disk
926        let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT);
927
928        let hash_a = B256::with_last_byte(1);
929        let hash_b = B256::with_last_byte(2);
930
931        storage.insert(hash_a, StateDb::new(MemDb::default()));
932        storage.insert(hash_b, StateDb::new(MemDb::default()));
933
934        // Wait for disk flush
935        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
936
937        assert!(storage.on_disk_states.contains_key(&hash_a));
938
939        // Remove hash_a (on disk)
940        storage.remove_block_states(&[hash_a]);
941
942        assert!(!storage.on_disk_states.contains_key(&hash_a));
943        assert!(!storage.oldest_on_disk.contains(&hash_a));
944        assert!(storage.get_on_disk_state(&hash_a).is_none());
945    }
946
947    // verifies that blocks and transactions in BlockchainStorage remain the same when dumped and
948    // reloaded
949    #[test]
950    fn test_storage_dump_reload_cycle() {
951        let mut dump_storage = BlockchainStorage::<FoundryNetwork>::empty();
952
953        let header = Header { gas_limit: 123456, ..Default::default() };
954        let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..];
955        let tx: MaybeImpersonatedTransaction<FoundryTxEnvelope> =
956            FoundryTxEnvelope::decode(&mut &bytes_first[..]).unwrap().into();
957        let block = create_block(header.clone().into(), vec![tx.clone()]);
958        let block_hash = block.header.hash_slow();
959        dump_storage.blocks.insert(block_hash, block);
960
961        let serialized_blocks = dump_storage.serialized_blocks();
962        let serialized_transactions = dump_storage.serialized_transactions();
963
964        let mut load_storage = BlockchainStorage::<FoundryNetwork>::empty();
965
966        load_storage.load_blocks(serialized_blocks, None);
967        load_storage.load_transactions(serialized_transactions, None);
968
969        let loaded_block = load_storage.blocks.get(&block_hash).unwrap();
970        assert_eq!(loaded_block.header.gas_limit(), header.gas_limit());
971        let loaded_tx = loaded_block.body.transactions.first().unwrap();
972        assert_eq!(loaded_tx, &tx);
973    }
974
975    #[test]
976    fn serialized_blocks_puts_canonical_block_last() {
977        let block = |timestamp| {
978            create_block(
979                Header { number: 1, timestamp, ..Default::default() }.into(),
980                Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
981            )
982        };
983        let block_a = block(1);
984        let block_b = block(2);
985        let (canonical, stale) = if block_a.header.hash_slow() < block_b.header.hash_slow() {
986            (block_a, block_b)
987        } else {
988            (block_b, block_a)
989        };
990
991        let mut storage = BlockchainStorage::<FoundryNetwork>::empty();
992        let stale_hash = storage.insert_block(stale);
993        let canonical_hash = storage.insert_block(canonical);
994        assert!(canonical_hash < stale_hash);
995
996        let mut loaded = BlockchainStorage::<FoundryNetwork>::empty();
997        loaded.load_blocks(storage.serialized_blocks(), None);
998        assert_eq!(loaded.hashes.get(&1), Some(&canonical_hash));
999    }
1000
1001    #[test]
1002    fn serialized_transactions_are_sorted() {
1003        let transaction = |block_number, transaction_index, transaction_hash| MinedTransaction::<
1004            FoundryNetwork,
1005        > {
1006            info: TransactionInfo {
1007                transaction_hash,
1008                transaction_index,
1009                from: Address::ZERO,
1010                to: None,
1011                contract_address: None,
1012                traces: Vec::new(),
1013                exit: InstructionResult::Stop,
1014                out: None,
1015                nonce: 0,
1016                gas_used: 0,
1017            },
1018            receipt: FoundryReceiptEnvelope::Legacy(Default::default()),
1019            block_hash: B256::ZERO,
1020            block_number,
1021        };
1022        let first = B256::with_last_byte(1);
1023        let second = B256::with_last_byte(2);
1024        let third = B256::with_last_byte(3);
1025        let fourth = B256::with_last_byte(4);
1026        let mut storage = BlockchainStorage::<FoundryNetwork>::empty();
1027        for transaction in [
1028            transaction(2, 0, fourth),
1029            transaction(1, 1, third),
1030            transaction(1, 0, second),
1031            transaction(1, 0, first),
1032        ] {
1033            storage.transactions.insert(transaction.info.transaction_hash, transaction);
1034        }
1035
1036        let hashes = storage
1037            .serialized_transactions()
1038            .into_iter()
1039            .map(|transaction| transaction.info.transaction_hash)
1040            .collect::<Vec<_>>();
1041        assert_eq!(hashes, [first, second, third, fourth]);
1042    }
1043
1044    #[test]
1045    fn serialized_historical_states_are_sorted() {
1046        let hashes = [3, 1, 2].map(|number| B256::from(U256::from(number)));
1047        let mut states = InMemoryBlockStates::default();
1048        for hash in hashes {
1049            states.insert(hash, StateDb::new(MemDb::default()));
1050        }
1051
1052        let serialized_hashes =
1053            states.serialized_states().into_iter().map(|(hash, _)| hash).collect::<Vec<_>>();
1054        assert_eq!(serialized_hashes, [hashes[1], hashes[2], hashes[0]]);
1055    }
1056
1057    #[test]
1058    fn test_tempo_storage_dump_reload_cycle() {
1059        let mut dump_storage = BlockchainStorage::<FoundryNetwork>::empty();
1060        let header = TempoHeader {
1061            general_gas_limit: 30_000_000,
1062            shared_gas_limit: 1_000_000,
1063            timestamp_millis_part: 123,
1064            inner: Header { number: 7, gas_limit: 30_000_000, timestamp: 42, ..Default::default() },
1065            consensus_context: None,
1066        };
1067        let block = create_block(
1068            header.into(),
1069            Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
1070        );
1071        let expected_header = block.header.clone();
1072        let block_hash = block.header.hash_slow();
1073        dump_storage.blocks.insert(block_hash, block);
1074
1075        let serialized = serde_json::to_string(&dump_storage.serialized_blocks()).unwrap();
1076        let blocks: Vec<SerializableBlock> = serde_json::from_str(&serialized).unwrap();
1077        let mut load_storage = BlockchainStorage::<FoundryNetwork>::empty();
1078        load_storage.load_blocks(blocks, None);
1079
1080        let loaded_block = load_storage.blocks.get(&block_hash).unwrap();
1081        assert_eq!(loaded_block.header, expected_header);
1082        assert_eq!(loaded_block.header.as_tempo().unwrap().shared_gas_limit, 1_000_000);
1083        assert_eq!(load_storage.hashes.get(&7), Some(&block_hash));
1084    }
1085
1086    #[test]
1087    fn test_hash_safe_with_non_zero_genesis_number() {
1088        let mut storage = BlockchainStorage::<FoundryNetwork>::new(
1089            Header { number: 100, ..Default::default() }.into(),
1090        );
1091
1092        for best_number in [100, 131, 132] {
1093            storage.best_number = best_number;
1094            assert_eq!(storage.hash(BlockNumberOrTag::Safe, 32), Some(storage.genesis_hash));
1095        }
1096    }
1097
1098    #[test]
1099    fn test_hash_finalized_with_non_zero_genesis_number() {
1100        let mut storage = BlockchainStorage::<FoundryNetwork>::new(
1101            Header { number: 100, ..Default::default() }.into(),
1102        );
1103
1104        for best_number in [100, 163, 164] {
1105            storage.best_number = best_number;
1106            assert_eq!(storage.hash(BlockNumberOrTag::Finalized, 32), Some(storage.genesis_hash));
1107        }
1108    }
1109
1110    #[test]
1111    fn test_hash_safe_finalized_with_deep_non_zero_genesis() {
1112        let mut storage = BlockchainStorage::<FoundryNetwork>::new(
1113            Header { number: 100, ..Default::default() }.into(),
1114        );
1115        storage.best_number = 200;
1116        let safe_hash = B256::repeat_byte(0xab);
1117        let finalized_hash = B256::repeat_byte(0xcd);
1118        storage.hashes.insert(168, safe_hash);
1119        storage.hashes.insert(136, finalized_hash);
1120
1121        assert_eq!(storage.hash(BlockNumberOrTag::Safe, 32), Some(safe_hash));
1122        assert_eq!(storage.hash(BlockNumberOrTag::Finalized, 32), Some(finalized_hash));
1123    }
1124
1125    #[test]
1126    fn test_hash_safe_finalized_with_zero_genesis() {
1127        let mut storage = BlockchainStorage::<FoundryNetwork>::new(Header::default().into());
1128
1129        for (tag, offset) in [(BlockNumberOrTag::Safe, 32), (BlockNumberOrTag::Finalized, 64)] {
1130            for best_number in [0, offset - 1, offset] {
1131                storage.best_number = best_number;
1132                assert_eq!(storage.hash(tag, 32), Some(storage.genesis_hash));
1133            }
1134
1135            storage.best_number = offset + 1;
1136            assert_eq!(storage.hash(tag, 32), None);
1137            let hash = B256::repeat_byte(0xab);
1138            storage.hashes.insert(1, hash);
1139            assert_eq!(storage.hash(tag, 32), Some(hash));
1140            storage.hashes.remove(&1);
1141        }
1142    }
1143
1144    #[test]
1145    fn test_hash_safe_finalized_fork_preserves_local_miss() {
1146        let mut storage = BlockchainStorage::<FoundryNetwork>::forked(
1147            23_000_000,
1148            B256::repeat_byte(0xab),
1149            U256::ZERO,
1150        );
1151
1152        for (tag, number) in
1153            [(BlockNumberOrTag::Safe, 22_999_968), (BlockNumberOrTag::Finalized, 22_999_936)]
1154        {
1155            assert_eq!(storage.hash(tag, 32), None);
1156            let hash = B256::repeat_byte(0xcd);
1157            storage.hashes.insert(number, hash);
1158            assert_eq!(storage.hash(tag, 32), Some(hash));
1159        }
1160    }
1161
1162    // Regression test for https://github.com/foundry-rs/foundry/issues/12645:
1163    // when a non-zero genesis number is configured (e.g. `--block-number 73 --load-state ...`),
1164    // `load_blocks` must set `genesis_hash` to the loaded block matching `genesis_number`,
1165    // not the hardcoded block 0.
1166    #[test]
1167    fn test_load_blocks_sets_genesis_hash_with_non_zero_genesis_number() {
1168        const GENESIS_NUMBER: u64 = 73;
1169
1170        // Build a serialized block at the configured genesis number.
1171        let header = Header { number: GENESIS_NUMBER, gas_limit: 123456, ..Default::default() };
1172        let block = create_block(
1173            header.into(),
1174            Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
1175        );
1176        let block_hash = block.header.hash_slow();
1177        let serialized_blocks: Vec<SerializableBlock> = vec![block.into()];
1178
1179        // Simulate a fresh storage started with `--block-number 73`: the dummy block created by
1180        // `new()` is hash X, and `genesis_number` is 73. Loading a state snapshot whose genesis
1181        // is also 73 must rewrite `genesis_hash` to the loaded block's hash.
1182        let mut load_storage = BlockchainStorage::<FoundryNetwork>::empty();
1183        load_storage.genesis_number = GENESIS_NUMBER;
1184        let dummy_genesis_hash = B256::repeat_byte(0xab);
1185        load_storage.genesis_hash = dummy_genesis_hash;
1186
1187        load_storage.load_blocks(serialized_blocks, None);
1188
1189        assert_eq!(load_storage.genesis_hash, block_hash);
1190        assert_ne!(load_storage.genesis_hash, dummy_genesis_hash);
1191
1192        // Sanity check: with the old hardcoded `block_number == 0` logic, `genesis_hash` would
1193        // never be updated when no block 0 is present, so the dummy hash would leak through.
1194        let mut sanity_storage = BlockchainStorage::<FoundryNetwork>::empty();
1195        sanity_storage.genesis_number = 0;
1196        sanity_storage.genesis_hash = dummy_genesis_hash;
1197
1198        let header_only_73 =
1199            Header { number: GENESIS_NUMBER, gas_limit: 123456, ..Default::default() };
1200        let block_73 = create_block(
1201            header_only_73.into(),
1202            Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
1203        );
1204        sanity_storage.load_blocks(vec![block_73.into()], None);
1205        assert_eq!(sanity_storage.genesis_hash, dummy_genesis_hash);
1206    }
1207
1208    #[test]
1209    fn current_state_retention_preserves_history_limits() {
1210        for disk_limit in [0, MAX_ON_DISK_HISTORY_LIMIT] {
1211            let mut states = InMemoryBlockStates::new(2, disk_limit).with_current_state();
1212            for number in 0..4 {
1213                states.insert(B256::from(U256::from(number)), StateDb::new(MemDb::default()));
1214            }
1215            assert_eq!(states.states.len(), 3);
1216            assert!(states.get_state(&B256::ZERO).is_none());
1217            for number in 1..4 {
1218                assert!(states.get_state(&B256::from(U256::from(number))).is_some());
1219            }
1220
1221            states.set_cache_limit(1);
1222            assert_eq!(states.in_memory_limit, 2);
1223            assert_eq!(states.min_in_memory_limit, 2);
1224            states.clear();
1225            for number in 0..3 {
1226                states.insert(B256::from(U256::from(number)), StateDb::new(MemDb::default()));
1227            }
1228            assert_eq!(states.states.len(), 2);
1229            states.update_interval_mine_block_time(Duration::from_secs(1));
1230            assert_eq!(
1231                states.in_memory_limit,
1232                if disk_limit == 0 { 2 } else { DEFAULT_HISTORY_LIMIT * 3 + 1 },
1233            );
1234        }
1235    }
1236}