Skip to main content

reth_provider/providers/database/
provider.rs

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