stratus/eth/storage/
stratus_storage.rs

1use parking_lot::RwLockReadGuard;
2use tracing::Span;
3
4use super::InMemoryTemporaryStorage;
5use super::RocksPermanentStorage;
6use super::StorageCache;
7#[cfg(feature = "dev")]
8use crate::eth::genesis::GenesisConfig;
9use crate::eth::primitives::Account;
10use crate::eth::primitives::Address;
11use crate::eth::primitives::Block;
12use crate::eth::primitives::BlockFilter;
13use crate::eth::primitives::BlockNumber;
14#[cfg(feature = "dev")]
15use crate::eth::primitives::Bytes;
16use crate::eth::primitives::ExecutionChanges;
17use crate::eth::primitives::Hash;
18use crate::eth::primitives::LogFilter;
19use crate::eth::primitives::LogMined;
20#[cfg(feature = "dev")]
21use crate::eth::primitives::Nonce;
22use crate::eth::primitives::PendingBlock;
23use crate::eth::primitives::PendingBlockHeader;
24use crate::eth::primitives::PointInTime;
25use crate::eth::primitives::Slot;
26use crate::eth::primitives::SlotIndex;
27#[cfg(feature = "dev")]
28use crate::eth::primitives::SlotValue;
29use crate::eth::primitives::StorageError;
30use crate::eth::primitives::TransactionExecution;
31use crate::eth::primitives::TransactionStage;
32#[cfg(feature = "dev")]
33use crate::eth::primitives::Wei;
34#[cfg(feature = "dev")]
35use crate::eth::primitives::test_accounts;
36use crate::eth::storage::ReadKind;
37use crate::eth::storage::TxCount;
38use crate::ext::not;
39use crate::infra::metrics;
40use crate::infra::metrics::timed;
41use crate::infra::tracing::SpanExt;
42
43mod label {
44    pub(super) const TEMP: &str = "temporary";
45    pub(super) const PERM: &str = "permanent";
46    pub(super) const CACHE: &str = "cache";
47}
48
49/// Proxy that simplifies interaction with permanent and temporary storages.
50///
51/// Additionaly it tracks metrics that are independent of the storage implementation.
52pub struct StratusStorage {
53    temp: InMemoryTemporaryStorage,
54    cache: StorageCache,
55    pub perm: RocksPermanentStorage,
56    // CONTRACT: Always acquire a lock when reading slots or accounts from latest (cache OR perm) and when saving a block
57    transient_state_lock: parking_lot::RwLock<()>,
58    #[cfg(feature = "dev")]
59    perm_config: crate::eth::storage::permanent::PermanentStorageConfig,
60}
61
62impl StratusStorage {
63    /// Creates a new storage with the specified temporary and permanent implementations.
64    pub fn new(
65        temp: InMemoryTemporaryStorage,
66        perm: RocksPermanentStorage,
67        cache: StorageCache,
68        #[cfg(feature = "dev")] perm_config: crate::eth::storage::permanent::PermanentStorageConfig,
69    ) -> Result<Self, StorageError> {
70        let this = Self {
71            temp,
72            cache,
73            perm,
74            transient_state_lock: parking_lot::RwLock::new(()),
75            #[cfg(feature = "dev")]
76            perm_config,
77        };
78
79        // create genesis block and accounts if necessary
80        #[cfg(feature = "dev")]
81        if !this.has_genesis()? {
82            this.reset_to_genesis()?;
83        }
84
85        Ok(this)
86    }
87
88    /// Returns whether the genesis block exists
89    pub fn has_genesis(&self) -> Result<bool, StorageError> {
90        self.perm.has_genesis()
91    }
92
93    /// Clears the storage cache.
94    pub fn clear_cache(&self) {
95        tracing::info!("clearing storage cache");
96        self.cache.clear();
97    }
98
99    #[cfg(test)]
100    pub fn new_test() -> Result<Self, StorageError> {
101        use tempfile::tempdir;
102
103        use super::cache::CacheConfig;
104
105        let temp = InMemoryTemporaryStorage::new(0.into());
106
107        // Create a temporary directory for RocksDB
108        let rocks_dir = tempdir().expect("Failed to create temporary directory for tests");
109        let rocks_path_prefix = rocks_dir.path().to_str().unwrap().to_string();
110
111        let perm = RocksPermanentStorage::new(Some(rocks_path_prefix.clone()), std::time::Duration::from_secs(240), None, true, None, 1024)
112            .expect("Failed to create RocksPermanentStorage for tests");
113
114        let cache = CacheConfig {
115            slot_cache_capacity: 100000,
116            account_cache_capacity: 20000,
117            account_history_cache_capacity: 20000,
118            slot_history_cache_capacity: 100000,
119        }
120        .init();
121
122        Self::new(
123            temp,
124            perm,
125            cache,
126            #[cfg(feature = "dev")]
127            super::permanent::PermanentStorageConfig {
128                rocks_path_prefix: Some(rocks_path_prefix),
129                rocks_shutdown_timeout: std::time::Duration::from_secs(240),
130                rocks_cache_size_multiplier: None,
131                rocks_disable_sync_write: false,
132                rocks_cf_size_metrics_interval: None,
133                genesis_file: crate::config::GenesisFileConfig::default(),
134                rocks_min_file_descriptors: 1024,
135            },
136        )
137    }
138
139    pub fn read_block_number_to_resume_import(&self) -> Result<BlockNumber, StorageError> {
140        #[cfg(feature = "tracing")]
141        let _span = tracing::info_span!("storage::read_block_number_to_resume_import").entered();
142
143        let number = self.read_pending_block_header().0.number;
144        tracing::info!(?number, "got block number to resume import");
145
146        Ok(number)
147    }
148
149    pub fn read_pending_block_header(&self) -> (PendingBlockHeader, TxCount) {
150        #[cfg(feature = "tracing")]
151        let _span = tracing::info_span!("storage::read_pending_block_number").entered();
152        tracing::debug!(storage = %label::TEMP, "reading pending block number");
153
154        timed(|| self.temp.read_pending_block_header()).with(|m| {
155            metrics::inc_storage_read_pending_block_number(m.elapsed, label::TEMP, true);
156        })
157    }
158
159    pub fn read_mined_block_number(&self) -> BlockNumber {
160        #[cfg(feature = "tracing")]
161        let _span = tracing::info_span!("storage::read_mined_block_number").entered();
162        tracing::debug!(storage = %label::PERM, "reading mined block number");
163
164        timed(|| self.perm.read_mined_block_number()).with(|m| {
165            metrics::inc_storage_read_mined_block_number(m.elapsed, label::PERM, true);
166        })
167    }
168
169    // TODO: make this infallible
170    pub fn set_mined_block_number(&self, block_number: BlockNumber) {
171        #[cfg(feature = "tracing")]
172        let _span = tracing::info_span!("storage::set_mined_block_number", %block_number).entered();
173        tracing::debug!(storage = %label::PERM, %block_number, "setting mined block number");
174
175        timed(|| self.perm.set_mined_block_number(block_number)).with(|m| {
176            metrics::inc_storage_set_mined_block_number(m.elapsed, label::PERM, true);
177        });
178    }
179
180    // -------------------------------------------------------------------------
181    // Accounts and slots
182    // -------------------------------------------------------------------------
183
184    pub fn save_accounts(&self, accounts: Vec<Account>) -> Result<(), StorageError> {
185        #[cfg(feature = "tracing")]
186        let _span = tracing::info_span!("storage::save_accounts").entered();
187
188        // keep only accounts that does not exist in permanent storage
189        let mut missing_accounts = Vec::new();
190        for account in accounts {
191            let perm_account = self.perm.read_account(account.address, PointInTime::Mined)?;
192            if perm_account.is_none() {
193                missing_accounts.push(account);
194            }
195        }
196
197        tracing::debug!(storage = %label::PERM, accounts = ?missing_accounts, "saving initial accounts");
198        timed(|| self.perm.save_accounts(missing_accounts)).with(|m| {
199            metrics::inc_storage_save_accounts(m.elapsed, label::PERM, m.result.is_ok());
200            if let Err(ref e) = m.result {
201                tracing::error!(reason = ?e, "failed to save accounts");
202            }
203        })
204    }
205
206    fn _read_account_pending_cache(&self, address: Address) -> Option<Account> {
207        timed(|| self.cache.get_account(address)).with(|m| {
208            if m.result.is_some() {
209                tracing::debug!(storage = %label::CACHE, %address, "account found in cache");
210                metrics::inc_storage_read_account(m.elapsed, label::CACHE, PointInTime::Pending);
211            }
212        })
213    }
214
215    fn _read_account_latest_cache(&self, address: Address) -> Option<Account> {
216        timed(|| self.cache.get_account_latest(address)).with(|m| {
217            if m.result.is_some() {
218                tracing::debug!(storage = %label::CACHE, %address, "account found in cache");
219                metrics::inc_storage_read_account(m.elapsed, label::CACHE, PointInTime::Mined);
220            }
221        })
222    }
223
224    fn _read_account_temp(&self, address: Address, kind: ReadKind) -> Result<Option<Account>, StorageError> {
225        tracing::debug!(storage = %label::TEMP, %address, "reading account");
226        timed(|| self.temp.read_account(address, kind)).with(|m| {
227            if m.result.as_ref().is_ok_and(|opt| opt.is_some()) {
228                metrics::inc_storage_read_account(m.elapsed, label::TEMP, PointInTime::Pending);
229            }
230            if let Err(ref e) = m.result {
231                tracing::error!(reason = ?e, "failed to read account from temporary storage");
232            }
233        })
234    }
235
236    fn _read_account_perm(&self, address: Address, point_in_time: PointInTime) -> Result<Account, StorageError> {
237        tracing::debug!(storage = %label::PERM, %address, "reading account");
238        let account = timed(|| self.perm.read_account(address, point_in_time)).with(|m| {
239            if m.result.as_ref().is_ok_and(|opt| opt.is_some()) {
240                metrics::inc_storage_read_account(m.elapsed, label::PERM, point_in_time);
241            }
242            if let Err(ref e) = m.result {
243                tracing::error!(reason = ?e, "failed to read account from permanent storage");
244            }
245        })?;
246        Ok(match account {
247            Some(account) => {
248                tracing::debug!(storage = %label::PERM, %address, ?account, "account found in permanent storage");
249                account
250            }
251            None => {
252                tracing::debug!(storage = %label::PERM, %address, "account not found, assuming default value");
253                Account::new_empty(address)
254            }
255        })
256    }
257
258    // For calls, this function returns a guard if latest is safe to read
259    fn _latest_is_valid(&self, point_in_time: PointInTime, kind: ReadKind) -> (bool, Option<RwLockReadGuard<'_, ()>>) {
260        if matches!(point_in_time, PointInTime::MinedPast(_)) {
261            return (false, None);
262        }
263        match kind {
264            ReadKind::Call((block_number, _)) => {
265                let guard = self.transient_state_lock.read();
266                // Check if the provided block number is less than or equal to the mined block number
267                let is_valid = block_number <= self.read_mined_block_number();
268                (is_valid, (is_valid).then_some(guard))
269            }
270            _ => (true, None),
271        }
272    }
273
274    pub fn read_account(&self, address: Address, mut point_in_time: PointInTime, kind: ReadKind) -> Result<Account, StorageError> {
275        #[cfg(feature = "tracing")]
276        let _span = tracing::debug_span!("storage::read_account", %address, %point_in_time).entered();
277
278        let (account, found_in_perm) = 'query: {
279            if point_in_time == PointInTime::Pending {
280                if matches!(kind, ReadKind::Transaction)
281                    && let Some(account) = self._read_account_pending_cache(address)
282                {
283                    return Ok(account);
284                }
285
286                if let Some(account) = self._read_account_temp(address, kind)? {
287                    tracing::debug!(storage = %label::TEMP, %address, ?account, "account found in temporary storage");
288                    break 'query (account, false);
289                }
290            }
291
292            let (is_valid, guard) = self._latest_is_valid(point_in_time, kind);
293            if is_valid {
294                if let Some(account) = self._read_account_latest_cache(address) {
295                    return Ok(account);
296                }
297            } else if let ReadKind::Call((block_number, _)) = kind
298                && !matches!(point_in_time, PointInTime::MinedPast(_))
299            {
300                point_in_time = PointInTime::MinedPast(block_number);
301            }
302
303            // always read from perm if necessary
304            let ret = (self._read_account_perm(address, point_in_time)?, true);
305            if let Some(inner) = guard {
306                RwLockReadGuard::unlock_fair(inner);
307            }
308            ret
309        };
310
311        match (point_in_time, found_in_perm) {
312            // Pending accounts found in the permanent storage (or not found in any storage) are always mined already
313            (PointInTime::Pending, true) => {
314                self.cache.cache_account_if_missing(account.clone());
315                self.cache.cache_account_latest_if_missing(address, account.clone());
316            }
317            (PointInTime::Pending, false) => {
318                self.cache.cache_account_if_missing(account.clone());
319            }
320            (PointInTime::Mined, _) => {
321                self.cache.cache_account_latest_if_missing(address, account.clone());
322            }
323            _ => {}
324        }
325        Ok(account)
326    }
327
328    fn _read_slot_pending_cache(&self, address: Address, index: SlotIndex) -> Option<Slot> {
329        timed(|| self.cache.get_slot(address, index)).with(|m| {
330            if m.result.is_some() {
331                tracing::debug!(storage = %label::CACHE, %address, %index, "slot found in cache");
332                metrics::inc_storage_read_slot(m.elapsed, label::CACHE, PointInTime::Pending);
333            }
334        })
335    }
336
337    fn _read_slot_latest_cache(&self, address: Address, index: SlotIndex) -> Option<Slot> {
338        timed(|| self.cache.get_slot_latest(address, index)).with(|m| {
339            if m.result.is_some() {
340                tracing::debug!(storage = %label::CACHE, %address, %index, "slot found in cache");
341                metrics::inc_storage_read_slot(m.elapsed, label::CACHE, PointInTime::Mined);
342            }
343        })
344    }
345
346    fn _read_slot_temp(&self, address: Address, index: SlotIndex, kind: ReadKind) -> Result<Option<Slot>, StorageError> {
347        tracing::debug!(storage = %label::TEMP, %address, %index, "reading slot");
348        timed(|| self.temp.read_slot(address, index, kind)).with(|m| {
349            if m.result.as_ref().is_ok_and(|opt| opt.is_some()) {
350                metrics::inc_storage_read_slot(m.elapsed, label::TEMP, PointInTime::Pending);
351            }
352            if let Err(ref e) = m.result {
353                tracing::error!(reason = ?e, "failed to read slot from temporary storage");
354            }
355        })
356    }
357
358    fn _read_slot_perm(&self, address: Address, index: SlotIndex, point_in_time: PointInTime) -> Result<Slot, StorageError> {
359        tracing::debug!(storage = %label::PERM, %address, %index, %point_in_time, "reading slot");
360        let slot = timed(|| self.perm.read_slot(address, index, point_in_time)).with(|m| {
361            if m.result.as_ref().is_ok_and(|opt| opt.is_some()) {
362                metrics::inc_storage_read_slot(m.elapsed, label::PERM, point_in_time);
363            }
364            if let Err(ref e) = m.result {
365                tracing::error!(reason = ?e, "failed to read slot from permanent storage");
366            }
367        })?;
368        Ok(match slot {
369            Some(slot) => {
370                tracing::debug!(storage = %label::PERM, %address, %index, value = %slot.value, "slot found in permanent storage");
371                slot
372            }
373            None => {
374                tracing::debug!(storage = %label::PERM, %address, %index, "slot not found, assuming default value");
375                Slot::new_empty(index)
376            }
377        })
378    }
379
380    pub fn read_slot(&self, address: Address, index: SlotIndex, mut point_in_time: PointInTime, kind: ReadKind) -> Result<Slot, StorageError> {
381        #[cfg(feature = "tracing")]
382        let _span = tracing::debug_span!("storage::read_slot", %address, %index, %point_in_time).entered();
383
384        let (slot, found_in_perm) = 'query: {
385            if point_in_time == PointInTime::Pending {
386                // tx can read from pending cache
387                if matches!(kind, ReadKind::Transaction)
388                    && let Some(slot) = self._read_slot_pending_cache(address, index)
389                {
390                    return Ok(slot);
391                }
392
393                if let Some(slot) = self._read_slot_temp(address, index, kind)? {
394                    tracing::debug!(storage = %label::TEMP, %address, %index, value = %slot.value, "slot found in temporary storage");
395                    break 'query (slot, false);
396                }
397            }
398
399            let (is_valid, guard) = self._latest_is_valid(point_in_time, kind);
400            if is_valid {
401                if let Some(slot) = self._read_slot_latest_cache(address, index) {
402                    return Ok(slot);
403                }
404            } else if let ReadKind::Call((block_number, _)) = kind
405                && !matches!(point_in_time, PointInTime::MinedPast(_))
406            {
407                point_in_time = PointInTime::MinedPast(block_number);
408            }
409
410            // always read from perm if necessary
411            let ret = (self._read_slot_perm(address, index, point_in_time)?, true);
412            if let Some(inner) = guard {
413                RwLockReadGuard::unlock_fair(inner);
414            }
415            ret
416        };
417
418        match (point_in_time, found_in_perm) {
419            // Pending slots found in the permanent storage (or not found in any storage) are always mined already
420            (PointInTime::Pending, true) => {
421                self.cache.cache_slot_if_missing(address, slot);
422                self.cache.cache_slot_latest_if_missing(address, slot);
423            }
424            (PointInTime::Pending, false) => {
425                self.cache.cache_slot_if_missing(address, slot);
426            }
427            (PointInTime::Mined, _) => {
428                self.cache.cache_slot_latest_if_missing(address, slot);
429            }
430            _ => {}
431        }
432        Ok(slot)
433    }
434
435    // -------------------------------------------------------------------------
436    // Blocks
437    // -------------------------------------------------------------------------
438
439    pub fn save_execution(&self, tx: TransactionExecution, is_local: bool) -> Result<(), StorageError> {
440        let changes = tx.result.execution.changes.clone();
441
442        #[cfg(feature = "tracing")]
443        let _span = tracing::info_span!("storage::save_execution", tx_hash = %tx.input.hash).entered();
444        tracing::debug!(storage = %label::TEMP, tx_hash = %tx.input.hash, "saving execution");
445
446        timed(|| self.temp.save_pending_execution(tx, is_local))
447            .with(|m| {
448                metrics::inc_storage_save_execution(m.elapsed, label::TEMP, m.result.is_ok());
449                match &m.result {
450                    Err(StorageError::EvmInputMismatch { .. }) => {
451                        tracing::warn!("failed to save execution due to mismatch, will retry");
452                    }
453                    Err(e) => tracing::error!(reason = ?e, "failed to save execution"),
454                    _ => (),
455                }
456            })
457            .inspect(|_| self.cache.cache_account_and_slots_from_changes(changes))
458    }
459
460    /// Retrieves pending transactions being mined.
461    pub fn pending_transactions(&self) -> Vec<TransactionExecution> {
462        self.temp.read_pending_executions()
463    }
464
465    pub fn finish_pending_block(&self) -> Result<(PendingBlock, ExecutionChanges), StorageError> {
466        #[cfg(feature = "tracing")]
467        let _span = tracing::info_span!("storage::finish_pending_block", block_number = tracing::field::Empty).entered();
468        tracing::debug!(storage = %label::TEMP, "finishing pending block");
469
470        let result = timed(|| self.temp.finish_pending_block()).with(|m| {
471            metrics::inc_storage_finish_pending_block(m.elapsed, label::TEMP, m.result.is_ok());
472            if let Err(ref e) = m.result {
473                tracing::error!(reason = ?e, "failed to finish pending block");
474            }
475        });
476
477        if let Ok((ref block, _)) = result {
478            Span::with(|s| s.rec_str("block_number", &block.header.number));
479        }
480
481        result
482    }
483
484    pub fn save_genesis_block(&self, block: Block, accounts: Vec<Account>, changes: ExecutionChanges) -> Result<(), StorageError> {
485        let block_number = block.number();
486
487        #[cfg(feature = "tracing")]
488        let _span = tracing::info_span!("storage::save_genesis_block", block_number = %block_number).entered();
489        tracing::debug!(storage = %label::PERM, "saving genesis block");
490
491        timed(|| self.perm.save_genesis_block(block, accounts, changes)).with(|m| {
492            metrics::inc_storage_save_block(m.elapsed, label::PERM, "genesis", "genesis", m.result.is_ok());
493            if let Err(ref e) = m.result {
494                tracing::error!(reason = ?e, "failed to save genesis block");
495            }
496        })
497    }
498
499    pub fn save_block(&self, block: Block, changes: ExecutionChanges) -> Result<(), StorageError> {
500        let block_number = block.number();
501
502        #[cfg(feature = "tracing")]
503        let _span = tracing::info_span!("storage::save_block", block_number = %block.number()).entered();
504        tracing::debug!(storage = %label::PERM, block_number = %block_number, transactions_len = %block.transactions.len(), "saving block");
505
506        // check mined number
507        let mined_number = self.read_mined_block_number();
508        if not(block_number.is_zero()) && block_number != mined_number.next_block_number() {
509            tracing::error!(%block_number, %mined_number, "failed to save block because mismatch with mined block number");
510            return Err(StorageError::MinedNumberConflict {
511                new: block_number,
512                mined: mined_number,
513            });
514        }
515
516        // check pending number
517        let pending_header = self.read_pending_block_header();
518        if block_number >= pending_header.0.number {
519            tracing::error!(%block_number, pending_number = %pending_header.0.number, "failed to save block because mismatch with pending block number");
520            return Err(StorageError::PendingNumberConflict {
521                new: block_number,
522                pending: pending_header.0.number,
523            });
524        }
525
526        // check mined block
527        let existing_block = self.read_block(BlockFilter::Number(block_number))?;
528        if existing_block.is_some() {
529            tracing::error!(%block_number, %mined_number, "failed to save block because block with the same number already exists in the permanent storage");
530            return Err(StorageError::BlockConflict { number: block_number });
531        }
532
533        // save block
534        let (label_size_by_tx, label_size_by_gas) = (block.label_size_by_transactions(), block.label_size_by_gas());
535        timed(|| {
536            let guard = self.transient_state_lock.write();
537            self.perm.save_block(block, changes.clone())?;
538            self.cache.cache_account_and_slots_latest_from_changes(changes);
539            drop(guard);
540            Ok(())
541        })
542        .with(|m| {
543            metrics::inc_storage_save_block(m.elapsed, label::PERM, label_size_by_tx, label_size_by_gas, m.result.is_ok());
544            if let Err(ref e) = m.result {
545                tracing::error!(reason = ?e, %block_number, "failed to save block");
546            }
547        })?;
548
549        self.set_mined_block_number(block_number);
550
551        Ok(())
552    }
553
554    pub fn read_block(&self, filter: BlockFilter) -> Result<Option<Block>, StorageError> {
555        #[cfg(feature = "tracing")]
556        let _span = tracing::info_span!("storage::read_block", %filter).entered();
557        tracing::debug!(storage = %label::PERM, ?filter, "reading block");
558
559        timed(|| self.perm.read_block(filter)).with(|m| {
560            metrics::inc_storage_read_block(m.elapsed, label::PERM, m.result.is_ok());
561            if let Err(ref e) = m.result {
562                tracing::error!(reason = ?e, "failed to read block");
563            }
564        })
565    }
566
567    pub fn read_block_with_changes(&self, filter: BlockFilter) -> Result<Option<(Block, ExecutionChanges)>, StorageError> {
568        #[cfg(feature = "tracing")]
569        let _span = tracing::info_span!("storage::read_block_with_changes", %filter).entered();
570        tracing::debug!(storage = %label::PERM, ?filter, "reading block with changes");
571
572        timed(|| self.perm.read_block_with_changes(filter)).with(|m| {
573            metrics::inc_storage_read_block_with_changes(m.elapsed, label::PERM, m.result.is_ok());
574            if let Err(ref e) = m.result {
575                tracing::error!(reason = ?e, "failed to read block with changes");
576            }
577        })
578    }
579
580    pub fn read_transaction(&self, tx_hash: Hash) -> Result<Option<TransactionStage>, StorageError> {
581        #[cfg(feature = "tracing")]
582        let _span = tracing::info_span!("storage::read_transaction", %tx_hash).entered();
583
584        // read from temp
585        tracing::debug!(storage = %label::TEMP, %tx_hash, "reading transaction");
586        let temp_tx = timed(|| self.temp.read_pending_execution(tx_hash)).with(|m| {
587            metrics::inc_storage_read_transaction(m.elapsed, label::TEMP, m.result.is_ok());
588            if let Err(ref e) = m.result {
589                tracing::error!(reason = ?e, "failed to read transaction from temporary storage");
590            }
591        })?;
592        if let Some(tx_temp) = temp_tx {
593            return Ok(Some(TransactionStage::new_executed(tx_temp)));
594        }
595
596        // read from perm
597        tracing::debug!(storage = %label::PERM, %tx_hash, "reading transaction");
598        let perm_tx = timed(|| self.perm.read_transaction(tx_hash)).with(|m| {
599            metrics::inc_storage_read_transaction(m.elapsed, label::PERM, m.result.is_ok());
600            if let Err(ref e) = m.result {
601                tracing::error!(reason = ?e, "failed to read transaction from permanent storage");
602            }
603        })?;
604        match perm_tx {
605            Some(tx) => Ok(Some(TransactionStage::new_mined(tx))),
606            None => Ok(None),
607        }
608    }
609
610    pub fn read_logs(&self, filter: &LogFilter) -> Result<Vec<LogMined>, StorageError> {
611        #[cfg(feature = "tracing")]
612        let _span = tracing::info_span!("storage::read_logs", ?filter).entered();
613        tracing::debug!(storage = %label::PERM, ?filter, "reading logs");
614
615        timed(|| self.perm.read_logs(filter)).with(|m| {
616            metrics::inc_storage_read_logs(m.elapsed, label::PERM, m.result.is_ok());
617            if let Err(ref e) = m.result {
618                tracing::error!(reason = ?e, "failed to read logs");
619            }
620        })
621    }
622
623    // -------------------------------------------------------------------------
624    // Direct state manipulation (for testing)
625    // -------------------------------------------------------------------------
626
627    #[cfg(feature = "dev")]
628    pub fn set_storage_at(&self, address: Address, index: SlotIndex, value: SlotValue) -> Result<(), StorageError> {
629        // Create a slot with the given index and value
630        let slot = Slot::new(index, value);
631        self.cache.clear();
632
633        // Update permanent storage
634        self.perm.save_slot(address, slot)?;
635
636        // Update temporary storage
637        self.temp.save_slot(address, slot)?;
638
639        Ok(())
640    }
641
642    #[cfg(feature = "dev")]
643    pub fn set_nonce(&self, address: Address, nonce: Nonce) -> Result<(), StorageError> {
644        self.cache.clear();
645
646        // Update permanent storage
647        self.perm.save_account_nonce(address, nonce)?;
648
649        // Update temporary storage
650        self.temp.save_account_nonce(address, nonce)?;
651
652        Ok(())
653    }
654
655    #[cfg(feature = "dev")]
656    pub fn set_balance(&self, address: Address, balance: Wei) -> Result<(), StorageError> {
657        self.cache.clear();
658
659        // Update permanent storage
660        self.perm.save_account_balance(address, balance)?;
661
662        // Update temporary storage
663        self.temp.save_account_balance(address, balance)?;
664
665        Ok(())
666    }
667
668    #[cfg(feature = "dev")]
669    pub fn set_code(&self, address: Address, code: Bytes) -> Result<(), StorageError> {
670        self.cache.clear();
671        // Update permanent storage
672        self.perm.save_account_code(address, code.clone())?;
673
674        // Update temporary storage
675        self.temp.save_account_code(address, code)?;
676
677        Ok(())
678    }
679
680    // -------------------------------------------------------------------------
681    // General state
682    // -------------------------------------------------------------------------
683
684    #[cfg(feature = "dev")]
685    /// Resets the storage to the genesis state.
686    /// If a genesis.json file is available, it will be used.
687    /// Otherwise, it will use the default genesis configuration.
688    pub fn reset_to_genesis(&self) -> Result<(), StorageError> {
689        tracing::info!("resetting storage to genesis state");
690
691        self.cache.clear();
692
693        #[cfg(feature = "tracing")]
694        let _span = tracing::info_span!("storage::reset").entered();
695
696        // reset perm
697        tracing::debug!(storage = %label::PERM, "resetting permanent storage");
698        timed(|| self.perm.reset()).with(|m| {
699            metrics::inc_storage_reset(m.elapsed, label::PERM, m.result.is_ok());
700            if let Err(ref e) = m.result {
701                tracing::error!(reason = ?e, "failed to reset permanent storage");
702            }
703        })?;
704
705        // reset temp
706        tracing::debug!(storage = %label::TEMP, "reseting temporary storage");
707        timed(|| self.temp.reset()).with(|m| {
708            metrics::inc_storage_reset(m.elapsed, label::TEMP, m.result.is_ok());
709            if let Err(ref e) = m.result {
710                tracing::error!(reason = ?e, "failed to reset temporary storage");
711            }
712        })?;
713
714        // Try to load genesis block from the genesis file or use default
715        let genesis_block = if let Some(genesis_path) = &self.perm_config.genesis_file.genesis_path {
716            if std::path::Path::new(genesis_path).exists() {
717                match GenesisConfig::load_from_file(genesis_path) {
718                    Ok(genesis_config) => match genesis_config.to_genesis_block() {
719                        Ok(block) => {
720                            tracing::info!("using genesis block from file: {:?}", genesis_path);
721                            block
722                        }
723                        Err(e) => {
724                            tracing::error!("failed to create genesis block from file: {:?}", e);
725                            Block::genesis()
726                        }
727                    },
728                    Err(e) => {
729                        tracing::error!("failed to load genesis file: {:?}", e);
730                        Block::genesis()
731                    }
732                }
733            } else {
734                tracing::error!("genesis file not found at: {:?}", genesis_path);
735                Block::genesis()
736            }
737        } else {
738            tracing::info!("using default genesis block");
739            Block::genesis()
740        };
741        // Try to load genesis.json from the path specified in GenesisFileConfig
742        // or use default genesis configuration
743        let (genesis_accounts, genesis_slots) = if let Some(genesis_path) = &self.perm_config.genesis_file.genesis_path {
744            if std::path::Path::new(genesis_path).exists() {
745                tracing::info!("found genesis file at: {:?}", genesis_path);
746                match GenesisConfig::load_from_file(genesis_path) {
747                    Ok(genesis) => match genesis.to_stratus_accounts_and_slots() {
748                        Ok((accounts, slots)) => {
749                            tracing::info!("loaded {} accounts from genesis.json", accounts.len());
750                            if !slots.is_empty() {
751                                tracing::info!("loaded {} storage slots from genesis.json", slots.len());
752                            }
753                            (accounts, slots)
754                        }
755                        Err(e) => {
756                            tracing::error!("failed to convert genesis accounts: {:?}", e);
757                            // Fallback to test accounts
758                            (test_accounts(), vec![])
759                        }
760                    },
761                    Err(e) => {
762                        tracing::error!("failed to load genesis file: {:?}", e);
763                        // Fallback to test accounts
764                        (test_accounts(), vec![])
765                    }
766                }
767            } else {
768                tracing::error!("genesis file not found at: {:?}", genesis_path);
769                // Fallback to test accounts
770                (test_accounts(), vec![])
771            }
772        } else {
773            // No genesis path specified, use default genesis configuration
774            match GenesisConfig::default().to_stratus_accounts_and_slots() {
775                Ok((accounts, slots)) => {
776                    tracing::info!("using default genesis configuration with {} accounts", accounts.len());
777                    (accounts, slots)
778                }
779                Err(e) => {
780                    tracing::error!("failed to convert default genesis accounts: {:?}", e);
781                    // Fallback to test accounts
782                    (test_accounts(), vec![])
783                }
784            }
785        };
786        // Save the genesis block
787        self.save_block(genesis_block, ExecutionChanges::default())?;
788
789        // accounts
790        self.save_accounts(genesis_accounts)?;
791
792        // Save slots if any
793        if !genesis_slots.is_empty() {
794            tracing::info!("saving {} storage slots from genesis", genesis_slots.len());
795            for (address, slot) in genesis_slots {
796                self.perm.save_slot(address, slot)?;
797            }
798        }
799
800        // block number
801        self.set_mined_block_number(BlockNumber::ZERO);
802
803        Ok(())
804    }
805
806    // -------------------------------------------------------------------------
807    // Utils
808    // -------------------------------------------------------------------------
809
810    /// Translates a block filter to a specific storage point-in-time indicator.
811    pub fn translate_to_point_in_time(&self, block_filter: BlockFilter) -> Result<PointInTime, StorageError> {
812        match block_filter {
813            BlockFilter::Pending => Ok(PointInTime::Pending),
814            BlockFilter::Latest => Ok(PointInTime::Mined),
815            BlockFilter::Earliest => Ok(PointInTime::MinedPast(BlockNumber::ZERO)),
816            BlockFilter::Number(number) => Ok(PointInTime::MinedPast(number)),
817            BlockFilter::Hash(_) => match self.read_block(block_filter)? {
818                Some(block) => Ok(PointInTime::MinedPast(block.header.number)),
819                None => Err(StorageError::BlockNotFound { filter: block_filter }),
820            },
821        }
822    }
823}