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