Skip to main content

reth_provider/providers/database/
provider.rs

1use super::SaveBlocksInput;
2use crate::{
3    changesets_utils::StorageRevertsIter,
4    providers::{
5        database::{chain::ChainStorage, metrics, DatabaseProviderMetrics},
6        rocksdb::{
7            OwnedRocksReadSnapshot, PendingRocksDBBatches, RocksDBProvider, RocksDBWriteCtx,
8            RocksReadSnapshot,
9        },
10        static_file::{StaticFileWriteCtx, StaticFileWriter},
11        NodeTypesForProvider, StaticFileProvider,
12    },
13    to_range,
14    traits::{
15        AccountExtReader, BlockSource, ChangeSetReader, ReceiptProvider, StageCheckpointWriter,
16    },
17    AccountReader, BlockBodyWriter, BlockExecutionWriter, BlockHashReader, BlockNumReader,
18    BlockReader, BlockWriter, BundleStateInit, ChainStateBlockReader, ChainStateBlockWriter,
19    DBProvider, DbTxProvider, EitherReader, EitherWriter, EitherWriterDestination, HashingWriter,
20    HeaderProvider, HeaderSyncGapProvider, HistoryWriter, LatestStateProviderRef,
21    OriginalValuesKnown, PersistenceFrontiers, ProviderError, PruneCheckpointReader,
22    PruneCheckpointWriter, RawRocksDBBatch, RevertsInit, RocksBatchArg, RocksDBProviderFactory,
23    StageCheckpointReader, StateWriter, StaticFileProviderFactory, StatsReader, StorageReader,
24    StorageTrieWriter, TransactionVariant, TransactionsProvider, TransactionsProviderExt,
25    TrieWriter,
26};
27use alloy_consensus::{
28    transaction::{SignerRecoverable, TransactionMeta, TxHashRef},
29    BlockHeader, TxReceipt,
30};
31use alloy_eips::BlockHashOrNumber;
32use alloy_primitives::{
33    keccak256,
34    map::{hash_map, AddressSet, B256Map, HashMap},
35    Address, BlockHash, BlockNumber, StorageKey, StorageValue, TxHash, TxNumber, B256,
36};
37use itertools::Itertools;
38use parking_lot::RwLock;
39use rayon::slice::ParallelSliceMut;
40use reth_chain_state::ExecutedBlock;
41use reth_chainspec::{ChainInfo, ChainSpecProvider, EthChainSpec};
42use reth_db_api::{
43    cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW},
44    database::{Database, ReaderTxnTracker},
45    models::{
46        sharded_key, storage_sharded_key::StorageShardedKey, AccountBeforeTx, BlockNumberAddress,
47        BlockNumberAddressRange, ShardedKey, StorageBeforeTx, StorageSettings,
48        StoredBlockBodyIndices,
49    },
50    table::Table,
51    tables,
52    transaction::{DbTx, DbTxMut},
53    BlockNumberList,
54};
55use reth_execution_types::{
56    BlockExecutionOutput, BlockExecutionResult, Chain, ExecutionOutcome,
57    RecoveredBlockAndExecutionOutput,
58};
59use reth_node_types::{BlockTy, BodyTy, HeaderTy, NodeTypes, ReceiptTy, TxTy};
60use reth_primitives_traits::{
61    Account, Block as _, BlockBody as _, Bytecode, FastInstant as Instant, RecoveredBlock,
62    SealedHeader, StorageEntry,
63};
64use reth_prune_types::{
65    PruneCheckpoint, PruneMode, PruneModes, PruneSegment, MINIMUM_UNWIND_SAFE_DISTANCE,
66};
67use reth_stages_types::{FinishCheckpoint, StageCheckpoint, StageId};
68use reth_static_file_types::StaticFileSegment;
69use reth_storage_api::{
70    BlockBodyIndicesProvider, BlockBodyReader, HistoryInfo, HistoryReader, MetadataProvider,
71    MetadataWriter, NodePrimitivesProvider, StateProvider, StateWriteConfig,
72    StorageChangeSetReader, StoragePath, StorageSettingsCache, WriteStateInput,
73};
74use reth_storage_errors::provider::{ProviderResult, StaticFileWriterError};
75use reth_storage_overlay::OverlayManager;
76use reth_trie::{
77    bundle_state::{PlainStateReverts, PlainStorageChangeset, PlainStorageRevert, StateChangeset},
78    updates::{StorageTrieUpdatesSorted, TrieUpdatesSorted},
79    HashedPostStateSorted, SortedTrieData,
80};
81use reth_trie_db::{DatabaseStorageTrieCursor, TrieTableAdapter};
82use smallvec::SmallVec;
83use std::{
84    cmp::Ordering,
85    collections::{BTreeMap, BTreeSet},
86    fmt::Debug,
87    ops::{Deref, DerefMut, Range, RangeBounds, RangeInclusive},
88    path::PathBuf,
89    sync::{Arc, OnceLock},
90};
91use tracing::{debug, instrument, trace};
92
93/// Determines the commit order for database operations.
94#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
95pub enum CommitOrder {
96    /// Normal commit order: static files first, then `RocksDB`, then MDBX.
97    #[default]
98    Normal,
99    /// Unwind commit order: MDBX first, then `RocksDB`, then static files.
100    /// Used for unwind operations to allow recovery by truncating static files on restart.
101    Unwind,
102}
103
104impl CommitOrder {
105    /// Returns true if this is unwind commit order.
106    pub const fn is_unwind(&self) -> bool {
107        matches!(self, Self::Unwind)
108    }
109}
110
111/// A [`DatabaseProvider`] that holds a read-only database transaction.
112pub type DatabaseProviderRO<DB, N> = DatabaseProvider<<DB as Database>::TX, N>;
113
114/// A [`DatabaseProvider`] that holds a read-write database transaction.
115///
116/// Ideally this would be an alias type. However, there's some weird compiler error (<https://github.com/rust-lang/rust/issues/102211>), that forces us to wrap this in a struct instead.
117/// Once that issue is solved, we can probably revert back to being an alias type.
118#[derive(Debug)]
119pub struct DatabaseProviderRW<DB: Database, N: NodeTypes>(
120    pub DatabaseProvider<<DB as Database>::TXMut, N>,
121);
122
123impl<DB: Database, N: NodeTypes> Deref for DatabaseProviderRW<DB, N> {
124    type Target = DatabaseProvider<<DB as Database>::TXMut, N>;
125
126    fn deref(&self) -> &Self::Target {
127        &self.0
128    }
129}
130
131impl<DB: Database, N: NodeTypes> DerefMut for DatabaseProviderRW<DB, N> {
132    fn deref_mut(&mut self) -> &mut Self::Target {
133        &mut self.0
134    }
135}
136
137impl<DB: Database, N: NodeTypes> AsRef<DatabaseProvider<<DB as Database>::TXMut, N>>
138    for DatabaseProviderRW<DB, N>
139{
140    fn as_ref(&self) -> &DatabaseProvider<<DB as Database>::TXMut, N> {
141        &self.0
142    }
143}
144
145impl<DB: Database, N: NodeTypes> DatabaseProviderRW<DB, N> {
146    /// Commit database transaction and static file if it exists.
147    pub fn commit(self) -> ProviderResult<()> {
148        self.0.commit()
149    }
150
151    /// Consume `DbTx` or `DbTxMut`.
152    pub fn into_tx(self) -> <DB as Database>::TXMut {
153        self.0.into_tx()
154    }
155
156    /// Override the minimum pruning distance for testing purposes.
157    #[cfg(any(test, feature = "test-utils"))]
158    pub const fn with_minimum_pruning_distance(mut self, distance: u64) -> Self {
159        self.0.minimum_pruning_distance = distance;
160        self
161    }
162}
163
164impl<DB: Database, N: NodeTypes> From<DatabaseProviderRW<DB, N>>
165    for DatabaseProvider<<DB as Database>::TXMut, N>
166{
167    fn from(provider: DatabaseProviderRW<DB, N>) -> Self {
168        provider.0
169    }
170}
171
172/// Mode for [`DatabaseProvider::save_blocks_inner`].
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174enum SaveBlocksMode {
175    /// Full mode: write block structure + receipts + state + trie.
176    /// Used by engine/production code.
177    Full,
178    /// Blocks only: write block structure (headers, txs, senders, indices).
179    /// Receipts/state/trie are skipped - they may come later via separate calls.
180    /// Used by `insert_block`.
181    BlocksOnly,
182}
183
184impl SaveBlocksMode {
185    /// Returns `true` if this is [`SaveBlocksMode::Full`].
186    const fn with_state(self) -> bool {
187        matches!(self, Self::Full)
188    }
189}
190
191/// A provider struct that fetches data from the database.
192/// Wrapper around [`DbTx`] and [`DbTxMut`]. Example: [`HeaderProvider`] [`BlockHashReader`]
193pub struct DatabaseProvider<TX, N: NodeTypes> {
194    /// Database transaction.
195    tx: TX,
196    /// Chain spec
197    chain_spec: Arc<N::ChainSpec>,
198    /// Static File provider
199    static_file_provider: StaticFileProvider<N::Primitives>,
200    /// Pruning configuration
201    prune_modes: PruneModes,
202    /// Node storage handler.
203    storage: Arc<N::Storage>,
204    /// Storage configuration settings for this node
205    storage_settings: Arc<RwLock<StorageSettings>>,
206    /// `RocksDB` provider
207    rocksdb_provider: RocksDBProvider,
208    /// `RocksDB` snapshot shared by all history lookups made through this provider.
209    ///
210    /// It lives as long as the provider, next to the MDBX read transaction, and pins the
211    /// `RocksDB` versions its cached iterators were created on for that long; see
212    /// [`Self::history_rocksdb_snapshot`].
213    rocksdb_history_snapshot: OnceLock<Option<OwnedRocksReadSnapshot>>,
214    /// Manager for state trie overlays and cached changesets.
215    overlay_manager: OverlayManager<N::Primitives>,
216    /// Task runtime for spawning parallel I/O work.
217    runtime: reth_tasks::Runtime,
218    /// Path to the database directory.
219    db_path: PathBuf,
220    /// Pending `RocksDB` batches to be committed at provider commit time.
221    pending_rocksdb_batches: PendingRocksDBBatches,
222    /// Commit order for database operations.
223    commit_order: CommitOrder,
224    /// Minimum distance from tip required for pruning
225    minimum_pruning_distance: u64,
226    /// Database provider metrics
227    metrics: Arc<DatabaseProviderMetrics>,
228    /// Database handle used to inspect active MDBX readers during unwind commits.
229    reader_txn_tracker: Option<Arc<dyn ReaderTxnTracker>>,
230}
231
232impl<TX: Debug, N: NodeTypes> Debug for DatabaseProvider<TX, N> {
233    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234        let mut s = f.debug_struct("DatabaseProvider");
235        s.field("tx", &self.tx)
236            .field("chain_spec", &self.chain_spec)
237            .field("static_file_provider", &self.static_file_provider)
238            .field("prune_modes", &self.prune_modes)
239            .field("storage", &self.storage)
240            .field("storage_settings", &self.storage_settings)
241            .field("rocksdb_provider", &self.rocksdb_provider)
242            .field("overlay_manager", &self.overlay_manager)
243            .field("runtime", &self.runtime)
244            .field("pending_rocksdb_batches", &"<pending batches>")
245            .field("commit_order", &self.commit_order)
246            .field("minimum_pruning_distance", &self.minimum_pruning_distance)
247            .field("reader_txn_tracker", &"<reader txn tracker>")
248            .finish()
249    }
250}
251
252impl<TX, N: NodeTypes> DatabaseProvider<TX, N> {
253    /// Returns reference to prune modes.
254    pub const fn prune_modes_ref(&self) -> &PruneModes {
255        &self.prune_modes
256    }
257
258    /// Sets the minimum pruning distance.
259    pub const fn with_minimum_pruning_distance(mut self, distance: u64) -> Self {
260        self.minimum_pruning_distance = distance;
261        self
262    }
263
264    /// Attaches reader tracking so unwind commits can wait on active readers.
265    pub(crate) fn with_reader_txn_tracker<T>(mut self, reader_txn_tracker: T) -> Self
266    where
267        T: ReaderTxnTracker + 'static,
268    {
269        self.reader_txn_tracker = Some(Arc::new(reader_txn_tracker));
270        self
271    }
272}
273
274impl<TX: DbTx + 'static, N: NodeTypes> DatabaseProvider<TX, N> {
275    /// Commits unwind writes in MDBX -> `RocksDB` -> static-file order.
276    ///
277    /// This keeps MDBX as the first durable step so an interrupted unwind can be recovered by
278    /// truncating static files from checkpoints on the next startup.
279    ///
280    /// This waits after the MDBX commit so readers holding older MDBX-visible views cannot overlap
281    /// later cross-store unwind steps.
282    ///
283    /// Historical `storage_v2` reads ignore `RocksDB` history entries above their MDBX-visible tip,
284    /// so no additional post-`RocksDB` wait is needed before static-file commit.
285    fn commit_unwind(self) -> ProviderResult<()> {
286        let storage_v2 = self.cached_storage_settings().storage_v2;
287        let reader_txn_tracker = self.reader_txn_tracker.clone();
288        self.tx.commit()?;
289
290        if let Some(reader_txn_tracker) = reader_txn_tracker.as_ref() {
291            reader_txn_tracker.wait_for_pre_commit_readers();
292        }
293
294        if storage_v2 {
295            let batches = std::mem::take(&mut *self.pending_rocksdb_batches.lock());
296            for batch in batches {
297                self.rocksdb_provider.commit_batch(batch)?;
298            }
299        }
300
301        self.static_file_provider.commit()?;
302        Ok(())
303    }
304
305    /// State provider for latest state
306    pub fn latest<'a>(&'a self) -> Box<dyn StateProvider + 'a> {
307        trace!(target: "providers::db", "Returning latest state provider");
308        Box::new(LatestStateProviderRef::new(self))
309    }
310
311    #[cfg(feature = "test-utils")]
312    /// Sets the prune modes for provider.
313    pub fn set_prune_modes(&mut self, prune_modes: PruneModes) {
314        self.prune_modes = prune_modes;
315    }
316}
317
318impl<TX, N: NodeTypes> NodePrimitivesProvider for DatabaseProvider<TX, N> {
319    type Primitives = N::Primitives;
320}
321
322impl<TX, N: NodeTypes> StaticFileProviderFactory for DatabaseProvider<TX, N> {
323    /// Returns a static file provider
324    fn static_file_provider(&self) -> StaticFileProvider<Self::Primitives> {
325        self.static_file_provider.clone()
326    }
327
328    fn get_static_file_writer(
329        &self,
330        block: BlockNumber,
331        segment: StaticFileSegment,
332    ) -> ProviderResult<crate::providers::StaticFileProviderRWRefMut<'_, Self::Primitives>> {
333        self.static_file_provider.get_writer(block, segment)
334    }
335}
336
337impl<TX, N: NodeTypes> RocksDBProviderFactory for DatabaseProvider<TX, N> {
338    /// Returns the `RocksDB` provider.
339    fn rocksdb_provider(&self) -> RocksDBProvider {
340        self.rocksdb_provider.clone()
341    }
342
343    fn set_pending_rocksdb_batch(&self, batch: rocksdb::WriteBatchWithTransaction<true>) {
344        self.pending_rocksdb_batches.lock().push(batch);
345    }
346
347    fn commit_pending_rocksdb_batches(&self) -> ProviderResult<()> {
348        let batches = std::mem::take(&mut *self.pending_rocksdb_batches.lock());
349        for batch in batches {
350            self.rocksdb_provider.commit_batch(batch)?;
351        }
352        Ok(())
353    }
354}
355
356impl<TX: Debug + Send, N: NodeTypes<ChainSpec: EthChainSpec + 'static>> ChainSpecProvider
357    for DatabaseProvider<TX, N>
358{
359    type ChainSpec = N::ChainSpec;
360
361    fn chain_spec(&self) -> Arc<Self::ChainSpec> {
362        self.chain_spec.clone()
363    }
364}
365
366impl<TX: DbTxMut, N: NodeTypes> DatabaseProvider<TX, N> {
367    /// Creates a provider with an inner read-write transaction.
368    #[expect(clippy::too_many_arguments)]
369    fn new_rw_inner(
370        tx: TX,
371        chain_spec: Arc<N::ChainSpec>,
372        static_file_provider: StaticFileProvider<N::Primitives>,
373        prune_modes: PruneModes,
374        storage: Arc<N::Storage>,
375        storage_settings: Arc<RwLock<StorageSettings>>,
376        rocksdb_provider: RocksDBProvider,
377        overlay_manager: OverlayManager<N::Primitives>,
378        runtime: reth_tasks::Runtime,
379        db_path: PathBuf,
380        commit_order: CommitOrder,
381        metrics: Arc<DatabaseProviderMetrics>,
382    ) -> Self {
383        Self {
384            tx,
385            chain_spec,
386            static_file_provider,
387            prune_modes,
388            storage,
389            storage_settings,
390            rocksdb_provider,
391            overlay_manager,
392            runtime,
393            db_path,
394            rocksdb_history_snapshot: OnceLock::new(),
395            pending_rocksdb_batches: Default::default(),
396            commit_order,
397            minimum_pruning_distance: MINIMUM_UNWIND_SAFE_DISTANCE,
398            metrics,
399            reader_txn_tracker: None,
400        }
401    }
402
403    /// Creates a provider with an inner read-write transaction using normal commit order.
404    #[expect(clippy::too_many_arguments)]
405    pub fn new_rw(
406        tx: TX,
407        chain_spec: Arc<N::ChainSpec>,
408        static_file_provider: StaticFileProvider<N::Primitives>,
409        prune_modes: PruneModes,
410        storage: Arc<N::Storage>,
411        storage_settings: Arc<RwLock<StorageSettings>>,
412        rocksdb_provider: RocksDBProvider,
413        overlay_manager: OverlayManager<N::Primitives>,
414        runtime: reth_tasks::Runtime,
415        db_path: PathBuf,
416        metrics: Arc<DatabaseProviderMetrics>,
417    ) -> Self {
418        Self::new_rw_inner(
419            tx,
420            chain_spec,
421            static_file_provider,
422            prune_modes,
423            storage,
424            storage_settings,
425            rocksdb_provider,
426            overlay_manager,
427            runtime,
428            db_path,
429            CommitOrder::Normal,
430            metrics,
431        )
432    }
433
434    /// Creates a provider with an inner read-write transaction using unwind commit order.
435    #[expect(clippy::too_many_arguments)]
436    pub fn new_unwind_rw(
437        tx: TX,
438        chain_spec: Arc<N::ChainSpec>,
439        static_file_provider: StaticFileProvider<N::Primitives>,
440        prune_modes: PruneModes,
441        storage: Arc<N::Storage>,
442        storage_settings: Arc<RwLock<StorageSettings>>,
443        rocksdb_provider: RocksDBProvider,
444        overlay_manager: OverlayManager<N::Primitives>,
445        runtime: reth_tasks::Runtime,
446        db_path: PathBuf,
447        metrics: Arc<DatabaseProviderMetrics>,
448    ) -> Self {
449        Self::new_rw_inner(
450            tx,
451            chain_spec,
452            static_file_provider,
453            prune_modes,
454            storage,
455            storage_settings,
456            rocksdb_provider,
457            overlay_manager,
458            runtime,
459            db_path,
460            CommitOrder::Unwind,
461            metrics,
462        )
463    }
464}
465
466impl<TX, N: NodeTypes> AsRef<Self> for DatabaseProvider<TX, N> {
467    fn as_ref(&self) -> &Self {
468        self
469    }
470}
471
472impl<TX: DbTx + DbTxMut + 'static, N: NodeTypesForProvider> DatabaseProvider<TX, N> {
473    /// Executes a closure with a `RocksDB` batch, automatically registering it for commit.
474    ///
475    /// This helper encapsulates all the cfg-gated `RocksDB` batch handling.
476    pub fn with_rocksdb_batch<F, R>(&self, f: F) -> ProviderResult<R>
477    where
478        F: FnOnce(RocksBatchArg<'_>) -> ProviderResult<(R, Option<RawRocksDBBatch>)>,
479    {
480        let rocksdb = self.rocksdb_provider();
481        let rocksdb_batch = rocksdb.batch();
482
483        let (result, raw_batch) = f(rocksdb_batch)?;
484
485        if let Some(batch) = raw_batch {
486            self.set_pending_rocksdb_batch(batch);
487        }
488        let _ = raw_batch; // silence unused warning when rocksdb feature is disabled
489
490        Ok(result)
491    }
492
493    /// Creates the context for static file writes.
494    fn static_file_write_ctx(
495        &self,
496        save_mode: SaveBlocksMode,
497        first_block: BlockNumber,
498        last_block: BlockNumber,
499    ) -> ProviderResult<StaticFileWriteCtx> {
500        let tip = self.last_block_number()?.max(last_block);
501        Ok(StaticFileWriteCtx {
502            write_senders: EitherWriterDestination::senders(self).is_static_file() &&
503                self.prune_modes.sender_recovery.is_none_or(|m| !m.is_full()),
504            write_receipts: save_mode.with_state() &&
505                EitherWriter::receipts_destination(self).is_static_file(),
506            write_account_changesets: save_mode.with_state() &&
507                EitherWriterDestination::account_changesets(self).is_static_file(),
508            write_storage_changesets: save_mode.with_state() &&
509                EitherWriterDestination::storage_changesets(self).is_static_file(),
510            tip,
511            receipts_prune_mode: self.prune_modes.receipts,
512            // Receipts are prunable if no receipts exist in SF yet and within pruning distance
513            receipts_prunable: self
514                .static_file_provider
515                .get_highest_static_file_tx(StaticFileSegment::Receipts)
516                .is_none() &&
517                PruneMode::Distance(self.minimum_pruning_distance)
518                    .should_prune(first_block, tip),
519        })
520    }
521
522    /// Creates the context for `RocksDB` writes.
523    fn rocksdb_write_ctx(&self, first_block: BlockNumber) -> RocksDBWriteCtx {
524        RocksDBWriteCtx {
525            first_block_number: first_block,
526            prune_tx_lookup: self.prune_modes.transaction_lookup,
527            storage_settings: self.cached_storage_settings(),
528            pending_batches: self.pending_rocksdb_batches.clone(),
529        }
530    }
531
532    /// Advances the independent persistence frontiers described by [`SaveBlocksInput`].
533    ///
534    /// Ordinary block data and hashed-state/trie updates advance independently according to the
535    /// ranges derived by the input.
536    #[instrument(level = "debug", target = "providers::db", skip_all, fields(block_count = input.persist_rest_blocks().len()))]
537    pub fn save_blocks(&self, input: &SaveBlocksInput<N::Primitives>) -> ProviderResult<()> {
538        let (db_tip, partial_state_trie) = self
539            .get_stage_checkpoint(StageId::Finish)?
540            .map(|checkpoint| {
541                let partial_state_trie = checkpoint
542                    .finish_stage_checkpoint()
543                    .and_then(|finish| finish.partial_state_trie())
544                    .unwrap_or(checkpoint.block_number);
545                (checkpoint.block_number, partial_state_trie)
546            })
547            .unwrap_or_default();
548
549        if db_tip != input.prev_db_tip() || partial_state_trie != input.prev_partial_state_trie() {
550            return Err(ProviderError::other(std::io::Error::other(format!(
551                "persistence frontiers do not match Finish checkpoint: expected database/state-trie tips #{}/{}, got #{}/{}",
552                input.prev_db_tip(),
553                input.prev_partial_state_trie(),
554                db_tip,
555                partial_state_trie,
556            ))))
557        }
558
559        self.save_blocks_inner(
560            input.persist_rest_blocks(),
561            input.state_trie_blocks(),
562            input.state_trie_masking_blocks(),
563            (input.new_partial_state_trie() < input.new_db_tip())
564                .then_some(input.new_partial_state_trie()),
565            SaveBlocksMode::Full,
566        )
567    }
568
569    fn save_blocks_inner(
570        &self,
571        blocks: &[ExecutedBlock<N::Primitives>],
572        state_trie_blocks: &[ExecutedBlock<N::Primitives>],
573        state_trie_masking_blocks: &[ExecutedBlock<N::Primitives>],
574        partial_state_trie: Option<BlockNumber>,
575        save_mode: SaveBlocksMode,
576    ) -> ProviderResult<()> {
577        let total_start = Instant::now();
578        let block_count = blocks.len() as u64;
579        // With no new block data, the masking suffix still ends at the database tip. If the
580        // masking suffix is empty, the state/trie range ends there instead.
581        let last_block_number = blocks
582            .last()
583            .or_else(|| state_trie_masking_blocks.last())
584            .or_else(|| state_trie_blocks.last())
585            .expect("at least one persistence range must be non-empty")
586            .recovered_block()
587            .number();
588        let first_number = blocks.first().map(|block| block.recovered_block().number());
589
590        debug!(target: "providers::db", block_count, "Writing blocks and execution data to storage");
591
592        // Compute tx_nums upfront (both threads need these)
593        let mut tx_nums: SmallVec<[TxNumber; 4]> = SmallVec::with_capacity(blocks.len());
594        if !blocks.is_empty() {
595            let first_tx_num = self
596                .tx
597                .cursor_read::<tables::TransactionBlocks>()?
598                .last()?
599                .map(|(n, _)| n + 1)
600                .unwrap_or_default();
601            let mut current = first_tx_num;
602            for block in blocks {
603                tx_nums.push(current);
604                current += block.recovered_block().body().transaction_count() as u64;
605            }
606        }
607
608        let mut timings =
609            metrics::SaveBlocksTimings { batch_size: block_count, ..Default::default() };
610
611        // avoid capturing &self.tx in scope below.
612        let sf_provider = &self.static_file_provider;
613        let rocksdb_provider = &self.rocksdb_provider;
614        let sf_ctx = first_number
615            .map(|first_number| {
616                self.static_file_write_ctx(save_mode, first_number, last_block_number)
617            })
618            .transpose()?;
619        let rocksdb_ctx = first_number.map(|first_number| self.rocksdb_write_ctx(first_number));
620        let rocksdb_enabled =
621            rocksdb_ctx.as_ref().is_some_and(|ctx| ctx.storage_settings.storage_v2);
622
623        let mut sf_result = None;
624        let mut rocksdb_result = None;
625
626        // Write to all backends in parallel.
627        let runtime = &self.runtime;
628        // Propagate tracing context into rayon-spawned threads so that static file
629        // and RocksDB write spans appear as children of save_blocks in traces.
630        let span = tracing::Span::current();
631        runtime.storage_pool().in_place_scope(|s| {
632            // SF writes
633            if sf_ctx.is_some() {
634                s.spawn(|_| {
635                    let _guard = span.enter();
636                    let start = Instant::now();
637                    let sf_ctx =
638                        sf_ctx.expect("static file context exists when blocks are persisted");
639                    sf_result = Some(
640                        sf_provider
641                            .write_blocks_data(blocks, &tx_nums, sf_ctx, runtime)
642                            .map(|()| start.elapsed()),
643                    );
644                });
645            }
646
647            // RocksDB writes
648            if rocksdb_enabled {
649                s.spawn(|_| {
650                    let _guard = span.enter();
651                    let start = Instant::now();
652                    let rocksdb_ctx =
653                        rocksdb_ctx.clone().expect("RocksDB context exists when enabled");
654                    rocksdb_result = Some(
655                        rocksdb_provider
656                            .write_blocks_data(blocks, &tx_nums, rocksdb_ctx, runtime)
657                            .map(|()| start.elapsed()),
658                    );
659                });
660            }
661
662            // MDBX writes
663            let mdbx_start = Instant::now();
664
665            // Collect all transaction hashes across all blocks, sort them, and write in batch
666            if !blocks.is_empty() &&
667                !self.cached_storage_settings().storage_v2 &&
668                self.prune_modes.transaction_lookup.is_none_or(|m| !m.is_full())
669            {
670                let start = Instant::now();
671                let total_tx_count: usize =
672                    blocks.iter().map(|b| b.recovered_block().body().transaction_count()).sum();
673                let mut all_tx_hashes = Vec::with_capacity(total_tx_count);
674                for (i, block) in blocks.iter().enumerate() {
675                    let recovered_block = block.recovered_block();
676                    for (tx_num, transaction) in
677                        (tx_nums[i]..).zip(recovered_block.body().transactions_iter())
678                    {
679                        all_tx_hashes.push((*transaction.tx_hash(), tx_num));
680                    }
681                }
682
683                // Sort by hash for optimal MDBX insertion performance
684                all_tx_hashes.sort_unstable_by_key(|(hash, _)| *hash);
685
686                // Write all transaction hash numbers in a single batch
687                self.with_rocksdb_batch(|batch| {
688                    let mut tx_hash_writer =
689                        EitherWriter::new_transaction_hash_numbers(self, batch)?;
690                    tx_hash_writer.put_transaction_hash_numbers_batch(all_tx_hashes, false)?;
691                    let raw_batch = tx_hash_writer.into_raw_rocksdb_batch();
692                    Ok(((), raw_batch))
693                })?;
694                self.metrics.record_duration(
695                    metrics::Action::InsertTransactionHashNumbers,
696                    start.elapsed(),
697                );
698            }
699
700            for (i, block) in blocks.iter().enumerate() {
701                let recovered_block = block.recovered_block();
702
703                let start = Instant::now();
704                self.insert_block_mdbx_only(recovered_block, tx_nums[i])?;
705                timings.insert_block += start.elapsed();
706
707                if save_mode.with_state() {
708                    let execution_output = block.execution_outcome();
709                    let sf_ctx =
710                        sf_ctx.expect("static file context exists when blocks are persisted");
711
712                    // Write state and changesets to the database.
713                    // Must be written after blocks because of the receipt lookup.
714                    // Skip receipts/account changesets if they're being written to static files.
715                    let start = Instant::now();
716                    self.write_state(
717                        WriteStateInput::Single {
718                            outcome: execution_output,
719                            block: recovered_block.number(),
720                        },
721                        OriginalValuesKnown::No,
722                        StateWriteConfig {
723                            write_receipts: !sf_ctx.write_receipts,
724                            write_account_changesets: !sf_ctx.write_account_changesets,
725                            write_storage_changesets: !sf_ctx.write_storage_changesets,
726                        },
727                    )?;
728                    timings.write_state += start.elapsed();
729                }
730            }
731
732            // Write all hashed state and trie updates in single batches.
733            // This reduces cursor open/close overhead from N calls to 1.
734            if save_mode.with_state() && !state_trie_blocks.is_empty() {
735                let start = Instant::now();
736                let batch = ExecutedBlock::hashed_state_refs(state_trie_blocks);
737                let mask = ExecutedBlock::hashed_state_refs(state_trie_masking_blocks);
738                let merged_hashed_state =
739                    HashedPostStateSorted::disjointed_merge_batch(&batch, &mask);
740                if !merged_hashed_state.is_empty() {
741                    self.write_hashed_state(&merged_hashed_state)?;
742                }
743                timings.write_hashed_state += start.elapsed();
744
745                let start = Instant::now();
746                let batch = ExecutedBlock::trie_updates_refs(state_trie_blocks);
747                let mask = ExecutedBlock::trie_updates_refs(state_trie_masking_blocks);
748                let merged_trie =
749                    Arc::new(TrieUpdatesSorted::disjointed_merge_batch(&batch, &mask));
750                if !merged_trie.is_empty() {
751                    self.write_trie_updates_sorted(&merged_trie)?;
752                }
753                timings.write_trie_updates += start.elapsed();
754            }
755
756            // Full mode: update history indices
757            if save_mode.with_state() &&
758                let Some(first_number) = first_number
759            {
760                let start = Instant::now();
761                self.update_history_indices(first_number..=last_block_number)?;
762                timings.update_history_indices = start.elapsed();
763            }
764
765            // Update pipeline progress
766            let start = Instant::now();
767            if !blocks.is_empty() {
768                self.update_pipeline_stages(last_block_number, false)?;
769            }
770            if save_mode.with_state() {
771                let checkpoint = match partial_state_trie {
772                    Some(partial_state_trie) => StageCheckpoint::new(last_block_number)
773                        .with_finish_stage_checkpoint(FinishCheckpoint {
774                            partial_state_trie: Some(partial_state_trie),
775                        }),
776                    None => StageCheckpoint::new(last_block_number),
777                };
778                self.save_stage_checkpoint(StageId::Finish, checkpoint)?;
779            }
780            timings.update_pipeline_stages = start.elapsed();
781
782            timings.mdbx = mdbx_start.elapsed();
783
784            Ok::<_, ProviderError>(())
785        })?;
786
787        // Collect results from spawned tasks
788        if !blocks.is_empty() {
789            timings.sf = sf_result.ok_or(StaticFileWriterError::ThreadPanic("static file"))??;
790        }
791
792        if rocksdb_enabled {
793            timings.rocksdb = rocksdb_result.ok_or_else(|| {
794                ProviderError::Database(reth_db_api::DatabaseError::Other(
795                    "RocksDB thread panicked".into(),
796                ))
797            })??;
798        }
799
800        timings.total = total_start.elapsed();
801
802        self.metrics.record_save_blocks(&timings);
803        if let Some(first_number) = first_number {
804            debug!(target: "providers::db", range = ?first_number..=last_block_number, "Appended block data");
805        }
806
807        Ok(())
808    }
809
810    /// Writes MDBX-only data for a block (indices, lookups, and senders if configured for MDBX).
811    ///
812    /// SF data (headers, transactions, senders if SF, receipts if SF) must be written separately.
813    #[instrument(level = "debug", target = "providers::db", skip_all)]
814    fn insert_block_mdbx_only(
815        &self,
816        block: &RecoveredBlock<BlockTy<N>>,
817        first_tx_num: TxNumber,
818    ) -> ProviderResult<StoredBlockBodyIndices> {
819        if self.prune_modes.sender_recovery.is_none_or(|m| !m.is_full()) &&
820            EitherWriterDestination::senders(self).is_database()
821        {
822            let start = Instant::now();
823            let tx_nums_iter = std::iter::successors(Some(first_tx_num), |n| Some(n + 1));
824            let mut cursor = self.tx.cursor_write::<tables::TransactionSenders>()?;
825            for (tx_num, sender) in tx_nums_iter.zip(block.senders_iter().copied()) {
826                cursor.append(tx_num, &sender)?;
827            }
828            self.metrics
829                .record_duration(metrics::Action::InsertTransactionSenders, start.elapsed());
830        }
831
832        let block_number = block.number();
833        let tx_count = block.body().transaction_count() as u64;
834
835        let start = Instant::now();
836        self.tx.put::<tables::HeaderNumbers>(block.hash(), block_number)?;
837        self.metrics.record_duration(metrics::Action::InsertHeaderNumbers, start.elapsed());
838
839        self.write_block_body_indices(block_number, block.body(), first_tx_num, tx_count)?;
840
841        Ok(StoredBlockBodyIndices { first_tx_num, tx_count })
842    }
843
844    /// Writes MDBX block body indices (`BlockBodyIndices`, `TransactionBlocks`,
845    /// `Ommers`/`Withdrawals`).
846    fn write_block_body_indices(
847        &self,
848        block_number: BlockNumber,
849        body: &BodyTy<N>,
850        first_tx_num: TxNumber,
851        tx_count: u64,
852    ) -> ProviderResult<()> {
853        // MDBX: BlockBodyIndices
854        let start = Instant::now();
855        self.tx
856            .cursor_write::<tables::BlockBodyIndices>()?
857            .append(block_number, &StoredBlockBodyIndices { first_tx_num, tx_count })?;
858        self.metrics.record_duration(metrics::Action::InsertBlockBodyIndices, start.elapsed());
859
860        // MDBX: TransactionBlocks (last tx -> block mapping)
861        if tx_count > 0 {
862            let start = Instant::now();
863            self.tx
864                .cursor_write::<tables::TransactionBlocks>()?
865                .append(first_tx_num + tx_count - 1, &block_number)?;
866            self.metrics.record_duration(metrics::Action::InsertTransactionBlocks, start.elapsed());
867        }
868
869        // MDBX: Ommers/Withdrawals
870        self.storage.writer().write_block_bodies(self, vec![(block_number, Some(body))])?;
871
872        Ok(())
873    }
874
875    /// Unwinds trie state starting at and including the given block.
876    ///
877    /// This includes calculating the resulted state root and comparing it with the parent block
878    /// state root.
879    pub fn unwind_trie_state_from(&self, from: BlockNumber) -> ProviderResult<()> {
880        let finish =
881            self.get_stage_checkpoint(reth_stages_types::StageId::Finish)?.ok_or_else(|| {
882                ProviderError::InsufficientChangesets { requested: from, available: 0..=0 }
883            })?;
884
885        // Calculate changesets before modifying hashed state used to reconstruct historical tries.
886        let trie_revert = self
887            .overlay_manager
888            .get_or_compute_cached_changesets_range(self, from..=finish.block_number)?;
889
890        let changed_accounts = self.account_changesets_range(from..)?;
891
892        // Unwind account hashes.
893        self.unwind_account_hashing(changed_accounts.iter())?;
894
895        // Unwind account history indices.
896        self.unwind_account_history_indices(changed_accounts.iter())?;
897
898        let changed_storages = self.storage_changesets_range(from..)?;
899
900        // Unwind storage hashes.
901        self.unwind_storage_hashing(changed_storages.iter().copied())?;
902
903        // Unwind storage history indices.
904        self.unwind_storage_history_indices(changed_storages.iter().copied())?;
905
906        self.write_trie_updates_sorted(&trie_revert)?;
907
908        Ok(())
909    }
910
911    /// Removes receipts from all transactions starting with provided number (inclusive).
912    fn remove_receipts_from(
913        &self,
914        from_tx: TxNumber,
915        last_block: BlockNumber,
916    ) -> ProviderResult<()> {
917        // iterate over block body and remove receipts
918        self.remove::<tables::Receipts<ReceiptTy<N>>>(from_tx..)?;
919
920        if EitherWriter::receipts_destination(self).is_static_file() {
921            let static_file_receipt_num =
922                self.static_file_provider.get_highest_static_file_tx(StaticFileSegment::Receipts);
923
924            let to_delete = static_file_receipt_num
925                .map(|static_num| (static_num + 1).saturating_sub(from_tx))
926                .unwrap_or_default();
927
928            self.static_file_provider
929                .latest_writer(StaticFileSegment::Receipts)?
930                .prune_receipts(to_delete, last_block)?;
931        }
932
933        Ok(())
934    }
935
936    /// Writes bytecodes to MDBX.
937    fn write_bytecodes(
938        &self,
939        bytecodes: impl IntoIterator<Item = (B256, Bytecode)>,
940    ) -> ProviderResult<()> {
941        let mut bytecodes_cursor = self.tx_ref().cursor_write::<tables::Bytecodes>()?;
942        for (hash, bytecode) in bytecodes {
943            bytecodes_cursor.upsert(hash, &bytecode)?;
944        }
945        Ok(())
946    }
947}
948
949/// For a given key, unwind all history shards that contain block numbers at or above the given
950/// block number.
951///
952/// S - Sharded key subtype.
953/// T - Table to walk over.
954/// C - Cursor implementation.
955///
956/// This function walks the entries from the given start key and deletes all shards that belong to
957/// the key and contain block numbers at or above the given block number. Shards entirely below
958/// the block number are preserved.
959///
960/// The boundary shard (the shard that spans across the block number) is removed from the database.
961/// Any indices that are below the block number are filtered out and returned for reinsertion.
962/// The boundary shard is returned for reinsertion (if it's not empty).
963fn unwind_history_shards<S, T, C>(
964    cursor: &mut C,
965    start_key: T::Key,
966    block_number: BlockNumber,
967    mut shard_belongs_to_key: impl FnMut(&T::Key) -> bool,
968) -> ProviderResult<Vec<u64>>
969where
970    T: Table<Value = BlockNumberList>,
971    T::Key: AsRef<ShardedKey<S>>,
972    C: DbCursorRO<T> + DbCursorRW<T>,
973{
974    // Start from the given key and iterate through shards
975    let mut item = cursor.seek_exact(start_key)?;
976    while let Some((sharded_key, list)) = item {
977        // If the shard does not belong to the key, break.
978        if !shard_belongs_to_key(&sharded_key) {
979            break
980        }
981
982        // Always delete the current shard from the database first
983        // We'll decide later what (if anything) to reinsert
984        cursor.delete_current()?;
985
986        // Get the first (lowest) block number in this shard
987        // All block numbers in a shard are sorted in ascending order
988        let first = list.iter().next().expect("List can't be empty");
989
990        // Case 1: Entire shard is at or above the unwinding point
991        // Keep it deleted (don't return anything for reinsertion)
992        if first >= block_number {
993            item = cursor.prev()?;
994            continue
995        }
996        // Case 2: This is a boundary shard (spans across the unwinding point)
997        // The shard contains some blocks below and some at/above the unwinding point
998        else if block_number <= sharded_key.as_ref().highest_block_number {
999            // Return only the block numbers that are below the unwinding point
1000            // These will be reinserted to preserve the historical data
1001            return Ok(list.iter().take_while(|i| *i < block_number).collect::<Vec<_>>())
1002        }
1003        // Case 3: Entire shard is below the unwinding point
1004        // Return all block numbers for reinsertion (preserve entire shard)
1005        return Ok(list.iter().collect::<Vec<_>>())
1006    }
1007
1008    // No shards found or all processed
1009    Ok(Vec::new())
1010}
1011
1012impl<TX: DbTx + 'static, N: NodeTypesForProvider> DatabaseProvider<TX, N> {
1013    /// Creates a provider with an inner read-only transaction.
1014    #[expect(clippy::too_many_arguments)]
1015    pub fn new(
1016        tx: TX,
1017        chain_spec: Arc<N::ChainSpec>,
1018        static_file_provider: StaticFileProvider<N::Primitives>,
1019        prune_modes: PruneModes,
1020        storage: Arc<N::Storage>,
1021        storage_settings: Arc<RwLock<StorageSettings>>,
1022        rocksdb_provider: RocksDBProvider,
1023        overlay_manager: OverlayManager<N::Primitives>,
1024        runtime: reth_tasks::Runtime,
1025        db_path: PathBuf,
1026        metrics: Arc<DatabaseProviderMetrics>,
1027    ) -> Self {
1028        Self {
1029            tx,
1030            chain_spec,
1031            static_file_provider,
1032            prune_modes,
1033            storage,
1034            storage_settings,
1035            rocksdb_provider,
1036            overlay_manager,
1037            runtime,
1038            db_path,
1039            rocksdb_history_snapshot: OnceLock::new(),
1040            pending_rocksdb_batches: Default::default(),
1041            commit_order: CommitOrder::Normal,
1042            minimum_pruning_distance: MINIMUM_UNWIND_SAFE_DISTANCE,
1043            metrics,
1044            reader_txn_tracker: None,
1045        }
1046    }
1047
1048    /// Consume `DbTx` or `DbTxMut`.
1049    pub fn into_tx(self) -> TX {
1050        self.tx
1051    }
1052
1053    /// Pass `DbTx` or `DbTxMut` mutable reference.
1054    pub const fn tx_mut(&mut self) -> &mut TX {
1055        &mut self.tx
1056    }
1057
1058    /// Pass `DbTx` or `DbTxMut` immutable reference.
1059    pub const fn tx_ref(&self) -> &TX {
1060        &self.tx
1061    }
1062
1063    /// Returns a reference to the chain specification.
1064    pub fn chain_spec(&self) -> &N::ChainSpec {
1065        &self.chain_spec
1066    }
1067}
1068
1069impl<TX: DbTx + 'static, N: NodeTypesForProvider> DatabaseProvider<TX, N> {
1070    fn recovered_block<H, HF, B, BF>(
1071        &self,
1072        id: BlockHashOrNumber,
1073        _transaction_kind: TransactionVariant,
1074        header_by_number: HF,
1075        construct_block: BF,
1076    ) -> ProviderResult<Option<B>>
1077    where
1078        H: AsRef<HeaderTy<N>>,
1079        HF: FnOnce(BlockNumber) -> ProviderResult<Option<H>>,
1080        BF: FnOnce(H, BodyTy<N>, Vec<Address>) -> ProviderResult<Option<B>>,
1081    {
1082        let Some(block_number) = self.convert_hash_or_number(id)? else { return Ok(None) };
1083        let earliest_available = self.static_file_provider.earliest_history_height();
1084        if block_number < earliest_available {
1085            return Err(ProviderError::BlockExpired { requested: block_number, earliest_available })
1086        }
1087        let Some(header) = header_by_number(block_number)? else { return Ok(None) };
1088
1089        // Get the block body
1090        //
1091        // If the body indices are not found, this means that the transactions either do not exist
1092        // in the database yet, or they do exit but are not indexed. If they exist but are not
1093        // indexed, we don't have enough information to return the block anyways, so we return
1094        // `None`.
1095        let Some(body) = self.block_body_indices(block_number)? else { return Ok(None) };
1096
1097        let tx_range = body.tx_num_range();
1098
1099        let transactions = if tx_range.is_empty() {
1100            vec![]
1101        } else {
1102            self.transactions_by_tx_range(tx_range.clone())?
1103        };
1104
1105        let body = self
1106            .storage
1107            .reader()
1108            .read_block_bodies(self, vec![(header.as_ref(), transactions)])?
1109            .pop()
1110            .ok_or(ProviderError::InvalidStorageOutput)?;
1111
1112        let senders = if tx_range.is_empty() {
1113            vec![]
1114        } else {
1115            let known_senders: HashMap<TxNumber, Address> =
1116                EitherReader::new_senders(self)?.senders_by_tx_range(tx_range.clone())?;
1117
1118            let mut senders = Vec::with_capacity(body.transactions().len());
1119            for (tx_num, tx) in tx_range.zip(body.transactions()) {
1120                match known_senders.get(&tx_num) {
1121                    None => {
1122                        let sender = tx.recover_signer_unchecked()?;
1123                        senders.push(sender);
1124                    }
1125                    Some(sender) => senders.push(*sender),
1126                }
1127            }
1128            senders
1129        };
1130
1131        construct_block(header, body, senders)
1132    }
1133
1134    /// Returns a range of blocks from the database.
1135    ///
1136    /// Uses the provided `headers_range` to get the headers for the range, and `assemble_block` to
1137    /// construct blocks from the following inputs:
1138    ///     – Header
1139    ///     - Range of transaction numbers
1140    ///     – Ommers
1141    ///     – Withdrawals
1142    ///     – Senders
1143    fn block_range<F, H, HF, R>(
1144        &self,
1145        range: RangeInclusive<BlockNumber>,
1146        headers_range: HF,
1147        mut assemble_block: F,
1148    ) -> ProviderResult<Vec<R>>
1149    where
1150        H: AsRef<HeaderTy<N>>,
1151        HF: FnOnce(RangeInclusive<BlockNumber>) -> ProviderResult<Vec<H>>,
1152        F: FnMut(H, BodyTy<N>, Range<TxNumber>) -> ProviderResult<R>,
1153    {
1154        if range.is_empty() {
1155            return Ok(Vec::new())
1156        }
1157
1158        // like the single block lookups, reject ranges that reach into expired history instead
1159        // of assembling blocks whose bodies are no longer available
1160        let earliest_available = self.static_file_provider.earliest_history_height();
1161        if *range.start() < earliest_available {
1162            return Err(ProviderError::BlockExpired {
1163                requested: *range.start(),
1164                earliest_available,
1165            })
1166        }
1167
1168        let len = range.end().saturating_sub(*range.start()) as usize + 1;
1169        let mut blocks = Vec::with_capacity(len);
1170
1171        let headers = headers_range(range.clone())?;
1172
1173        // If the body indices are not found, this means that the transactions either do
1174        // not exist in the database yet, or they do exit but are
1175        // not indexed. If they exist but are not indexed, we don't
1176        // have enough information to return the block anyways, so
1177        // we skip the block.
1178        let present_headers = self
1179            .block_body_indices_range(range)?
1180            .into_iter()
1181            .map(|b| b.tx_num_range())
1182            .zip(headers)
1183            .collect::<Vec<_>>();
1184
1185        let mut inputs = Vec::with_capacity(present_headers.len());
1186        for (tx_range, header) in &present_headers {
1187            let transactions = if tx_range.is_empty() {
1188                Vec::new()
1189            } else {
1190                self.transactions_by_tx_range(tx_range.clone())?
1191            };
1192
1193            inputs.push((header.as_ref(), transactions));
1194        }
1195
1196        let bodies = self.storage.reader().read_block_bodies(self, inputs)?;
1197
1198        for ((tx_range, header), body) in present_headers.into_iter().zip(bodies) {
1199            blocks.push(assemble_block(header, body, tx_range)?);
1200        }
1201
1202        Ok(blocks)
1203    }
1204
1205    /// Returns a range of blocks from the database, along with the senders of each
1206    /// transaction in the blocks.
1207    ///
1208    /// Uses the provided `headers_range` to get the headers for the range, and `assemble_block` to
1209    /// construct blocks from the following inputs:
1210    ///     – Header
1211    ///     - Transactions
1212    ///     – Ommers
1213    ///     – Withdrawals
1214    ///     – Senders
1215    fn block_with_senders_range<H, HF, B, BF>(
1216        &self,
1217        range: RangeInclusive<BlockNumber>,
1218        headers_range: HF,
1219        assemble_block: BF,
1220    ) -> ProviderResult<Vec<B>>
1221    where
1222        H: AsRef<HeaderTy<N>>,
1223        HF: Fn(RangeInclusive<BlockNumber>) -> ProviderResult<Vec<H>>,
1224        BF: Fn(H, BodyTy<N>, Vec<Address>) -> ProviderResult<B>,
1225    {
1226        self.block_range(range, headers_range, |header, body, tx_range| {
1227            let senders = if tx_range.is_empty() {
1228                Vec::new()
1229            } else {
1230                let known_senders: HashMap<TxNumber, Address> =
1231                    EitherReader::new_senders(self)?.senders_by_tx_range(tx_range.clone())?;
1232
1233                let mut senders = Vec::with_capacity(body.transactions().len());
1234                for (tx_num, tx) in tx_range.zip(body.transactions()) {
1235                    match known_senders.get(&tx_num) {
1236                        None => {
1237                            // recover the sender from the transaction if not found
1238                            let sender = tx.recover_signer_unchecked()?;
1239                            senders.push(sender);
1240                        }
1241                        Some(sender) => senders.push(*sender),
1242                    }
1243                }
1244
1245                senders
1246            };
1247
1248            assemble_block(header, body, senders)
1249        })
1250    }
1251
1252    /// Populate a [`BundleStateInit`] and [`RevertsInit`] using cursors over the
1253    /// [`tables::PlainAccountState`] and [`tables::PlainStorageState`] tables, based on the given
1254    /// storage and account changesets.
1255    #[allow(clippy::clone_on_copy)]
1256    pub(crate) fn populate_bundle_state(
1257        &self,
1258        account_changeset: Vec<(u64, AccountBeforeTx)>,
1259        storage_changeset: Vec<(BlockNumberAddress, StorageEntry)>,
1260        mut get_account: impl FnMut(Address) -> ProviderResult<Option<Account>>,
1261        mut get_storage: impl FnMut(Address, StorageKey) -> ProviderResult<Option<StorageValue>>,
1262    ) -> ProviderResult<(BundleStateInit, RevertsInit)> {
1263        // iterate previous value and get plain state value to create changeset
1264        // Double option around Account represent if Account state is know (first option) and
1265        // account is removed (Second Option)
1266        let mut state: BundleStateInit = HashMap::default();
1267
1268        // This is not working for blocks that are not at tip. as plain state is not the last
1269        // state of end range. We should rename the functions or add support to access
1270        // History state. Accessing history state can be tricky but we are not gaining
1271        // anything.
1272
1273        let mut reverts: RevertsInit = HashMap::default();
1274
1275        // add account changeset changes
1276        for (block_number, account_before) in account_changeset.into_iter().rev() {
1277            let AccountBeforeTx { info: old_info, address } = account_before;
1278            match state.entry(address) {
1279                hash_map::Entry::Vacant(entry) => {
1280                    let new_info = get_account(address)?;
1281                    entry.insert((old_info.clone(), new_info, HashMap::default()));
1282                }
1283                hash_map::Entry::Occupied(mut entry) => {
1284                    // overwrite old account state.
1285                    entry.get_mut().0 = old_info.clone();
1286                }
1287            }
1288            // insert old info into reverts.
1289            reverts.entry(block_number).or_default().entry(address).or_default().0 = Some(old_info);
1290        }
1291
1292        // add storage changeset changes
1293        for (block_and_address, old_storage) in storage_changeset.into_iter().rev() {
1294            let BlockNumberAddress((block_number, address)) = block_and_address;
1295            // get account state or insert from plain state.
1296            let account_state = match state.entry(address) {
1297                hash_map::Entry::Vacant(entry) => {
1298                    let present_info = get_account(address)?;
1299                    entry.insert((present_info.clone(), present_info, HashMap::default()))
1300                }
1301                hash_map::Entry::Occupied(entry) => entry.into_mut(),
1302            };
1303
1304            // match storage.
1305            match account_state.2.entry(old_storage.key) {
1306                hash_map::Entry::Vacant(entry) => {
1307                    let new_storage = get_storage(address, old_storage.key)?.unwrap_or_default();
1308                    entry.insert((old_storage.value, new_storage));
1309                }
1310                hash_map::Entry::Occupied(mut entry) => {
1311                    entry.get_mut().0 = old_storage.value;
1312                }
1313            };
1314
1315            reverts
1316                .entry(block_number)
1317                .or_default()
1318                .entry(address)
1319                .or_default()
1320                .1
1321                .push(old_storage);
1322        }
1323
1324        Ok((state, reverts))
1325    }
1326
1327    /// Invokes [`populate_bundle_state`](Self::populate_bundle_state) with the given plain state
1328    /// cursors.
1329    fn populate_bundle_state_plain(
1330        &self,
1331        account_changeset: Vec<(u64, AccountBeforeTx)>,
1332        storage_changeset: Vec<(BlockNumberAddress, StorageEntry)>,
1333        plain_accounts_cursor: &mut impl DbCursorRO<tables::PlainAccountState>,
1334        plain_storage_cursor: &mut impl DbDupCursorRO<tables::PlainStorageState>,
1335    ) -> ProviderResult<(BundleStateInit, RevertsInit)> {
1336        self.populate_bundle_state(
1337            account_changeset,
1338            storage_changeset,
1339            |address| Ok(plain_accounts_cursor.seek_exact(address)?.map(|kv| kv.1)),
1340            |address, storage_key| {
1341                Ok(plain_storage_cursor
1342                    .seek_by_key_subkey(address, storage_key)?
1343                    .filter(|s| s.key == storage_key)
1344                    .map(|s| s.value))
1345            },
1346        )
1347    }
1348
1349    /// Like [`populate_bundle_state`](Self::populate_bundle_state), but reads current values from
1350    /// `HashedAccounts`/`HashedStorages`. Addresses and storage keys are hashed via `keccak256`
1351    /// for DB lookups. The output `BundleStateInit`/`RevertsInit` structures remain keyed by
1352    /// plain address and plain storage key.
1353    fn populate_bundle_state_hashed(
1354        &self,
1355        account_changeset: Vec<(u64, AccountBeforeTx)>,
1356        storage_changeset: Vec<(BlockNumberAddress, StorageEntry)>,
1357        hashed_accounts_cursor: &mut impl DbCursorRO<tables::HashedAccounts>,
1358        hashed_storage_cursor: &mut impl DbDupCursorRO<tables::HashedStorages>,
1359    ) -> ProviderResult<(BundleStateInit, RevertsInit)> {
1360        self.populate_bundle_state(
1361            account_changeset,
1362            storage_changeset,
1363            |address| Ok(hashed_accounts_cursor.seek_exact(keccak256(address))?.map(|kv| kv.1)),
1364            |address, storage_key| {
1365                let hashed_storage_key = keccak256(storage_key);
1366                Ok(hashed_storage_cursor
1367                    .seek_by_key_subkey(keccak256(address), hashed_storage_key)?
1368                    .filter(|s| s.key == hashed_storage_key)
1369                    .map(|s| s.value))
1370            },
1371        )
1372    }
1373}
1374
1375impl<TX: DbTxMut + DbTx + 'static, N: NodeTypes> DatabaseProvider<TX, N> {
1376    /// Insert history index to the database.
1377    ///
1378    /// For each updated partial key, this function retrieves the last shard from the database
1379    /// (if any), appends the new indices to it, chunks the resulting list if needed, and upserts
1380    /// the shards back into the database.
1381    ///
1382    /// This function is used by history indexing stages.
1383    fn append_history_index<P, T>(
1384        &self,
1385        index_updates: impl IntoIterator<Item = (P, impl IntoIterator<Item = u64>)>,
1386        mut sharded_key_factory: impl FnMut(P, BlockNumber) -> T::Key,
1387    ) -> ProviderResult<()>
1388    where
1389        P: Copy,
1390        T: Table<Value = BlockNumberList>,
1391    {
1392        // This function cannot be used with DUPSORT tables because `upsert` on DUPSORT tables
1393        // will append duplicate entries instead of updating existing ones, causing data corruption.
1394        assert!(!T::DUPSORT, "append_history_index cannot be used with DUPSORT tables");
1395
1396        let mut cursor = self.tx.cursor_write::<T>()?;
1397
1398        for (partial_key, indices) in index_updates {
1399            let last_key = sharded_key_factory(partial_key, u64::MAX);
1400            let mut last_shard = cursor
1401                .seek_exact(last_key.clone())?
1402                .map(|(_, list)| list)
1403                .unwrap_or_else(BlockNumberList::empty);
1404
1405            last_shard.append(indices).map_err(ProviderError::other)?;
1406
1407            // fast path: all indices fit in one shard
1408            if last_shard.len() <= sharded_key::NUM_OF_INDICES_IN_SHARD as u64 {
1409                cursor.upsert(last_key, &last_shard)?;
1410                continue;
1411            }
1412
1413            // slow path: rechunk into multiple shards
1414            let chunks = last_shard.iter().chunks(sharded_key::NUM_OF_INDICES_IN_SHARD);
1415            let mut chunks_peekable = chunks.into_iter().peekable();
1416
1417            while let Some(chunk) = chunks_peekable.next() {
1418                let shard = BlockNumberList::new_pre_sorted(chunk);
1419                let highest_block_number = if chunks_peekable.peek().is_some() {
1420                    shard.iter().next_back().expect("`chunks` does not return empty list")
1421                } else {
1422                    // Insert last list with `u64::MAX`.
1423                    u64::MAX
1424                };
1425
1426                cursor.upsert(sharded_key_factory(partial_key, highest_block_number), &shard)?;
1427            }
1428        }
1429
1430        Ok(())
1431    }
1432
1433    /// Deletes the transaction, receipt, sender and changeset static files and restarts each
1434    /// segment after `pivot`, so the next block appended is `pivot + 1`. Headers are kept.
1435    ///
1436    /// Each segment gets an empty file anchored at `pivot`. Blocks below it read as expired history
1437    /// although the pruner never ran, and `pivot` itself reads as missing since nothing is stored
1438    /// for it.
1439    ///
1440    /// CAUTION: destructive. The files are deleted immediately, while the anchor is written on
1441    /// commit. The caller moves the stage and prune checkpoints to `pivot` in the same commit and
1442    /// must be able to resume if the process stops between the static file and database commits.
1443    /// Errors unless storage v2 is enabled.
1444    pub fn anchor_pruned_static_files(&self, pivot: BlockNumber) -> ProviderResult<()> {
1445        if !self.cached_storage_settings().storage_v2 {
1446            return Err(ProviderError::other(StaticFileWriterError::new(
1447                "pruned anchor requires storage v2",
1448            )))
1449        }
1450        let static_files = self.static_file_provider();
1451        for segment in StaticFileSegment::iter().filter(|segment| !segment.is_headers()) {
1452            static_files.delete_segment(segment)?;
1453            static_files.get_writer(pivot, segment)?.initialize_pruned_anchor(pivot)?;
1454        }
1455        // The pivot's own body is never stored, so reads of `pivot` find nothing while blocks
1456        // below it are reported as expired.
1457        static_files.set_earliest_history_height(pivot);
1458        Ok(())
1459    }
1460}
1461
1462impl<TX: DbTx, N: NodeTypes> DatabaseProvider<TX, N> {
1463    /// Rejects advances of either the Finish block number or its state/trie frontier while a
1464    /// persisted snap attempt is unverified. A missing partial frontier equals the block
1465    /// number, so clearing it can advance state even when the block number stays put or rewinds.
1466    /// Updates that advance neither frontier are allowed.
1467    fn ensure_finish_may_advance(&self, proposed: &StageCheckpoint) -> ProviderResult<()> {
1468        let current = self.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default();
1469        let state_trie_frontier = |checkpoint: &StageCheckpoint| {
1470            checkpoint
1471                .finish_stage_checkpoint()
1472                .and_then(|finish| finish.partial_state_trie())
1473                .unwrap_or(checkpoint.block_number)
1474        };
1475        if proposed.block_number <= current.block_number &&
1476            state_trie_frontier(proposed) <= state_trie_frontier(&current)
1477        {
1478            return Ok(())
1479        }
1480        match self.snap_attempt()? {
1481            Some(attempt) if !attempt.is_verified() => {
1482                Err(ProviderError::UnverifiedSnapState { attempt: attempt.id().into() })
1483            }
1484            _ => Ok(()),
1485        }
1486    }
1487
1488    /// Refuses snap sync on a database without the hashed state layout it downloads into.
1489    pub fn ensure_snap_sync_layout(&self) -> ProviderResult<()> {
1490        if self.cached_storage_settings().use_hashed_state() {
1491            Ok(())
1492        } else {
1493            Err(ProviderError::SnapStorageLayoutUnsupported)
1494        }
1495    }
1496
1497    /// Returns whether snap sync bootstraps this database: an attempt is left to finish or
1498    /// replace, or nothing has executed past genesis.
1499    ///
1500    /// Call this after genesis is initialized: until then the cached settings fall back to the
1501    /// legacy layout and no execution checkpoint exists.
1502    pub fn snap_bootstraps(&self) -> ProviderResult<bool> {
1503        if let Some(attempt) = self.snap_attempt()? {
1504            return Ok(!attempt.is_verified())
1505        }
1506        let genesis = self.chain_spec().genesis_header().number();
1507        Ok(self
1508            .get_stage_checkpoint(StageId::Execution)?
1509            .is_none_or(|checkpoint| checkpoint.block_number == genesis))
1510    }
1511
1512    /// Refuses a database the selected sync can't continue: snap needs the hashed state layout
1513    /// when it bootstraps, and only snap can finish or replace an unverified snap attempt.
1514    ///
1515    /// Call this after genesis is initialized, see [`Self::snap_bootstraps`].
1516    pub fn ensure_sync_mode(&self, snap_enabled: bool) -> ProviderResult<()> {
1517        if snap_enabled {
1518            return if self.snap_bootstraps()? { self.ensure_snap_sync_layout() } else { Ok(()) }
1519        }
1520        match self.snap_attempt()? {
1521            Some(attempt) if !attempt.is_verified() => {
1522                Err(ProviderError::SnapStateRequiresSnapSync { attempt: attempt.id().into() })
1523            }
1524            _ => Ok(()),
1525        }
1526    }
1527}
1528
1529impl<TX: DbTx, N: NodeTypes> AccountReader for DatabaseProvider<TX, N> {
1530    fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
1531        if self.cached_storage_settings().use_hashed_state() {
1532            let hashed_address = keccak256(address);
1533            Ok(self.tx.get_by_encoded_key::<tables::HashedAccounts>(&hashed_address)?)
1534        } else {
1535            Ok(self.tx.get_by_encoded_key::<tables::PlainAccountState>(address)?)
1536        }
1537    }
1538}
1539
1540impl<TX: DbTx + 'static, N: NodeTypes> AccountExtReader for DatabaseProvider<TX, N> {
1541    fn changed_accounts_with_range(
1542        &self,
1543        range: RangeInclusive<BlockNumber>,
1544    ) -> ProviderResult<BTreeSet<Address>> {
1545        let mut reader = EitherReader::new_account_changesets(self)?;
1546
1547        reader.changed_accounts_with_range(range)
1548    }
1549
1550    fn basic_accounts(
1551        &self,
1552        iter: impl IntoIterator<Item = Address>,
1553    ) -> ProviderResult<Vec<(Address, Option<Account>)>> {
1554        if self.cached_storage_settings().use_hashed_state() {
1555            let mut hashed_accounts = self.tx.cursor_read::<tables::HashedAccounts>()?;
1556            Ok(iter
1557                .into_iter()
1558                .map(|address| {
1559                    let hashed_address = keccak256(address);
1560                    hashed_accounts.seek_exact(hashed_address).map(|a| (address, a.map(|(_, v)| v)))
1561                })
1562                .collect::<Result<Vec<_>, _>>()?)
1563        } else {
1564            let mut plain_accounts = self.tx.cursor_read::<tables::PlainAccountState>()?;
1565            Ok(iter
1566                .into_iter()
1567                .map(|address| {
1568                    plain_accounts.seek_exact(address).map(|a| (address, a.map(|(_, v)| v)))
1569                })
1570                .collect::<Result<Vec<_>, _>>()?)
1571        }
1572    }
1573
1574    fn changed_accounts_and_blocks_with_range(
1575        &self,
1576        range: RangeInclusive<BlockNumber>,
1577    ) -> ProviderResult<BTreeMap<Address, Vec<u64>>> {
1578        let highest_static_block = self
1579            .static_file_provider
1580            .get_highest_static_file_block(StaticFileSegment::AccountChangeSets);
1581
1582        if let Some(highest) = highest_static_block &&
1583            self.cached_storage_settings().storage_v2
1584        {
1585            let start = *range.start();
1586            let static_end = (*range.end()).min(highest);
1587
1588            let mut changed_accounts_and_blocks: BTreeMap<_, Vec<u64>> = BTreeMap::default();
1589            if start <= static_end {
1590                for block in start..=static_end {
1591                    let block_changesets = self.account_block_changeset(block)?;
1592                    for changeset in block_changesets {
1593                        changed_accounts_and_blocks
1594                            .entry(changeset.address)
1595                            .or_default()
1596                            .push(block);
1597                    }
1598                }
1599            }
1600
1601            Ok(changed_accounts_and_blocks)
1602        } else {
1603            let mut changeset_cursor = self.tx.cursor_read::<tables::AccountChangeSets>()?;
1604
1605            let account_transitions = changeset_cursor.walk_range(range)?.try_fold(
1606                BTreeMap::new(),
1607                |mut accounts: BTreeMap<Address, Vec<u64>>, entry| -> ProviderResult<_> {
1608                    let (index, account) = entry?;
1609                    accounts.entry(account.address).or_default().push(index);
1610                    Ok(accounts)
1611                },
1612            )?;
1613
1614            Ok(account_transitions)
1615        }
1616    }
1617}
1618
1619impl<TX: DbTx, N: NodeTypes> StorageChangeSetReader for DatabaseProvider<TX, N> {
1620    fn storage_changeset(
1621        &self,
1622        block_number: BlockNumber,
1623    ) -> ProviderResult<Vec<(BlockNumberAddress, StorageEntry)>> {
1624        if self.cached_storage_settings().storage_v2 {
1625            self.static_file_provider.storage_changeset(block_number)
1626        } else {
1627            let range = block_number..=block_number;
1628            let storage_range = BlockNumberAddress::range(range);
1629            self.tx
1630                .cursor_dup_read::<tables::StorageChangeSets>()?
1631                .walk_range(storage_range)?
1632                .map(|r| {
1633                    let (bna, entry) = r?;
1634                    Ok((bna, entry))
1635                })
1636                .collect()
1637        }
1638    }
1639
1640    fn get_storage_before_block(
1641        &self,
1642        block_number: BlockNumber,
1643        address: Address,
1644        storage_key: B256,
1645    ) -> ProviderResult<Option<StorageEntry>> {
1646        if self.cached_storage_settings().storage_v2 {
1647            self.static_file_provider.get_storage_before_block(block_number, address, storage_key)
1648        } else {
1649            Ok(self
1650                .tx
1651                .cursor_dup_read::<tables::StorageChangeSets>()?
1652                .seek_by_key_subkey(BlockNumberAddress((block_number, address)), storage_key)?
1653                .filter(|entry| entry.key == storage_key))
1654        }
1655    }
1656
1657    fn storage_changesets_range(
1658        &self,
1659        range: impl RangeBounds<BlockNumber>,
1660    ) -> ProviderResult<Vec<(BlockNumberAddress, StorageEntry)>> {
1661        if self.cached_storage_settings().storage_v2 {
1662            self.static_file_provider.storage_changesets_range(range)
1663        } else {
1664            self.tx
1665                .cursor_dup_read::<tables::StorageChangeSets>()?
1666                .walk_range(BlockNumberAddressRange::from(range))?
1667                .map(|r| {
1668                    let (bna, entry) = r?;
1669                    Ok((bna, entry))
1670                })
1671                .collect()
1672        }
1673    }
1674}
1675
1676impl<TX: DbTx, N: NodeTypes> ChangeSetReader for DatabaseProvider<TX, N> {
1677    fn account_block_changeset(
1678        &self,
1679        block_number: BlockNumber,
1680    ) -> ProviderResult<Vec<AccountBeforeTx>> {
1681        if self.cached_storage_settings().storage_v2 {
1682            let static_changesets =
1683                self.static_file_provider.account_block_changeset(block_number)?;
1684            Ok(static_changesets)
1685        } else {
1686            let range = block_number..=block_number;
1687            self.tx
1688                .cursor_read::<tables::AccountChangeSets>()?
1689                .walk_range(range)?
1690                .map(|result| -> ProviderResult<_> {
1691                    let (_, account_before) = result?;
1692                    Ok(account_before)
1693                })
1694                .collect()
1695        }
1696    }
1697
1698    fn get_account_before_block(
1699        &self,
1700        block_number: BlockNumber,
1701        address: Address,
1702    ) -> ProviderResult<Option<AccountBeforeTx>> {
1703        if self.cached_storage_settings().storage_v2 {
1704            Ok(self.static_file_provider.get_account_before_block(block_number, address)?)
1705        } else {
1706            self.tx
1707                .cursor_dup_read::<tables::AccountChangeSets>()?
1708                .seek_by_key_subkey(block_number, address)?
1709                .filter(|acc| acc.address == address)
1710                .map(Ok)
1711                .transpose()
1712        }
1713    }
1714
1715    fn account_changesets_range(
1716        &self,
1717        range: impl core::ops::RangeBounds<BlockNumber>,
1718    ) -> ProviderResult<Vec<(BlockNumber, AccountBeforeTx)>> {
1719        if self.cached_storage_settings().storage_v2 {
1720            self.static_file_provider.account_changesets_range(range)
1721        } else {
1722            self.tx
1723                .cursor_read::<tables::AccountChangeSets>()?
1724                .walk_range(to_range(range))?
1725                .map(|r| r.map_err(Into::into))
1726                .collect()
1727        }
1728    }
1729}
1730
1731impl<TX: DbTx + 'static, N: NodeTypes> DatabaseProvider<TX, N> {
1732    /// Returns the `RocksDB` snapshot used for history lookups, creating it on first use.
1733    ///
1734    /// A reading provider serves a single request, so one snapshot is created for all of its
1735    /// history lookups instead of one per lookup. The snapshot also caches the raw iterator of
1736    /// each history column family, which is what makes repeated lookups cheap. The storage
1737    /// settings are read once here as well, so a lookup does not take the settings lock.
1738    ///
1739    /// The snapshot and its iterators pin the `RocksDB` snapshot and the SST files and memtables
1740    /// they were created on until the provider is dropped, the same way the provider's MDBX read
1741    /// transaction pins its pages. Provider lifetimes are request or job scoped, and a provider
1742    /// whose MDBX transaction hits the read-transaction timeout errors on every later read and is
1743    /// dropped by its caller, so that timeout bounds the `RocksDB` retention as well.
1744    fn history_rocksdb_snapshot(&self) -> Option<&RocksReadSnapshot<'_>> {
1745        self.rocksdb_history_snapshot
1746            .get_or_init(|| {
1747                self.cached_storage_settings()
1748                    .storage_v2
1749                    .then(|| self.rocksdb_provider.owned_snapshot())
1750            })
1751            .as_ref()
1752            .map(OwnedRocksReadSnapshot::as_snapshot)
1753    }
1754}
1755
1756impl<TX: DbTx + 'static, N: NodeTypes> HistoryReader for DatabaseProvider<TX, N> {
1757    fn account_history_info(
1758        &self,
1759        address: Address,
1760        block_number: BlockNumber,
1761        lowest_available_block_number: Option<BlockNumber>,
1762    ) -> ProviderResult<HistoryInfo> {
1763        let visible_tip = self.best_block_number()?;
1764        let mut reader = EitherReader::new_accounts_history(self, self.history_rocksdb_snapshot())?;
1765        reader
1766            .account_history_info(address, block_number, lowest_available_block_number, visible_tip)
1767            .map(Into::into)
1768    }
1769
1770    fn storage_history_info(
1771        &self,
1772        address: Address,
1773        storage_key: B256,
1774        block_number: BlockNumber,
1775        lowest_available_block_number: Option<BlockNumber>,
1776    ) -> ProviderResult<HistoryInfo> {
1777        let visible_tip = self.best_block_number()?;
1778        let mut reader = EitherReader::new_storages_history(self, self.history_rocksdb_snapshot())?;
1779        reader
1780            .storage_history_info(
1781                address,
1782                storage_key,
1783                block_number,
1784                lowest_available_block_number,
1785                visible_tip,
1786            )
1787            .map(Into::into)
1788    }
1789}
1790
1791impl<TX: DbTx + 'static, N: NodeTypesForProvider> HeaderSyncGapProvider
1792    for DatabaseProvider<TX, N>
1793{
1794    type Header = HeaderTy<N>;
1795
1796    fn local_tip_header(
1797        &self,
1798        highest_uninterrupted_block: BlockNumber,
1799    ) -> ProviderResult<SealedHeader<Self::Header>> {
1800        let static_file_provider = self.static_file_provider();
1801
1802        // Make sure Headers static file is at the same height. If it's further, this
1803        // input execution was interrupted previously and we need to unwind the static file.
1804        let next_static_file_block_num = static_file_provider
1805            .get_highest_static_file_block(StaticFileSegment::Headers)
1806            .map(|id| id + 1)
1807            .unwrap_or_default();
1808        let next_block = highest_uninterrupted_block + 1;
1809
1810        match next_static_file_block_num.cmp(&next_block) {
1811            // The node shutdown between an executed static file commit and before the database
1812            // commit, so we need to unwind the static files.
1813            Ordering::Greater => {
1814                let mut static_file_producer =
1815                    static_file_provider.latest_writer(StaticFileSegment::Headers)?;
1816                static_file_producer.prune_headers(next_static_file_block_num - next_block)?;
1817                // Since this is a database <-> static file inconsistency, we commit the change
1818                // straight away.
1819                static_file_producer.commit()?
1820            }
1821            Ordering::Less => {
1822                // There's either missing or corrupted files.
1823                return Err(ProviderError::HeaderNotFound(next_static_file_block_num.into()))
1824            }
1825            Ordering::Equal => {}
1826        }
1827
1828        let local_head = static_file_provider
1829            .sealed_header(highest_uninterrupted_block)?
1830            .ok_or_else(|| ProviderError::HeaderNotFound(highest_uninterrupted_block.into()))?;
1831
1832        Ok(local_head)
1833    }
1834}
1835
1836impl<TX: DbTx + 'static, N: NodeTypesForProvider> HeaderProvider for DatabaseProvider<TX, N> {
1837    type Header = HeaderTy<N>;
1838
1839    fn header(&self, block_hash: BlockHash) -> ProviderResult<Option<Self::Header>> {
1840        if let Some(num) = self.block_number(block_hash)? {
1841            Ok(self.header_by_number(num)?)
1842        } else {
1843            Ok(None)
1844        }
1845    }
1846
1847    fn header_by_number(&self, num: BlockNumber) -> ProviderResult<Option<Self::Header>> {
1848        self.static_file_provider.header_by_number(num)
1849    }
1850
1851    fn headers_range(
1852        &self,
1853        range: impl RangeBounds<BlockNumber>,
1854    ) -> ProviderResult<Vec<Self::Header>> {
1855        self.static_file_provider.headers_range(range)
1856    }
1857
1858    fn sealed_header(
1859        &self,
1860        number: BlockNumber,
1861    ) -> ProviderResult<Option<SealedHeader<Self::Header>>> {
1862        self.static_file_provider.sealed_header(number)
1863    }
1864
1865    fn sealed_headers_while(
1866        &self,
1867        range: impl RangeBounds<BlockNumber>,
1868        predicate: impl FnMut(&SealedHeader<Self::Header>) -> bool,
1869    ) -> ProviderResult<Vec<SealedHeader<Self::Header>>> {
1870        self.static_file_provider.sealed_headers_while(range, predicate)
1871    }
1872}
1873
1874impl<TX: DbTx + 'static, N: NodeTypes> BlockHashReader for DatabaseProvider<TX, N> {
1875    fn block_hash(&self, number: u64) -> ProviderResult<Option<B256>> {
1876        self.static_file_provider.block_hash(number)
1877    }
1878
1879    fn canonical_hashes_range(
1880        &self,
1881        start: BlockNumber,
1882        end: BlockNumber,
1883    ) -> ProviderResult<Vec<B256>> {
1884        self.static_file_provider.canonical_hashes_range(start, end)
1885    }
1886}
1887
1888impl<TX: DbTx + 'static, N: NodeTypes> BlockNumReader for DatabaseProvider<TX, N> {
1889    fn chain_info(&self) -> ProviderResult<ChainInfo> {
1890        let best_number = self.best_block_number()?;
1891        let best_hash = self.block_hash(best_number)?.unwrap_or_default();
1892        Ok(ChainInfo { best_hash, best_number })
1893    }
1894
1895    fn best_block_number(&self) -> ProviderResult<BlockNumber> {
1896        // The best block number is tracked via the finished stage which gets updated in the same tx
1897        // when new blocks committed
1898        Ok(self
1899            .get_stage_checkpoint(StageId::Finish)?
1900            .map(|checkpoint| checkpoint.block_number)
1901            .unwrap_or_default())
1902    }
1903
1904    fn last_block_number(&self) -> ProviderResult<BlockNumber> {
1905        self.static_file_provider.last_block_number()
1906    }
1907
1908    fn block_number(&self, hash: B256) -> ProviderResult<Option<BlockNumber>> {
1909        Ok(self.tx.get::<tables::HeaderNumbers>(hash)?)
1910    }
1911}
1912
1913impl<TX: DbTx + 'static, N: NodeTypesForProvider> BlockReader for DatabaseProvider<TX, N> {
1914    type Block = BlockTy<N>;
1915
1916    fn find_block_by_hash(
1917        &self,
1918        hash: B256,
1919        source: BlockSource,
1920    ) -> ProviderResult<Option<Self::Block>> {
1921        if source.is_canonical() {
1922            self.block(hash.into())
1923        } else {
1924            Ok(None)
1925        }
1926    }
1927
1928    /// Returns the block with matching number from database.
1929    ///
1930    /// If the header for this block is not found, this returns `None`.
1931    /// If the header is found, but the transactions either do not exist, or are not indexed, this
1932    /// will return None.
1933    ///
1934    /// Returns an error if the requested block is below the earliest available history.
1935    fn block(&self, id: BlockHashOrNumber) -> ProviderResult<Option<Self::Block>> {
1936        if let Some(number) = self.convert_hash_or_number(id)? {
1937            let earliest_available = self.static_file_provider.earliest_history_height();
1938            if number < earliest_available {
1939                return Err(ProviderError::BlockExpired { requested: number, earliest_available })
1940            }
1941
1942            let Some(header) = self.header_by_number(number)? else { return Ok(None) };
1943
1944            // If the body indices are not found, this means that the transactions either do not
1945            // exist in the database yet, or they do exit but are not indexed.
1946            // If they exist but are not indexed, we don't have enough
1947            // information to return the block anyways, so we return `None`.
1948            let Some(transactions) = self.transactions_by_block(number.into())? else {
1949                return Ok(None)
1950            };
1951
1952            let body = self
1953                .storage
1954                .reader()
1955                .read_block_bodies(self, vec![(&header, transactions)])?
1956                .pop()
1957                .ok_or(ProviderError::InvalidStorageOutput)?;
1958
1959            return Ok(Some(Self::Block::new(header, body)))
1960        }
1961
1962        Ok(None)
1963    }
1964
1965    fn pending_block(&self) -> ProviderResult<Option<Arc<RecoveredBlock<Self::Block>>>> {
1966        Ok(None)
1967    }
1968
1969    fn pending_block_and_receipts(
1970        &self,
1971    ) -> ProviderResult<Option<RecoveredBlockAndExecutionOutput<Self::Block, Self::Receipt>>> {
1972        Ok(None)
1973    }
1974
1975    /// Returns the block with senders with matching number or hash from database.
1976    ///
1977    /// **NOTE: The transactions have invalid hashes, since they would need to be calculated on the
1978    /// spot, and we want fast querying.**
1979    ///
1980    /// If the header for this block is not found, this returns `None`.
1981    /// If the header is found, but the transactions either do not exist, or are not indexed, this
1982    /// will return None.
1983    fn recovered_block(
1984        &self,
1985        id: BlockHashOrNumber,
1986        transaction_kind: TransactionVariant,
1987    ) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
1988        self.recovered_block(
1989            id,
1990            transaction_kind,
1991            |block_number| self.header_by_number(block_number),
1992            |header, body, senders| {
1993                Self::Block::new(header, body)
1994                    // Note: we're using unchecked here because we know the block contains valid txs
1995                    // wrt to its height and can ignore the s value check so pre
1996                    // EIP-2 txs are allowed
1997                    .try_into_recovered_unchecked(senders)
1998                    .map(Some)
1999                    .map_err(|_| ProviderError::SenderRecoveryError)
2000            },
2001        )
2002    }
2003
2004    fn sealed_block_with_senders(
2005        &self,
2006        id: BlockHashOrNumber,
2007        transaction_kind: TransactionVariant,
2008    ) -> ProviderResult<Option<RecoveredBlock<Self::Block>>> {
2009        self.recovered_block(
2010            id,
2011            transaction_kind,
2012            |block_number| self.sealed_header(block_number),
2013            |header, body, senders| {
2014                Self::Block::new_sealed(header, body)
2015                    // Note: we're using unchecked here because we know the block contains valid txs
2016                    // wrt to its height and can ignore the s value check so pre
2017                    // EIP-2 txs are allowed
2018                    .try_with_senders_unchecked(senders)
2019                    .map(Some)
2020                    .map_err(|_| ProviderError::SenderRecoveryError)
2021            },
2022        )
2023    }
2024
2025    fn block_range(&self, range: RangeInclusive<BlockNumber>) -> ProviderResult<Vec<Self::Block>> {
2026        self.block_range(
2027            range,
2028            |range| self.headers_range(range),
2029            |header, body, _| Ok(Self::Block::new(header, body)),
2030        )
2031    }
2032
2033    fn block_with_senders_range(
2034        &self,
2035        range: RangeInclusive<BlockNumber>,
2036    ) -> ProviderResult<Vec<RecoveredBlock<Self::Block>>> {
2037        self.block_with_senders_range(
2038            range,
2039            |range| self.headers_range(range),
2040            |header, body, senders| {
2041                Self::Block::new(header, body)
2042                    .try_into_recovered_unchecked(senders)
2043                    .map_err(|_| ProviderError::SenderRecoveryError)
2044            },
2045        )
2046    }
2047
2048    fn recovered_block_range(
2049        &self,
2050        range: RangeInclusive<BlockNumber>,
2051    ) -> ProviderResult<Vec<RecoveredBlock<Self::Block>>> {
2052        self.block_with_senders_range(
2053            range,
2054            |range| self.sealed_headers_range(range),
2055            |header, body, senders| {
2056                Self::Block::new_sealed(header, body)
2057                    .try_with_senders(senders)
2058                    .map_err(|_| ProviderError::SenderRecoveryError)
2059            },
2060        )
2061    }
2062
2063    fn block_by_transaction_id(&self, id: TxNumber) -> ProviderResult<Option<BlockNumber>> {
2064        Ok(self
2065            .tx
2066            .cursor_read::<tables::TransactionBlocks>()?
2067            .seek(id)
2068            .map(|b| b.map(|(_, bn)| bn))?)
2069    }
2070}
2071
2072impl<TX: DbTx + 'static, N: NodeTypesForProvider> TransactionsProviderExt
2073    for DatabaseProvider<TX, N>
2074{
2075    /// Recovers transaction hashes by walking through `Transactions` table and
2076    /// calculating them in a parallel manner. Returned unsorted.
2077    fn transaction_hashes_by_range(
2078        &self,
2079        tx_range: Range<TxNumber>,
2080    ) -> ProviderResult<Vec<(TxHash, TxNumber)>> {
2081        self.static_file_provider.transaction_hashes_by_range(tx_range)
2082    }
2083}
2084
2085// Calculates the hash of the given transaction
2086impl<TX: DbTx + 'static, N: NodeTypesForProvider> TransactionsProvider for DatabaseProvider<TX, N> {
2087    type Transaction = TxTy<N>;
2088
2089    fn transaction_id(&self, tx_hash: TxHash) -> ProviderResult<Option<TxNumber>> {
2090        self.with_rocksdb_snapshot(|rocksdb_ref| {
2091            let mut reader = EitherReader::new_transaction_hash_numbers(self, rocksdb_ref)?;
2092            reader.get_transaction_hash_number(tx_hash)
2093        })
2094    }
2095
2096    fn transaction_by_id(&self, id: TxNumber) -> ProviderResult<Option<Self::Transaction>> {
2097        self.static_file_provider.transaction_by_id(id)
2098    }
2099
2100    fn transaction_by_id_unhashed(
2101        &self,
2102        id: TxNumber,
2103    ) -> ProviderResult<Option<Self::Transaction>> {
2104        self.static_file_provider.transaction_by_id_unhashed(id)
2105    }
2106
2107    fn transaction_by_hash(&self, hash: TxHash) -> ProviderResult<Option<Self::Transaction>> {
2108        if let Some(id) = self.transaction_id(hash)? {
2109            Ok(self.transaction_by_id_unhashed(id)?)
2110        } else {
2111            Ok(None)
2112        }
2113    }
2114
2115    fn transaction_by_hash_with_meta(
2116        &self,
2117        tx_hash: TxHash,
2118    ) -> ProviderResult<Option<(Self::Transaction, TransactionMeta)>> {
2119        if let Some(transaction_id) = self.transaction_id(tx_hash)? &&
2120            let Some(transaction) = self.transaction_by_id_unhashed(transaction_id)? &&
2121            let Some(block_number) = self.block_by_transaction_id(transaction_id)? &&
2122            let Some(sealed_header) = self.sealed_header(block_number)?
2123        {
2124            let (header, block_hash) = sealed_header.split();
2125            if let Some(block_body) = self.block_body_indices(block_number)? {
2126                // the index of the tx in the block is the offset:
2127                // len([start..tx_id])
2128                // NOTE: `transaction_id` is always `>=` the block's first
2129                // index
2130                let index = transaction_id - block_body.first_tx_num();
2131
2132                let meta = TransactionMeta {
2133                    tx_hash,
2134                    index,
2135                    block_hash,
2136                    block_number,
2137                    base_fee: header.base_fee_per_gas(),
2138                    excess_blob_gas: header.excess_blob_gas(),
2139                    timestamp: header.timestamp(),
2140                };
2141
2142                return Ok(Some((transaction, meta)))
2143            }
2144        }
2145
2146        Ok(None)
2147    }
2148
2149    fn transactions_by_block(
2150        &self,
2151        id: BlockHashOrNumber,
2152    ) -> ProviderResult<Option<Vec<Self::Transaction>>> {
2153        if let Some(block_number) = self.convert_hash_or_number(id)? &&
2154            let Some(body) = self.block_body_indices(block_number)?
2155        {
2156            let tx_range = body.tx_num_range();
2157            return if tx_range.is_empty() {
2158                Ok(Some(Vec::new()))
2159            } else {
2160                self.transactions_by_tx_range(tx_range).map(Some)
2161            }
2162        }
2163        Ok(None)
2164    }
2165
2166    fn transactions_by_block_range(
2167        &self,
2168        range: impl RangeBounds<BlockNumber>,
2169    ) -> ProviderResult<Vec<Vec<Self::Transaction>>> {
2170        let range = to_range(range);
2171
2172        self.block_body_indices_range(range.start..=range.end.saturating_sub(1))?
2173            .into_iter()
2174            .map(|body| {
2175                let tx_num_range = body.tx_num_range();
2176                if tx_num_range.is_empty() {
2177                    Ok(Vec::new())
2178                } else {
2179                    self.transactions_by_tx_range(tx_num_range)
2180                }
2181            })
2182            .collect()
2183    }
2184
2185    fn transactions_by_tx_range(
2186        &self,
2187        range: impl RangeBounds<TxNumber>,
2188    ) -> ProviderResult<Vec<Self::Transaction>> {
2189        self.static_file_provider.transactions_by_tx_range(range)
2190    }
2191
2192    fn senders_by_tx_range(
2193        &self,
2194        range: impl RangeBounds<TxNumber>,
2195    ) -> ProviderResult<Vec<Address>> {
2196        if EitherWriterDestination::senders(self).is_static_file() {
2197            self.static_file_provider.senders_by_tx_range(range)
2198        } else {
2199            self.cursor_read_collect::<tables::TransactionSenders>(range)
2200        }
2201    }
2202
2203    fn transaction_sender(&self, id: TxNumber) -> ProviderResult<Option<Address>> {
2204        if EitherWriterDestination::senders(self).is_static_file() {
2205            self.static_file_provider.transaction_sender(id)
2206        } else {
2207            Ok(self.tx.get::<tables::TransactionSenders>(id)?)
2208        }
2209    }
2210}
2211
2212impl<TX: DbTx + 'static, N: NodeTypesForProvider> ReceiptProvider for DatabaseProvider<TX, N> {
2213    type Receipt = ReceiptTy<N>;
2214
2215    fn receipt(&self, id: TxNumber) -> ProviderResult<Option<Self::Receipt>> {
2216        self.static_file_provider.get_with_static_file_or_database(
2217            StaticFileSegment::Receipts,
2218            id,
2219            |static_file| static_file.receipt(id),
2220            || Ok(self.tx.get::<tables::Receipts<Self::Receipt>>(id)?),
2221        )
2222    }
2223
2224    fn receipt_by_hash(&self, hash: TxHash) -> ProviderResult<Option<Self::Receipt>> {
2225        if let Some(id) = self.transaction_id(hash)? {
2226            self.receipt(id)
2227        } else {
2228            Ok(None)
2229        }
2230    }
2231
2232    fn receipts_by_block(
2233        &self,
2234        block: BlockHashOrNumber,
2235    ) -> ProviderResult<Option<Vec<Self::Receipt>>> {
2236        if let Some(number) = self.convert_hash_or_number(block)? &&
2237            let Some(body) = self.block_body_indices(number)?
2238        {
2239            let tx_range = body.tx_num_range();
2240            return if tx_range.is_empty() {
2241                Ok(Some(Vec::new()))
2242            } else {
2243                let receipts = self.receipts_by_tx_range(tx_range)?;
2244
2245                if receipts.len() != body.tx_count as usize {
2246                    return Ok(None)
2247                }
2248
2249                Ok(Some(receipts))
2250            }
2251        }
2252        Ok(None)
2253    }
2254
2255    fn receipts_by_tx_range(
2256        &self,
2257        range: impl RangeBounds<TxNumber>,
2258    ) -> ProviderResult<Vec<Self::Receipt>> {
2259        self.static_file_provider.get_range_with_static_file_or_database(
2260            StaticFileSegment::Receipts,
2261            to_range(range),
2262            |static_file, range, _| static_file.receipts_by_tx_range(range),
2263            |range, _| self.cursor_read_collect::<tables::Receipts<Self::Receipt>>(range),
2264            |_| true,
2265        )
2266    }
2267
2268    fn receipts_by_block_range(
2269        &self,
2270        block_range: RangeInclusive<BlockNumber>,
2271    ) -> ProviderResult<Vec<Vec<Self::Receipt>>> {
2272        if block_range.is_empty() {
2273            return Ok(Vec::new());
2274        }
2275
2276        // collect block body indices for each block in the range
2277        let range_len = block_range.end().saturating_sub(*block_range.start()) as usize + 1;
2278        let mut block_body_indices = Vec::with_capacity(range_len);
2279        for block_num in block_range {
2280            if let Some(indices) = self.block_body_indices(block_num)? {
2281                block_body_indices.push(indices);
2282            } else {
2283                // use default indices for missing blocks (empty block)
2284                block_body_indices.push(StoredBlockBodyIndices::default());
2285            }
2286        }
2287
2288        if block_body_indices.is_empty() {
2289            return Ok(Vec::new());
2290        }
2291
2292        // find blocks with transactions to determine transaction range
2293        let non_empty_blocks: Vec<_> =
2294            block_body_indices.iter().filter(|indices| indices.tx_count > 0).collect();
2295
2296        if non_empty_blocks.is_empty() {
2297            // all blocks are empty
2298            return Ok(vec![Vec::new(); block_body_indices.len()]);
2299        }
2300
2301        // calculate the overall transaction range
2302        let first_tx = non_empty_blocks[0].first_tx_num();
2303        let last_tx = non_empty_blocks[non_empty_blocks.len() - 1].last_tx_num();
2304
2305        // fetch all receipts in the transaction range
2306        let all_receipts = self.receipts_by_tx_range(first_tx..=last_tx)?;
2307        let mut receipts_iter = all_receipts.into_iter();
2308
2309        // distribute receipts to their respective blocks
2310        let mut result = Vec::with_capacity(block_body_indices.len());
2311        for indices in &block_body_indices {
2312            if indices.tx_count == 0 {
2313                result.push(Vec::new());
2314            } else {
2315                let block_receipts =
2316                    receipts_iter.by_ref().take(indices.tx_count as usize).collect();
2317                result.push(block_receipts);
2318            }
2319        }
2320
2321        Ok(result)
2322    }
2323}
2324
2325impl<TX: DbTx + 'static, N: NodeTypesForProvider> BlockBodyIndicesProvider
2326    for DatabaseProvider<TX, N>
2327{
2328    fn block_body_indices(&self, num: u64) -> ProviderResult<Option<StoredBlockBodyIndices>> {
2329        Ok(self.tx.get::<tables::BlockBodyIndices>(num)?)
2330    }
2331
2332    fn block_body_indices_range(
2333        &self,
2334        range: RangeInclusive<BlockNumber>,
2335    ) -> ProviderResult<Vec<StoredBlockBodyIndices>> {
2336        self.cursor_read_collect::<tables::BlockBodyIndices>(range)
2337    }
2338}
2339
2340impl<TX: DbTx, N: NodeTypes> StageCheckpointReader for DatabaseProvider<TX, N> {
2341    fn get_stage_checkpoint(&self, id: StageId) -> ProviderResult<Option<StageCheckpoint>> {
2342        Ok(if let Some(encoded) = id.get_pre_encoded() {
2343            self.tx.get_by_encoded_key::<tables::StageCheckpoints>(encoded)?
2344        } else {
2345            self.tx.get::<tables::StageCheckpoints>(id.to_string())?
2346        })
2347    }
2348
2349    /// Get stage checkpoint progress.
2350    fn get_stage_checkpoint_progress(&self, id: StageId) -> ProviderResult<Option<Vec<u8>>> {
2351        Ok(self.tx.get::<tables::StageCheckpointProgresses>(id.to_string())?)
2352    }
2353
2354    fn get_all_checkpoints(&self) -> ProviderResult<Vec<(String, StageCheckpoint)>> {
2355        self.tx
2356            .cursor_read::<tables::StageCheckpoints>()?
2357            .walk(None)?
2358            .collect::<Result<Vec<(String, StageCheckpoint)>, _>>()
2359            .map_err(ProviderError::Database)
2360    }
2361}
2362
2363impl<TX: DbTxMut + DbTx, N: NodeTypes> StageCheckpointWriter for DatabaseProvider<TX, N> {
2364    /// Save stage checkpoint.
2365    fn save_stage_checkpoint(
2366        &self,
2367        id: StageId,
2368        checkpoint: StageCheckpoint,
2369    ) -> ProviderResult<()> {
2370        if id.is_finish() {
2371            self.ensure_finish_may_advance(&checkpoint)?;
2372        }
2373        Ok(self.tx.put::<tables::StageCheckpoints>(id.to_string(), checkpoint)?)
2374    }
2375
2376    /// Save stage checkpoint progress.
2377    fn save_stage_checkpoint_progress(
2378        &self,
2379        id: StageId,
2380        checkpoint: Vec<u8>,
2381    ) -> ProviderResult<()> {
2382        Ok(self.tx.put::<tables::StageCheckpointProgresses>(id.to_string(), checkpoint)?)
2383    }
2384
2385    #[instrument(level = "debug", target = "providers::db", skip_all)]
2386    fn update_pipeline_stages(
2387        &self,
2388        block_number: BlockNumber,
2389        drop_stage_checkpoint: bool,
2390    ) -> ProviderResult<()> {
2391        let current = self.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default();
2392        let proposed = StageCheckpoint {
2393            block_number,
2394            ..if drop_stage_checkpoint { Default::default() } else { current }
2395        };
2396        self.ensure_finish_may_advance(&proposed)?;
2397
2398        // iterate over all existing stages in the table and update its progress.
2399        let mut cursor = self.tx.cursor_write::<tables::StageCheckpoints>()?;
2400        for stage_id in StageId::ALL {
2401            let (_, checkpoint) = cursor.seek_exact(stage_id.to_string())?.unwrap_or_default();
2402            cursor.upsert(
2403                stage_id.to_string(),
2404                &StageCheckpoint {
2405                    block_number,
2406                    ..if drop_stage_checkpoint { Default::default() } else { checkpoint }
2407                },
2408            )?;
2409        }
2410
2411        Ok(())
2412    }
2413}
2414
2415impl<TX: DbTxMut + DbTx, N: NodeTypes> DatabaseProvider<TX, N> {
2416    /// Updates pipeline checkpoints after an unwind while preserving an explicitly lagging
2417    /// state/trie frontier.
2418    fn update_pipeline_stages_after_unwind(
2419        &self,
2420        block_number: BlockNumber,
2421    ) -> ProviderResult<PersistenceFrontiers> {
2422        let partial_state_trie = self
2423            .get_stage_checkpoint(StageId::Finish)?
2424            .map(|checkpoint| {
2425                checkpoint
2426                    .finish_stage_checkpoint()
2427                    .and_then(|finish| finish.partial_state_trie())
2428                    .unwrap_or(checkpoint.block_number)
2429            })
2430            .unwrap_or(block_number)
2431            .min(block_number);
2432
2433        self.update_pipeline_stages(block_number, true)?;
2434        if partial_state_trie < block_number {
2435            self.save_stage_checkpoint(
2436                StageId::Finish,
2437                StageCheckpoint::new(block_number).with_finish_stage_checkpoint(FinishCheckpoint {
2438                    partial_state_trie: Some(partial_state_trie),
2439                }),
2440            )?;
2441        }
2442
2443        Ok(PersistenceFrontiers { db_tip: block_number, partial_state_trie })
2444    }
2445}
2446
2447impl<TX: DbTx + 'static, N: NodeTypes> StorageReader for DatabaseProvider<TX, N> {
2448    fn plain_state_storages(
2449        &self,
2450        addresses_with_keys: impl IntoIterator<Item = (Address, impl IntoIterator<Item = B256>)>,
2451    ) -> ProviderResult<Vec<(Address, Vec<StorageEntry>)>> {
2452        if self.cached_storage_settings().use_hashed_state() {
2453            let mut hashed_storage = self.tx.cursor_dup_read::<tables::HashedStorages>()?;
2454
2455            addresses_with_keys
2456                .into_iter()
2457                .map(|(address, storage)| {
2458                    let hashed_address = keccak256(address);
2459                    storage
2460                        .into_iter()
2461                        .map(|key| -> ProviderResult<_> {
2462                            let hashed_key = keccak256(key);
2463                            let value = hashed_storage
2464                                .seek_by_key_subkey(hashed_address, hashed_key)?
2465                                .filter(|v| v.key == hashed_key)
2466                                .map(|v| v.value)
2467                                .unwrap_or_default();
2468                            Ok(StorageEntry { key, value })
2469                        })
2470                        .collect::<ProviderResult<Vec<_>>>()
2471                        .map(|storage| (address, storage))
2472                })
2473                .collect::<ProviderResult<Vec<(_, _)>>>()
2474        } else {
2475            let mut plain_storage = self.tx.cursor_dup_read::<tables::PlainStorageState>()?;
2476
2477            addresses_with_keys
2478                .into_iter()
2479                .map(|(address, storage)| {
2480                    storage
2481                        .into_iter()
2482                        .map(|key| -> ProviderResult<_> {
2483                            Ok(plain_storage
2484                                .seek_by_key_subkey(address, key)?
2485                                .filter(|v| v.key == key)
2486                                .unwrap_or_else(|| StorageEntry { key, value: Default::default() }))
2487                        })
2488                        .collect::<ProviderResult<Vec<_>>>()
2489                        .map(|storage| (address, storage))
2490                })
2491                .collect::<ProviderResult<Vec<(_, _)>>>()
2492        }
2493    }
2494
2495    fn changed_storages_with_range(
2496        &self,
2497        range: RangeInclusive<BlockNumber>,
2498    ) -> ProviderResult<BTreeMap<Address, BTreeSet<B256>>> {
2499        if self.cached_storage_settings().storage_v2 {
2500            self.storage_changesets_range(range)?.into_iter().try_fold(
2501                BTreeMap::new(),
2502                |mut accounts: BTreeMap<Address, BTreeSet<B256>>, entry| {
2503                    let (BlockNumberAddress((_, address)), storage_entry) = entry;
2504                    accounts.entry(address).or_default().insert(storage_entry.key);
2505                    Ok(accounts)
2506                },
2507            )
2508        } else {
2509            self.tx
2510                .cursor_read::<tables::StorageChangeSets>()?
2511                .walk_range(BlockNumberAddress::range(range))?
2512                // fold all storages and save its old state so we can remove it from HashedStorage
2513                // it is needed as it is dup table.
2514                .try_fold(
2515                    BTreeMap::new(),
2516                    |mut accounts: BTreeMap<Address, BTreeSet<B256>>, entry| {
2517                        let (BlockNumberAddress((_, address)), storage_entry) = entry?;
2518                        accounts.entry(address).or_default().insert(storage_entry.key);
2519                        Ok(accounts)
2520                    },
2521                )
2522        }
2523    }
2524
2525    fn changed_storages_and_blocks_with_range(
2526        &self,
2527        range: RangeInclusive<BlockNumber>,
2528    ) -> ProviderResult<BTreeMap<(Address, B256), Vec<u64>>> {
2529        if self.cached_storage_settings().storage_v2 {
2530            self.storage_changesets_range(range)?.into_iter().try_fold(
2531                BTreeMap::new(),
2532                |mut storages: BTreeMap<(Address, B256), Vec<u64>>, (index, storage)| {
2533                    storages
2534                        .entry((index.address(), storage.key))
2535                        .or_default()
2536                        .push(index.block_number());
2537                    Ok(storages)
2538                },
2539            )
2540        } else {
2541            let mut changeset_cursor = self.tx.cursor_read::<tables::StorageChangeSets>()?;
2542
2543            let storage_changeset_lists =
2544                changeset_cursor.walk_range(BlockNumberAddress::range(range))?.try_fold(
2545                    BTreeMap::new(),
2546                    |mut storages: BTreeMap<(Address, B256), Vec<u64>>,
2547                     entry|
2548                     -> ProviderResult<_> {
2549                        let (index, storage) = entry?;
2550                        storages
2551                            .entry((index.address(), storage.key))
2552                            .or_default()
2553                            .push(index.block_number());
2554                        Ok(storages)
2555                    },
2556                )?;
2557
2558            Ok(storage_changeset_lists)
2559        }
2560    }
2561}
2562
2563impl<TX: DbTxMut + DbTx + 'static, N: NodeTypesForProvider> StateWriter
2564    for DatabaseProvider<TX, N>
2565{
2566    type Receipt = ReceiptTy<N>;
2567
2568    #[instrument(level = "debug", target = "providers::db", skip_all)]
2569    fn write_state<'a>(
2570        &self,
2571        execution_outcome: impl Into<WriteStateInput<'a, Self::Receipt>>,
2572        is_value_known: OriginalValuesKnown,
2573        config: StateWriteConfig,
2574    ) -> ProviderResult<()> {
2575        let execution_outcome = execution_outcome.into();
2576
2577        if self.cached_storage_settings().use_hashed_state() &&
2578            !config.write_receipts &&
2579            !config.write_account_changesets &&
2580            !config.write_storage_changesets
2581        {
2582            // In storage v2 with all outputs directed to static files, plain state and changesets
2583            // are written elsewhere. Only bytecodes need MDBX writes, so skip the expensive
2584            // to_plain_state_and_reverts conversion that iterates all accounts and storage.
2585            self.write_bytecodes(
2586                execution_outcome.state().contracts.iter().map(|(h, b)| (*h, Bytecode(b.clone()))),
2587            )?;
2588            return Ok(());
2589        }
2590
2591        let first_block = execution_outcome.first_block();
2592        let (plain_state, reverts) =
2593            execution_outcome.state().to_plain_state_and_reverts(is_value_known);
2594
2595        self.write_state_reverts(reverts, first_block, config)?;
2596        self.write_state_changes(plain_state)?;
2597
2598        if !config.write_receipts {
2599            return Ok(());
2600        }
2601
2602        let block_count = execution_outcome.len() as u64;
2603        let last_block = execution_outcome.last_block();
2604        let block_range = first_block..=last_block;
2605
2606        let tip = self.last_block_number()?.max(last_block);
2607
2608        // Fetch the first transaction number for each block in the range
2609        let block_indices: Vec<_> = self
2610            .block_body_indices_range(block_range)?
2611            .into_iter()
2612            .map(|b| b.first_tx_num)
2613            .collect();
2614
2615        // Ensure all expected blocks are present.
2616        if block_indices.len() < block_count as usize {
2617            let missing_blocks = block_count - block_indices.len() as u64;
2618            return Err(ProviderError::BlockBodyIndicesNotFound(
2619                last_block.saturating_sub(missing_blocks - 1),
2620            ));
2621        }
2622
2623        let mut receipts_writer = EitherWriter::new_receipts(self, first_block)?;
2624
2625        let has_contract_log_filter = !self.prune_modes.receipts_log_filter.is_empty();
2626        let contract_log_pruner = self.prune_modes.receipts_log_filter.group_by_block(tip, None)?;
2627
2628        // All receipts from the last 128 blocks are required for blockchain tree, even with
2629        // [`PruneSegment::ContractLogs`].
2630        //
2631        // Receipts can only be skipped if we're dealing with legacy nodes that write them to
2632        // Database, OR if receipts_in_static_files is enabled but no receipts exist in static
2633        // files yet. Once receipts exist in static files, we must continue writing to maintain
2634        // continuity and have no gaps.
2635        let prunable_receipts = (EitherWriter::receipts_destination(self).is_database() ||
2636            self.static_file_provider()
2637                .get_highest_static_file_tx(StaticFileSegment::Receipts)
2638                .is_none()) &&
2639            PruneMode::Distance(self.minimum_pruning_distance).should_prune(first_block, tip);
2640
2641        // Prepare set of addresses which logs should not be pruned.
2642        let mut allowed_addresses: AddressSet = AddressSet::default();
2643        for (_, addresses) in contract_log_pruner.range(..first_block) {
2644            allowed_addresses.extend(addresses.iter().copied());
2645        }
2646
2647        for (idx, (receipts, first_tx_index)) in
2648            execution_outcome.receipts().zip(block_indices).enumerate()
2649        {
2650            let block_number = first_block + idx as u64;
2651
2652            // Increment block number for receipts static file writer
2653            receipts_writer.increment_block(block_number)?;
2654
2655            // Skip writing receipts if pruning configuration requires us to.
2656            if prunable_receipts &&
2657                self.prune_modes
2658                    .receipts
2659                    .is_some_and(|mode| mode.should_prune(block_number, tip))
2660            {
2661                continue
2662            }
2663
2664            // If there are new addresses to retain after this block number, track them
2665            if let Some(new_addresses) = contract_log_pruner.get(&block_number) {
2666                allowed_addresses.extend(new_addresses.iter().copied());
2667            }
2668
2669            for (idx, receipt) in receipts.iter().enumerate() {
2670                let receipt_idx = first_tx_index + idx as u64;
2671                // Skip writing receipt if log filter is active and it does not have any logs to
2672                // retain
2673                if prunable_receipts &&
2674                    has_contract_log_filter &&
2675                    !receipt.logs().iter().any(|log| allowed_addresses.contains(&log.address))
2676                {
2677                    continue
2678                }
2679
2680                receipts_writer.append_receipt(receipt_idx, receipt)?;
2681            }
2682        }
2683
2684        Ok(())
2685    }
2686
2687    fn write_state_reverts(
2688        &self,
2689        reverts: PlainStateReverts,
2690        first_block: BlockNumber,
2691        config: StateWriteConfig,
2692    ) -> ProviderResult<()> {
2693        // Write storage changes
2694        if config.write_storage_changesets {
2695            tracing::trace!("Writing storage changes");
2696            let mut storages_cursor =
2697                self.tx_ref().cursor_dup_write::<tables::PlainStorageState>()?;
2698            for (block_index, mut storage_changes) in reverts.storage.into_iter().enumerate() {
2699                let block_number = first_block + block_index as BlockNumber;
2700
2701                tracing::trace!(block_number, "Writing block change");
2702                // sort changes by address.
2703                storage_changes.par_sort_unstable_by_key(|a| a.address);
2704                let total_changes =
2705                    storage_changes.iter().map(|change| change.storage_revert.len()).sum();
2706                let mut changeset = Vec::with_capacity(total_changes);
2707                for PlainStorageRevert { address, wiped, storage_revert } in storage_changes {
2708                    let mut storage = storage_revert
2709                        .into_iter()
2710                        .map(|(k, v)| (B256::from(k), v))
2711                        .collect::<Vec<_>>();
2712                    // sort storage slots by key.
2713                    storage.par_sort_unstable_by_key(|a| a.0);
2714
2715                    // If we are writing the primary storage wipe transition, the pre-existing
2716                    // storage state has to be taken from the database and written to storage
2717                    // history. See [StorageWipe::Primary] for more details.
2718                    //
2719                    // TODO(mediocregopher): This could be rewritten in a way which doesn't
2720                    // require collecting wiped entries into a Vec like this, see
2721                    // `write_storage_trie_changesets`.
2722                    let mut wiped_storage = Vec::new();
2723                    if wiped {
2724                        tracing::trace!(?address, "Wiping storage");
2725                        if let Some((_, entry)) = storages_cursor.seek_exact(address)? {
2726                            wiped_storage.push((entry.key, entry.value));
2727                            while let Some(entry) = storages_cursor.next_dup_val()? {
2728                                wiped_storage.push((entry.key, entry.value))
2729                            }
2730                        }
2731                    }
2732
2733                    tracing::trace!(?address, ?storage, "Writing storage reverts");
2734                    for (key, value) in StorageRevertsIter::new(storage, wiped_storage) {
2735                        changeset.push(StorageBeforeTx { address, key, value });
2736                    }
2737                }
2738
2739                let mut storage_changesets_writer =
2740                    EitherWriter::new_storage_changesets(self, block_number)?;
2741                storage_changesets_writer.append_storage_changeset(block_number, changeset)?;
2742            }
2743        }
2744
2745        if !config.write_account_changesets {
2746            return Ok(());
2747        }
2748
2749        // Write account changes
2750        tracing::trace!(?first_block, "Writing account changes");
2751        for (block_index, account_block_reverts) in reverts.accounts.into_iter().enumerate() {
2752            let block_number = first_block + block_index as BlockNumber;
2753            let changeset = account_block_reverts
2754                .into_iter()
2755                .map(|(address, info)| AccountBeforeTx { address, info: info.map(Into::into) })
2756                .collect::<Vec<_>>();
2757            let mut account_changesets_writer =
2758                EitherWriter::new_account_changesets(self, block_number)?;
2759
2760            account_changesets_writer.append_account_changeset(block_number, changeset)?;
2761        }
2762
2763        Ok(())
2764    }
2765
2766    fn write_state_changes(&self, mut changes: StateChangeset) -> ProviderResult<()> {
2767        // sort all entries so they can be written to database in more performant way.
2768        // and take smaller memory footprint.
2769        changes.accounts.par_sort_by_key(|a| a.0);
2770        changes.storage.par_sort_by_key(|a| a.address);
2771        changes.contracts.par_sort_by_key(|a| a.0);
2772
2773        if !self.cached_storage_settings().use_hashed_state() {
2774            // Write new account state
2775            tracing::trace!(len = changes.accounts.len(), "Writing new account state");
2776            let mut accounts_cursor = self.tx_ref().cursor_write::<tables::PlainAccountState>()?;
2777            // write account to database.
2778            for (address, account) in changes.accounts {
2779                if let Some(account) = account {
2780                    tracing::trace!(?address, "Updating plain state account");
2781                    accounts_cursor.upsert(address, &account.into())?;
2782                } else if accounts_cursor.seek_exact(address)?.is_some() {
2783                    tracing::trace!(?address, "Deleting plain state account");
2784                    accounts_cursor.delete_current()?;
2785                }
2786            }
2787
2788            // Write new storage state and wipe storage if needed.
2789            tracing::trace!(len = changes.storage.len(), "Writing new storage state");
2790            let mut storages_cursor =
2791                self.tx_ref().cursor_dup_write::<tables::PlainStorageState>()?;
2792            for PlainStorageChangeset { address, wipe_storage, storage } in changes.storage {
2793                // Wiping of storage.
2794                if wipe_storage && storages_cursor.seek_exact(address)?.is_some() {
2795                    storages_cursor.delete_current_duplicates()?;
2796                }
2797                // cast storages to B256.
2798                let mut storage = storage
2799                    .into_iter()
2800                    .map(|(k, value)| StorageEntry { key: k.into(), value })
2801                    .collect::<Vec<_>>();
2802                // sort storage slots by key.
2803                storage.par_sort_unstable_by_key(|a| a.key);
2804
2805                for entry in storage {
2806                    tracing::trace!(?address, ?entry.key, "Updating plain state storage");
2807                    if let Some(db_entry) =
2808                        storages_cursor.seek_by_key_subkey(address, entry.key)? &&
2809                        db_entry.key == entry.key
2810                    {
2811                        storages_cursor.delete_current()?;
2812                    }
2813
2814                    if !entry.value.is_zero() {
2815                        storages_cursor.upsert(address, &entry)?;
2816                    }
2817                }
2818            }
2819        }
2820
2821        // Write bytecode
2822        tracing::trace!(len = changes.contracts.len(), "Writing bytecodes");
2823        self.write_bytecodes(
2824            changes.contracts.into_iter().map(|(hash, bytecode)| (hash, Bytecode(bytecode))),
2825        )?;
2826
2827        Ok(())
2828    }
2829
2830    #[instrument(level = "debug", target = "providers::db", skip_all)]
2831    fn write_hashed_state(&self, hashed_state: &HashedPostStateSorted) -> ProviderResult<()> {
2832        // Write hashed account updates.
2833        let mut hashed_accounts_cursor = self.tx_ref().cursor_write::<tables::HashedAccounts>()?;
2834        for (hashed_address, account) in hashed_state.accounts() {
2835            if let Some(account) = account {
2836                hashed_accounts_cursor.upsert(*hashed_address, account)?;
2837            } else if hashed_accounts_cursor.seek_exact(*hashed_address)?.is_some() {
2838                hashed_accounts_cursor.delete_current()?;
2839            }
2840        }
2841
2842        // Write hashed storage changes.
2843        let sorted_storages = hashed_state.account_storages().iter().sorted_by_key(|(key, _)| *key);
2844        let mut hashed_storage_cursor =
2845            self.tx_ref().cursor_dup_write::<tables::HashedStorages>()?;
2846        for (hashed_address, storage) in sorted_storages {
2847            for (hashed_slot, value) in storage.storage_slots_ref() {
2848                let entry = StorageEntry { key: *hashed_slot, value: *value };
2849
2850                if let Some(db_entry) =
2851                    hashed_storage_cursor.seek_by_key_subkey(*hashed_address, entry.key)? &&
2852                    db_entry.key == entry.key
2853                {
2854                    hashed_storage_cursor.delete_current()?;
2855                }
2856
2857                if !entry.value.is_zero() {
2858                    hashed_storage_cursor.upsert(*hashed_address, &entry)?;
2859                }
2860            }
2861        }
2862
2863        Ok(())
2864    }
2865
2866    /// Remove the last N blocks of state.
2867    ///
2868    /// The latest state will be unwound
2869    ///
2870    /// 1. Read the retained block's [`BlockBodyIndices`][tables::BlockBodyIndices] entry to get the
2871    ///    first transaction id to remove.
2872    /// 2. Iterate over the [`StorageChangeSets`][tables::StorageChangeSets] table and the
2873    ///    [`AccountChangeSets`][tables::AccountChangeSets] tables in reverse order to reconstruct
2874    ///    the changesets.
2875    ///    - In order to have both the old and new values in the changesets, we also access the
2876    ///      plain state tables.
2877    /// 3. While iterating over the changeset tables, if we encounter a new account or storage slot,
2878    ///    we:
2879    ///     1. Take the old value from the changeset
2880    ///     2. Take the new value from the plain state
2881    ///     3. Save the old value to the local state
2882    /// 4. While iterating over the changeset tables, if we encounter an account/storage slot we
2883    ///    have seen before we:
2884    ///     1. Take the old value from the changeset
2885    ///     2. Take the new value from the local state
2886    ///     3. Set the local state to the value in the changeset
2887    fn remove_state_above(&self, block: BlockNumber) -> ProviderResult<()> {
2888        let range = block + 1..=self.last_block_number()?;
2889
2890        if range.is_empty() {
2891            return Ok(());
2892        }
2893
2894        // get transaction receipts
2895        let from_transaction_num = self.next_tx_num_after_block(block)?;
2896
2897        let storage_range = BlockNumberAddress::range(range.clone());
2898        let storage_changeset = if self.cached_storage_settings().storage_v2 {
2899            let changesets = self.storage_changesets_range(range.clone())?;
2900            let mut changeset_writer =
2901                self.static_file_provider.latest_writer(StaticFileSegment::StorageChangeSets)?;
2902            changeset_writer.prune_storage_changesets(block)?;
2903            changesets
2904        } else {
2905            self.take::<tables::StorageChangeSets>(storage_range)?.into_iter().collect()
2906        };
2907        let account_changeset = if self.cached_storage_settings().storage_v2 {
2908            let changesets = self.account_changesets_range(range)?;
2909            let mut changeset_writer =
2910                self.static_file_provider.latest_writer(StaticFileSegment::AccountChangeSets)?;
2911            changeset_writer.prune_account_changesets(block)?;
2912            changesets
2913        } else {
2914            self.take::<tables::AccountChangeSets>(range)?
2915        };
2916
2917        if self.cached_storage_settings().use_hashed_state() {
2918            let mut hashed_accounts_cursor = self.tx.cursor_write::<tables::HashedAccounts>()?;
2919            let mut hashed_storage_cursor = self.tx.cursor_dup_write::<tables::HashedStorages>()?;
2920
2921            let (state, _) = self.populate_bundle_state_hashed(
2922                account_changeset,
2923                storage_changeset,
2924                &mut hashed_accounts_cursor,
2925                &mut hashed_storage_cursor,
2926            )?;
2927
2928            for (address, (old_account, new_account, storage)) in &state {
2929                if old_account != new_account {
2930                    let hashed_address = keccak256(address);
2931                    let existing_entry = hashed_accounts_cursor.seek_exact(hashed_address)?;
2932                    if let Some(account) = old_account {
2933                        hashed_accounts_cursor.upsert(hashed_address, account)?;
2934                    } else if existing_entry.is_some() {
2935                        hashed_accounts_cursor.delete_current()?;
2936                    }
2937                }
2938
2939                for (storage_key, (old_storage_value, _new_storage_value)) in storage {
2940                    let hashed_address = keccak256(address);
2941                    let hashed_storage_key = keccak256(storage_key);
2942                    let storage_entry =
2943                        StorageEntry { key: hashed_storage_key, value: *old_storage_value };
2944                    if hashed_storage_cursor
2945                        .seek_by_key_subkey(hashed_address, hashed_storage_key)?
2946                        .is_some_and(|s| s.key == hashed_storage_key)
2947                    {
2948                        hashed_storage_cursor.delete_current()?
2949                    }
2950
2951                    if !old_storage_value.is_zero() {
2952                        hashed_storage_cursor.upsert(hashed_address, &storage_entry)?;
2953                    }
2954                }
2955            }
2956        } else {
2957            // This is not working for blocks that are not at tip. as plain state is not the last
2958            // state of end range. We should rename the functions or add support to access
2959            // History state. Accessing history state can be tricky but we are not gaining
2960            // anything.
2961            let mut plain_accounts_cursor = self.tx.cursor_write::<tables::PlainAccountState>()?;
2962            let mut plain_storage_cursor =
2963                self.tx.cursor_dup_write::<tables::PlainStorageState>()?;
2964
2965            let (state, _) = self.populate_bundle_state_plain(
2966                account_changeset,
2967                storage_changeset,
2968                &mut plain_accounts_cursor,
2969                &mut plain_storage_cursor,
2970            )?;
2971
2972            for (address, (old_account, new_account, storage)) in &state {
2973                if old_account != new_account {
2974                    let existing_entry = plain_accounts_cursor.seek_exact(*address)?;
2975                    if let Some(account) = old_account {
2976                        plain_accounts_cursor.upsert(*address, account)?;
2977                    } else if existing_entry.is_some() {
2978                        plain_accounts_cursor.delete_current()?;
2979                    }
2980                }
2981
2982                for (storage_key, (old_storage_value, _new_storage_value)) in storage {
2983                    let storage_entry =
2984                        StorageEntry { key: *storage_key, value: *old_storage_value };
2985                    if plain_storage_cursor
2986                        .seek_by_key_subkey(*address, *storage_key)?
2987                        .is_some_and(|s| s.key == *storage_key)
2988                    {
2989                        plain_storage_cursor.delete_current()?
2990                    }
2991
2992                    if !old_storage_value.is_zero() {
2993                        plain_storage_cursor.upsert(*address, &storage_entry)?;
2994                    }
2995                }
2996            }
2997        }
2998
2999        self.remove_receipts_from(from_transaction_num, block)?;
3000
3001        Ok(())
3002    }
3003
3004    /// Take the last N blocks of state, recreating the [`ExecutionOutcome`].
3005    ///
3006    /// The latest state will be unwound and returned back with all the blocks
3007    ///
3008    /// 1. Iterate over the [`BlockBodyIndices`][tables::BlockBodyIndices] table to get all the
3009    ///    transaction ids.
3010    /// 2. Iterate over the [`StorageChangeSets`][tables::StorageChangeSets] table and the
3011    ///    [`AccountChangeSets`][tables::AccountChangeSets] tables in reverse order to reconstruct
3012    ///    the changesets.
3013    ///    - In order to have both the old and new values in the changesets, we also access the
3014    ///      plain state tables.
3015    /// 3. While iterating over the changeset tables, if we encounter a new account or storage slot,
3016    ///    we:
3017    ///     1. Take the old value from the changeset
3018    ///     2. Take the new value from the plain state
3019    ///     3. Save the old value to the local state
3020    /// 4. While iterating over the changeset tables, if we encounter an account/storage slot we
3021    ///    have seen before we:
3022    ///     1. Take the old value from the changeset
3023    ///     2. Take the new value from the local state
3024    ///     3. Set the local state to the value in the changeset
3025    fn take_state_above(
3026        &self,
3027        block: BlockNumber,
3028    ) -> ProviderResult<ExecutionOutcome<Self::Receipt>> {
3029        let range = block + 1..=self.last_block_number()?;
3030
3031        if range.is_empty() {
3032            return Ok(ExecutionOutcome::default())
3033        }
3034        let start_block_number = *range.start();
3035
3036        // We are not removing block meta as it is used to get block changesets.
3037        let block_bodies = self.block_body_indices_range(range.clone())?;
3038
3039        // get transaction receipts
3040        let from_transaction_num =
3041            block_bodies.first().expect("already checked if there are blocks").first_tx_num();
3042        let to_transaction_num =
3043            block_bodies.last().expect("already checked if there are blocks").last_tx_num();
3044
3045        let storage_range = BlockNumberAddress::range(range.clone());
3046        let storage_changeset = if let Some(highest_block) = self
3047            .static_file_provider
3048            .get_highest_static_file_block(StaticFileSegment::StorageChangeSets) &&
3049            self.cached_storage_settings().storage_v2
3050        {
3051            let changesets = self.storage_changesets_range(block + 1..=highest_block)?;
3052            let mut changeset_writer =
3053                self.static_file_provider.latest_writer(StaticFileSegment::StorageChangeSets)?;
3054            changeset_writer.prune_storage_changesets(block)?;
3055            changesets
3056        } else {
3057            self.take::<tables::StorageChangeSets>(storage_range)?.into_iter().collect()
3058        };
3059
3060        // if there are static files for this segment, prune them.
3061        let highest_changeset_block = self
3062            .static_file_provider
3063            .get_highest_static_file_block(StaticFileSegment::AccountChangeSets);
3064        let account_changeset = if let Some(highest_block) = highest_changeset_block &&
3065            self.cached_storage_settings().storage_v2
3066        {
3067            // TODO: add a `take` method that removes and returns the items instead of doing this
3068            let changesets = self.account_changesets_range(block + 1..highest_block + 1)?;
3069            let mut changeset_writer =
3070                self.static_file_provider.latest_writer(StaticFileSegment::AccountChangeSets)?;
3071            changeset_writer.prune_account_changesets(block)?;
3072
3073            changesets
3074        } else {
3075            // Have to remove from static files if they exist, otherwise remove using `take` for the
3076            // changeset tables
3077            self.take::<tables::AccountChangeSets>(range)?
3078        };
3079
3080        let (state, reverts) = if self.cached_storage_settings().use_hashed_state() {
3081            let mut hashed_accounts_cursor = self.tx.cursor_write::<tables::HashedAccounts>()?;
3082            let mut hashed_storage_cursor = self.tx.cursor_dup_write::<tables::HashedStorages>()?;
3083
3084            let (state, reverts) = self.populate_bundle_state_hashed(
3085                account_changeset,
3086                storage_changeset,
3087                &mut hashed_accounts_cursor,
3088                &mut hashed_storage_cursor,
3089            )?;
3090
3091            for (address, (old_account, new_account, storage)) in &state {
3092                if old_account != new_account {
3093                    let hashed_address = keccak256(address);
3094                    let existing_entry = hashed_accounts_cursor.seek_exact(hashed_address)?;
3095                    if let Some(account) = old_account {
3096                        hashed_accounts_cursor.upsert(hashed_address, account)?;
3097                    } else if existing_entry.is_some() {
3098                        hashed_accounts_cursor.delete_current()?;
3099                    }
3100                }
3101
3102                for (storage_key, (old_storage_value, _new_storage_value)) in storage {
3103                    let hashed_address = keccak256(address);
3104                    let hashed_storage_key = keccak256(storage_key);
3105                    let storage_entry =
3106                        StorageEntry { key: hashed_storage_key, value: *old_storage_value };
3107                    if hashed_storage_cursor
3108                        .seek_by_key_subkey(hashed_address, hashed_storage_key)?
3109                        .is_some_and(|s| s.key == hashed_storage_key)
3110                    {
3111                        hashed_storage_cursor.delete_current()?
3112                    }
3113
3114                    if !old_storage_value.is_zero() {
3115                        hashed_storage_cursor.upsert(hashed_address, &storage_entry)?;
3116                    }
3117                }
3118            }
3119
3120            (state, reverts)
3121        } else {
3122            // This is not working for blocks that are not at tip. as plain state is not the last
3123            // state of end range. We should rename the functions or add support to access
3124            // History state. Accessing history state can be tricky but we are not gaining
3125            // anything.
3126            let mut plain_accounts_cursor = self.tx.cursor_write::<tables::PlainAccountState>()?;
3127            let mut plain_storage_cursor =
3128                self.tx.cursor_dup_write::<tables::PlainStorageState>()?;
3129
3130            let (state, reverts) = self.populate_bundle_state_plain(
3131                account_changeset,
3132                storage_changeset,
3133                &mut plain_accounts_cursor,
3134                &mut plain_storage_cursor,
3135            )?;
3136
3137            for (address, (old_account, new_account, storage)) in &state {
3138                if old_account != new_account {
3139                    let existing_entry = plain_accounts_cursor.seek_exact(*address)?;
3140                    if let Some(account) = old_account {
3141                        plain_accounts_cursor.upsert(*address, account)?;
3142                    } else if existing_entry.is_some() {
3143                        plain_accounts_cursor.delete_current()?;
3144                    }
3145                }
3146
3147                for (storage_key, (old_storage_value, _new_storage_value)) in storage {
3148                    let storage_entry =
3149                        StorageEntry { key: *storage_key, value: *old_storage_value };
3150                    if plain_storage_cursor
3151                        .seek_by_key_subkey(*address, *storage_key)?
3152                        .is_some_and(|s| s.key == *storage_key)
3153                    {
3154                        plain_storage_cursor.delete_current()?
3155                    }
3156
3157                    if !old_storage_value.is_zero() {
3158                        plain_storage_cursor.upsert(*address, &storage_entry)?;
3159                    }
3160                }
3161            }
3162
3163            (state, reverts)
3164        };
3165
3166        // Collect receipts into tuples (tx_num, receipt) to correctly handle pruned receipts
3167        let mut receipts_iter = self
3168            .static_file_provider
3169            .get_range_with_static_file_or_database(
3170                StaticFileSegment::Receipts,
3171                from_transaction_num..to_transaction_num + 1,
3172                |static_file, range, _| {
3173                    static_file
3174                        .receipts_by_tx_range(range.clone())
3175                        .map(|r| range.into_iter().zip(r).collect())
3176                },
3177                |range, _| {
3178                    self.tx
3179                        .cursor_read::<tables::Receipts<Self::Receipt>>()?
3180                        .walk_range(range)?
3181                        .map(|r| r.map_err(Into::into))
3182                        .collect()
3183                },
3184                |_| true,
3185            )?
3186            .into_iter()
3187            .peekable();
3188
3189        let mut receipts = Vec::with_capacity(block_bodies.len());
3190        // loop break if we are at the end of the blocks.
3191        for block_body in block_bodies {
3192            let mut block_receipts = Vec::with_capacity(block_body.tx_count as usize);
3193            for num in block_body.tx_num_range() {
3194                if receipts_iter.peek().is_some_and(|(n, _)| *n == num) {
3195                    block_receipts.push(receipts_iter.next().unwrap().1);
3196                }
3197            }
3198            receipts.push(block_receipts);
3199        }
3200
3201        self.remove_receipts_from(from_transaction_num, block)?;
3202
3203        Ok(ExecutionOutcome::new_init(
3204            state,
3205            reverts,
3206            Vec::new(),
3207            receipts,
3208            start_block_number,
3209            Vec::new(),
3210        ))
3211    }
3212}
3213
3214impl<TX: DbTxMut + DbTx + 'static, N: NodeTypes> DatabaseProvider<TX, N> {
3215    fn write_account_trie_updates<A: TrieTableAdapter>(
3216        tx: &TX,
3217        trie_updates: &TrieUpdatesSorted,
3218        num_entries: &mut usize,
3219    ) -> ProviderResult<()>
3220    where
3221        TX: DbTxMut,
3222    {
3223        let mut account_trie_cursor = tx.cursor_write::<A::AccountTrieTable>()?;
3224        // Process sorted account nodes
3225        for (key, updated_node) in trie_updates.account_nodes_ref() {
3226            let nibbles = A::AccountKey::from(*key);
3227            match updated_node {
3228                Some(node) => {
3229                    if !key.is_empty() {
3230                        *num_entries += 1;
3231                        account_trie_cursor.upsert(nibbles, node)?;
3232                    }
3233                }
3234                None => {
3235                    *num_entries += 1;
3236                    if account_trie_cursor.seek_exact(nibbles)?.is_some() {
3237                        account_trie_cursor.delete_current()?;
3238                    }
3239                }
3240            }
3241        }
3242        Ok(())
3243    }
3244
3245    fn write_storage_tries<A: TrieTableAdapter>(
3246        tx: &TX,
3247        storage_tries: Vec<(&B256, &StorageTrieUpdatesSorted)>,
3248        num_entries: &mut usize,
3249    ) -> ProviderResult<()>
3250    where
3251        TX: DbTxMut,
3252    {
3253        let mut cursor = tx.cursor_dup_write::<A::StorageTrieTable>()?;
3254        for (hashed_address, storage_trie_updates) in storage_tries {
3255            let mut db_storage_trie_cursor: DatabaseStorageTrieCursor<_, A> =
3256                DatabaseStorageTrieCursor::new(cursor, *hashed_address);
3257            *num_entries +=
3258                db_storage_trie_cursor.write_storage_trie_updates_sorted(storage_trie_updates)?;
3259            cursor = db_storage_trie_cursor.cursor;
3260        }
3261        Ok(())
3262    }
3263}
3264
3265impl<TX: DbTxMut + DbTx + 'static, N: NodeTypes> TrieWriter for DatabaseProvider<TX, N> {
3266    /// Writes trie updates to the database with already sorted updates.
3267    ///
3268    /// Returns the number of entries modified.
3269    #[instrument(level = "debug", target = "providers::db", skip_all)]
3270    fn write_trie_updates_sorted(&self, trie_updates: &TrieUpdatesSorted) -> ProviderResult<usize> {
3271        if trie_updates.is_empty() {
3272            return Ok(0)
3273        }
3274
3275        // Track the number of inserted entries.
3276        let mut num_entries = 0;
3277
3278        reth_trie_db::with_adapter!(self, |A| {
3279            Self::write_account_trie_updates::<A>(self.tx_ref(), trie_updates, &mut num_entries)?;
3280        });
3281
3282        num_entries +=
3283            self.write_storage_trie_updates_sorted(trie_updates.storage_tries_ref().iter())?;
3284
3285        Ok(num_entries)
3286    }
3287}
3288
3289impl<TX: DbTxMut + DbTx + 'static, N: NodeTypes> StorageTrieWriter for DatabaseProvider<TX, N> {
3290    /// Writes storage trie updates from the given storage trie map with already sorted updates.
3291    ///
3292    /// Expects the storage trie updates to already be sorted by the hashed address key.
3293    ///
3294    /// Returns the number of entries modified.
3295    fn write_storage_trie_updates_sorted<'a>(
3296        &self,
3297        storage_tries: impl Iterator<Item = (&'a B256, &'a StorageTrieUpdatesSorted)>,
3298    ) -> ProviderResult<usize> {
3299        let mut num_entries = 0;
3300        let mut storage_tries = storage_tries.collect::<Vec<_>>();
3301        storage_tries.sort_unstable_by(|a, b| a.0.cmp(b.0));
3302        reth_trie_db::with_adapter!(self, |A| {
3303            Self::write_storage_tries::<A>(self.tx_ref(), storage_tries, &mut num_entries)?;
3304        });
3305        Ok(num_entries)
3306    }
3307}
3308
3309impl<TX: DbTxMut + DbTx + 'static, N: NodeTypes> HashingWriter for DatabaseProvider<TX, N> {
3310    #[allow(clippy::clone_on_copy)]
3311    fn unwind_account_hashing<'a>(
3312        &self,
3313        changesets: impl Iterator<Item = &'a (BlockNumber, AccountBeforeTx)>,
3314    ) -> ProviderResult<BTreeMap<B256, Option<Account>>> {
3315        // Aggregate all block changesets and make a list of accounts that have been changed.
3316        // Note that collecting and then reversing the order is necessary to ensure that the
3317        // changes are applied in the correct order.
3318        let hashed_accounts = changesets
3319            .into_iter()
3320            .map(|(_, e)| (keccak256(e.address), e.info.clone()))
3321            .collect::<Vec<_>>()
3322            .into_iter()
3323            .rev()
3324            .collect::<BTreeMap<_, _>>();
3325
3326        // Apply values to HashedState, and remove the account if it's None.
3327        let mut hashed_accounts_cursor = self.tx.cursor_write::<tables::HashedAccounts>()?;
3328        for (hashed_address, account) in &hashed_accounts {
3329            if let Some(account) = account {
3330                hashed_accounts_cursor.upsert(*hashed_address, account)?;
3331            } else if hashed_accounts_cursor.seek_exact(*hashed_address)?.is_some() {
3332                hashed_accounts_cursor.delete_current()?;
3333            }
3334        }
3335
3336        Ok(hashed_accounts)
3337    }
3338
3339    fn unwind_account_hashing_range(
3340        &self,
3341        range: impl RangeBounds<BlockNumber>,
3342    ) -> ProviderResult<BTreeMap<B256, Option<Account>>> {
3343        let changesets = self.account_changesets_range(range)?;
3344        self.unwind_account_hashing(changesets.iter())
3345    }
3346
3347    fn insert_account_for_hashing(
3348        &self,
3349        changesets: impl IntoIterator<Item = (Address, Option<Account>)>,
3350    ) -> ProviderResult<BTreeMap<B256, Option<Account>>> {
3351        let mut hashed_accounts_cursor = self.tx.cursor_write::<tables::HashedAccounts>()?;
3352        let hashed_accounts =
3353            changesets.into_iter().map(|(ad, ac)| (keccak256(ad), ac)).collect::<BTreeMap<_, _>>();
3354        for (hashed_address, account) in &hashed_accounts {
3355            if let Some(account) = account {
3356                hashed_accounts_cursor.upsert(*hashed_address, account)?;
3357            } else if hashed_accounts_cursor.seek_exact(*hashed_address)?.is_some() {
3358                hashed_accounts_cursor.delete_current()?;
3359            }
3360        }
3361        Ok(hashed_accounts)
3362    }
3363
3364    fn unwind_storage_hashing(
3365        &self,
3366        changesets: impl Iterator<Item = (BlockNumberAddress, StorageEntry)>,
3367    ) -> ProviderResult<B256Map<BTreeSet<B256>>> {
3368        // Aggregate all block changesets and make list of accounts that have been changed.
3369        let mut hashed_storages = changesets
3370            .into_iter()
3371            .map(|(BlockNumberAddress((_, address)), storage_entry)| {
3372                let hashed_key = keccak256(storage_entry.key);
3373                (keccak256(address), hashed_key, storage_entry.value)
3374            })
3375            .collect::<Vec<_>>();
3376        hashed_storages.sort_by_key(|(ha, hk, _)| (*ha, *hk));
3377
3378        // Apply values to HashedState, and remove the account if it's None.
3379        let mut hashed_storage_keys: B256Map<BTreeSet<B256>> =
3380            B256Map::with_capacity_and_hasher(hashed_storages.len(), Default::default());
3381        let mut hashed_storage = self.tx.cursor_dup_write::<tables::HashedStorages>()?;
3382        for (hashed_address, key, value) in hashed_storages.into_iter().rev() {
3383            hashed_storage_keys.entry(hashed_address).or_default().insert(key);
3384
3385            if hashed_storage
3386                .seek_by_key_subkey(hashed_address, key)?
3387                .is_some_and(|entry| entry.key == key)
3388            {
3389                hashed_storage.delete_current()?;
3390            }
3391
3392            if !value.is_zero() {
3393                hashed_storage.upsert(hashed_address, &StorageEntry { key, value })?;
3394            }
3395        }
3396        Ok(hashed_storage_keys)
3397    }
3398
3399    fn unwind_storage_hashing_range(
3400        &self,
3401        range: impl RangeBounds<BlockNumber>,
3402    ) -> ProviderResult<B256Map<BTreeSet<B256>>> {
3403        let changesets = self.storage_changesets_range(range)?;
3404        self.unwind_storage_hashing(changesets.into_iter())
3405    }
3406
3407    fn insert_storage_for_hashing(
3408        &self,
3409        storages: impl IntoIterator<Item = (Address, impl IntoIterator<Item = StorageEntry>)>,
3410    ) -> ProviderResult<B256Map<BTreeSet<B256>>> {
3411        // hash values
3412        let hashed_storages =
3413            storages.into_iter().fold(BTreeMap::new(), |mut map, (address, storage)| {
3414                let storage = storage.into_iter().fold(BTreeMap::new(), |mut map, entry| {
3415                    map.insert(keccak256(entry.key), entry.value);
3416                    map
3417                });
3418                map.insert(keccak256(address), storage);
3419                map
3420            });
3421
3422        let hashed_storage_keys = hashed_storages
3423            .iter()
3424            .map(|(hashed_address, entries)| (*hashed_address, entries.keys().copied().collect()))
3425            .collect();
3426
3427        let mut hashed_storage_cursor = self.tx.cursor_dup_write::<tables::HashedStorages>()?;
3428        // Hash the address and key and apply them to HashedStorage (if Storage is None
3429        // just remove it);
3430        hashed_storages.into_iter().try_for_each(|(hashed_address, storage)| {
3431            storage.into_iter().try_for_each(|(key, value)| -> ProviderResult<()> {
3432                if hashed_storage_cursor
3433                    .seek_by_key_subkey(hashed_address, key)?
3434                    .is_some_and(|entry| entry.key == key)
3435                {
3436                    hashed_storage_cursor.delete_current()?;
3437                }
3438
3439                if !value.is_zero() {
3440                    hashed_storage_cursor.upsert(hashed_address, &StorageEntry { key, value })?;
3441                }
3442                Ok(())
3443            })
3444        })?;
3445
3446        Ok(hashed_storage_keys)
3447    }
3448}
3449
3450impl<TX: DbTxMut + DbTx + 'static, N: NodeTypes> HistoryWriter for DatabaseProvider<TX, N> {
3451    fn unwind_account_history_indices<'a>(
3452        &self,
3453        changesets: impl Iterator<Item = &'a (BlockNumber, AccountBeforeTx)>,
3454    ) -> ProviderResult<usize> {
3455        let mut last_indices = changesets
3456            .into_iter()
3457            .map(|(index, account)| (account.address, *index))
3458            .collect::<Vec<_>>();
3459        last_indices.sort_unstable_by_key(|(a, _)| *a);
3460
3461        if self.cached_storage_settings().storage_v2 {
3462            let batch = self.rocksdb_provider.unwind_account_history_indices(&last_indices)?;
3463            self.pending_rocksdb_batches.lock().push(batch);
3464        } else {
3465            // Unwind the account history index in MDBX.
3466            let mut cursor = self.tx.cursor_write::<tables::AccountsHistory>()?;
3467            for &(address, rem_index) in &last_indices {
3468                let partial_shard = unwind_history_shards::<_, tables::AccountsHistory, _>(
3469                    &mut cursor,
3470                    ShardedKey::last(address),
3471                    rem_index,
3472                    |sharded_key| sharded_key.key == address,
3473                )?;
3474
3475                // Check the last returned partial shard.
3476                // If it's not empty, the shard needs to be reinserted.
3477                if !partial_shard.is_empty() {
3478                    cursor.insert(
3479                        ShardedKey::last(address),
3480                        &BlockNumberList::new_pre_sorted(partial_shard),
3481                    )?;
3482                }
3483            }
3484        }
3485
3486        let changesets = last_indices.len();
3487        Ok(changesets)
3488    }
3489
3490    fn unwind_account_history_indices_range(
3491        &self,
3492        range: impl RangeBounds<BlockNumber>,
3493    ) -> ProviderResult<usize> {
3494        let changesets = self.account_changesets_range(range)?;
3495        self.unwind_account_history_indices(changesets.iter())
3496    }
3497
3498    fn insert_account_history_index(
3499        &self,
3500        account_transitions: impl IntoIterator<Item = (Address, impl IntoIterator<Item = u64>)>,
3501    ) -> ProviderResult<()> {
3502        self.append_history_index::<_, tables::AccountsHistory>(
3503            account_transitions,
3504            ShardedKey::new,
3505        )
3506    }
3507
3508    fn unwind_storage_history_indices(
3509        &self,
3510        changesets: impl Iterator<Item = (BlockNumberAddress, StorageEntry)>,
3511    ) -> ProviderResult<usize> {
3512        let mut storage_changesets = changesets
3513            .into_iter()
3514            .map(|(BlockNumberAddress((bn, address)), storage)| (address, storage.key, bn))
3515            .collect::<Vec<_>>();
3516        storage_changesets.sort_unstable_by_key(|(address, key, _)| (*address, *key));
3517
3518        if self.cached_storage_settings().storage_v2 {
3519            let batch =
3520                self.rocksdb_provider.unwind_storage_history_indices(&storage_changesets)?;
3521            self.pending_rocksdb_batches.lock().push(batch);
3522        } else {
3523            // Unwind the storage history index in MDBX.
3524            let mut cursor = self.tx.cursor_write::<tables::StoragesHistory>()?;
3525            for &(address, storage_key, rem_index) in &storage_changesets {
3526                let partial_shard = unwind_history_shards::<_, tables::StoragesHistory, _>(
3527                    &mut cursor,
3528                    StorageShardedKey::last(address, storage_key),
3529                    rem_index,
3530                    |storage_sharded_key| {
3531                        storage_sharded_key.address == address &&
3532                            storage_sharded_key.sharded_key.key == storage_key
3533                    },
3534                )?;
3535
3536                // Check the last returned partial shard.
3537                // If it's not empty, the shard needs to be reinserted.
3538                if !partial_shard.is_empty() {
3539                    cursor.insert(
3540                        StorageShardedKey::last(address, storage_key),
3541                        &BlockNumberList::new_pre_sorted(partial_shard),
3542                    )?;
3543                }
3544            }
3545        }
3546
3547        let changesets = storage_changesets.len();
3548        Ok(changesets)
3549    }
3550
3551    fn unwind_storage_history_indices_range(
3552        &self,
3553        range: impl RangeBounds<BlockNumber>,
3554    ) -> ProviderResult<usize> {
3555        let changesets = self.storage_changesets_range(range)?;
3556        self.unwind_storage_history_indices(changesets.into_iter())
3557    }
3558
3559    fn insert_storage_history_index(
3560        &self,
3561        storage_transitions: impl IntoIterator<Item = ((Address, B256), impl IntoIterator<Item = u64>)>,
3562    ) -> ProviderResult<()> {
3563        self.append_history_index::<_, tables::StoragesHistory>(
3564            storage_transitions,
3565            |(address, storage_key), highest_block_number| {
3566                StorageShardedKey::new(address, storage_key, highest_block_number)
3567            },
3568        )
3569    }
3570
3571    #[instrument(level = "debug", target = "providers::db", skip_all)]
3572    fn update_history_indices(&self, range: RangeInclusive<BlockNumber>) -> ProviderResult<()> {
3573        let storage_settings = self.cached_storage_settings();
3574        if !storage_settings.storage_v2 {
3575            let indices = self.changed_accounts_and_blocks_with_range(range.clone())?;
3576            self.insert_account_history_index(indices)?;
3577        }
3578
3579        if !storage_settings.storage_v2 {
3580            let indices = self.changed_storages_and_blocks_with_range(range)?;
3581            self.insert_storage_history_index(indices)?;
3582        }
3583
3584        Ok(())
3585    }
3586}
3587
3588impl<TX: DbTxMut + DbTx + 'static, N: NodeTypesForProvider> BlockExecutionWriter
3589    for DatabaseProvider<TX, N>
3590{
3591    fn take_block_and_execution_above(
3592        &self,
3593        block: BlockNumber,
3594    ) -> ProviderResult<Chain<Self::Primitives>> {
3595        let range = block + 1..=self.last_block_number()?;
3596
3597        self.unwind_trie_state_from(block + 1)?;
3598
3599        // get execution res
3600        let execution_state = self.take_state_above(block)?;
3601
3602        let blocks = self.recovered_block_range(range)?;
3603
3604        // remove block bodies it is needed for both get block range and get block execution results
3605        // that is why it is deleted afterwards.
3606        self.remove_blocks_above(block)?;
3607
3608        // Update pipeline progress
3609        self.update_pipeline_stages_after_unwind(block)?;
3610
3611        Ok(Chain::new(blocks, execution_state, BTreeMap::new()))
3612    }
3613
3614    fn remove_block_and_execution_above(
3615        &self,
3616        block: BlockNumber,
3617    ) -> ProviderResult<PersistenceFrontiers> {
3618        self.unwind_trie_state_from(block + 1)?;
3619
3620        // remove execution res
3621        self.remove_state_above(block)?;
3622
3623        // remove block bodies it is needed for both get block range and get block execution results
3624        // that is why it is deleted afterwards.
3625        self.remove_blocks_above(block)?;
3626
3627        // Update pipeline progress
3628        self.update_pipeline_stages_after_unwind(block)
3629    }
3630}
3631
3632impl<TX: DbTxMut + DbTx + 'static, N: NodeTypesForProvider> BlockWriter
3633    for DatabaseProvider<TX, N>
3634{
3635    type Block = BlockTy<N>;
3636    type Receipt = ReceiptTy<N>;
3637
3638    /// Inserts the block into the database, writing to both static files and MDBX.
3639    ///
3640    /// This is a convenience method primarily used in tests. For production use,
3641    /// prefer [`Self::save_blocks`] which handles execution output and trie data.
3642    fn insert_block(
3643        &self,
3644        block: &RecoveredBlock<Self::Block>,
3645    ) -> ProviderResult<StoredBlockBodyIndices> {
3646        let block_number = block.number();
3647
3648        // Wrap block in ExecutedBlock with empty execution output (no receipts/state/trie)
3649        let executed_block = ExecutedBlock::new(
3650            Arc::new(block.clone()),
3651            Arc::new(BlockExecutionOutput {
3652                result: BlockExecutionResult {
3653                    receipts: Default::default(),
3654                    requests: Default::default(),
3655                    gas_used: 0,
3656                    blob_gas_used: 0,
3657                },
3658                state: Default::default(),
3659            }),
3660            SortedTrieData::default(),
3661        );
3662
3663        self.save_blocks_inner(
3664            std::slice::from_ref(&executed_block),
3665            &[],
3666            &[],
3667            None,
3668            SaveBlocksMode::BlocksOnly,
3669        )?;
3670
3671        // Return the body indices
3672        self.block_body_indices(block_number)?
3673            .ok_or(ProviderError::BlockBodyIndicesNotFound(block_number))
3674    }
3675
3676    fn append_block_bodies(
3677        &self,
3678        bodies: Vec<(BlockNumber, Option<&BodyTy<N>>)>,
3679    ) -> ProviderResult<()> {
3680        let Some(from_block) = bodies.first().map(|(block, _)| *block) else { return Ok(()) };
3681
3682        // Initialize writer if we will be writing transactions to staticfiles
3683        let mut tx_writer =
3684            self.static_file_provider.get_writer(from_block, StaticFileSegment::Transactions)?;
3685
3686        let mut block_indices_cursor = self.tx.cursor_write::<tables::BlockBodyIndices>()?;
3687        let mut tx_block_cursor = self.tx.cursor_write::<tables::TransactionBlocks>()?;
3688
3689        // Get id for the next tx_num or zero if there are no transactions.
3690        let mut next_tx_num = tx_block_cursor.last()?.map(|(id, _)| id + 1).unwrap_or_default();
3691
3692        for (block_number, body) in &bodies {
3693            // Increment block on static file header.
3694            tx_writer.increment_block(*block_number)?;
3695
3696            let tx_count = body.as_ref().map(|b| b.transactions().len() as u64).unwrap_or_default();
3697            let block_indices = StoredBlockBodyIndices { first_tx_num: next_tx_num, tx_count };
3698
3699            let mut durations_recorder = metrics::DurationsRecorder::new(&self.metrics);
3700
3701            // insert block meta
3702            block_indices_cursor.append(*block_number, &block_indices)?;
3703
3704            durations_recorder.record_relative(metrics::Action::InsertBlockBodyIndices);
3705
3706            let Some(body) = body else { continue };
3707
3708            // write transaction block index
3709            if !body.transactions().is_empty() {
3710                tx_block_cursor.append(block_indices.last_tx_num(), block_number)?;
3711                durations_recorder.record_relative(metrics::Action::InsertTransactionBlocks);
3712            }
3713
3714            // write transactions
3715            for transaction in body.transactions() {
3716                tx_writer.append_transaction(next_tx_num, transaction)?;
3717
3718                // Increment transaction id for each transaction.
3719                next_tx_num += 1;
3720            }
3721        }
3722
3723        self.storage.writer().write_block_bodies(self, bodies)?;
3724
3725        Ok(())
3726    }
3727
3728    fn remove_blocks_above(&self, block: BlockNumber) -> ProviderResult<()> {
3729        let last_block_number = self.last_block_number()?;
3730        // Clean up HeaderNumbers for blocks being removed, we must clear all indexes from MDBX.
3731        for hash in self.canonical_hashes_range(block + 1, last_block_number + 1)? {
3732            self.tx.delete::<tables::HeaderNumbers>(hash, None)?;
3733        }
3734
3735        // Get highest static file block for the total block range
3736        let highest_static_file_block = self
3737            .static_file_provider()
3738            .get_highest_static_file_block(StaticFileSegment::Headers)
3739            .expect("todo: error handling, headers should exist");
3740
3741        // IMPORTANT: we use `highest_static_file_block.saturating_sub(block_number)` to make sure
3742        // we remove only what is ABOVE the block.
3743        //
3744        // i.e., if the highest static file block is 8, we want to remove above block 5 only, we
3745        // will have three blocks to remove, which will be block 8, 7, and 6.
3746        debug!(target: "providers::db", ?block, "Removing static file blocks above block_number");
3747        self.static_file_provider()
3748            .get_writer(block, StaticFileSegment::Headers)?
3749            .prune_headers(highest_static_file_block.saturating_sub(block))?;
3750
3751        // First transaction to be removed
3752        let unwind_tx_from = self.next_tx_num_after_block(block)?;
3753
3754        // Last transaction to be removed
3755        let unwind_tx_to = self
3756            .tx
3757            .cursor_read::<tables::BlockBodyIndices>()?
3758            .last()?
3759            // shouldn't happen because this was OK above
3760            .ok_or(ProviderError::BlockBodyIndicesNotFound(block))?
3761            .1
3762            .last_tx_num();
3763
3764        if unwind_tx_from <= unwind_tx_to {
3765            let hashes = self.transaction_hashes_by_range(unwind_tx_from..(unwind_tx_to + 1))?;
3766            self.with_rocksdb_batch(|batch| {
3767                let mut writer = EitherWriter::new_transaction_hash_numbers(self, batch)?;
3768                for (hash, _) in hashes {
3769                    writer.delete_transaction_hash_number(hash)?;
3770                }
3771                Ok(((), writer.into_raw_rocksdb_batch()))
3772            })?;
3773        }
3774
3775        // Skip sender pruning when sender_recovery is fully pruned, since no sender data
3776        // exists in static files or the database.
3777        if self.prune_modes.sender_recovery.is_none_or(|m| !m.is_full()) {
3778            EitherWriter::new_senders(self, last_block_number)?
3779                .prune_senders(unwind_tx_from, block)?;
3780        }
3781
3782        self.remove_bodies_above(block)?;
3783
3784        Ok(())
3785    }
3786
3787    fn remove_bodies_above(&self, block: BlockNumber) -> ProviderResult<()> {
3788        self.storage.writer().remove_block_bodies_above(self, block)?;
3789
3790        // First transaction to be removed
3791        let unwind_tx_from = self.next_tx_num_after_block(block)?;
3792
3793        self.remove::<tables::BlockBodyIndices>(block + 1..)?;
3794        self.remove::<tables::TransactionBlocks>(unwind_tx_from..)?;
3795
3796        let static_file_tx_num =
3797            self.static_file_provider.get_highest_static_file_tx(StaticFileSegment::Transactions);
3798
3799        let to_delete = static_file_tx_num
3800            .map(|static_tx| (static_tx + 1).saturating_sub(unwind_tx_from))
3801            .unwrap_or_default();
3802
3803        self.static_file_provider
3804            .latest_writer(StaticFileSegment::Transactions)?
3805            .prune_transactions(to_delete, block)?;
3806
3807        Ok(())
3808    }
3809
3810    /// Appends blocks with their execution state to the database.
3811    ///
3812    /// **Note:** This function is only used in tests.
3813    ///
3814    /// History indices are written to the appropriate backend based on storage settings:
3815    /// MDBX when `*_history_in_rocksdb` is false, `RocksDB` when true.
3816    ///
3817    /// TODO(joshie): this fn should be moved to `UnifiedStorageWriter` eventually
3818    fn append_blocks_with_state(
3819        &self,
3820        blocks: Vec<RecoveredBlock<Self::Block>>,
3821        execution_outcome: &ExecutionOutcome<Self::Receipt>,
3822        hashed_state: HashedPostStateSorted,
3823    ) -> ProviderResult<()> {
3824        if blocks.is_empty() {
3825            debug!(target: "providers::db", "Attempted to append empty block range");
3826            return Ok(())
3827        }
3828
3829        // Blocks are not empty, so no need to handle the case of `blocks.first()` being
3830        // `None`.
3831        let first_number = blocks[0].number();
3832
3833        // Blocks are not empty, so no need to handle the case of `blocks.last()` being
3834        // `None`.
3835        let last_block_number = blocks[blocks.len() - 1].number();
3836
3837        let mut durations_recorder = metrics::DurationsRecorder::new(&self.metrics);
3838
3839        // Extract account and storage transitions from the bundle reverts BEFORE writing state.
3840        // This is necessary because with edge storage, changesets are written to static files
3841        // whose index isn't updated until commit, making them invisible to subsequent reads
3842        // within the same transaction.
3843        let (account_transitions, storage_transitions) = {
3844            let mut account_transitions: BTreeMap<Address, Vec<u64>> = BTreeMap::new();
3845            let mut storage_transitions: BTreeMap<(Address, B256), Vec<u64>> = BTreeMap::new();
3846            for (block_idx, block_reverts) in execution_outcome.bundle.reverts.iter().enumerate() {
3847                let block_number = first_number + block_idx as u64;
3848                for (address, account_revert) in block_reverts {
3849                    account_transitions.entry(*address).or_default().push(block_number);
3850                    for storage_key in account_revert.storage.keys() {
3851                        let key = B256::from(*storage_key);
3852                        storage_transitions.entry((*address, key)).or_default().push(block_number);
3853                    }
3854                }
3855            }
3856            (account_transitions, storage_transitions)
3857        };
3858
3859        // Insert the blocks
3860        for block in blocks {
3861            self.insert_block(&block)?;
3862            durations_recorder.record_relative(metrics::Action::InsertBlock);
3863        }
3864
3865        self.write_state(execution_outcome, OriginalValuesKnown::No, StateWriteConfig::default())?;
3866        durations_recorder.record_relative(metrics::Action::InsertState);
3867
3868        // insert hashes and intermediate merkle nodes
3869        self.write_hashed_state(&hashed_state)?;
3870        durations_recorder.record_relative(metrics::Action::InsertHashes);
3871
3872        // Use pre-computed transitions for history indices since static file
3873        // writes aren't visible until commit.
3874        // Note: For MDBX we use insert_*_history_index. For RocksDB we use
3875        // append_*_history_shard which handles read-merge-write internally.
3876        let storage_settings = self.cached_storage_settings();
3877        if storage_settings.storage_v2 {
3878            self.with_rocksdb_batch(|mut batch| {
3879                for (address, blocks) in account_transitions {
3880                    batch.append_account_history_shard(address, blocks)?;
3881                }
3882                Ok(((), Some(batch.into_inner())))
3883            })?;
3884        } else {
3885            self.insert_account_history_index(account_transitions)?;
3886        }
3887        if storage_settings.storage_v2 {
3888            self.with_rocksdb_batch(|mut batch| {
3889                for ((address, key), blocks) in storage_transitions {
3890                    batch.append_storage_history_shard(address, key, blocks)?;
3891                }
3892                Ok(((), Some(batch.into_inner())))
3893            })?;
3894        } else {
3895            self.insert_storage_history_index(storage_transitions)?;
3896        }
3897        durations_recorder.record_relative(metrics::Action::InsertHistoryIndices);
3898
3899        // Update pipeline progress
3900        self.update_pipeline_stages(last_block_number, false)?;
3901        durations_recorder.record_relative(metrics::Action::UpdatePipelineStages);
3902
3903        debug!(target: "providers::db", range = ?first_number..=last_block_number, actions = ?durations_recorder.actions, "Appended blocks");
3904
3905        Ok(())
3906    }
3907
3908    fn clear_transaction_lookup(&self) -> ProviderResult<()> {
3909        if self.cached_storage_settings().storage_v2 {
3910            self.rocksdb_provider.clear::<tables::TransactionHashNumbers>()
3911        } else {
3912            self.tx.clear::<tables::TransactionHashNumbers>().map_err(Into::into)
3913        }
3914    }
3915}
3916
3917impl<TX: DbTx + 'static, N: NodeTypes> PruneCheckpointReader for DatabaseProvider<TX, N> {
3918    fn get_prune_checkpoint(
3919        &self,
3920        segment: PruneSegment,
3921    ) -> ProviderResult<Option<PruneCheckpoint>> {
3922        Ok(self.tx.get::<tables::PruneCheckpoints>(segment)?)
3923    }
3924
3925    fn get_prune_checkpoints(&self) -> ProviderResult<Vec<(PruneSegment, PruneCheckpoint)>> {
3926        Ok(PruneSegment::variants()
3927            .filter_map(|segment| {
3928                self.tx
3929                    .get::<tables::PruneCheckpoints>(segment)
3930                    .transpose()
3931                    .map(|chk| chk.map(|chk| (segment, chk)))
3932            })
3933            .collect::<Result<_, _>>()?)
3934    }
3935
3936    fn get_prune_checkpoints_for_availability(
3937        &self,
3938    ) -> ProviderResult<Vec<(PruneSegment, PruneCheckpoint)>> {
3939        let mut checkpoints = self.get_prune_checkpoints()?;
3940        if !EitherWriter::receipts_destination(self).is_static_file() ||
3941            !checkpoints.iter().any(|(segment, checkpoint)| {
3942                matches!(segment, PruneSegment::Receipts | PruneSegment::ContractLogs) &&
3943                    checkpoint.tx_number.is_some()
3944            })
3945        {
3946            return Ok(checkpoints)
3947        }
3948
3949        let Some(lowest_block) =
3950            self.static_file_provider.get_lowest_range_start(StaticFileSegment::Receipts)
3951        else {
3952            return Ok(checkpoints)
3953        };
3954        let Some(first_tx_range) = self
3955            .static_file_provider
3956            .get_segment_provider_for_block(StaticFileSegment::Receipts, lowest_block, None)?
3957            .user_header()
3958            .tx_range()
3959        else {
3960            return Ok(checkpoints)
3961        };
3962
3963        // Migration can pad the first file with blocks whose receipts were already pruned.
3964        // TransactionBlocks::seek can also land after expired history, so verify the body range.
3965        let pruned_tx = first_tx_range.start().checked_sub(1);
3966        let pruned_block = if let Some(tx) = pruned_tx {
3967            if let Some((_, block)) =
3968                self.tx.cursor_read::<tables::TransactionBlocks>()?.seek(tx)? &&
3969                let Some(indices) = self.tx.get::<tables::BlockBodyIndices>(block)? &&
3970                indices.tx_num_range().contains(&tx)
3971            {
3972                Some(block).max(lowest_block.checked_sub(1))
3973            } else {
3974                return Ok(checkpoints)
3975            }
3976        } else {
3977            lowest_block.checked_sub(1)
3978        };
3979
3980        // The removed StaticFileReceipts pruner checkpointed moves into static files.
3981        // Writers enforce continuity within each file, but migration can leave gaps between files.
3982        // Reuse the verified coverage for both receipts and logs.
3983        let mut last_tx = first_tx_range.end();
3984        for (segment, checkpoint) in &mut checkpoints {
3985            if matches!(segment, PruneSegment::Receipts | PruneSegment::ContractLogs) &&
3986                let Some(tx) = checkpoint.tx_number &&
3987                tx >= first_tx_range.start()
3988            {
3989                while let Some(next_tx) = last_tx.checked_add(1) &&
3990                    next_tx <= tx &&
3991                    let Some(next_file) = self
3992                        .static_file_provider
3993                        .get_maybe_segment_provider(StaticFileSegment::Receipts, next_tx)? &&
3994                    let Some(next_range) = next_file.user_header().tx_range() &&
3995                    next_range.start() == next_tx
3996                {
3997                    last_tx = next_range.end();
3998                }
3999                if tx <= last_tx {
4000                    checkpoint.block_number = pruned_block;
4001                    checkpoint.tx_number = pruned_tx;
4002                }
4003            }
4004        }
4005
4006        Ok(checkpoints)
4007    }
4008}
4009
4010impl<TX: DbTxMut, N: NodeTypes> PruneCheckpointWriter for DatabaseProvider<TX, N> {
4011    fn save_prune_checkpoint(
4012        &self,
4013        segment: PruneSegment,
4014        checkpoint: PruneCheckpoint,
4015    ) -> ProviderResult<()> {
4016        Ok(self.tx.put::<tables::PruneCheckpoints>(segment, checkpoint)?)
4017    }
4018}
4019
4020impl<TX: DbTx + 'static, N: NodeTypesForProvider> StatsReader for DatabaseProvider<TX, N> {
4021    fn count_entries<T: Table>(&self) -> ProviderResult<usize> {
4022        let db_entries = self.tx.entries::<T>()?;
4023        let static_file_entries = match self.static_file_provider.count_entries::<T>() {
4024            Ok(entries) => entries,
4025            Err(ProviderError::UnsupportedProvider) => 0,
4026            Err(err) => return Err(err),
4027        };
4028
4029        Ok(db_entries + static_file_entries)
4030    }
4031}
4032
4033impl<TX: DbTx + 'static, N: NodeTypes> ChainStateBlockReader for DatabaseProvider<TX, N> {
4034    fn last_finalized_block_number(&self) -> ProviderResult<Option<BlockNumber>> {
4035        Ok(self.tx.get::<tables::ChainState>(tables::ChainStateKey::LastFinalizedBlock)?)
4036    }
4037
4038    fn last_safe_block_number(&self) -> ProviderResult<Option<BlockNumber>> {
4039        Ok(self.tx.get::<tables::ChainState>(tables::ChainStateKey::LastSafeBlock)?)
4040    }
4041}
4042
4043impl<TX: DbTxMut, N: NodeTypes> ChainStateBlockWriter for DatabaseProvider<TX, N> {
4044    fn save_finalized_block_number(&self, block_number: BlockNumber) -> ProviderResult<()> {
4045        Ok(self
4046            .tx
4047            .put::<tables::ChainState>(tables::ChainStateKey::LastFinalizedBlock, block_number)?)
4048    }
4049
4050    fn save_safe_block_number(&self, block_number: BlockNumber) -> ProviderResult<()> {
4051        Ok(self.tx.put::<tables::ChainState>(tables::ChainStateKey::LastSafeBlock, block_number)?)
4052    }
4053}
4054
4055impl<TX: DbTx + 'static, N: NodeTypes> DbTxProvider for DatabaseProvider<TX, N> {
4056    type Tx = TX;
4057
4058    fn tx(&self) -> &Self::Tx {
4059        &self.tx
4060    }
4061}
4062
4063impl<TX: DbTx + 'static, N: NodeTypes> DBProvider for DatabaseProvider<TX, N> {
4064    fn tx_mut(&mut self) -> &mut Self::Tx {
4065        &mut self.tx
4066    }
4067
4068    fn into_tx(self) -> Self::Tx {
4069        self.tx
4070    }
4071
4072    fn prune_modes_ref(&self) -> &PruneModes {
4073        self.prune_modes_ref()
4074    }
4075
4076    /// Commit database transaction, static files, and pending `RocksDB` batches.
4077    #[instrument(
4078        name = "DatabaseProvider::commit",
4079        level = "debug",
4080        target = "providers::db",
4081        skip_all
4082    )]
4083    fn commit(self) -> ProviderResult<()> {
4084        if self.static_file_provider.has_unwind_queued() || self.commit_order.is_unwind() {
4085            self.commit_unwind()?;
4086        } else {
4087            // Normal path: finalize() will call sync_all() if not already synced
4088            let mut timings = metrics::CommitTimings::default();
4089
4090            let start = Instant::now();
4091            self.static_file_provider.finalize()?;
4092            timings.sf = start.elapsed();
4093
4094            let start = Instant::now();
4095            let batches = std::mem::take(&mut *self.pending_rocksdb_batches.lock());
4096            for batch in batches {
4097                self.rocksdb_provider.commit_batch(batch)?;
4098            }
4099            timings.rocksdb = start.elapsed();
4100
4101            let start = Instant::now();
4102            self.tx.commit()?;
4103            timings.mdbx = start.elapsed();
4104
4105            self.metrics.record_commit(&timings);
4106        }
4107
4108        Ok(())
4109    }
4110}
4111
4112impl<TX: DbTx, N: NodeTypes> MetadataProvider for DatabaseProvider<TX, N> {
4113    fn get_metadata(&self, key: &str) -> ProviderResult<Option<Vec<u8>>> {
4114        self.tx.get::<tables::Metadata>(key.to_string()).map_err(Into::into)
4115    }
4116}
4117
4118impl<TX: DbTxMut, N: NodeTypes> MetadataWriter for DatabaseProvider<TX, N> {
4119    fn write_metadata(&self, key: &str, value: Vec<u8>) -> ProviderResult<()> {
4120        self.tx.put::<tables::Metadata>(key.to_string(), value).map_err(Into::into)
4121    }
4122
4123    fn delete_metadata(&self, key: &str) -> ProviderResult<()> {
4124        self.tx.delete::<tables::Metadata>(key.to_string(), None)?;
4125        Ok(())
4126    }
4127}
4128
4129impl<TX: Send, N: NodeTypes> StorageSettingsCache for DatabaseProvider<TX, N> {
4130    fn cached_storage_settings(&self) -> StorageSettings {
4131        *self.storage_settings.read()
4132    }
4133
4134    fn set_storage_settings_cache(&self, settings: StorageSettings) {
4135        *self.storage_settings.write() = settings;
4136    }
4137}
4138
4139impl<TX: Send, N: NodeTypes> StoragePath for DatabaseProvider<TX, N> {
4140    fn storage_path(&self) -> PathBuf {
4141        self.db_path.clone()
4142    }
4143}
4144
4145#[cfg(test)]
4146mod tests {
4147    use super::*;
4148    use crate::{
4149        test_utils::{blocks::BlockchainTestData, create_test_provider_factory},
4150        BlockWriter,
4151    };
4152    use alloy_consensus::Header;
4153    use alloy_eips::BlockNumHash;
4154    use alloy_primitives::{
4155        map::{AddressMap, B256Map},
4156        U256,
4157    };
4158    use evm2::evm::AccountInfo;
4159    use reth_chain_state::{test_utils::TestBlockBuilder, ExecutedBlock};
4160    use reth_db_api::models::{SnapAttempt, StorageSettings};
4161    use reth_ethereum_primitives::Receipt;
4162    use reth_execution_types::{AccountRevertInit, BlockExecutionOutput, BlockExecutionResult};
4163    use reth_primitives_traits::SealedBlock;
4164    use reth_storage_api::{DatabaseProviderFactory, MetadataWriter};
4165    use reth_testing_utils::generators::{self, random_block, BlockParams};
4166    use reth_trie::{
4167        bundle_state::BundleState, HashedPostState, KeccakKeyHasher, Nibbles, SortedTrieData,
4168        StoredNibbles, StoredNibblesSubKey,
4169    };
4170    use std::{sync::mpsc, time::Duration};
4171
4172    /// Seeds block zero through the writer core because [`SaveBlocksInput`] only describes
4173    /// advancing an existing persistence frontier.
4174    fn save_genesis<TX, N>(
4175        provider: &DatabaseProvider<TX, N>,
4176        genesis: &ExecutedBlock<N::Primitives>,
4177    ) -> ProviderResult<()>
4178    where
4179        TX: DbTx + DbTxMut + 'static,
4180        N: NodeTypesForProvider,
4181    {
4182        assert_eq!(genesis.recovered_block().number(), 0);
4183        provider.save_blocks_inner(
4184            std::slice::from_ref(genesis),
4185            std::slice::from_ref(genesis),
4186            &[],
4187            None,
4188            SaveBlocksMode::Full,
4189        )
4190    }
4191
4192    #[test]
4193    fn snap_attempt_guards_finish_checkpoint_writers() {
4194        let factory = create_test_provider_factory();
4195        let provider = factory.database_provider_rw().unwrap();
4196        provider.update_pipeline_stages(5, false).unwrap();
4197        let mut attempt = SnapAttempt::start(
4198            None,
4199            BlockNumHash::new(10, B256::repeat_byte(1)),
4200            B256::repeat_byte(2),
4201        );
4202        provider.write_snap_attempt(&attempt).unwrap();
4203        provider.commit().unwrap();
4204
4205        let provider = factory.database_provider_rw().unwrap();
4206        let refused = provider.save_stage_checkpoint(StageId::Finish, StageCheckpoint::new(10));
4207        assert!(matches!(refused, Err(ProviderError::UnverifiedSnapState { attempt: 0 })));
4208        let refused = provider.update_pipeline_stages(10, false);
4209        assert!(matches!(refused, Err(ProviderError::UnverifiedSnapState { attempt: 0 })));
4210        for stage in [StageId::Finish, StageId::Headers] {
4211            assert_eq!(provider.get_stage_checkpoint(stage).unwrap().unwrap().block_number, 5);
4212        }
4213
4214        // Rewinding claims nothing about the downloaded state, through either writer.
4215        provider.update_pipeline_stages(4, true).unwrap();
4216        provider.save_stage_checkpoint(StageId::Finish, StageCheckpoint::new(3)).unwrap();
4217        assert_eq!(
4218            provider.get_stage_checkpoint(StageId::Finish).unwrap().unwrap().block_number,
4219            3
4220        );
4221
4222        // Abandoned leftovers are still not complete state.
4223        attempt.abandon();
4224        provider.write_snap_attempt(&attempt).unwrap();
4225        let refused = provider.save_stage_checkpoint(StageId::Finish, StageCheckpoint::new(10));
4226        assert!(matches!(refused, Err(ProviderError::UnverifiedSnapState { attempt: 0 })));
4227
4228        // Header progress does not claim the downloaded state is complete.
4229        provider.save_stage_checkpoint(StageId::Headers, StageCheckpoint::new(10)).unwrap();
4230        attempt.verify();
4231        provider.write_snap_attempt(&attempt).unwrap();
4232        provider.save_stage_checkpoint(StageId::Finish, StageCheckpoint::new(10)).unwrap();
4233        provider.update_pipeline_stages(11, false).unwrap();
4234        provider.commit().unwrap();
4235
4236        let provider = factory.database_provider_ro().unwrap();
4237        assert_eq!(
4238            provider.get_stage_checkpoint(StageId::Finish).unwrap().unwrap().block_number,
4239            11
4240        );
4241    }
4242
4243    #[test]
4244    fn snap_attempt_guards_partial_state_trie_advances() {
4245        for abandoned in [false, true] {
4246            let factory = create_test_provider_factory();
4247            let provider = factory.database_provider_rw().unwrap();
4248            provider.update_pipeline_stages(100, false).unwrap();
4249            let checkpoint = StageCheckpoint::new(100)
4250                .with_finish_stage_checkpoint(FinishCheckpoint { partial_state_trie: Some(50) });
4251            provider.save_stage_checkpoint(StageId::Finish, checkpoint).unwrap();
4252            let mut attempt = SnapAttempt::start(None, BlockNumHash::default(), B256::ZERO);
4253            if abandoned {
4254                attempt.abandon();
4255            }
4256            provider.write_snap_attempt(&attempt).unwrap();
4257
4258            for block in [100, 90] {
4259                let result =
4260                    provider.save_stage_checkpoint(StageId::Finish, StageCheckpoint::new(block));
4261                assert!(matches!(result, Err(ProviderError::UnverifiedSnapState { .. })));
4262                let result = provider.update_pipeline_stages(block, true);
4263                assert!(matches!(result, Err(ProviderError::UnverifiedSnapState { .. })));
4264                for stage in [StageId::Finish, StageId::Headers] {
4265                    assert_eq!(
4266                        provider.get_stage_checkpoint(stage).unwrap().unwrap().block_number,
4267                        100
4268                    );
4269                }
4270                assert_eq!(
4271                    provider.get_stage_checkpoint(StageId::Finish).unwrap(),
4272                    Some(checkpoint)
4273                );
4274            }
4275
4276            let advanced = StageCheckpoint::new(100)
4277                .with_finish_stage_checkpoint(FinishCheckpoint { partial_state_trie: Some(60) });
4278            assert!(matches!(
4279                provider.save_stage_checkpoint(StageId::Finish, advanced),
4280                Err(ProviderError::UnverifiedSnapState { .. })
4281            ));
4282
4283            // Retaining the partial frontier does not claim additional state progress.
4284            provider.update_pipeline_stages(90, false).unwrap();
4285            let rewind = StageCheckpoint::new(80)
4286                .with_finish_stage_checkpoint(FinishCheckpoint { partial_state_trie: Some(40) });
4287            provider.save_stage_checkpoint(StageId::Finish, rewind).unwrap();
4288            provider.update_pipeline_stages(30, true).unwrap();
4289
4290            attempt.verify();
4291            provider.write_snap_attempt(&attempt).unwrap();
4292            provider.save_stage_checkpoint(StageId::Finish, advanced).unwrap();
4293            provider.update_pipeline_stages(100, true).unwrap();
4294        }
4295    }
4296
4297    #[test]
4298    fn test_receipts_by_block_range_empty_range() {
4299        let factory = create_test_provider_factory();
4300        let provider = factory.provider().unwrap();
4301
4302        // empty range should return empty vec
4303        let start = 10u64;
4304        let end = 9u64;
4305        let result = provider.receipts_by_block_range(start..=end).unwrap();
4306        assert_eq!(result, Vec::<Vec<reth_ethereum_primitives::Receipt>>::new());
4307    }
4308
4309    #[test]
4310    fn unwind_commit_waits_for_pre_commit_readers() {
4311        let factory = create_test_provider_factory();
4312
4313        let reader = factory.provider().unwrap();
4314        let provider_rw = factory.unwind_provider_rw().unwrap();
4315        provider_rw.write_metadata("unwind-wait-test", vec![1]).unwrap();
4316        let (done_tx, done_rx) = mpsc::channel();
4317
4318        let handle = std::thread::spawn(move || {
4319            let result = provider_rw.commit();
4320            done_tx.send(result).unwrap();
4321        });
4322
4323        assert!(
4324            done_rx.recv_timeout(Duration::from_millis(50)).is_err(),
4325            "unwind commit should wait while an older read transaction is still open"
4326        );
4327
4328        drop(reader);
4329
4330        done_rx.recv_timeout(Duration::from_secs(1)).unwrap().unwrap();
4331        handle.join().unwrap();
4332    }
4333
4334    #[test]
4335    fn test_receipts_by_block_range_nonexistent_blocks() {
4336        let factory = create_test_provider_factory();
4337        let provider = factory.provider().unwrap();
4338
4339        // non-existent blocks should return empty vecs for each block
4340        let result = provider.receipts_by_block_range(10..=12).unwrap();
4341        assert_eq!(result, vec![vec![], vec![], vec![]]);
4342    }
4343
4344    #[test]
4345    fn test_receipts_by_block_range_single_block() {
4346        let factory = create_test_provider_factory();
4347        let data = BlockchainTestData::default();
4348
4349        let provider_rw = factory.provider_rw().unwrap();
4350        provider_rw.insert_block(&data.genesis.try_recover().unwrap()).unwrap();
4351        provider_rw
4352            .write_state(
4353                &ExecutionOutcome { first_block: 0, receipts: vec![vec![]], ..Default::default() },
4354                crate::OriginalValuesKnown::No,
4355                StateWriteConfig::default(),
4356            )
4357            .unwrap();
4358        provider_rw.insert_block(&data.blocks[0].0).unwrap();
4359        provider_rw
4360            .write_state(
4361                &data.blocks[0].1,
4362                crate::OriginalValuesKnown::No,
4363                StateWriteConfig::default(),
4364            )
4365            .unwrap();
4366        provider_rw.commit().unwrap();
4367
4368        let provider = factory.provider().unwrap();
4369        let result = provider.receipts_by_block_range(1..=1).unwrap();
4370
4371        // should have one vec with one receipt
4372        assert_eq!(result.len(), 1);
4373        assert_eq!(result[0].len(), 1);
4374        assert_eq!(result[0][0], data.blocks[0].1.receipts()[0][0]);
4375    }
4376
4377    #[test]
4378    fn test_receipts_by_block_range_multiple_blocks() {
4379        let factory = create_test_provider_factory();
4380        let data = BlockchainTestData::default();
4381
4382        let provider_rw = factory.provider_rw().unwrap();
4383        provider_rw.insert_block(&data.genesis.try_recover().unwrap()).unwrap();
4384        provider_rw
4385            .write_state(
4386                &ExecutionOutcome { first_block: 0, receipts: vec![vec![]], ..Default::default() },
4387                crate::OriginalValuesKnown::No,
4388                StateWriteConfig::default(),
4389            )
4390            .unwrap();
4391        for (block, outcome) in data.blocks.iter().take(3) {
4392            provider_rw.insert_block(block).unwrap();
4393            provider_rw
4394                .write_state(outcome, crate::OriginalValuesKnown::No, StateWriteConfig::default())
4395                .unwrap();
4396        }
4397        provider_rw.commit().unwrap();
4398
4399        let provider = factory.provider().unwrap();
4400        let result = provider.receipts_by_block_range(1..=3).unwrap();
4401
4402        // should have 3 vecs, each with one receipt
4403        assert_eq!(result.len(), 3);
4404        for (i, block_receipts) in result.iter().enumerate() {
4405            assert_eq!(block_receipts.len(), 1);
4406            assert_eq!(block_receipts[0], data.blocks[i].1.receipts()[0][0]);
4407        }
4408    }
4409
4410    #[test]
4411    fn test_receipts_by_block_range_blocks_with_varying_tx_counts() {
4412        let factory = create_test_provider_factory();
4413        let data = BlockchainTestData::default();
4414
4415        let provider_rw = factory.provider_rw().unwrap();
4416        provider_rw.insert_block(&data.genesis.try_recover().unwrap()).unwrap();
4417        provider_rw
4418            .write_state(
4419                &ExecutionOutcome { first_block: 0, receipts: vec![vec![]], ..Default::default() },
4420                crate::OriginalValuesKnown::No,
4421                StateWriteConfig::default(),
4422            )
4423            .unwrap();
4424
4425        // insert blocks 1-3 with receipts
4426        for (block, outcome) in data.blocks.iter().take(3) {
4427            provider_rw.insert_block(block).unwrap();
4428            provider_rw
4429                .write_state(outcome, crate::OriginalValuesKnown::No, StateWriteConfig::default())
4430                .unwrap();
4431        }
4432        provider_rw.commit().unwrap();
4433
4434        let provider = factory.provider().unwrap();
4435        let result = provider.receipts_by_block_range(1..=3).unwrap();
4436
4437        // verify each block has one receipt
4438        assert_eq!(result.len(), 3);
4439        for block_receipts in &result {
4440            assert_eq!(block_receipts.len(), 1);
4441        }
4442    }
4443
4444    #[test]
4445    fn test_receipts_by_block_range_partial_range() {
4446        let factory = create_test_provider_factory();
4447        let data = BlockchainTestData::default();
4448
4449        let provider_rw = factory.provider_rw().unwrap();
4450        provider_rw.insert_block(&data.genesis.try_recover().unwrap()).unwrap();
4451        provider_rw
4452            .write_state(
4453                &ExecutionOutcome { first_block: 0, receipts: vec![vec![]], ..Default::default() },
4454                crate::OriginalValuesKnown::No,
4455                StateWriteConfig::default(),
4456            )
4457            .unwrap();
4458        for (block, outcome) in data.blocks.iter().take(3) {
4459            provider_rw.insert_block(block).unwrap();
4460            provider_rw
4461                .write_state(outcome, crate::OriginalValuesKnown::No, StateWriteConfig::default())
4462                .unwrap();
4463        }
4464        provider_rw.commit().unwrap();
4465
4466        let provider = factory.provider().unwrap();
4467
4468        // request range that includes both existing and non-existing blocks
4469        let result = provider.receipts_by_block_range(2..=5).unwrap();
4470        assert_eq!(result.len(), 4);
4471
4472        // blocks 2-3 should have receipts, blocks 4-5 should be empty
4473        assert_eq!(result[0].len(), 1); // block 2
4474        assert_eq!(result[1].len(), 1); // block 3
4475        assert_eq!(result[2].len(), 0); // block 4 (doesn't exist)
4476        assert_eq!(result[3].len(), 0); // block 5 (doesn't exist)
4477
4478        assert_eq!(result[0][0], data.blocks[1].1.receipts()[0][0]);
4479        assert_eq!(result[1][0], data.blocks[2].1.receipts()[0][0]);
4480    }
4481
4482    #[test]
4483    fn test_receipts_by_block_range_all_empty_blocks() {
4484        let factory = create_test_provider_factory();
4485        let mut rng = generators::rng();
4486
4487        // create blocks with no transactions
4488        let mut blocks = Vec::new();
4489        for i in 0..3 {
4490            let block =
4491                random_block(&mut rng, i, BlockParams { tx_count: Some(0), ..Default::default() });
4492            blocks.push(block);
4493        }
4494
4495        let provider_rw = factory.provider_rw().unwrap();
4496        for block in blocks {
4497            provider_rw.insert_block(&block.try_recover().unwrap()).unwrap();
4498        }
4499        provider_rw.commit().unwrap();
4500
4501        let provider = factory.provider().unwrap();
4502        let result = provider.receipts_by_block_range(1..=3).unwrap();
4503
4504        assert_eq!(result.len(), 3);
4505        for block_receipts in result {
4506            assert_eq!(block_receipts.len(), 0);
4507        }
4508    }
4509
4510    #[test]
4511    fn test_receipts_by_block_range_consistency_with_individual_calls() {
4512        let factory = create_test_provider_factory();
4513        let data = BlockchainTestData::default();
4514
4515        let provider_rw = factory.provider_rw().unwrap();
4516        provider_rw.insert_block(&data.genesis.try_recover().unwrap()).unwrap();
4517        provider_rw
4518            .write_state(
4519                &ExecutionOutcome { first_block: 0, receipts: vec![vec![]], ..Default::default() },
4520                crate::OriginalValuesKnown::No,
4521                StateWriteConfig::default(),
4522            )
4523            .unwrap();
4524        for (block, outcome) in data.blocks.iter().take(3) {
4525            provider_rw.insert_block(block).unwrap();
4526            provider_rw
4527                .write_state(outcome, crate::OriginalValuesKnown::No, StateWriteConfig::default())
4528                .unwrap();
4529        }
4530        provider_rw.commit().unwrap();
4531
4532        let provider = factory.provider().unwrap();
4533
4534        // get receipts using block range method
4535        let range_result = provider.receipts_by_block_range(1..=3).unwrap();
4536
4537        // get receipts using individual block calls
4538        let mut individual_results = Vec::new();
4539        for block_num in 1..=3 {
4540            let receipts =
4541                provider.receipts_by_block(block_num.into()).unwrap().unwrap_or_default();
4542            individual_results.push(receipts);
4543        }
4544
4545        assert_eq!(range_result, individual_results);
4546    }
4547
4548    #[test]
4549    fn test_receipts_by_block_returns_none_for_missing_unpruned_receipts() {
4550        let factory = create_test_provider_factory();
4551        let data = BlockchainTestData::default();
4552
4553        let provider_rw = factory.provider_rw().unwrap();
4554        provider_rw.insert_block(&data.genesis.try_recover().unwrap()).unwrap();
4555        provider_rw.insert_block(&data.blocks[0].0).unwrap();
4556        provider_rw.commit().unwrap();
4557
4558        let provider = factory.provider().unwrap();
4559        assert!(provider.receipts_by_block(1.into()).unwrap().is_none());
4560    }
4561
4562    #[test]
4563    fn test_write_trie_updates_sorted() {
4564        use reth_trie::{
4565            updates::{StorageTrieUpdatesSorted, TrieUpdatesSorted},
4566            BranchNodeCompact, StorageTrieEntry,
4567        };
4568
4569        let factory = create_test_provider_factory();
4570        let provider_rw = factory.provider_rw().unwrap();
4571
4572        // Pre-populate account trie with data that will be deleted
4573        {
4574            let tx = provider_rw.tx_ref();
4575            let mut cursor = tx.cursor_write::<tables::AccountsTrie>().unwrap();
4576
4577            // Add account node that will be deleted
4578            let to_delete = StoredNibbles(Nibbles::from_nibbles([0x3, 0x4]));
4579            cursor
4580                .upsert(
4581                    to_delete,
4582                    &BranchNodeCompact::new(
4583                        0b1010_1010_1010_1010, // state_mask
4584                        0b0000_0000_0000_0000, // tree_mask
4585                        0b0000_0000_0000_0000, // hash_mask
4586                        vec![],
4587                        None,
4588                    ),
4589                )
4590                .unwrap();
4591
4592            // Add account node that will be updated
4593            let to_update = StoredNibbles(Nibbles::from_nibbles([0x1, 0x2]));
4594            cursor
4595                .upsert(
4596                    to_update,
4597                    &BranchNodeCompact::new(
4598                        0b0101_0101_0101_0101, // old state_mask (will be updated)
4599                        0b0000_0000_0000_0000, // tree_mask
4600                        0b0000_0000_0000_0000, // hash_mask
4601                        vec![],
4602                        None,
4603                    ),
4604                )
4605                .unwrap();
4606        }
4607
4608        // Pre-populate storage tries with data
4609        let storage_address1 = B256::repeat_byte(1u8);
4610        let storage_address2 = B256::repeat_byte(2u8);
4611        {
4612            let tx = provider_rw.tx_ref();
4613            let mut storage_cursor = tx.cursor_dup_write::<tables::StoragesTrie>().unwrap();
4614
4615            // Add storage nodes for address1 (one will be deleted)
4616            storage_cursor
4617                .upsert(
4618                    storage_address1,
4619                    &StorageTrieEntry {
4620                        nibbles: StoredNibblesSubKey(Nibbles::from_nibbles([0x2, 0x0])),
4621                        node: BranchNodeCompact::new(
4622                            0b0011_0011_0011_0011, // will be deleted
4623                            0b0000_0000_0000_0000,
4624                            0b0000_0000_0000_0000,
4625                            vec![],
4626                            None,
4627                        ),
4628                    },
4629                )
4630                .unwrap();
4631
4632            // Add storage nodes for address2.
4633            storage_cursor
4634                .upsert(
4635                    storage_address2,
4636                    &StorageTrieEntry {
4637                        nibbles: StoredNibblesSubKey(Nibbles::from_nibbles([0xa, 0xb])),
4638                        node: BranchNodeCompact::new(
4639                            0b1100_1100_1100_1100,
4640                            0b0000_0000_0000_0000,
4641                            0b0000_0000_0000_0000,
4642                            vec![],
4643                            None,
4644                        ),
4645                    },
4646                )
4647                .unwrap();
4648            storage_cursor
4649                .upsert(
4650                    storage_address2,
4651                    &StorageTrieEntry {
4652                        nibbles: StoredNibblesSubKey(Nibbles::from_nibbles([0xc, 0xd])),
4653                        node: BranchNodeCompact::new(
4654                            0b0011_1100_0011_1100,
4655                            0b0000_0000_0000_0000,
4656                            0b0000_0000_0000_0000,
4657                            vec![],
4658                            None,
4659                        ),
4660                    },
4661                )
4662                .unwrap();
4663        }
4664
4665        // Create sorted account trie updates
4666        let account_nodes = vec![
4667            (
4668                Nibbles::from_nibbles([0x1, 0x2]),
4669                Some(BranchNodeCompact::new(
4670                    0b1111_1111_1111_1111, // state_mask (updated)
4671                    0b0000_0000_0000_0000, // tree_mask
4672                    0b0000_0000_0000_0000, // hash_mask (no hashes)
4673                    vec![],
4674                    None,
4675                )),
4676            ),
4677            (Nibbles::from_nibbles([0x3, 0x4]), None), // Deletion
4678            (
4679                Nibbles::from_nibbles([0x5, 0x6]),
4680                Some(BranchNodeCompact::new(
4681                    0b1111_1111_1111_1111, // state_mask
4682                    0b0000_0000_0000_0000, // tree_mask
4683                    0b0000_0000_0000_0000, // hash_mask (no hashes)
4684                    vec![],
4685                    None,
4686                )),
4687            ),
4688        ];
4689
4690        // Create sorted storage trie updates
4691        let storage_trie1 = StorageTrieUpdatesSorted {
4692            storage_nodes: vec![
4693                (
4694                    Nibbles::from_nibbles([0x1, 0x0]),
4695                    Some(BranchNodeCompact::new(
4696                        0b1111_0000_0000_0000, // state_mask
4697                        0b0000_0000_0000_0000, // tree_mask
4698                        0b0000_0000_0000_0000, // hash_mask (no hashes)
4699                        vec![],
4700                        None,
4701                    )),
4702                ),
4703                (Nibbles::from_nibbles([0x2, 0x0]), None), // Deletion of existing node
4704            ],
4705        };
4706
4707        let storage_trie2 = StorageTrieUpdatesSorted {
4708            storage_nodes: vec![
4709                (Nibbles::from_nibbles([0xa, 0xb]), None),
4710                (Nibbles::from_nibbles([0xc, 0xd]), None),
4711            ],
4712        };
4713
4714        let mut storage_tries = B256Map::default();
4715        storage_tries.insert(storage_address1, storage_trie1);
4716        storage_tries.insert(storage_address2, storage_trie2);
4717
4718        let trie_updates = TrieUpdatesSorted::new(account_nodes, storage_tries);
4719
4720        // Write the sorted trie updates
4721        let num_entries = provider_rw.write_trie_updates_sorted(&trie_updates).unwrap();
4722
4723        // We should have 2 account insertions + 1 account deletion + 1 storage insertion + 3
4724        // storage deletions = 7
4725        assert_eq!(num_entries, 7);
4726
4727        // Verify account trie updates were written correctly
4728        let tx = provider_rw.tx_ref();
4729        let mut cursor = tx.cursor_read::<tables::AccountsTrie>().unwrap();
4730
4731        // Check first account node was updated
4732        let nibbles1 = StoredNibbles(Nibbles::from_nibbles([0x1, 0x2]));
4733        let entry1 = cursor.seek_exact(nibbles1).unwrap();
4734        assert!(entry1.is_some(), "Updated account node should exist");
4735        let expected_mask = reth_trie::TrieMask::new(0b1111_1111_1111_1111);
4736        assert_eq!(
4737            entry1.unwrap().1.state_mask,
4738            expected_mask,
4739            "Account node should have updated state_mask"
4740        );
4741
4742        // Check deleted account node no longer exists
4743        let nibbles2 = StoredNibbles(Nibbles::from_nibbles([0x3, 0x4]));
4744        let entry2 = cursor.seek_exact(nibbles2).unwrap();
4745        assert!(entry2.is_none(), "Deleted account node should not exist");
4746
4747        // Check new account node exists
4748        let nibbles3 = StoredNibbles(Nibbles::from_nibbles([0x5, 0x6]));
4749        let entry3 = cursor.seek_exact(nibbles3).unwrap();
4750        assert!(entry3.is_some(), "New account node should exist");
4751
4752        // Verify storage trie updates were written correctly
4753        let mut storage_cursor = tx.cursor_dup_read::<tables::StoragesTrie>().unwrap();
4754
4755        // Check storage for address1
4756        let storage_entries1: Vec<_> = storage_cursor
4757            .walk_dup(Some(storage_address1), None)
4758            .unwrap()
4759            .collect::<Result<Vec<_>, _>>()
4760            .unwrap();
4761        assert_eq!(
4762            storage_entries1.len(),
4763            1,
4764            "Storage address1 should have 1 entry after deletion"
4765        );
4766        assert_eq!(
4767            storage_entries1[0].1.nibbles.0,
4768            Nibbles::from_nibbles([0x1, 0x0]),
4769            "Remaining entry should be [0x1, 0x0]"
4770        );
4771
4772        // Check storage for address2 was removed
4773        let storage_entries2: Vec<_> = storage_cursor
4774            .walk_dup(Some(storage_address2), None)
4775            .unwrap()
4776            .collect::<Result<Vec<_>, _>>()
4777            .unwrap();
4778        assert_eq!(storage_entries2.len(), 0, "Storage address2 should be empty after removal");
4779
4780        provider_rw.commit().unwrap();
4781    }
4782
4783    #[test]
4784    fn test_save_blocks_only_masks_trie_with_deferred_blocks() {
4785        use reth_trie::{
4786            updates::{StorageTrieUpdatesSorted, TrieUpdatesSorted},
4787            BranchNodeCompact, HashedPostStateSorted, HashedStorageSorted,
4788        };
4789
4790        fn branch(mask: u16) -> BranchNodeCompact {
4791            BranchNodeCompact::new(mask, 0, 0, vec![], None)
4792        }
4793
4794        let factory = create_test_provider_factory();
4795        factory.set_storage_settings_cache(StorageSettings::v1());
4796
4797        let mut test_block_builder = TestBlockBuilder::eth().with_state();
4798        let genesis = test_block_builder.get_executed_blocks(0..1).next().unwrap();
4799        let blocks: Vec<_> = test_block_builder.get_executed_blocks(1..3).collect();
4800
4801        let provider_rw = factory.provider_rw().unwrap();
4802        save_genesis(&provider_rw, &genesis).unwrap();
4803        provider_rw.commit().unwrap();
4804
4805        let kept_account = B256::with_last_byte(0x11);
4806        let masked_account = B256::with_last_byte(0x12);
4807        let kept_storage = B256::with_last_byte(0x21);
4808        let masked_storage = B256::with_last_byte(0x22);
4809        let kept_slot = B256::with_last_byte(0x31);
4810        let masked_slot = B256::with_last_byte(0x32);
4811        let kept_account_node = Nibbles::from_nibbles([0x1, 0x2]);
4812        let masked_account_node = Nibbles::from_nibbles([0x1, 0x3]);
4813        let kept_storage_node = Nibbles::from_nibbles([0x2, 0x1]);
4814        let masked_storage_node = Nibbles::from_nibbles([0x2, 0x2]);
4815        let full_persist_base = &blocks[0];
4816        let deferred_trie_base = &blocks[1];
4817
4818        let full_persist_hashed_state = HashedPostStateSorted::new(
4819            vec![
4820                (kept_account, Some(Account::default())),
4821                (masked_account, Some(Account { nonce: 1, ..Default::default() })),
4822            ],
4823            B256Map::from_iter([
4824                (kept_storage, HashedStorageSorted { storage_slots: vec![(kept_slot, U256::ONE)] }),
4825                (
4826                    masked_storage,
4827                    HashedStorageSorted { storage_slots: vec![(masked_slot, U256::from(2))] },
4828                ),
4829            ]),
4830        );
4831        let full_persist_trie_updates = TrieUpdatesSorted::new(
4832            vec![
4833                (kept_account_node, Some(branch(0b0000_1111_0000_1111))),
4834                (masked_account_node, Some(branch(0b1111_0000_1111_0000))),
4835            ],
4836            B256Map::from_iter([
4837                (
4838                    kept_storage,
4839                    StorageTrieUpdatesSorted {
4840                        storage_nodes: vec![(kept_storage_node, Some(branch(0b1010)))],
4841                    },
4842                ),
4843                (
4844                    masked_storage,
4845                    StorageTrieUpdatesSorted {
4846                        storage_nodes: vec![(masked_storage_node, Some(branch(0b0101)))],
4847                    },
4848                ),
4849            ]),
4850        );
4851
4852        let full_persist_block = ExecutedBlock::new(
4853            Arc::clone(&full_persist_base.recovered_block),
4854            Arc::clone(&full_persist_base.execution_output),
4855            SortedTrieData::new(
4856                Arc::new(full_persist_hashed_state),
4857                Arc::new(full_persist_trie_updates),
4858            ),
4859        );
4860
4861        let deferred_trie_hashed_state = HashedPostStateSorted::new(
4862            vec![(masked_account, Some(Account { nonce: 3, ..Default::default() }))],
4863            B256Map::from_iter([(
4864                masked_storage,
4865                HashedStorageSorted { storage_slots: vec![(masked_slot, U256::from(4))] },
4866            )]),
4867        );
4868        let deferred_trie_updates = TrieUpdatesSorted::new(
4869            vec![(masked_account_node, Some(branch(0b0011_0011)))],
4870            B256Map::from_iter([(
4871                masked_storage,
4872                StorageTrieUpdatesSorted {
4873                    storage_nodes: vec![(masked_storage_node, Some(branch(0b1100)))],
4874                },
4875            )]),
4876        );
4877        let deferred_trie_block = ExecutedBlock::new(
4878            Arc::clone(&deferred_trie_base.recovered_block),
4879            Arc::clone(&deferred_trie_base.execution_output),
4880            SortedTrieData::new(
4881                Arc::new(deferred_trie_hashed_state),
4882                Arc::new(deferred_trie_updates),
4883            ),
4884        );
4885
4886        let provider_rw = factory.provider_rw().unwrap();
4887        let input = SaveBlocksInput::new(
4888            vec![full_persist_block.clone(), deferred_trie_block.clone()],
4889            0,
4890            0,
4891            2,
4892            0,
4893        );
4894        provider_rw.save_blocks(&input).unwrap();
4895        provider_rw.commit().unwrap();
4896
4897        let provider_rw = factory.provider_rw().unwrap();
4898        let input =
4899            SaveBlocksInput::new(vec![full_persist_block, deferred_trie_block.clone()], 2, 0, 2, 1);
4900        assert!(input.persist_rest_blocks().is_empty());
4901        provider_rw.save_blocks(&input).unwrap();
4902        provider_rw.commit().unwrap();
4903
4904        let provider = factory.provider().unwrap();
4905        let tx = provider.tx_ref();
4906        let finish_checkpoint = provider.get_stage_checkpoint(StageId::Finish).unwrap().unwrap();
4907        assert_eq!(finish_checkpoint.block_number, 2);
4908        assert_eq!(
4909            finish_checkpoint.finish_stage_checkpoint().unwrap().partial_state_trie,
4910            Some(1)
4911        );
4912        assert!(provider.block_hash(2).unwrap().is_some());
4913
4914        let mut hashed_accounts = tx.cursor_read::<tables::HashedAccounts>().unwrap();
4915        assert!(hashed_accounts.seek_exact(kept_account).unwrap().is_some());
4916        assert!(hashed_accounts.seek_exact(masked_account).unwrap().is_none());
4917
4918        let mut hashed_storages = tx.cursor_dup_read::<tables::HashedStorages>().unwrap();
4919        assert!(hashed_storages.seek_by_key_subkey(kept_storage, kept_slot).unwrap().is_some());
4920        assert!(hashed_storages
4921            .walk_dup(Some(masked_storage), None)
4922            .unwrap()
4923            .next()
4924            .transpose()
4925            .unwrap()
4926            .is_none());
4927
4928        let mut account_trie = tx.cursor_read::<tables::AccountsTrie>().unwrap();
4929        assert!(account_trie.seek_exact(StoredNibbles(kept_account_node)).unwrap().is_some());
4930        assert!(account_trie.seek_exact(StoredNibbles(masked_account_node)).unwrap().is_none());
4931
4932        let mut storage_trie = tx.cursor_dup_read::<tables::StoragesTrie>().unwrap();
4933        let kept_entries: Vec<_> = storage_trie
4934            .walk_dup(Some(kept_storage), None)
4935            .unwrap()
4936            .collect::<Result<Vec<_>, _>>()
4937            .unwrap();
4938        assert_eq!(kept_entries.len(), 1);
4939        assert_eq!(kept_entries[0].1.nibbles.0, kept_storage_node);
4940
4941        let masked_entries: Vec<_> = storage_trie
4942            .walk_dup(Some(masked_storage), None)
4943            .unwrap()
4944            .collect::<Result<Vec<_>, _>>()
4945            .unwrap();
4946        assert!(masked_entries.is_empty());
4947
4948        drop(storage_trie);
4949        drop(account_trie);
4950        drop(hashed_storages);
4951        drop(hashed_accounts);
4952        drop(provider);
4953
4954        let provider_rw = factory.provider_rw().unwrap();
4955        let input = SaveBlocksInput::new(vec![deferred_trie_block], 2, 1, 2, 2);
4956        assert!(input.persist_rest_blocks().is_empty());
4957        provider_rw.save_blocks(&input).unwrap();
4958        provider_rw.commit().unwrap();
4959
4960        let provider = factory.provider().unwrap();
4961        let finish_checkpoint = provider.get_stage_checkpoint(StageId::Finish).unwrap().unwrap();
4962        assert_eq!(finish_checkpoint.block_number, 2);
4963        assert!(finish_checkpoint.finish_stage_checkpoint().is_none());
4964
4965        let mut hashed_accounts =
4966            provider.tx_ref().cursor_read::<tables::HashedAccounts>().unwrap();
4967        let (_, account) = hashed_accounts.seek_exact(masked_account).unwrap().unwrap();
4968        assert_eq!(account.nonce, 3);
4969
4970        let mut hashed_storages =
4971            provider.tx_ref().cursor_dup_read::<tables::HashedStorages>().unwrap();
4972        let storage =
4973            hashed_storages.seek_by_key_subkey(masked_storage, masked_slot).unwrap().unwrap();
4974        assert_eq!(storage.value, U256::from(4));
4975
4976        let mut account_trie = provider.tx_ref().cursor_read::<tables::AccountsTrie>().unwrap();
4977        assert!(account_trie.seek_exact(StoredNibbles(masked_account_node)).unwrap().is_some());
4978
4979        let mut storage_trie = provider.tx_ref().cursor_dup_read::<tables::StoragesTrie>().unwrap();
4980        let masked_entries: Vec<_> = storage_trie
4981            .walk_dup(Some(masked_storage), None)
4982            .unwrap()
4983            .collect::<Result<Vec<_>, _>>()
4984            .unwrap();
4985        assert_eq!(masked_entries.len(), 1);
4986        assert_eq!(masked_entries[0].1.nibbles.0, masked_storage_node);
4987    }
4988
4989    #[test]
4990    fn test_save_blocks_partial_cycles_do_not_duplicate_static_file_writes() {
4991        let factory = create_test_provider_factory();
4992        let mut test_block_builder = TestBlockBuilder::eth().with_state();
4993
4994        let genesis = test_block_builder.get_executed_blocks(0..1).next().unwrap();
4995        let blocks: Vec<_> = test_block_builder.get_executed_blocks(1..5).collect();
4996
4997        let provider_rw = factory.provider_rw().unwrap();
4998        save_genesis(&provider_rw, &genesis).unwrap();
4999        provider_rw.commit().unwrap();
5000
5001        let provider_rw = factory.provider_rw().unwrap();
5002        let input = SaveBlocksInput::new(blocks[..2].to_vec(), 0, 0, 2, 2);
5003        provider_rw.save_blocks(&input).unwrap();
5004        provider_rw.commit().unwrap();
5005
5006        let provider_rw = factory.provider_rw().unwrap();
5007        let input = SaveBlocksInput::new(blocks[2..].to_vec(), 2, 2, 4, 2);
5008        provider_rw.save_blocks(&input).unwrap();
5009        provider_rw.commit().unwrap();
5010
5011        let provider_rw = factory.provider_rw().unwrap();
5012        let stale_input = SaveBlocksInput::new(vec![blocks[0].clone()], 0, 0, 1, 1);
5013        let err = provider_rw.save_blocks(&stale_input).unwrap_err();
5014        assert!(err.to_string().contains("persistence frontiers do not match Finish checkpoint"));
5015        drop(provider_rw);
5016
5017        let provider = factory.provider().unwrap();
5018        let finish_checkpoint = provider.get_stage_checkpoint(StageId::Finish).unwrap().unwrap();
5019        assert_eq!(finish_checkpoint.block_number, 4);
5020        assert_eq!(
5021            finish_checkpoint.finish_stage_checkpoint().unwrap().partial_state_trie,
5022            Some(2)
5023        );
5024
5025        let static_files = factory.static_file_provider();
5026        assert_eq!(static_files.get_highest_static_file_block(StaticFileSegment::Headers), Some(4));
5027        assert_eq!(
5028            static_files.get_highest_static_file_block(StaticFileSegment::Transactions),
5029            Some(4)
5030        );
5031        assert_eq!(
5032            static_files.get_highest_static_file_block(StaticFileSegment::Receipts),
5033            Some(4)
5034        );
5035    }
5036
5037    #[test]
5038    fn unwind_restores_trie_nodes_masked_above_the_frontier() {
5039        let factory = create_test_provider_factory();
5040        let blocks = TestBlockBuilder::eth().get_executed_blocks(0..11).collect::<Vec<_>>();
5041        let provider = factory.provider_rw().unwrap();
5042        for block in &blocks {
5043            provider.insert_block(block.recovered_block()).unwrap();
5044        }
5045        for block in &blocks[6..] {
5046            factory.overlay_manager().insert_block(block.clone());
5047        }
5048        provider
5049            .save_stage_checkpoint(
5050                StageId::Finish,
5051                StageCheckpoint::new(10)
5052                    .with_finish_stage_checkpoint(FinishCheckpoint { partial_state_trie: Some(5) }),
5053            )
5054            .unwrap();
5055        let address = Address::with_last_byte(1);
5056        // Block 9 deletes storage that existed at block 4. Its masking leaves the trie tables
5057        // empty despite the persisted frontier being block 5; the changeset restores that storage.
5058        provider
5059            .tx_ref()
5060            .put::<tables::AccountChangeSets>(
5061                9,
5062                AccountBeforeTx { address, info: Some(Account { nonce: 1, ..Default::default() }) },
5063            )
5064            .unwrap();
5065        for slot in 0..257 {
5066            provider
5067                .tx_ref()
5068                .put::<tables::StorageChangeSets>(
5069                    BlockNumberAddress((9, address)),
5070                    StorageEntry { key: B256::from(U256::from(slot)), value: U256::from(1) },
5071                )
5072                .unwrap();
5073        }
5074        provider.commit().unwrap();
5075        let provider = factory.provider_rw().unwrap();
5076        assert_eq!(provider.tx_ref().entries::<tables::StoragesTrie>().unwrap(), 0);
5077        provider.unwind_trie_state_from(9).unwrap();
5078        assert!(provider.tx_ref().entries::<tables::StoragesTrie>().unwrap() > 0);
5079    }
5080
5081    #[test]
5082    fn remove_block_and_execution_above_returns_persistence_frontiers() {
5083        let factory = create_test_provider_factory();
5084        let mut test_block_builder = TestBlockBuilder::eth().with_state();
5085
5086        let genesis = test_block_builder.get_executed_blocks(0..1).next().unwrap();
5087        let blocks: Vec<_> = test_block_builder.get_executed_blocks(1..5).collect();
5088
5089        let provider_rw = factory.provider_rw().unwrap();
5090        save_genesis(&provider_rw, &genesis).unwrap();
5091        provider_rw.commit().unwrap();
5092
5093        for block in &blocks[2..] {
5094            factory.overlay_manager().insert_block(block.clone());
5095        }
5096
5097        let provider_rw = factory.provider_rw().unwrap();
5098        let input = SaveBlocksInput::new(blocks, 0, 0, 4, 2);
5099        provider_rw.save_blocks(&input).unwrap();
5100        provider_rw.commit().unwrap();
5101
5102        let provider_rw = factory.provider_rw().unwrap();
5103        let frontiers = provider_rw.remove_block_and_execution_above(3).unwrap();
5104        assert_eq!(frontiers, PersistenceFrontiers { db_tip: 3, partial_state_trie: 2 });
5105        provider_rw.commit().unwrap();
5106
5107        let provider = factory.provider().unwrap();
5108        let checkpoint = provider.get_stage_checkpoint(StageId::Finish).unwrap().unwrap();
5109        assert_eq!(
5110            checkpoint.finish_stage_checkpoint().and_then(|finish| finish.partial_state_trie()),
5111            Some(2)
5112        );
5113    }
5114
5115    #[test]
5116    fn test_prunable_receipts_logic() {
5117        let insert_blocks =
5118            |provider_rw: &DatabaseProviderRW<_, _>, tip_block: u64, tx_count: u8| {
5119                let mut rng = generators::rng();
5120                for block_num in 0..=tip_block {
5121                    let block = random_block(
5122                        &mut rng,
5123                        block_num,
5124                        BlockParams { tx_count: Some(tx_count), ..Default::default() },
5125                    );
5126                    provider_rw.insert_block(&block.try_recover().unwrap()).unwrap();
5127                }
5128            };
5129
5130        let write_receipts = |provider_rw: DatabaseProviderRW<_, _>, block: u64| {
5131            let outcome = ExecutionOutcome {
5132                first_block: block,
5133                receipts: vec![vec![Receipt {
5134                    tx_type: Default::default(),
5135                    success: true,
5136                    cumulative_gas_used: block, // identifier to assert against
5137                    logs: vec![],
5138                }]],
5139                ..Default::default()
5140            };
5141            provider_rw
5142                .write_state(&outcome, crate::OriginalValuesKnown::No, StateWriteConfig::default())
5143                .unwrap();
5144            provider_rw.commit().unwrap();
5145        };
5146
5147        // Legacy mode (receipts in DB) - should be prunable
5148        {
5149            let factory = create_test_provider_factory();
5150            let storage_settings = StorageSettings::v1();
5151            factory.set_storage_settings_cache(storage_settings);
5152            let factory = factory.with_prune_modes(PruneModes {
5153                receipts: Some(PruneMode::Before(100)),
5154                ..Default::default()
5155            });
5156
5157            let tip_block = 200u64;
5158            let first_block = 1u64;
5159
5160            // create chain
5161            let provider_rw = factory.provider_rw().unwrap();
5162            insert_blocks(&provider_rw, tip_block, 1);
5163            provider_rw.commit().unwrap();
5164
5165            write_receipts(
5166                factory.provider_rw().unwrap().with_minimum_pruning_distance(100),
5167                first_block,
5168            );
5169            write_receipts(
5170                factory.provider_rw().unwrap().with_minimum_pruning_distance(100),
5171                tip_block - 1,
5172            );
5173
5174            let provider = factory.provider().unwrap();
5175
5176            assert!(provider.receipts_by_block(0.into()).unwrap().is_none());
5177            assert!(provider
5178                .receipts_by_block((tip_block - 1).into())
5179                .unwrap()
5180                .is_some_and(|r| r.len() == 1));
5181        }
5182
5183        // Static files mode
5184        {
5185            let factory = create_test_provider_factory();
5186            let storage_settings = StorageSettings::v2();
5187            factory.set_storage_settings_cache(storage_settings);
5188            let factory = factory.with_prune_modes(PruneModes {
5189                receipts: Some(PruneMode::Before(2)),
5190                ..Default::default()
5191            });
5192
5193            let tip_block = 200u64;
5194
5195            // create chain
5196            let provider_rw = factory.provider_rw().unwrap();
5197            insert_blocks(&provider_rw, tip_block, 1);
5198            provider_rw.commit().unwrap();
5199
5200            // Attempt to write receipts for block 0 and 1 (should be skipped)
5201            write_receipts(factory.provider_rw().unwrap().with_minimum_pruning_distance(100), 0);
5202            write_receipts(factory.provider_rw().unwrap().with_minimum_pruning_distance(100), 1);
5203
5204            assert!(factory
5205                .static_file_provider()
5206                .get_highest_static_file_tx(StaticFileSegment::Receipts)
5207                .is_none(),);
5208            assert!(factory
5209                .static_file_provider()
5210                .get_highest_static_file_block(StaticFileSegment::Receipts)
5211                .is_some_and(|b| b == 1),);
5212
5213            // Since we have prune mode Before(2), the next receipt (block 2) should be written to
5214            // static files.
5215            write_receipts(factory.provider_rw().unwrap().with_minimum_pruning_distance(100), 2);
5216            assert!(factory
5217                .static_file_provider()
5218                .get_highest_static_file_tx(StaticFileSegment::Receipts)
5219                .is_some_and(|num| num == 2),);
5220
5221            // After having a receipt already in static files, attempt to skip the next receipt by
5222            // changing the prune mode. It should NOT skip it and should still write the receipt,
5223            // since static files do not support gaps.
5224            let factory = factory.with_prune_modes(PruneModes {
5225                receipts: Some(PruneMode::Before(100)),
5226                ..Default::default()
5227            });
5228            let provider_rw = factory.provider_rw().unwrap().with_minimum_pruning_distance(1);
5229            assert!(PruneMode::Distance(1).should_prune(3, tip_block));
5230            write_receipts(provider_rw, 3);
5231
5232            // Ensure we can only fetch the 2 last receipts.
5233            //
5234            // Test setup only has 1 tx per block and each receipt has its cumulative_gas_used set
5235            // to the block number it belongs to easily identify and assert.
5236            let provider = factory.provider().unwrap();
5237            assert!(EitherWriter::receipts_destination(&provider).is_static_file());
5238            for (num, has_receipt) in [(0, false), (1, false), (2, true), (3, true)] {
5239                let receipts = provider.receipts_by_block(num.into()).unwrap();
5240                if has_receipt {
5241                    assert!(receipts.is_some_and(|r| r.len() == 1));
5242                } else {
5243                    assert!(receipts.is_none());
5244                }
5245
5246                let receipt = provider.receipt(num).unwrap();
5247                if has_receipt {
5248                    assert!(receipt.is_some_and(|r| r.cumulative_gas_used == num));
5249                } else {
5250                    assert!(receipt.is_none());
5251                }
5252            }
5253        }
5254    }
5255
5256    #[test]
5257    fn test_unwind_storage_hashing_with_hashed_state() {
5258        let factory = create_test_provider_factory();
5259        let storage_settings = StorageSettings::v2();
5260        factory.set_storage_settings_cache(storage_settings);
5261
5262        let address = Address::random();
5263        let hashed_address = keccak256(address);
5264
5265        let plain_slot = B256::random();
5266        let hashed_slot = keccak256(plain_slot);
5267
5268        let current_value = U256::from(100);
5269        let old_value = U256::from(42);
5270
5271        let provider_rw = factory.provider_rw().unwrap();
5272        provider_rw
5273            .tx
5274            .cursor_dup_write::<tables::HashedStorages>()
5275            .unwrap()
5276            .upsert(hashed_address, &StorageEntry { key: hashed_slot, value: current_value })
5277            .unwrap();
5278
5279        let changesets = vec![(
5280            BlockNumberAddress((1, address)),
5281            StorageEntry { key: plain_slot, value: old_value },
5282        )];
5283
5284        let result = provider_rw.unwind_storage_hashing(changesets.into_iter()).unwrap();
5285
5286        assert_eq!(result.len(), 1);
5287        assert!(result.contains_key(&hashed_address));
5288        assert!(result[&hashed_address].contains(&hashed_slot));
5289
5290        let mut cursor = provider_rw.tx.cursor_dup_read::<tables::HashedStorages>().unwrap();
5291        let entry = cursor
5292            .seek_by_key_subkey(hashed_address, hashed_slot)
5293            .unwrap()
5294            .expect("entry should exist");
5295        assert_eq!(entry.key, hashed_slot);
5296        assert_eq!(entry.value, old_value);
5297    }
5298
5299    #[test]
5300    fn test_write_and_remove_state_roundtrip_legacy() {
5301        let factory = create_test_provider_factory();
5302        let storage_settings = StorageSettings::v1();
5303        assert!(!storage_settings.use_hashed_state());
5304        factory.set_storage_settings_cache(storage_settings);
5305
5306        let address = Address::with_last_byte(1);
5307        let hashed_address = keccak256(address);
5308        let slot = U256::from(5);
5309        let slot_key = B256::from(slot);
5310        let hashed_slot = keccak256(slot_key);
5311
5312        let mut rng = generators::rng();
5313        let block0 =
5314            random_block(&mut rng, 0, BlockParams { tx_count: Some(0), ..Default::default() });
5315        let block1 =
5316            random_block(&mut rng, 1, BlockParams { tx_count: Some(0), ..Default::default() });
5317
5318        {
5319            let provider_rw = factory.provider_rw().unwrap();
5320            provider_rw.insert_block(&block0.try_recover().unwrap()).unwrap();
5321            provider_rw.insert_block(&block1.try_recover().unwrap()).unwrap();
5322            provider_rw
5323                .tx
5324                .cursor_write::<tables::PlainAccountState>()
5325                .unwrap()
5326                .upsert(address, &Account::default())
5327                .unwrap();
5328            provider_rw.commit().unwrap();
5329        }
5330
5331        let provider_rw = factory.provider_rw().unwrap();
5332
5333        let mut state_init: BundleStateInit = AddressMap::default();
5334        let mut storage_map: B256Map<(U256, U256)> = B256Map::default();
5335        storage_map.insert(slot_key, (U256::ZERO, U256::from(10)));
5336        state_init.insert(
5337            address,
5338            (
5339                Some(Account::default()),
5340                Some(Account { nonce: 1, ..Default::default() }),
5341                storage_map,
5342            ),
5343        );
5344
5345        let mut reverts_init: RevertsInit = HashMap::default();
5346        let mut block_reverts: AddressMap<AccountRevertInit> = AddressMap::default();
5347        block_reverts.insert(
5348            address,
5349            (
5350                Some(Some(Account::default())),
5351                vec![StorageEntry { key: slot_key, value: U256::ZERO }],
5352            ),
5353        );
5354        reverts_init.insert(1, block_reverts);
5355
5356        let execution_outcome =
5357            ExecutionOutcome::new_init(state_init, reverts_init, [], vec![vec![]], 1, vec![]);
5358
5359        provider_rw
5360            .write_state(
5361                &execution_outcome,
5362                OriginalValuesKnown::Yes,
5363                StateWriteConfig {
5364                    write_receipts: false,
5365                    write_account_changesets: true,
5366                    write_storage_changesets: true,
5367                },
5368            )
5369            .unwrap();
5370
5371        let hashed_state =
5372            execution_outcome.hash_state_slow::<reth_trie::KeccakKeyHasher>().into_sorted();
5373        provider_rw.write_hashed_state(&hashed_state).unwrap();
5374
5375        let account = provider_rw
5376            .tx
5377            .cursor_read::<tables::PlainAccountState>()
5378            .unwrap()
5379            .seek_exact(address)
5380            .unwrap()
5381            .unwrap()
5382            .1;
5383        assert_eq!(account.nonce, 1);
5384
5385        let storage_entry = provider_rw
5386            .tx
5387            .cursor_dup_read::<tables::PlainStorageState>()
5388            .unwrap()
5389            .seek_by_key_subkey(address, slot_key)
5390            .unwrap()
5391            .unwrap();
5392        assert_eq!(storage_entry.key, slot_key);
5393        assert_eq!(storage_entry.value, U256::from(10));
5394
5395        let hashed_entry = provider_rw
5396            .tx
5397            .cursor_dup_read::<tables::HashedStorages>()
5398            .unwrap()
5399            .seek_by_key_subkey(hashed_address, hashed_slot)
5400            .unwrap()
5401            .unwrap();
5402        assert_eq!(hashed_entry.key, hashed_slot);
5403        assert_eq!(hashed_entry.value, U256::from(10));
5404
5405        let account_cs_entries = provider_rw
5406            .tx
5407            .cursor_dup_read::<tables::AccountChangeSets>()
5408            .unwrap()
5409            .walk(Some(1))
5410            .unwrap()
5411            .collect::<Result<Vec<_>, _>>()
5412            .unwrap();
5413        assert!(!account_cs_entries.is_empty());
5414
5415        let storage_cs_entries = provider_rw
5416            .tx
5417            .cursor_read::<tables::StorageChangeSets>()
5418            .unwrap()
5419            .walk(Some(BlockNumberAddress((1, address))))
5420            .unwrap()
5421            .collect::<Result<Vec<_>, _>>()
5422            .unwrap();
5423        assert!(!storage_cs_entries.is_empty());
5424        assert_eq!(storage_cs_entries[0].1.key, slot_key);
5425
5426        provider_rw.remove_state_above(0).unwrap();
5427
5428        let restored_account = provider_rw
5429            .tx
5430            .cursor_read::<tables::PlainAccountState>()
5431            .unwrap()
5432            .seek_exact(address)
5433            .unwrap()
5434            .unwrap()
5435            .1;
5436        assert_eq!(restored_account.nonce, 0);
5437
5438        let storage_gone = provider_rw
5439            .tx
5440            .cursor_dup_read::<tables::PlainStorageState>()
5441            .unwrap()
5442            .seek_by_key_subkey(address, slot_key)
5443            .unwrap();
5444        assert!(storage_gone.is_none() || storage_gone.unwrap().key != slot_key);
5445
5446        let account_cs_after = provider_rw
5447            .tx
5448            .cursor_dup_read::<tables::AccountChangeSets>()
5449            .unwrap()
5450            .walk(Some(1))
5451            .unwrap()
5452            .collect::<Result<Vec<_>, _>>()
5453            .unwrap();
5454        assert!(account_cs_after.is_empty());
5455
5456        let storage_cs_after = provider_rw
5457            .tx
5458            .cursor_read::<tables::StorageChangeSets>()
5459            .unwrap()
5460            .walk(Some(BlockNumberAddress((1, address))))
5461            .unwrap()
5462            .collect::<Result<Vec<_>, _>>()
5463            .unwrap();
5464        assert!(storage_cs_after.is_empty());
5465    }
5466
5467    #[test]
5468    fn test_unwind_storage_hashing_legacy() {
5469        let factory = create_test_provider_factory();
5470        let storage_settings = StorageSettings::v1();
5471        assert!(!storage_settings.use_hashed_state());
5472        factory.set_storage_settings_cache(storage_settings);
5473
5474        let address = Address::random();
5475        let hashed_address = keccak256(address);
5476
5477        let plain_slot = B256::random();
5478        let hashed_slot = keccak256(plain_slot);
5479
5480        let current_value = U256::from(100);
5481        let old_value = U256::from(42);
5482
5483        let provider_rw = factory.provider_rw().unwrap();
5484        provider_rw
5485            .tx
5486            .cursor_dup_write::<tables::HashedStorages>()
5487            .unwrap()
5488            .upsert(hashed_address, &StorageEntry { key: hashed_slot, value: current_value })
5489            .unwrap();
5490
5491        let changesets = vec![(
5492            BlockNumberAddress((1, address)),
5493            StorageEntry { key: plain_slot, value: old_value },
5494        )];
5495
5496        let result = provider_rw.unwind_storage_hashing(changesets.into_iter()).unwrap();
5497
5498        assert_eq!(result.len(), 1);
5499        assert!(result.contains_key(&hashed_address));
5500        assert!(result[&hashed_address].contains(&hashed_slot));
5501
5502        let mut cursor = provider_rw.tx.cursor_dup_read::<tables::HashedStorages>().unwrap();
5503        let entry = cursor
5504            .seek_by_key_subkey(hashed_address, hashed_slot)
5505            .unwrap()
5506            .expect("entry should exist");
5507        assert_eq!(entry.key, hashed_slot);
5508        assert_eq!(entry.value, old_value);
5509    }
5510
5511    #[test]
5512    fn test_write_state_hashed() {
5513        use reth_trie::{HashedPostState, KeccakKeyHasher};
5514
5515        let factory = create_test_provider_factory();
5516        factory.set_storage_settings_cache(StorageSettings::v2());
5517
5518        let address = Address::with_last_byte(1);
5519        let slot = U256::from(5);
5520        let slot_key = B256::from(slot);
5521        let hashed_address = keccak256(address);
5522        let hashed_slot = keccak256(slot_key);
5523
5524        {
5525            let sf = factory.static_file_provider();
5526            let mut hw = sf.latest_writer(StaticFileSegment::Headers).unwrap();
5527            let h0 = alloy_consensus::Header { number: 0, ..Default::default() };
5528            hw.append_header(&h0, &B256::ZERO).unwrap();
5529            let h1 = alloy_consensus::Header { number: 1, ..Default::default() };
5530            hw.append_header(&h1, &B256::ZERO).unwrap();
5531            hw.commit().unwrap();
5532
5533            let mut aw = sf.latest_writer(StaticFileSegment::AccountChangeSets).unwrap();
5534            aw.append_account_changeset(vec![], 0).unwrap();
5535            aw.commit().unwrap();
5536
5537            let mut sw = sf.latest_writer(StaticFileSegment::StorageChangeSets).unwrap();
5538            sw.append_storage_changeset(vec![], 0).unwrap();
5539            sw.commit().unwrap();
5540        }
5541
5542        let provider_rw = factory.provider_rw().unwrap();
5543
5544        let bundle = BundleState::builder(1..=1)
5545            .state_present_account_info(
5546                address,
5547                AccountInfo { nonce: 1, balance: U256::from(10), ..Default::default() },
5548            )
5549            .state_storage(address, HashMap::from_iter([(slot, (U256::ZERO, U256::from(10)))]))
5550            .revert_account_info(1, address, Some(None))
5551            .revert_storage(1, address, vec![(slot, U256::ZERO)])
5552            .build();
5553
5554        let execution_outcome = ExecutionOutcome::new(bundle.clone(), vec![vec![]], 1, Vec::new());
5555
5556        provider_rw
5557            .tx
5558            .put::<tables::BlockBodyIndices>(
5559                1,
5560                StoredBlockBodyIndices { first_tx_num: 0, tx_count: 0 },
5561            )
5562            .unwrap();
5563
5564        provider_rw
5565            .write_state(
5566                &execution_outcome,
5567                OriginalValuesKnown::Yes,
5568                StateWriteConfig {
5569                    write_receipts: false,
5570                    write_account_changesets: true,
5571                    write_storage_changesets: true,
5572                },
5573            )
5574            .unwrap();
5575
5576        let hashed_state =
5577            HashedPostState::from_bundle_state::<KeccakKeyHasher>(bundle.state()).into_sorted();
5578        provider_rw.write_hashed_state(&hashed_state).unwrap();
5579
5580        let plain_storage_entries = provider_rw
5581            .tx
5582            .cursor_dup_read::<tables::PlainStorageState>()
5583            .unwrap()
5584            .walk(None)
5585            .unwrap()
5586            .collect::<Result<Vec<_>, _>>()
5587            .unwrap();
5588        assert!(plain_storage_entries.is_empty());
5589
5590        let hashed_entry = provider_rw
5591            .tx
5592            .cursor_dup_read::<tables::HashedStorages>()
5593            .unwrap()
5594            .seek_by_key_subkey(hashed_address, hashed_slot)
5595            .unwrap()
5596            .unwrap();
5597        assert_eq!(hashed_entry.key, hashed_slot);
5598        assert_eq!(hashed_entry.value, U256::from(10));
5599
5600        provider_rw.static_file_provider().commit().unwrap();
5601
5602        let sf = factory.static_file_provider();
5603        let storage_cs = sf.storage_changeset(1).unwrap();
5604        assert!(!storage_cs.is_empty());
5605        assert_eq!(storage_cs[0].1.key, slot_key);
5606
5607        let account_cs = sf.account_block_changeset(1).unwrap();
5608        assert!(!account_cs.is_empty());
5609        assert_eq!(account_cs[0].address, address);
5610    }
5611
5612    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
5613    enum StorageMode {
5614        V1,
5615        V2,
5616    }
5617
5618    fn run_save_blocks_and_verify(mode: StorageMode) {
5619        use alloy_primitives::map::{FbBuildHasher, HashMap};
5620
5621        let factory = create_test_provider_factory();
5622
5623        match mode {
5624            StorageMode::V1 => factory.set_storage_settings_cache(StorageSettings::v1()),
5625            StorageMode::V2 => factory.set_storage_settings_cache(StorageSettings::v2()),
5626        }
5627
5628        let num_blocks = 3u64;
5629        let accounts_per_block = 5usize;
5630        let slots_per_account = 3usize;
5631
5632        let genesis = SealedBlock::<reth_ethereum_primitives::Block>::from_sealed_parts(
5633            SealedHeader::new(
5634                Header { number: 0, difficulty: U256::ONE, ..Default::default() },
5635                B256::ZERO,
5636            ),
5637            Default::default(),
5638        );
5639
5640        let genesis_executed: ExecutedBlock = ExecutedBlock::new(
5641            Arc::new(genesis.try_recover().unwrap()),
5642            Arc::new(BlockExecutionOutput {
5643                result: BlockExecutionResult {
5644                    receipts: vec![],
5645                    requests: Default::default(),
5646                    gas_used: 0,
5647                    blob_gas_used: 0,
5648                },
5649                state: Default::default(),
5650            }),
5651            SortedTrieData::default(),
5652        );
5653        let provider_rw = factory.provider_rw().unwrap();
5654        save_genesis(&provider_rw, &genesis_executed).unwrap();
5655        provider_rw.commit().unwrap();
5656
5657        let mut blocks: Vec<ExecutedBlock> = Vec::new();
5658        let mut parent_hash = B256::ZERO;
5659
5660        for block_num in 1..=num_blocks {
5661            let mut builder = BundleState::builder(block_num..=block_num);
5662
5663            for acct_idx in 0..accounts_per_block {
5664                let address = Address::with_last_byte((block_num * 10 + acct_idx as u64) as u8);
5665                let info = AccountInfo {
5666                    nonce: block_num,
5667                    balance: U256::from(block_num * 100 + acct_idx as u64),
5668                    ..Default::default()
5669                };
5670
5671                let storage: HashMap<U256, (U256, U256), FbBuildHasher<32>> = (1..=
5672                    slots_per_account as u64)
5673                    .map(|s| {
5674                        (
5675                            U256::from(s + acct_idx as u64 * 100),
5676                            (U256::ZERO, U256::from(block_num * 1000 + s)),
5677                        )
5678                    })
5679                    .collect();
5680
5681                let revert_storage: Vec<(U256, U256)> = (1..=slots_per_account as u64)
5682                    .map(|s| (U256::from(s + acct_idx as u64 * 100), U256::ZERO))
5683                    .collect();
5684
5685                builder = builder
5686                    .state_present_account_info(address, info)
5687                    .revert_account_info(block_num, address, Some(None))
5688                    .state_storage(address, storage)
5689                    .revert_storage(block_num, address, revert_storage);
5690            }
5691
5692            let bundle = builder.build();
5693
5694            let hashed_state =
5695                HashedPostState::from_bundle_state::<KeccakKeyHasher>(bundle.state()).into_sorted();
5696
5697            let header = Header {
5698                number: block_num,
5699                parent_hash,
5700                difficulty: U256::ONE,
5701                ..Default::default()
5702            };
5703            let block = SealedBlock::<reth_ethereum_primitives::Block>::seal_parts(
5704                header,
5705                Default::default(),
5706            );
5707            parent_hash = block.hash();
5708
5709            let executed = ExecutedBlock::new(
5710                Arc::new(block.try_recover().unwrap()),
5711                Arc::new(BlockExecutionOutput {
5712                    result: BlockExecutionResult {
5713                        receipts: vec![],
5714                        requests: Default::default(),
5715                        gas_used: 0,
5716                        blob_gas_used: 0,
5717                    },
5718                    state: bundle,
5719                }),
5720                SortedTrieData::new(Arc::new(hashed_state), Default::default()),
5721            );
5722            blocks.push(executed);
5723        }
5724
5725        let provider_rw = factory.provider_rw().unwrap();
5726        let input = SaveBlocksInput::new(blocks, 0, 0, num_blocks, num_blocks);
5727        provider_rw.save_blocks(&input).unwrap();
5728        provider_rw.commit().unwrap();
5729
5730        let provider = factory.provider().unwrap();
5731
5732        for block_num in 1..=num_blocks {
5733            for acct_idx in 0..accounts_per_block {
5734                let address = Address::with_last_byte((block_num * 10 + acct_idx as u64) as u8);
5735                let hashed_address = keccak256(address);
5736
5737                let ha_entry = provider
5738                    .tx_ref()
5739                    .cursor_read::<tables::HashedAccounts>()
5740                    .unwrap()
5741                    .seek_exact(hashed_address)
5742                    .unwrap();
5743                assert!(
5744                    ha_entry.is_some(),
5745                    "HashedAccounts missing for block {block_num} acct {acct_idx}"
5746                );
5747
5748                for s in 1..=slots_per_account as u64 {
5749                    let slot = U256::from(s + acct_idx as u64 * 100);
5750                    let slot_key = B256::from(slot);
5751                    let hashed_slot = keccak256(slot_key);
5752
5753                    let hs_entry = provider
5754                        .tx_ref()
5755                        .cursor_dup_read::<tables::HashedStorages>()
5756                        .unwrap()
5757                        .seek_by_key_subkey(hashed_address, hashed_slot)
5758                        .unwrap();
5759                    assert!(
5760                        hs_entry.is_some(),
5761                        "HashedStorages missing for block {block_num} acct {acct_idx} slot {s}"
5762                    );
5763                    let entry = hs_entry.unwrap();
5764                    assert_eq!(entry.key, hashed_slot);
5765                    assert_eq!(entry.value, U256::from(block_num * 1000 + s));
5766                }
5767            }
5768        }
5769
5770        for block_num in 1..=num_blocks {
5771            let header = provider.header_by_number(block_num).unwrap();
5772            assert!(header.is_some(), "Header missing for block {block_num}");
5773
5774            let indices = provider.block_body_indices(block_num).unwrap();
5775            assert!(indices.is_some(), "BlockBodyIndices missing for block {block_num}");
5776        }
5777
5778        let plain_accounts = provider.tx_ref().entries::<tables::PlainAccountState>().unwrap();
5779        let plain_storage = provider.tx_ref().entries::<tables::PlainStorageState>().unwrap();
5780
5781        if mode == StorageMode::V2 {
5782            assert_eq!(plain_accounts, 0, "v2: PlainAccountState should be empty");
5783            assert_eq!(plain_storage, 0, "v2: PlainStorageState should be empty");
5784
5785            let mdbx_account_cs = provider.tx_ref().entries::<tables::AccountChangeSets>().unwrap();
5786            assert_eq!(mdbx_account_cs, 0, "v2: AccountChangeSets in MDBX should be empty");
5787
5788            let mdbx_storage_cs = provider.tx_ref().entries::<tables::StorageChangeSets>().unwrap();
5789            assert_eq!(mdbx_storage_cs, 0, "v2: StorageChangeSets in MDBX should be empty");
5790
5791            provider.static_file_provider().commit().unwrap();
5792            let sf = factory.static_file_provider();
5793
5794            for block_num in 1..=num_blocks {
5795                let account_cs = sf.account_block_changeset(block_num).unwrap();
5796                assert!(
5797                    !account_cs.is_empty(),
5798                    "v2: static file AccountChangeSets should exist for block {block_num}"
5799                );
5800
5801                let storage_cs = sf.storage_changeset(block_num).unwrap();
5802                assert!(
5803                    !storage_cs.is_empty(),
5804                    "v2: static file StorageChangeSets should exist for block {block_num}"
5805                );
5806
5807                for (_, entry) in &storage_cs {
5808                    assert!(
5809                        entry.key != keccak256(entry.key),
5810                        "v2: static file storage changeset should have plain slot keys"
5811                    );
5812                }
5813            }
5814
5815            let rocksdb = factory.rocksdb_provider();
5816            for block_num in 1..=num_blocks {
5817                for acct_idx in 0..accounts_per_block {
5818                    let address = Address::with_last_byte((block_num * 10 + acct_idx as u64) as u8);
5819                    let shards = rocksdb.account_history_shards(address).unwrap();
5820                    assert!(
5821                        !shards.is_empty(),
5822                        "v2: RocksDB AccountsHistory missing for block {block_num} acct {acct_idx}"
5823                    );
5824
5825                    for s in 1..=slots_per_account as u64 {
5826                        let slot = U256::from(s + acct_idx as u64 * 100);
5827                        let slot_key = B256::from(slot);
5828                        let shards = rocksdb.storage_history_shards(address, slot_key).unwrap();
5829                        assert!(
5830                            !shards.is_empty(),
5831                            "v2: RocksDB StoragesHistory missing for block {block_num} acct {acct_idx} slot {s}"
5832                        );
5833                    }
5834                }
5835            }
5836        } else {
5837            assert!(plain_accounts > 0, "v1: PlainAccountState should not be empty");
5838            assert!(plain_storage > 0, "v1: PlainStorageState should not be empty");
5839
5840            let mdbx_account_cs = provider.tx_ref().entries::<tables::AccountChangeSets>().unwrap();
5841            assert!(mdbx_account_cs > 0, "v1: AccountChangeSets in MDBX should not be empty");
5842
5843            let mdbx_storage_cs = provider.tx_ref().entries::<tables::StorageChangeSets>().unwrap();
5844            assert!(mdbx_storage_cs > 0, "v1: StorageChangeSets in MDBX should not be empty");
5845
5846            for block_num in 1..=num_blocks {
5847                let storage_entries: Vec<_> = provider
5848                    .tx_ref()
5849                    .cursor_dup_read::<tables::StorageChangeSets>()
5850                    .unwrap()
5851                    .walk_range(BlockNumberAddress::range(block_num..=block_num))
5852                    .unwrap()
5853                    .collect::<Result<Vec<_>, _>>()
5854                    .unwrap();
5855                assert!(
5856                    !storage_entries.is_empty(),
5857                    "v1: MDBX StorageChangeSets should have entries for block {block_num}"
5858                );
5859
5860                for (_, entry) in &storage_entries {
5861                    let slot_key = B256::from(entry.key);
5862                    assert!(
5863                        slot_key != keccak256(slot_key),
5864                        "v1: storage changeset keys should be plain (not hashed)"
5865                    );
5866                }
5867            }
5868
5869            let mdbx_account_history =
5870                provider.tx_ref().entries::<tables::AccountsHistory>().unwrap();
5871            assert!(mdbx_account_history > 0, "v1: AccountsHistory in MDBX should not be empty");
5872
5873            let mdbx_storage_history =
5874                provider.tx_ref().entries::<tables::StoragesHistory>().unwrap();
5875            assert!(mdbx_storage_history > 0, "v1: StoragesHistory in MDBX should not be empty");
5876        }
5877    }
5878
5879    #[test]
5880    fn test_save_blocks_v1_table_assertions() {
5881        run_save_blocks_and_verify(StorageMode::V1);
5882    }
5883
5884    #[test]
5885    fn test_save_blocks_v2_table_assertions() {
5886        run_save_blocks_and_verify(StorageMode::V2);
5887    }
5888
5889    #[test]
5890    fn test_write_and_remove_state_roundtrip_v2() {
5891        let factory = create_test_provider_factory();
5892        let storage_settings = StorageSettings::v2();
5893        assert!(storage_settings.use_hashed_state());
5894        factory.set_storage_settings_cache(storage_settings);
5895
5896        let address = Address::with_last_byte(1);
5897        let hashed_address = keccak256(address);
5898        let slot = U256::from(5);
5899        let slot_key = B256::from(slot);
5900        let hashed_slot = keccak256(slot_key);
5901
5902        {
5903            let sf = factory.static_file_provider();
5904            let mut hw = sf.latest_writer(StaticFileSegment::Headers).unwrap();
5905            let h0 = alloy_consensus::Header { number: 0, ..Default::default() };
5906            hw.append_header(&h0, &B256::ZERO).unwrap();
5907            let h1 = alloy_consensus::Header { number: 1, ..Default::default() };
5908            hw.append_header(&h1, &B256::ZERO).unwrap();
5909            hw.commit().unwrap();
5910
5911            let mut aw = sf.latest_writer(StaticFileSegment::AccountChangeSets).unwrap();
5912            aw.append_account_changeset(vec![], 0).unwrap();
5913            aw.commit().unwrap();
5914
5915            let mut sw = sf.latest_writer(StaticFileSegment::StorageChangeSets).unwrap();
5916            sw.append_storage_changeset(vec![], 0).unwrap();
5917            sw.commit().unwrap();
5918        }
5919
5920        {
5921            let provider_rw = factory.provider_rw().unwrap();
5922            provider_rw
5923                .tx
5924                .put::<tables::BlockBodyIndices>(
5925                    0,
5926                    StoredBlockBodyIndices { first_tx_num: 0, tx_count: 0 },
5927                )
5928                .unwrap();
5929            provider_rw
5930                .tx
5931                .put::<tables::BlockBodyIndices>(
5932                    1,
5933                    StoredBlockBodyIndices { first_tx_num: 0, tx_count: 0 },
5934                )
5935                .unwrap();
5936            provider_rw
5937                .tx
5938                .cursor_write::<tables::HashedAccounts>()
5939                .unwrap()
5940                .upsert(hashed_address, &Account::default())
5941                .unwrap();
5942            provider_rw.commit().unwrap();
5943        }
5944
5945        let provider_rw = factory.provider_rw().unwrap();
5946
5947        let bundle = BundleState::builder(1..=1)
5948            .state_present_account_info(
5949                address,
5950                AccountInfo { nonce: 1, balance: U256::from(10), ..Default::default() },
5951            )
5952            .state_storage(address, HashMap::from_iter([(slot, (U256::ZERO, U256::from(10)))]))
5953            .revert_account_info(1, address, Some(None))
5954            .revert_storage(1, address, vec![(slot, U256::ZERO)])
5955            .build();
5956
5957        let execution_outcome = ExecutionOutcome::new(bundle.clone(), vec![vec![]], 1, Vec::new());
5958
5959        provider_rw
5960            .write_state(
5961                &execution_outcome,
5962                OriginalValuesKnown::Yes,
5963                StateWriteConfig {
5964                    write_receipts: false,
5965                    write_account_changesets: true,
5966                    write_storage_changesets: true,
5967                },
5968            )
5969            .unwrap();
5970
5971        let hashed_state =
5972            HashedPostState::from_bundle_state::<KeccakKeyHasher>(bundle.state()).into_sorted();
5973        provider_rw.write_hashed_state(&hashed_state).unwrap();
5974
5975        let hashed_account = provider_rw
5976            .tx
5977            .cursor_read::<tables::HashedAccounts>()
5978            .unwrap()
5979            .seek_exact(hashed_address)
5980            .unwrap()
5981            .unwrap()
5982            .1;
5983        assert_eq!(hashed_account.nonce, 1);
5984
5985        let hashed_entry = provider_rw
5986            .tx
5987            .cursor_dup_read::<tables::HashedStorages>()
5988            .unwrap()
5989            .seek_by_key_subkey(hashed_address, hashed_slot)
5990            .unwrap()
5991            .unwrap();
5992        assert_eq!(hashed_entry.key, hashed_slot);
5993        assert_eq!(hashed_entry.value, U256::from(10));
5994
5995        let plain_accounts = provider_rw.tx.entries::<tables::PlainAccountState>().unwrap();
5996        assert_eq!(plain_accounts, 0, "v2: PlainAccountState should be empty");
5997
5998        let plain_storage = provider_rw.tx.entries::<tables::PlainStorageState>().unwrap();
5999        assert_eq!(plain_storage, 0, "v2: PlainStorageState should be empty");
6000
6001        provider_rw.static_file_provider().commit().unwrap();
6002
6003        let sf = factory.static_file_provider();
6004        let storage_cs = sf.storage_changeset(1).unwrap();
6005        assert!(!storage_cs.is_empty(), "v2: storage changesets should be in static files");
6006        assert_eq!(storage_cs[0].1.key, slot_key, "v2: changeset key should be plain");
6007
6008        provider_rw.remove_state_above(0).unwrap();
6009
6010        let restored_account = provider_rw
6011            .tx
6012            .cursor_read::<tables::HashedAccounts>()
6013            .unwrap()
6014            .seek_exact(hashed_address)
6015            .unwrap();
6016        assert!(
6017            restored_account.is_none(),
6018            "v2: account should be removed (didn't exist before block 1)"
6019        );
6020
6021        let storage_gone = provider_rw
6022            .tx
6023            .cursor_dup_read::<tables::HashedStorages>()
6024            .unwrap()
6025            .seek_by_key_subkey(hashed_address, hashed_slot)
6026            .unwrap();
6027        assert!(
6028            storage_gone.is_none() || storage_gone.unwrap().key != hashed_slot,
6029            "v2: storage should be reverted (removed or different key)"
6030        );
6031
6032        let mdbx_storage_cs = provider_rw.tx.entries::<tables::StorageChangeSets>().unwrap();
6033        assert_eq!(mdbx_storage_cs, 0, "v2: MDBX StorageChangeSets should remain empty");
6034
6035        let mdbx_account_cs = provider_rw.tx.entries::<tables::AccountChangeSets>().unwrap();
6036        assert_eq!(mdbx_account_cs, 0, "v2: MDBX AccountChangeSets should remain empty");
6037    }
6038
6039    #[test]
6040    fn test_unwind_storage_history_indices_v2() {
6041        let factory = create_test_provider_factory();
6042        factory.set_storage_settings_cache(StorageSettings::v2());
6043
6044        let address = Address::with_last_byte(1);
6045        let slot_key = B256::with_last_byte(42);
6046
6047        {
6048            let rocksdb = factory.rocksdb_provider();
6049            let mut batch = rocksdb.batch();
6050            batch.append_storage_history_shard(address, slot_key, vec![3u64, 7, 10]).unwrap();
6051            batch.commit().unwrap();
6052
6053            let shards = rocksdb.storage_history_shards(address, slot_key).unwrap();
6054            assert!(!shards.is_empty(), "history should be written to rocksdb");
6055        }
6056
6057        let provider_rw = factory.provider_rw().unwrap();
6058
6059        let changesets = vec![
6060            (
6061                BlockNumberAddress((7, address)),
6062                StorageEntry { key: slot_key, value: U256::from(5) },
6063            ),
6064            (
6065                BlockNumberAddress((10, address)),
6066                StorageEntry { key: slot_key, value: U256::from(8) },
6067            ),
6068        ];
6069
6070        let count = provider_rw.unwind_storage_history_indices(changesets.into_iter()).unwrap();
6071        assert_eq!(count, 2);
6072
6073        provider_rw.commit().unwrap();
6074
6075        let rocksdb = factory.rocksdb_provider();
6076        let shards = rocksdb.storage_history_shards(address, slot_key).unwrap();
6077
6078        assert!(
6079            !shards.is_empty(),
6080            "history shards should still exist with block 3 after partial unwind"
6081        );
6082
6083        let all_blocks: Vec<u64> = shards.iter().flat_map(|(_, list)| list.iter()).collect();
6084        assert!(all_blocks.contains(&3), "block 3 should remain");
6085        assert!(!all_blocks.contains(&7), "block 7 should be unwound");
6086        assert!(!all_blocks.contains(&10), "block 10 should be unwound");
6087    }
6088
6089    #[test]
6090    fn test_chain_state_block_numbers_are_read_by_exact_key() {
6091        for (finalized, safe) in
6092            [(None, None), (None, Some(100)), (Some(90), None), (Some(90), Some(100))]
6093        {
6094            let factory = create_test_provider_factory();
6095            let provider = factory.provider_rw().unwrap();
6096            if let Some(block_number) = finalized {
6097                provider.save_finalized_block_number(block_number).unwrap();
6098            }
6099            if let Some(block_number) = safe {
6100                provider.save_safe_block_number(block_number).unwrap();
6101            }
6102
6103            assert_eq!(provider.last_finalized_block_number().unwrap(), finalized);
6104            assert_eq!(provider.last_safe_block_number().unwrap(), safe);
6105            provider.commit().unwrap();
6106
6107            let provider = factory.provider().unwrap();
6108            assert_eq!(provider.last_finalized_block_number().unwrap(), finalized);
6109            assert_eq!(provider.last_safe_block_number().unwrap(), safe);
6110        }
6111    }
6112}