From 2cd31792451af9b96484dfd5f3813b832ba21ab2 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:20:57 +0000 Subject: [PATCH 01/18] add Metadata table and StorageSettings type --- crates/storage/db-api/src/models/metadata.rs | 31 +++++++++++++++ crates/storage/db-api/src/models/mod.rs | 2 + crates/storage/db-api/src/tables/mod.rs | 7 ++++ crates/storage/provider/src/lib.rs | 4 +- .../src/providers/database/provider.rs | 16 +++++++- crates/storage/storage-api/Cargo.toml | 2 + crates/storage/storage-api/src/lib.rs | 7 ++++ crates/storage/storage-api/src/metadata.rs | 38 +++++++++++++++++++ 8 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 crates/storage/db-api/src/models/metadata.rs create mode 100644 crates/storage/storage-api/src/metadata.rs diff --git a/crates/storage/db-api/src/models/metadata.rs b/crates/storage/db-api/src/models/metadata.rs new file mode 100644 index 00000000000..ad0c928f0e2 --- /dev/null +++ b/crates/storage/db-api/src/models/metadata.rs @@ -0,0 +1,31 @@ +//! Storage metadata models. + +use reth_codecs::{add_arbitrary_tests, Compact}; +use serde::{Deserialize, Serialize}; + +/// Storage configuration settings for this node. +/// +/// These should be set during `init_genesis` or `init_db` depending on whether we want dictate +/// behaviour of new or old nodes respectively. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, Compact)] +#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] +#[add_arbitrary_tests(compact)] +pub struct StorageSettings { + /// Whether this node always writes receipts to static files. + /// + /// If this is set to FALSE AND receipt pruning IS ENABLED, all receipts should be writtten to DB. Otherwise, they should be written to static files. This ensures that older nodes do not need to migrate their current DB tables to static files. For more, read: + pub receipts_static_files: bool, +} + +impl StorageSettings { + /// Creates a new `StorageSettings` with default values. + pub const fn new() -> Self { + Self { receipts_static_files: false } + } + + /// Sets the `receipts_static_files` flag to true. + pub const fn with_receipts_on_static_files(mut self) -> Self { + self.receipts_static_files = true; + self + } +} diff --git a/crates/storage/db-api/src/models/mod.rs b/crates/storage/db-api/src/models/mod.rs index 31d9b301f8c..ebc36252506 100644 --- a/crates/storage/db-api/src/models/mod.rs +++ b/crates/storage/db-api/src/models/mod.rs @@ -20,12 +20,14 @@ use serde::{Deserialize, Serialize}; pub mod accounts; pub mod blocks; pub mod integer_list; +pub mod metadata; pub mod sharded_key; pub mod storage_sharded_key; pub use accounts::*; pub use blocks::*; pub use integer_list::IntegerList; +pub use metadata::*; pub use reth_db_models::{ AccountBeforeTx, ClientVersion, StaticFileBlockWithdrawals, StoredBlockBodyIndices, StoredBlockWithdrawals, diff --git a/crates/storage/db-api/src/tables/mod.rs b/crates/storage/db-api/src/tables/mod.rs index cf2a20fff04..483048383ab 100644 --- a/crates/storage/db-api/src/tables/mod.rs +++ b/crates/storage/db-api/src/tables/mod.rs @@ -540,6 +540,13 @@ tables! { type Key = ChainStateKey; type Value = BlockNumber; } + + /// Stores generic node metadata as key-value pairs. + /// Can store feature flags, configuration markers, and other node-specific data. + table Metadata { + type Key = String; + type Value = Vec; + } } /// Keys for the `ChainState` table. diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index 70822c604bb..471664c078d 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -49,7 +49,9 @@ pub use reth_chain_state::{ }; // reexport traits to avoid breaking changes -pub use reth_storage_api::{HistoryWriter, StatsReader}; +pub use reth_storage_api::{ + HistoryWriter, MetadataProvider, MetadataWriter, StatsReader, StorageSettings, +}; pub(crate) fn to_range>(bounds: R) -> std::ops::Range { let start = match bounds.start_bound() { diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index ece6ef56c85..85a19d3a22b 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -57,8 +57,8 @@ use reth_prune_types::{ use reth_stages_types::{StageCheckpoint, StageId}; use reth_static_file_types::StaticFileSegment; use reth_storage_api::{ - BlockBodyIndicesProvider, BlockBodyReader, NodePrimitivesProvider, StateProvider, - StorageChangeSetReader, TryIntoHistoricalStateProvider, + BlockBodyIndicesProvider, BlockBodyReader, MetadataProvider, MetadataWriter, + NodePrimitivesProvider, StateProvider, StorageChangeSetReader, TryIntoHistoricalStateProvider, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ @@ -3130,6 +3130,18 @@ impl DBProvider for DatabaseProvider } } +impl MetadataProvider for DatabaseProvider { + fn get_metadata(&self, key: &str) -> ProviderResult>> { + self.tx.get::(key.to_string()).map_err(Into::into) + } +} + +impl MetadataWriter for DatabaseProvider { + fn write_metadata(&self, key: &str, value: Vec) -> ProviderResult<()> { + self.tx.put::(key.to_string(), value).map_err(Into::into) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/storage/storage-api/Cargo.toml b/crates/storage/storage-api/Cargo.toml index a62193a5dd8..a200c7aeacb 100644 --- a/crates/storage/storage-api/Cargo.toml +++ b/crates/storage/storage-api/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-codecs = { workspace = true, optional = true } reth-db-models.workspace = true reth-chainspec.workspace = true reth-db-api = { workspace = true, optional = true } @@ -53,6 +54,7 @@ std = [ ] db-api = [ + "dep:reth-codecs", "dep:reth-db-api", ] diff --git a/crates/storage/storage-api/src/lib.rs b/crates/storage/storage-api/src/lib.rs index 897802da980..fad3e7968de 100644 --- a/crates/storage/storage-api/src/lib.rs +++ b/crates/storage/storage-api/src/lib.rs @@ -94,5 +94,12 @@ pub use state_writer::*; mod header_sync_gap; pub use header_sync_gap::HeaderSyncGapProvider; +#[cfg(feature = "db-api")] +pub mod metadata; +#[cfg(feature = "db-api")] +pub use metadata::{MetadataProvider, MetadataWriter}; +#[cfg(feature = "db-api")] +pub use reth_db_api::models::StorageSettings; + mod full; pub use full::*; diff --git a/crates/storage/storage-api/src/metadata.rs b/crates/storage/storage-api/src/metadata.rs new file mode 100644 index 00000000000..923b152756b --- /dev/null +++ b/crates/storage/storage-api/src/metadata.rs @@ -0,0 +1,38 @@ +//! Metadata provider trait for reading and writing node metadata. + +use reth_codecs::Compact; +use reth_db_api::models::StorageSettings; +use reth_storage_errors::provider::ProviderResult; + +/// Metadata keys. +pub mod keys { + /// Storage configuration settings for this node. + pub const STORAGE_SETTINGS: &str = "storage_settings"; +} + +/// Client trait for reading node metadata from the database. +#[auto_impl::auto_impl(&, Arc)] +pub trait MetadataProvider: Send + Sync { + /// Get a metadata value by key + fn get_metadata(&self, key: &str) -> ProviderResult>>; + + /// Get storage settings for this node + fn storage_settings(&self) -> ProviderResult> { + Ok(self + .get_metadata(keys::STORAGE_SETTINGS)? + .map(|bytes| StorageSettings::from_compact(&bytes, bytes.len()).0)) + } +} + +/// Client trait for writing node metadata to the database. +pub trait MetadataWriter: Send + Sync { + /// Write a metadata value + fn write_metadata(&self, key: &str, value: Vec) -> ProviderResult<()>; + + /// Write storage settings for this node + fn write_storage_settings(&self, settings: StorageSettings) -> ProviderResult<()> { + let mut buf = Vec::new(); + settings.to_compact(&mut buf); + self.write_metadata(keys::STORAGE_SETTINGS, buf) + } +} From e295a4c5c9716b044f15b02b76ef07eeeff5e804 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:22:34 +0000 Subject: [PATCH 02/18] initialize storage_settings on provider factory --- Cargo.lock | 1 + crates/cli/commands/src/common.rs | 2 +- .../cli/commands/src/stage/dump/execution.rs | 2 +- .../src/stage/dump/hashing_account.rs | 2 +- .../src/stage/dump/hashing_storage.rs | 2 +- crates/cli/commands/src/stage/dump/merkle.rs | 2 +- crates/e2e-test-utils/src/setup_import.rs | 11 +++-- crates/exex/test-utils/src/lib.rs | 2 +- crates/node/builder/src/launch/common.rs | 2 +- .../stages/stages/src/test_utils/test_db.rs | 6 ++- crates/storage/db-common/src/init.rs | 12 +++-- .../src/providers/database/builder.rs | 20 ++++++--- .../provider/src/providers/database/mod.rs | 44 ++++++++++++++----- crates/storage/provider/src/test_utils/mod.rs | 7 +-- examples/rpc-db/src/main.rs | 2 +- 15 files changed, 77 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f5f5b87758..72e28654d00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10485,6 +10485,7 @@ dependencies = [ "alloy-rpc-types-engine", "auto_impl", "reth-chainspec", + "reth-codecs", "reth-db-api", "reth-db-models", "reth-ethereum-primitives", diff --git a/crates/cli/commands/src/common.rs b/crates/cli/commands/src/common.rs index 5b8cfce7716..4d18d811841 100644 --- a/crates/cli/commands/src/common.rs +++ b/crates/cli/commands/src/common.rs @@ -132,7 +132,7 @@ impl EnvironmentArgs { db, self.chain.clone(), static_file_provider, - ) + )? .with_prune_modes(prune_modes.clone()); // Check for consistency between database and static files. diff --git a/crates/cli/commands/src/stage/dump/execution.rs b/crates/cli/commands/src/stage/dump/execution.rs index 9e8e68e9800..887f97ddddf 100644 --- a/crates/cli/commands/src/stage/dump/execution.rs +++ b/crates/cli/commands/src/stage/dump/execution.rs @@ -42,7 +42,7 @@ where Arc::new(output_db), db_tool.chain(), StaticFileProvider::read_write(output_datadir.static_files())?, - ), + )?, to, from, evm_config, diff --git a/crates/cli/commands/src/stage/dump/hashing_account.rs b/crates/cli/commands/src/stage/dump/hashing_account.rs index 8b9ba5e937e..0e976d4235f 100644 --- a/crates/cli/commands/src/stage/dump/hashing_account.rs +++ b/crates/cli/commands/src/stage/dump/hashing_account.rs @@ -39,7 +39,7 @@ pub(crate) async fn dump_hashing_account_stage, PF::ChainSpec: EthChainSpec
::BlockHeader>, { @@ -161,6 +162,9 @@ where static_file_provider.latest_writer(StaticFileSegment::Receipts)?.increment_block(0)?; static_file_provider.latest_writer(StaticFileSegment::Transactions)?.increment_block(0)?; + // Behaviour only for new nodes should be set here. + provider_rw.write_storage_settings(StorageSettings::new())?; + // `commit_unwind`` will first commit the DB and then the static file provider, which is // necessary on `init_genesis`. provider_rw.commit()?; @@ -730,7 +734,7 @@ mod tests { factory.into_db(), MAINNET.clone(), static_file_provider, - )); + )?); assert!(matches!( genesis_hash.unwrap_err(), diff --git a/crates/storage/provider/src/providers/database/builder.rs b/crates/storage/provider/src/providers/database/builder.rs index 4bc8569432e..06a547aa7e0 100644 --- a/crates/storage/provider/src/providers/database/builder.rs +++ b/crates/storage/provider/src/providers/database/builder.rs @@ -3,13 +3,17 @@ //! This also includes general purpose staging types that provide builder style functions that lead //! up to the intended build target. -use crate::{providers::StaticFileProvider, ProviderFactory}; +use crate::{ + providers::{NodeTypesForProvider, StaticFileProvider}, + ProviderFactory, +}; use reth_db::{ mdbx::{DatabaseArguments, MaxReadTransactionDuration}, open_db_read_only, DatabaseEnv, }; use reth_db_api::{database_metrics::DatabaseMetrics, Database}; use reth_node_types::{NodeTypes, NodeTypesWithDBAdapter}; +use reth_storage_errors::provider::ProviderResult; use std::{ marker::PhantomData, path::{Path, PathBuf}, @@ -103,15 +107,15 @@ impl ProviderFactoryBuilder { config: impl Into, ) -> eyre::Result>>> where - N: NodeTypes, + N: NodeTypesForProvider, { let ReadOnlyConfig { db_dir, db_args, static_files_dir, watch_static_files } = config.into(); - Ok(self - .db(Arc::new(open_db_read_only(db_dir, db_args)?)) + self.db(Arc::new(open_db_read_only(db_dir, db_args)?)) .chainspec(chainspec) .static_file(StaticFileProvider::read_only(static_files_dir, watch_static_files)?) - .build_provider_factory()) + .build_provider_factory() + .map_err(Into::into) } } @@ -320,11 +324,13 @@ impl TypesAnd3 { impl TypesAnd3, StaticFileProvider> where - N: NodeTypes, + N: NodeTypesForProvider, DB: Database + DatabaseMetrics + Clone + Unpin + 'static, { /// Creates the [`ProviderFactory`]. - pub fn build_provider_factory(self) -> ProviderFactory> { + pub fn build_provider_factory( + self, + ) -> ProviderResult>> { let Self { _types, val_1, val_2, val_3 } = self; ProviderFactory::new(val_1, val_2, val_3) } diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 5d3b5280cda..916349b7e70 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -1,11 +1,11 @@ use crate::{ - providers::{state::latest::LatestStateProvider, StaticFileProvider}, + providers::{state::latest::LatestStateProvider, NodeTypesForProvider, StaticFileProvider}, to_range, traits::{BlockSource, ReceiptProvider}, BlockHashReader, BlockNumReader, BlockReader, ChainSpecProvider, DatabaseProviderFactory, - HashedPostStateProvider, HeaderProvider, HeaderSyncGapProvider, ProviderError, - PruneCheckpointReader, StageCheckpointReader, StateProviderBox, StaticFileProviderFactory, - TransactionVariant, TransactionsProvider, + HashedPostStateProvider, HeaderProvider, HeaderSyncGapProvider, MetadataProvider, + ProviderError, PruneCheckpointReader, StageCheckpointReader, StateProviderBox, + StaticFileProviderFactory, TransactionVariant, TransactionsProvider, }; use alloy_consensus::transaction::TransactionMeta; use alloy_eips::BlockHashOrNumber; @@ -16,14 +16,15 @@ use reth_db::{init_db, mdbx::DatabaseArguments, DatabaseEnv}; use reth_db_api::{database::Database, models::StoredBlockBodyIndices}; use reth_errors::{RethError, RethResult}; use reth_node_types::{ - BlockTy, HeaderTy, NodeTypes, NodeTypesWithDB, NodeTypesWithDBAdapter, ReceiptTy, TxTy, + BlockTy, HeaderTy, NodeTypesWithDB, NodeTypesWithDBAdapter, ReceiptTy, TxTy, }; use reth_primitives_traits::{RecoveredBlock, SealedHeader}; use reth_prune_types::{PruneCheckpoint, PruneModes, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; use reth_static_file_types::StaticFileSegment; use reth_storage_api::{ - BlockBodyIndicesProvider, NodePrimitivesProvider, TryIntoHistoricalStateProvider, + BlockBodyIndicesProvider, NodePrimitivesProvider, StorageSettings, + TryIntoHistoricalStateProvider, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::HashedPostState; @@ -64,31 +65,46 @@ pub struct ProviderFactory { prune_modes: PruneModes, /// The node storage handler. storage: Arc, + /// Storage configuration settings for this node + storage_settings: StorageSettings, } -impl ProviderFactory>> { +impl ProviderFactory>> { /// Instantiates the builder for this type pub fn builder() -> ProviderFactoryBuilder { ProviderFactoryBuilder::default() } } -impl ProviderFactory { +impl ProviderFactory { /// Create new database provider factory. pub fn new( db: N::DB, chain_spec: Arc, static_file_provider: StaticFileProvider, - ) -> Self { - Self { + ) -> ProviderResult { + let storage_settings = DatabaseProvider::<_, N>::new( + db.tx()?, + chain_spec.clone(), + static_file_provider.clone(), + Default::default(), + Default::default(), + ) + .storage_settings()? + .unwrap_or_default(); + + Ok(Self { db, chain_spec, static_file_provider, prune_modes: PruneModes::default(), storage: Default::default(), - } + storage_settings, + }) } +} +impl ProviderFactory { /// Enables metrics on the static file provider. pub fn with_static_files_metrics(mut self) -> Self { self.static_file_provider = self.static_file_provider.with_metrics(); @@ -128,6 +144,7 @@ impl>> ProviderFactory { static_file_provider, prune_modes: PruneModes::default(), storage: Default::default(), + storage_settings: StorageSettings::default(), }) } } @@ -545,13 +562,15 @@ where N: NodeTypesWithDB, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { db, chain_spec, static_file_provider, prune_modes, storage } = self; + let Self { db, chain_spec, static_file_provider, prune_modes, storage, storage_settings } = + self; f.debug_struct("ProviderFactory") .field("db", &db) .field("chain_spec", &chain_spec) .field("static_file_provider", &static_file_provider) .field("prune_modes", &prune_modes) .field("storage", &storage) + .field("storage_settings", &storage_settings) .finish() } } @@ -564,6 +583,7 @@ impl Clone for ProviderFactory { static_file_provider: self.static_file_provider.clone(), prune_modes: self.prune_modes.clone(), storage: self.storage.clone(), + storage_settings: self.storage_settings, } } } diff --git a/crates/storage/provider/src/test_utils/mod.rs b/crates/storage/provider/src/test_utils/mod.rs index ccda2d60e85..5530c7411c7 100644 --- a/crates/storage/provider/src/test_utils/mod.rs +++ b/crates/storage/provider/src/test_utils/mod.rs @@ -1,5 +1,5 @@ use crate::{ - providers::{ProviderNodeTypes, StaticFileProvider}, + providers::{NodeTypesForProvider, ProviderNodeTypes, StaticFileProvider}, HashingWriter, ProviderFactory, TrieWriter, }; use alloy_primitives::B256; @@ -10,7 +10,7 @@ use reth_db::{ }; use reth_errors::ProviderResult; use reth_ethereum_engine_primitives::EthEngineTypes; -use reth_node_types::{NodeTypes, NodeTypesWithDBAdapter}; +use reth_node_types::NodeTypesWithDBAdapter; use reth_primitives_traits::{Account, StorageEntry}; use reth_trie::StateRoot; use reth_trie_db::DatabaseStateRoot; @@ -50,7 +50,7 @@ pub fn create_test_provider_factory_with_chain_spec( } /// Creates test provider factory with provided chain spec. -pub fn create_test_provider_factory_with_node_types( +pub fn create_test_provider_factory_with_node_types( chain_spec: Arc, ) -> ProviderFactory>>> { let (static_dir, _) = create_test_static_files_dir(); @@ -60,6 +60,7 @@ pub fn create_test_provider_factory_with_node_types( chain_spec, StaticFileProvider::read_write(static_dir.keep()).expect("static file provider"), ) + .expect("failed to create test provider factory") } /// Inserts the genesis alloc from the provided chain spec into the trie. diff --git a/examples/rpc-db/src/main.rs b/examples/rpc-db/src/main.rs index 97bd1debdcc..b19d99776ab 100644 --- a/examples/rpc-db/src/main.rs +++ b/examples/rpc-db/src/main.rs @@ -53,7 +53,7 @@ async fn main() -> eyre::Result<()> { db.clone(), spec.clone(), StaticFileProvider::read_only(db_path.join("static_files"), true)?, - ); + )?; // 2. Set up the blockchain provider using only the database provider and a noop for the tree to // satisfy trait bounds. Tree is not used in this example since we are only operating on the From b4e32d6921e5182fdcda89840d4ee135a82387ae Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:55:12 +0000 Subject: [PATCH 03/18] update factory cached settings on init_genesis --- crates/storage/db-common/src/init.rs | 24 ++++++++++------- crates/storage/provider/src/lib.rs | 1 + .../provider/src/providers/database/mod.rs | 26 ++++++++++++++----- crates/storage/storage-api/src/lib.rs | 2 +- crates/storage/storage-api/src/metadata.rs | 13 ++++++++++ 5 files changed, 49 insertions(+), 17 deletions(-) diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index 14adfef5497..3579d5360d6 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -17,7 +17,7 @@ use reth_provider::{ BundleStateInit, ChainSpecProvider, DBProvider, DatabaseProviderFactory, ExecutionOutcome, HashingWriter, HeaderProvider, HistoryWriter, MetadataWriter, OriginalValuesKnown, ProviderError, RevertsInit, StageCheckpointReader, StageCheckpointWriter, StateWriter, - StaticFileProviderFactory, StorageSettings, TrieWriter, + StaticFileProviderFactory, StorageSettings, StorageSettingsCache, TrieWriter, }; use reth_stages_types::{StageCheckpoint, StageId}; use reth_static_file_types::StaticFileSegment; @@ -90,7 +90,8 @@ where + StaticFileProviderFactory> + ChainSpecProvider + StageCheckpointReader - + BlockHashReader, + + BlockHashReader + + StorageSettingsCache, PF::ProviderRW: StaticFileProviderFactory + StageCheckpointWriter + HistoryWriter @@ -162,12 +163,14 @@ where static_file_provider.latest_writer(StaticFileSegment::Receipts)?.increment_block(0)?; static_file_provider.latest_writer(StaticFileSegment::Transactions)?.increment_block(0)?; - // Behaviour only for new nodes should be set here. - provider_rw.write_storage_settings(StorageSettings::new())?; + // Behaviour reserved only for new nodes should be set here. + let storage_settings = StorageSettings::new(); + provider_rw.write_storage_settings(storage_settings)?; // `commit_unwind`` will first commit the DB and then the static file provider, which is // necessary on `init_genesis`. provider_rw.commit()?; + factory.set_storage_settings_cache(storage_settings); Ok(hash) } @@ -730,11 +733,14 @@ mod tests { init_genesis(&factory).unwrap(); // Try to init db with a different genesis block - let genesis_hash = init_genesis(&ProviderFactory::::new( - factory.into_db(), - MAINNET.clone(), - static_file_provider, - )?); + let genesis_hash = init_genesis( + &ProviderFactory::::new( + factory.into_db(), + MAINNET.clone(), + static_file_provider, + ) + .unwrap(), + ); assert!(matches!( genesis_hash.unwrap_err(), diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index 471664c078d..5cd598aa46b 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -51,6 +51,7 @@ pub use reth_chain_state::{ // reexport traits to avoid breaking changes pub use reth_storage_api::{ HistoryWriter, MetadataProvider, MetadataWriter, StatsReader, StorageSettings, + StorageSettingsCache, }; pub(crate) fn to_range>(bounds: R) -> std::ops::Range { diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 916349b7e70..5eaab34c733 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -23,7 +23,7 @@ use reth_prune_types::{PruneCheckpoint, PruneModes, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; use reth_static_file_types::StaticFileSegment; use reth_storage_api::{ - BlockBodyIndicesProvider, NodePrimitivesProvider, StorageSettings, + BlockBodyIndicesProvider, NodePrimitivesProvider, StorageSettings, StorageSettingsCache, TryIntoHistoricalStateProvider, }; use reth_storage_errors::provider::ProviderResult; @@ -32,7 +32,7 @@ use revm_database::BundleState; use std::{ ops::{RangeBounds, RangeInclusive}, path::Path, - sync::Arc, + sync::{Arc, Mutex}, }; use tracing::trace; @@ -66,7 +66,7 @@ pub struct ProviderFactory { /// The node storage handler. storage: Arc, /// Storage configuration settings for this node - storage_settings: StorageSettings, + storage_settings: Arc>, } impl ProviderFactory>> { @@ -99,7 +99,7 @@ impl ProviderFactory { static_file_provider, prune_modes: PruneModes::default(), storage: Default::default(), - storage_settings, + storage_settings: Arc::new(Mutex::new(storage_settings)), }) } } @@ -127,6 +127,17 @@ impl ProviderFactory { pub fn into_db(self) -> N::DB { self.db } + + /// Gets the current storage settings. + pub fn cached_storage_settings(&self) -> StorageSettings { + *self.storage_settings.lock().unwrap() + } +} + +impl StorageSettingsCache for ProviderFactory { + fn set_storage_settings_cache(&self, settings: StorageSettings) { + *self.storage_settings.lock().unwrap() = settings; + } } impl>> ProviderFactory { @@ -144,7 +155,7 @@ impl>> ProviderFactory { static_file_provider, prune_modes: PruneModes::default(), storage: Default::default(), - storage_settings: StorageSettings::default(), + storage_settings: Arc::new(Mutex::new(StorageSettings::default())), }) } } @@ -564,13 +575,14 @@ where fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { db, chain_spec, static_file_provider, prune_modes, storage, storage_settings } = self; + let settings = storage_settings.lock().expect("storage settings lock poisoned"); f.debug_struct("ProviderFactory") .field("db", &db) .field("chain_spec", &chain_spec) .field("static_file_provider", &static_file_provider) .field("prune_modes", &prune_modes) .field("storage", &storage) - .field("storage_settings", &storage_settings) + .field("storage_settings", &*settings) .finish() } } @@ -583,7 +595,7 @@ impl Clone for ProviderFactory { static_file_provider: self.static_file_provider.clone(), prune_modes: self.prune_modes.clone(), storage: self.storage.clone(), - storage_settings: self.storage_settings, + storage_settings: self.storage_settings.clone(), } } } diff --git a/crates/storage/storage-api/src/lib.rs b/crates/storage/storage-api/src/lib.rs index fad3e7968de..5a191f37505 100644 --- a/crates/storage/storage-api/src/lib.rs +++ b/crates/storage/storage-api/src/lib.rs @@ -97,7 +97,7 @@ pub use header_sync_gap::HeaderSyncGapProvider; #[cfg(feature = "db-api")] pub mod metadata; #[cfg(feature = "db-api")] -pub use metadata::{MetadataProvider, MetadataWriter}; +pub use metadata::{MetadataProvider, MetadataWriter, StorageSettingsCache}; #[cfg(feature = "db-api")] pub use reth_db_api::models::StorageSettings; diff --git a/crates/storage/storage-api/src/metadata.rs b/crates/storage/storage-api/src/metadata.rs index 923b152756b..defe91345e3 100644 --- a/crates/storage/storage-api/src/metadata.rs +++ b/crates/storage/storage-api/src/metadata.rs @@ -30,9 +30,22 @@ pub trait MetadataWriter: Send + Sync { fn write_metadata(&self, key: &str, value: Vec) -> ProviderResult<()>; /// Write storage settings for this node + /// + /// Be sure to update provider factory cache with + /// [`StorageSettingsCache::set_storage_settings_cache`]. fn write_storage_settings(&self, settings: StorageSettings) -> ProviderResult<()> { + use reth_codecs::Compact; let mut buf = Vec::new(); settings.to_compact(&mut buf); self.write_metadata(keys::STORAGE_SETTINGS, buf) } } + +/// Trait for caching storage settings on a provider factory. +pub trait StorageSettingsCache: Send + Sync { + /// Sets the storage settings of this `ProviderFactory`. + /// + /// IMPORTANT: It does not save settings in storage, that should be done by + /// [`MetadataWriter::write_storage_settings`] + fn set_storage_settings_cache(&self, settings: StorageSettings); +} From 5cd4a5883b129862edc56eb381a66778f8110627 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 29 Oct 2025 14:03:15 +0000 Subject: [PATCH 04/18] ci --- crates/storage/db-api/src/models/metadata.rs | 2 +- crates/storage/storage-api/Cargo.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/storage/db-api/src/models/metadata.rs b/crates/storage/db-api/src/models/metadata.rs index ad0c928f0e2..18311dc4383 100644 --- a/crates/storage/db-api/src/models/metadata.rs +++ b/crates/storage/db-api/src/models/metadata.rs @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; pub struct StorageSettings { /// Whether this node always writes receipts to static files. /// - /// If this is set to FALSE AND receipt pruning IS ENABLED, all receipts should be writtten to DB. Otherwise, they should be written to static files. This ensures that older nodes do not need to migrate their current DB tables to static files. For more, read: + /// If this is set to FALSE AND receipt pruning IS ENABLED, all receipts should be written to DB. Otherwise, they should be written to static files. This ensures that older nodes do not need to migrate their current DB tables to static files. For more, read: pub receipts_static_files: bool, } diff --git a/crates/storage/storage-api/Cargo.toml b/crates/storage/storage-api/Cargo.toml index a200c7aeacb..196c9d3516f 100644 --- a/crates/storage/storage-api/Cargo.toml +++ b/crates/storage/storage-api/Cargo.toml @@ -59,6 +59,7 @@ db-api = [ ] serde = [ + "reth-codecs/serde", "reth-ethereum-primitives/serde", "reth-db-models/serde", "reth-execution-types/serde", From 62b444357a3e59c45a9de1b10c1a2f69c798cf99 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 29 Oct 2025 20:57:25 +0000 Subject: [PATCH 05/18] update doctests --- crates/storage/db-api/src/models/metadata.rs | 6 +++--- .../provider/src/providers/database/builder.rs | 17 ++++++----------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/crates/storage/db-api/src/models/metadata.rs b/crates/storage/db-api/src/models/metadata.rs index 18311dc4383..5941d46bb43 100644 --- a/crates/storage/db-api/src/models/metadata.rs +++ b/crates/storage/db-api/src/models/metadata.rs @@ -14,18 +14,18 @@ pub struct StorageSettings { /// Whether this node always writes receipts to static files. /// /// If this is set to FALSE AND receipt pruning IS ENABLED, all receipts should be written to DB. Otherwise, they should be written to static files. This ensures that older nodes do not need to migrate their current DB tables to static files. For more, read: - pub receipts_static_files: bool, + pub receipts_on_static_files: bool, } impl StorageSettings { /// Creates a new `StorageSettings` with default values. pub const fn new() -> Self { - Self { receipts_static_files: false } + Self { receipts_on_static_files: false } } /// Sets the `receipts_static_files` flag to true. pub const fn with_receipts_on_static_files(mut self) -> Self { - self.receipts_static_files = true; + self.receipts_on_static_files = true; self } } diff --git a/crates/storage/provider/src/providers/database/builder.rs b/crates/storage/provider/src/providers/database/builder.rs index 06a547aa7e0..bcd61f188f9 100644 --- a/crates/storage/provider/src/providers/database/builder.rs +++ b/crates/storage/provider/src/providers/database/builder.rs @@ -52,10 +52,9 @@ impl ProviderFactoryBuilder { /// /// ```no_run /// use reth_chainspec::MAINNET; - /// use reth_node_types::NodeTypes; - /// use reth_provider::providers::ProviderFactoryBuilder; + /// use reth_provider::providers::{NodeTypesForProvider, ProviderFactoryBuilder}; /// - /// fn demo>() { + /// fn demo>() { /// let provider_factory = ProviderFactoryBuilder::::default() /// .open_read_only(MAINNET.clone(), "datadir") /// .unwrap(); @@ -68,11 +67,9 @@ impl ProviderFactoryBuilder { /// /// ```no_run /// use reth_chainspec::MAINNET; - /// use reth_node_types::NodeTypes; + /// use reth_provider::providers::{NodeTypesForProvider, ProviderFactoryBuilder, ReadOnlyConfig}; /// - /// use reth_provider::providers::{ProviderFactoryBuilder, ReadOnlyConfig}; - /// - /// fn demo>() { + /// fn demo>() { /// let provider_factory = ProviderFactoryBuilder::::default() /// .open_read_only(MAINNET.clone(), ReadOnlyConfig::from_datadir("datadir").no_watch()) /// .unwrap(); @@ -88,11 +85,9 @@ impl ProviderFactoryBuilder { /// /// ```no_run /// use reth_chainspec::MAINNET; - /// use reth_node_types::NodeTypes; - /// - /// use reth_provider::providers::{ProviderFactoryBuilder, ReadOnlyConfig}; + /// use reth_provider::providers::{NodeTypesForProvider, ProviderFactoryBuilder, ReadOnlyConfig}; /// - /// fn demo>() { + /// fn demo>() { /// let provider_factory = ProviderFactoryBuilder::::default() /// .open_read_only( /// MAINNET.clone(), From eefe3903bbcbeac9e2cbd94d5553a6e92a30ded7 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 29 Oct 2025 21:34:38 +0000 Subject: [PATCH 06/18] add cached storagesettings to databaseprovider --- .../provider/src/providers/database/mod.rs | 19 +++++++++++-------- .../src/providers/database/provider.rs | 13 +++++++++++-- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 5eaab34c733..2ec3fc2d0dd 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -11,6 +11,7 @@ use alloy_consensus::transaction::TransactionMeta; use alloy_eips::BlockHashOrNumber; use alloy_primitives::{Address, BlockHash, BlockNumber, TxHash, TxNumber, B256}; use core::fmt; +use parking_lot::RwLock; use reth_chainspec::ChainInfo; use reth_db::{init_db, mdbx::DatabaseArguments, DatabaseEnv}; use reth_db_api::{database::Database, models::StoredBlockBodyIndices}; @@ -32,7 +33,7 @@ use revm_database::BundleState; use std::{ ops::{RangeBounds, RangeInclusive}, path::Path, - sync::{Arc, Mutex}, + sync::Arc, }; use tracing::trace; @@ -66,7 +67,7 @@ pub struct ProviderFactory { /// The node storage handler. storage: Arc, /// Storage configuration settings for this node - storage_settings: Arc>, + storage_settings: Arc>, } impl ProviderFactory>> { @@ -89,6 +90,7 @@ impl ProviderFactory { static_file_provider.clone(), Default::default(), Default::default(), + Arc::new(RwLock::new(StorageSettings::default())), ) .storage_settings()? .unwrap_or_default(); @@ -99,7 +101,7 @@ impl ProviderFactory { static_file_provider, prune_modes: PruneModes::default(), storage: Default::default(), - storage_settings: Arc::new(Mutex::new(storage_settings)), + storage_settings: Arc::new(RwLock::new(storage_settings)), }) } } @@ -130,13 +132,13 @@ impl ProviderFactory { /// Gets the current storage settings. pub fn cached_storage_settings(&self) -> StorageSettings { - *self.storage_settings.lock().unwrap() + *self.storage_settings.read() } } impl StorageSettingsCache for ProviderFactory { fn set_storage_settings_cache(&self, settings: StorageSettings) { - *self.storage_settings.lock().unwrap() = settings; + *self.storage_settings.write() = settings; } } @@ -155,7 +157,7 @@ impl>> ProviderFactory { static_file_provider, prune_modes: PruneModes::default(), storage: Default::default(), - storage_settings: Arc::new(Mutex::new(StorageSettings::default())), + storage_settings: Arc::new(RwLock::new(StorageSettings::default())), }) } } @@ -175,6 +177,7 @@ impl ProviderFactory { self.static_file_provider.clone(), self.prune_modes.clone(), self.storage.clone(), + self.storage_settings.clone(), )) } @@ -190,6 +193,7 @@ impl ProviderFactory { self.static_file_provider.clone(), self.prune_modes.clone(), self.storage.clone(), + self.storage_settings.clone(), ))) } @@ -575,14 +579,13 @@ where fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { db, chain_spec, static_file_provider, prune_modes, storage, storage_settings } = self; - let settings = storage_settings.lock().expect("storage settings lock poisoned"); f.debug_struct("ProviderFactory") .field("db", &db) .field("chain_spec", &chain_spec) .field("static_file_provider", &static_file_provider) .field("prune_modes", &prune_modes) .field("storage", &storage) - .field("storage_settings", &*settings) + .field("storage_settings", &*storage_settings.read()) .finish() } } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 85a19d3a22b..d4fdbfb90ca 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -31,6 +31,7 @@ use alloy_primitives::{ Address, BlockHash, BlockNumber, TxHash, TxNumber, B256, }; use itertools::Itertools; +use parking_lot::RwLock; use rayon::slice::ParallelSliceMut; use reth_chain_state::ExecutedBlock; use reth_chainspec::{ChainInfo, ChainSpecProvider, EthChainSpec}; @@ -39,7 +40,7 @@ use reth_db_api::{ database::Database, models::{ sharded_key, storage_sharded_key::StorageShardedKey, AccountBeforeTx, BlockNumberAddress, - BlockNumberHashedAddress, ShardedKey, StoredBlockBodyIndices, + BlockNumberHashedAddress, ShardedKey, StorageSettings, StoredBlockBodyIndices, }, table::Table, tables, @@ -153,6 +154,8 @@ pub struct DatabaseProvider { prune_modes: PruneModes, /// Node storage handler. storage: Arc, + /// Storage configuration settings for this node + storage_settings: Arc>, } impl DatabaseProvider { @@ -160,6 +163,11 @@ impl DatabaseProvider { pub const fn prune_modes_ref(&self) -> &PruneModes { &self.prune_modes } + + /// Gets the cached storage settings from the factory. + pub fn cached_storage_settings(&self) -> StorageSettings { + *self.storage_settings.read() + } } impl DatabaseProvider { @@ -248,8 +256,9 @@ impl DatabaseProvider { static_file_provider: StaticFileProvider, prune_modes: PruneModes, storage: Arc, + storage_settings: Arc>, ) -> Self { - Self { tx, chain_spec, static_file_provider, prune_modes, storage } + Self { tx, chain_spec, static_file_provider, prune_modes, storage, storage_settings } } } From 40a3cb2f1f2d34e58b793e6c93a95369bedf5e1f Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 29 Oct 2025 21:39:04 +0000 Subject: [PATCH 07/18] add cached_storage_settings to StorageSettingsCache --- .../provider/src/providers/database/mod.rs | 8 ++++---- .../src/providers/database/provider.rs | 18 +++++++++++------- crates/storage/storage-api/src/metadata.rs | 3 +++ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 2ec3fc2d0dd..d3f657e5c9a 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -130,13 +130,13 @@ impl ProviderFactory { self.db } - /// Gets the current storage settings. - pub fn cached_storage_settings(&self) -> StorageSettings { - *self.storage_settings.read() - } } impl StorageSettingsCache for ProviderFactory { + fn cached_storage_settings(&self) -> StorageSettings { + *self.storage_settings.read() + } + fn set_storage_settings_cache(&self, settings: StorageSettings) { *self.storage_settings.write() = settings; } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index d4fdbfb90ca..5d50edf4ede 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -58,8 +58,7 @@ use reth_prune_types::{ use reth_stages_types::{StageCheckpoint, StageId}; use reth_static_file_types::StaticFileSegment; use reth_storage_api::{ - BlockBodyIndicesProvider, BlockBodyReader, MetadataProvider, MetadataWriter, - NodePrimitivesProvider, StateProvider, StorageChangeSetReader, TryIntoHistoricalStateProvider, + BlockBodyIndicesProvider, BlockBodyReader, MetadataProvider, MetadataWriter, NodePrimitivesProvider, StateProvider, StorageChangeSetReader, StorageSettingsCache, TryIntoHistoricalStateProvider }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ @@ -163,11 +162,6 @@ impl DatabaseProvider { pub const fn prune_modes_ref(&self) -> &PruneModes { &self.prune_modes } - - /// Gets the cached storage settings from the factory. - pub fn cached_storage_settings(&self) -> StorageSettings { - *self.storage_settings.read() - } } impl DatabaseProvider { @@ -3151,6 +3145,16 @@ impl MetadataWriter for DatabaseProvider { } } +impl StorageSettingsCache for DatabaseProvider { + fn cached_storage_settings(&self) -> StorageSettings { + *self.storage_settings.read() + } + + fn set_storage_settings_cache(&self, settings: StorageSettings) { + *self.storage_settings.write() = settings; + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/storage/storage-api/src/metadata.rs b/crates/storage/storage-api/src/metadata.rs index defe91345e3..75bb684b42c 100644 --- a/crates/storage/storage-api/src/metadata.rs +++ b/crates/storage/storage-api/src/metadata.rs @@ -43,6 +43,9 @@ pub trait MetadataWriter: Send + Sync { /// Trait for caching storage settings on a provider factory. pub trait StorageSettingsCache: Send + Sync { + /// Gets the cached storage settings. + fn cached_storage_settings(&self) -> StorageSettings; + /// Sets the storage settings of this `ProviderFactory`. /// /// IMPORTANT: It does not save settings in storage, that should be done by From 642925011d6ccfe5aff18896f7eb279fd0ffb76e Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 29 Oct 2025 21:43:06 +0000 Subject: [PATCH 08/18] use serde_json for storage settings --- Cargo.lock | 2 +- crates/storage/db-api/src/models/metadata.rs | 8 ++++++++ .../provider/src/providers/database/mod.rs | 18 ++++++++---------- .../src/providers/database/provider.rs | 7 +++++-- crates/storage/storage-api/Cargo.toml | 6 +++--- crates/storage/storage-api/src/metadata.rs | 17 ++++++++--------- 6 files changed, 33 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72e28654d00..50d9adf1ba1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10485,7 +10485,6 @@ dependencies = [ "alloy-rpc-types-engine", "auto_impl", "reth-chainspec", - "reth-codecs", "reth-db-api", "reth-db-models", "reth-ethereum-primitives", @@ -10496,6 +10495,7 @@ dependencies = [ "reth-storage-errors", "reth-trie-common", "revm-database", + "serde_json", ] [[package]] diff --git a/crates/storage/db-api/src/models/metadata.rs b/crates/storage/db-api/src/models/metadata.rs index 5941d46bb43..eec0a153054 100644 --- a/crates/storage/db-api/src/models/metadata.rs +++ b/crates/storage/db-api/src/models/metadata.rs @@ -23,6 +23,14 @@ impl StorageSettings { Self { receipts_on_static_files: false } } + /// Creates `StorageSettings` for legacy nodes. + /// + /// This explicitly sets `receipts_on_static_files` to `false`, ensuring older nodes + /// continue writing receipts to the database when receipt pruning is enabled. + pub const fn legacy() -> Self { + Self { receipts_on_static_files: false } + } + /// Sets the `receipts_static_files` flag to true. pub const fn with_receipts_on_static_files(mut self) -> Self { self.receipts_on_static_files = true; diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index d3f657e5c9a..de253569ba7 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -84,16 +84,17 @@ impl ProviderFactory { chain_spec: Arc, static_file_provider: StaticFileProvider, ) -> ProviderResult { + let legacy_settings = StorageSettings::legacy(); let storage_settings = DatabaseProvider::<_, N>::new( db.tx()?, chain_spec.clone(), static_file_provider.clone(), Default::default(), Default::default(), - Arc::new(RwLock::new(StorageSettings::default())), + Arc::new(RwLock::new(legacy_settings)), ) .storage_settings()? - .unwrap_or_default(); + .unwrap_or(legacy_settings); Ok(Self { db, @@ -129,7 +130,6 @@ impl ProviderFactory { pub fn into_db(self) -> N::DB { self.db } - } impl StorageSettingsCache for ProviderFactory { @@ -142,7 +142,7 @@ impl StorageSettingsCache for ProviderFactory { } } -impl>> ProviderFactory { +impl>> ProviderFactory { /// Create new database provider by passing a path. [`ProviderFactory`] will own the database /// instance. pub fn new_with_database_path>( @@ -151,14 +151,12 @@ impl>> ProviderFactory { args: DatabaseArguments, static_file_provider: StaticFileProvider, ) -> RethResult { - Ok(Self { - db: Arc::new(init_db(path, args).map_err(RethError::msg)?), + Self::new( + Arc::new(init_db(path, args).map_err(RethError::msg)?), chain_spec, static_file_provider, - prune_modes: PruneModes::default(), - storage: Default::default(), - storage_settings: Arc::new(RwLock::new(StorageSettings::default())), - }) + ) + .map_err(RethError::Provider) } } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 5d50edf4ede..ca1106a198f 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -58,7 +58,9 @@ use reth_prune_types::{ use reth_stages_types::{StageCheckpoint, StageId}; use reth_static_file_types::StaticFileSegment; use reth_storage_api::{ - BlockBodyIndicesProvider, BlockBodyReader, MetadataProvider, MetadataWriter, NodePrimitivesProvider, StateProvider, StorageChangeSetReader, StorageSettingsCache, TryIntoHistoricalStateProvider + BlockBodyIndicesProvider, BlockBodyReader, MetadataProvider, MetadataWriter, + NodePrimitivesProvider, StateProvider, StorageChangeSetReader, StorageSettingsCache, + TryIntoHistoricalStateProvider, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ @@ -497,8 +499,9 @@ impl DatabaseProvider { static_file_provider: StaticFileProvider, prune_modes: PruneModes, storage: Arc, + storage_settings: Arc>, ) -> Self { - Self { tx, chain_spec, static_file_provider, prune_modes, storage } + Self { tx, chain_spec, static_file_provider, prune_modes, storage, storage_settings } } /// Consume `DbTx` or `DbTxMut`. diff --git a/crates/storage/storage-api/Cargo.toml b/crates/storage/storage-api/Cargo.toml index 196c9d3516f..83cbbbd714e 100644 --- a/crates/storage/storage-api/Cargo.toml +++ b/crates/storage/storage-api/Cargo.toml @@ -13,7 +13,6 @@ workspace = true [dependencies] # reth -reth-codecs = { workspace = true, optional = true } reth-db-models.workspace = true reth-chainspec.workspace = true reth-db-api = { workspace = true, optional = true } @@ -33,6 +32,7 @@ alloy-consensus.workspace = true alloy-rpc-types-engine.workspace = true auto_impl.workspace = true +serde_json = { workspace = true, optional = true } [features] default = ["std"] @@ -51,15 +51,15 @@ std = [ "reth-storage-errors/std", "reth-db-models/std", "reth-trie-common/std", + "serde_json?/std", ] db-api = [ - "dep:reth-codecs", "dep:reth-db-api", + "dep:serde_json", ] serde = [ - "reth-codecs/serde", "reth-ethereum-primitives/serde", "reth-db-models/serde", "reth-execution-types/serde", diff --git a/crates/storage/storage-api/src/metadata.rs b/crates/storage/storage-api/src/metadata.rs index 75bb684b42c..2ff48f73385 100644 --- a/crates/storage/storage-api/src/metadata.rs +++ b/crates/storage/storage-api/src/metadata.rs @@ -1,8 +1,7 @@ //! Metadata provider trait for reading and writing node metadata. -use reth_codecs::Compact; use reth_db_api::models::StorageSettings; -use reth_storage_errors::provider::ProviderResult; +use reth_storage_errors::provider::{ProviderError, ProviderResult}; /// Metadata keys. pub mod keys { @@ -18,9 +17,9 @@ pub trait MetadataProvider: Send + Sync { /// Get storage settings for this node fn storage_settings(&self) -> ProviderResult> { - Ok(self - .get_metadata(keys::STORAGE_SETTINGS)? - .map(|bytes| StorageSettings::from_compact(&bytes, bytes.len()).0)) + self.get_metadata(keys::STORAGE_SETTINGS)? + .map(|bytes| serde_json::from_slice(&bytes).map_err(ProviderError::other)) + .transpose() } } @@ -34,10 +33,10 @@ pub trait MetadataWriter: Send + Sync { /// Be sure to update provider factory cache with /// [`StorageSettingsCache::set_storage_settings_cache`]. fn write_storage_settings(&self, settings: StorageSettings) -> ProviderResult<()> { - use reth_codecs::Compact; - let mut buf = Vec::new(); - settings.to_compact(&mut buf); - self.write_metadata(keys::STORAGE_SETTINGS, buf) + self.write_metadata( + keys::STORAGE_SETTINGS, + serde_json::to_vec(&settings).map_err(ProviderError::other)?, + ) } } From 57ba2fb71df323f68161990e015edcfd4295e69f Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 29 Oct 2025 21:46:31 +0000 Subject: [PATCH 09/18] on new nodes always write receipts to static files --- crates/stages/stages/src/stages/execution.rs | 19 +++++++++++++------ crates/storage/db-common/src/init.rs | 2 +- .../src/providers/database/provider.rs | 15 +++++++++++---- .../src/providers/static_file/manager.rs | 15 +++++++++++---- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index adfc87c5ccc..5558bc543eb 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -13,7 +13,7 @@ use reth_provider::{ providers::{StaticFileProvider, StaticFileWriter}, BlockHashReader, BlockReader, DBProvider, ExecutionOutcome, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderError, StateWriter, - StaticFileProviderFactory, StatsReader, TransactionVariant, + StaticFileProviderFactory, StatsReader, StorageSettingsCache, TransactionVariant, }; use reth_revm::database::StateProviderDatabase; use reth_stages_api::{ @@ -185,11 +185,17 @@ where unwind_to: Option, ) -> Result<(), StageError> where - Provider: StaticFileProviderFactory + DBProvider + BlockReader + HeaderProvider, + Provider: StaticFileProviderFactory + + DBProvider + + BlockReader + + HeaderProvider + + StorageSettingsCache, { - // If there's any receipts pruning configured, receipts are written directly to database and - // inconsistencies are expected. - if provider.prune_modes_ref().has_receipts_pruning() { + // On old nodes, if there's any receipts pruning configured, receipts are written directly + // to database and inconsistencies are expected. + if provider.prune_modes_ref().has_receipts_pruning() && + !provider.cached_storage_settings().receipts_on_static_files + { return Ok(()) } @@ -259,7 +265,8 @@ where Primitives: NodePrimitives, > + StatsReader + BlockHashReader - + StateWriter::Receipt>, + + StateWriter::Receipt> + + StorageSettingsCache, { /// Return the id of the stage fn id(&self) -> StageId { diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index 3579d5360d6..c55af3efecb 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -164,7 +164,7 @@ where static_file_provider.latest_writer(StaticFileSegment::Transactions)?.increment_block(0)?; // Behaviour reserved only for new nodes should be set here. - let storage_settings = StorageSettings::new(); + let storage_settings = StorageSettings::new().with_receipts_on_static_files(); provider_rw.write_storage_settings(storage_settings)?; // `commit_unwind`` will first commit the DB and then the static file provider, which is diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index ca1106a198f..83df9656372 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -369,7 +369,9 @@ impl DatabaseProvider>>(from_tx..)?; - if !self.prune_modes.has_receipts_pruning() { + if !self.prune_modes.has_receipts_pruning() || + self.cached_storage_settings().receipts_on_static_files + { let static_file_receipt_num = self.static_file_provider.get_highest_static_file_tx(StaticFileSegment::Receipts); @@ -1611,7 +1613,8 @@ impl StateWriter )); } - let has_receipts_pruning = self.prune_modes.has_receipts_pruning(); + let write_to_db = self.prune_modes.has_receipts_pruning() && + !self.cached_storage_settings().receipts_on_static_files; // Prepare receipts cursor if we are going to write receipts to the database // @@ -1622,14 +1625,18 @@ impl StateWriter // Prepare receipts static writer if we are going to write receipts to static files // // We are writing to static files if requested and if there's no receipt pruning configured - let mut receipts_static_writer = has_receipts_pruning + let mut receipts_static_writer = write_to_db .not() .then(|| self.static_file_provider.get_writer(first_block, StaticFileSegment::Receipts)) .transpose()?; // All receipts from the last 128 blocks are required for blockchain tree, even with // [`PruneSegment::ContractLogs`]. - let prunable_receipts = + // + // Receipts can only be skipped if we're dealing with legacy nodes that write them to + // Database. On newer nodes pruning will occur separately either by the PruneStage or the + // Pruner. + let prunable_receipts = !self.cached_storage_settings().receipts_on_static_files && PruneMode::Distance(MINIMUM_PRUNING_DISTANCE).should_prune(first_block, tip); for (idx, (receipts, first_tx_index)) in diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index 28d13cfbe29..218cd522e26 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -40,7 +40,7 @@ use reth_static_file_types::{ find_fixed_range, HighestStaticFiles, SegmentHeader, SegmentRangeInclusive, StaticFileSegment, DEFAULT_BLOCKS_PER_STATIC_FILE, }; -use reth_storage_api::{BlockBodyIndicesProvider, DBProvider}; +use reth_storage_api::{BlockBodyIndicesProvider, DBProvider, StorageSettingsCache}; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use std::{ collections::{hash_map::Entry, BTreeMap, HashMap}, @@ -762,7 +762,11 @@ impl StaticFileProvider { has_receipt_pruning: bool, ) -> ProviderResult> where - Provider: DBProvider + BlockReader + StageCheckpointReader + ChainSpecProvider, + Provider: DBProvider + + BlockReader + + StageCheckpointReader + + ChainSpecProvider + + StorageSettingsCache, N: NodePrimitives, { // OVM historical import is broken and does not work with this check. It's importing @@ -797,8 +801,11 @@ impl StaticFileProvider { }; for segment in StaticFileSegment::iter() { - if has_receipt_pruning && segment.is_receipts() { - // Pruned nodes (including full node) do not store receipts as static files. + if segment.is_receipts() && + has_receipt_pruning && + !provider.cached_storage_settings().receipts_on_static_files + { + // Old pruned nodes (including full node) do not store receipts as static files. continue } From 159ab62d46478943fd9210a1c1418c824da2d66e Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:15:41 +0000 Subject: [PATCH 10/18] add EitherWriter::receipts_destination --- crates/cli/commands/src/common.rs | 6 ++-- crates/node/builder/src/launch/common.rs | 7 ++-- crates/stages/stages/src/stages/execution.rs | 6 ++-- crates/stages/stages/src/stages/mod.rs | 29 +++++++++------- crates/storage/provider/src/either_writer.rs | 34 +++++++++++++++++++ .../src/providers/database/provider.rs | 4 +-- .../src/providers/static_file/manager.rs | 11 +++--- 7 files changed, 61 insertions(+), 36 deletions(-) diff --git a/crates/cli/commands/src/common.rs b/crates/cli/commands/src/common.rs index 4d18d811841..6bb038d97c8 100644 --- a/crates/cli/commands/src/common.rs +++ b/crates/cli/commands/src/common.rs @@ -126,7 +126,6 @@ impl EnvironmentArgs { where C: ChainSpecParser, { - let has_receipt_pruning = config.prune.has_receipts_pruning(); let prune_modes = config.prune.segments.clone(); let factory = ProviderFactory::>>::new( db, @@ -136,9 +135,8 @@ impl EnvironmentArgs { .with_prune_modes(prune_modes.clone()); // Check for consistency between database and static files. - if let Some(unwind_target) = factory - .static_file_provider() - .check_consistency(&factory.provider()?, has_receipt_pruning)? + if let Some(unwind_target) = + factory.static_file_provider().check_consistency(&factory.provider()?)? { if factory.db_ref().is_read_only()? { warn!(target: "reth::cli", ?unwind_target, "Inconsistent storage. Restart node to heal."); diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index e644ddd4dda..bbfbfbcf9f1 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -482,13 +482,10 @@ where ProviderFactory::new(self.right().clone(), self.chain_spec(), static_file_provider)? .with_prune_modes(self.prune_modes()); - let has_receipt_pruning = self.toml_config().prune.has_receipts_pruning(); - // Check for consistency between database and static files. If it fails, it unwinds to // the first block that's consistent between database and static files. - if let Some(unwind_target) = factory - .static_file_provider() - .check_consistency(&factory.provider()?, has_receipt_pruning)? + if let Some(unwind_target) = + factory.static_file_provider().check_consistency(&factory.provider()?)? { // Highly unlikely to happen, and given its destructive nature, it's better to panic // instead. diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 299a8dc5f9e..3476df0497f 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -11,7 +11,7 @@ use reth_exex::{ExExManagerHandle, ExExNotification, ExExNotificationSource}; use reth_primitives_traits::{format_gas_throughput, BlockBody, NodePrimitives}; use reth_provider::{ providers::{StaticFileProvider, StaticFileWriter}, - BlockHashReader, BlockReader, DBProvider, ExecutionOutcome, HeaderProvider, + BlockHashReader, BlockReader, DBProvider, EitherWriter, ExecutionOutcome, HeaderProvider, LatestStateProviderRef, OriginalValuesKnown, ProviderError, StateWriter, StaticFileProviderFactory, StatsReader, StorageSettingsCache, TransactionVariant, }; @@ -193,9 +193,7 @@ where { // On old nodes, if there's any receipts pruning configured, receipts are written directly // to database and inconsistencies are expected. - if provider.prune_modes_ref().has_receipts_pruning() && - !provider.cached_storage_settings().receipts_in_static_files - { + if EitherWriter::receipts_destination(provider).is_database() { return Ok(()) } diff --git a/crates/stages/stages/src/stages/mod.rs b/crates/stages/stages/src/stages/mod.rs index 58fa7cfb324..4434f153c81 100644 --- a/crates/stages/stages/src/stages/mod.rs +++ b/crates/stages/stages/src/stages/mod.rs @@ -303,7 +303,6 @@ mod tests { db: &TestStageDB, prune_count: usize, segment: StaticFileSegment, - is_full_node: bool, expected: Option, ) { // We recreate the static file provider, since consistency heals are done on fetching the @@ -330,7 +329,7 @@ mod tests { static_file_provider = StaticFileProvider::read_write(static_file_provider.path()).unwrap(); assert!(matches!( static_file_provider - .check_consistency(&db.factory.database_provider_ro().unwrap(), is_full_node,), + .check_consistency(&db.factory.database_provider_ro().unwrap()), Ok(e) if e == expected )); } @@ -352,7 +351,7 @@ mod tests { assert!(matches!( db.factory .static_file_provider() - .check_consistency(&db.factory.database_provider_ro().unwrap(), false,), + .check_consistency(&db.factory.database_provider_ro().unwrap(),), Ok(e) if e == expected )); } @@ -385,7 +384,7 @@ mod tests { assert!(matches!( db.factory .static_file_provider() - .check_consistency(&db.factory.database_provider_ro().unwrap(), false), + .check_consistency(&db.factory.database_provider_ro().unwrap()), Ok(e) if e == expected )); } @@ -396,36 +395,40 @@ mod tests { let db_provider = db.factory.database_provider_ro().unwrap(); assert!(matches!( - db.factory.static_file_provider().check_consistency(&db_provider, false), + db.factory.static_file_provider().check_consistency(&db_provider), Ok(None) )); } #[test] fn test_consistency_no_commit_prune() { - let db = seed_data(90).unwrap(); - let full_node = true; - let archive_node = !full_node; + // Test full node with receipt pruning + let mut db_full = seed_data(90).unwrap(); + db_full.factory = db_full.factory.with_prune_modes(PruneModes { + receipts: Some(PruneMode::Before(1)), + ..Default::default() + }); // Full node does not use receipts, therefore doesn't check for consistency on receipts // segment - simulate_behind_checkpoint_corruption(&db, 1, StaticFileSegment::Receipts, full_node, None); + simulate_behind_checkpoint_corruption(&db_full, 1, StaticFileSegment::Receipts, None); + + // Test archive node without receipt pruning + let db_archive = seed_data(90).unwrap(); // there are 2 to 3 transactions per block. however, if we lose one tx, we need to unwind to // the previous block. simulate_behind_checkpoint_corruption( - &db, + &db_archive, 1, StaticFileSegment::Receipts, - archive_node, Some(PipelineTarget::Unwind(88)), ); simulate_behind_checkpoint_corruption( - &db, + &db_archive, 3, StaticFileSegment::Headers, - archive_node, Some(PipelineTarget::Unwind(86)), ); } diff --git a/crates/storage/provider/src/either_writer.rs b/crates/storage/provider/src/either_writer.rs index 2797a642d61..a981371d2a0 100644 --- a/crates/storage/provider/src/either_writer.rs +++ b/crates/storage/provider/src/either_writer.rs @@ -12,6 +12,7 @@ use reth_primitives_traits::ReceiptTy; use reth_static_file_types::StaticFileSegment; use reth_storage_api::{DBProvider, NodePrimitivesProvider, StorageSettingsCache}; use reth_storage_errors::provider::ProviderResult; +use strum::EnumIs; /// Type alias for [`EitherWriter`] constructors. type EitherWriterTy<'a, P, T> = EitherWriter< @@ -81,3 +82,36 @@ where } } } + +impl EitherWriter<'_, (), ()> { + /// Returns the destination for writing receipts. + /// + /// The rules are as follows: + /// - If the node should not always write receipts to static files, and any receipt pruning is + /// enabled, write to the database. + /// - If the node should always write receipts to static files, but receipt log filter pruning + /// is enabled, write to the database. + /// - Otherwise, write to static files. + pub fn receipts_destination( + provider: &P, + ) -> EitherWriterDestination { + let receipts_in_static_files = provider.cached_storage_settings().receipts_in_static_files; + let prune_modes = provider.prune_modes_ref(); + + if !receipts_in_static_files && prune_modes.has_receipts_pruning() || + // TODO: support writing receipts to static files with log filter pruning enabled + receipts_in_static_files && !prune_modes.receipts_log_filter.is_empty() + { + EitherWriterDestination::Database + } else { + EitherWriterDestination::StaticFile + } + } +} + +#[derive(Debug, EnumIs)] +#[allow(missing_docs)] +pub enum EitherWriterDestination { + Database, + StaticFile, +} diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 384d646053b..7fdff4a2122 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -377,9 +377,7 @@ impl DatabaseProvider>>(from_tx..)?; - if !self.prune_modes.has_receipts_pruning() || - self.cached_storage_settings().receipts_in_static_files - { + if EitherWriter::receipts_destination(self).is_static_file() { let static_file_receipt_num = self.static_file_provider.get_highest_static_file_tx(StaticFileSegment::Receipts); diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index ad0d883171e..ab86b455bf6 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -3,9 +3,9 @@ use super::{ StaticFileJarProvider, StaticFileProviderRW, StaticFileProviderRWRefMut, }; use crate::{ - to_range, BlockHashReader, BlockNumReader, BlockReader, BlockSource, HeaderProvider, - ReceiptProvider, StageCheckpointReader, StatsReader, TransactionVariant, TransactionsProvider, - TransactionsProviderExt, + to_range, BlockHashReader, BlockNumReader, BlockReader, BlockSource, EitherWriter, + HeaderProvider, ReceiptProvider, StageCheckpointReader, StatsReader, TransactionVariant, + TransactionsProvider, TransactionsProviderExt, }; use alloy_consensus::{ transaction::{SignerRecoverable, TransactionMeta}, @@ -947,7 +947,6 @@ impl StaticFileProvider { pub fn check_consistency( &self, provider: &Provider, - has_receipt_pruning: bool, ) -> ProviderResult> where Provider: DBProvider @@ -992,9 +991,7 @@ impl StaticFileProvider { match segment { StaticFileSegment::Headers | StaticFileSegment::Transactions => {} StaticFileSegment::Receipts => { - if has_receipt_pruning && - !provider.cached_storage_settings().receipts_in_static_files - { + if EitherWriter::receipts_destination(provider).is_database() { // Old pruned nodes (including full node) do not store receipts as static // files. continue From ffa9181fb90d4ecf6a70335e4f1ca41f1fb2f101 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:35:26 +0000 Subject: [PATCH 11/18] allow to skip writing receipts to static files if none have been written yet --- .../provider/src/providers/database/provider.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 7fdff4a2122..839281ea5aa 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -1623,9 +1623,13 @@ impl StateWriter // [`PruneSegment::ContractLogs`]. // // Receipts can only be skipped if we're dealing with legacy nodes that write them to - // Database. On newer nodes pruning will occur separately either by the PruneStage or the - // Pruner. - let prunable_receipts = !self.cached_storage_settings().receipts_in_static_files && + // Database, OR if receipts_in_static_files is enabled but no receipts exist in static + // files yet. Once receipts exist in static files, we must continue writing to maintain + // continuity and have no gaps. + let prunable_receipts = (EitherWriter::receipts_destination(self).is_database() || + self.static_file_provider() + .get_highest_static_file_tx(StaticFileSegment::Receipts) + .is_none()) && PruneMode::Distance(MINIMUM_PRUNING_DISTANCE).should_prune(first_block, tip); // Prepare set of addresses which logs should not be pruned. From 4aabf6229a8ceef05e2ea899b31ce2035375d7d6 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:51:10 +0000 Subject: [PATCH 12/18] add prunable tests --- .../src/providers/database/provider.rs | 170 +++++++++++++++++- 1 file changed, 167 insertions(+), 3 deletions(-) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 839281ea5aa..00eb3963ce2 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -131,6 +131,13 @@ impl DatabaseProviderRW { pub fn into_tx(self) -> ::TXMut { self.0.into_tx() } + + /// Override the minimum pruning distance for testing purposes. + #[cfg(any(test, feature = "test-utils"))] + pub fn with_minimum_pruning_distance(mut self, distance: u64) -> Self { + self.0.minimum_pruning_distance = distance; + self + } } impl From> @@ -157,6 +164,8 @@ pub struct DatabaseProvider { storage: Arc, /// Storage configuration settings for this node storage_settings: Arc>, + /// Minimum distance from tip required for pruning + minimum_pruning_distance: u64, } impl DatabaseProvider { @@ -262,7 +271,15 @@ impl DatabaseProvider { storage: Arc, storage_settings: Arc>, ) -> Self { - Self { tx, chain_spec, static_file_provider, prune_modes, storage, storage_settings } + Self { + tx, + chain_spec, + static_file_provider, + prune_modes, + storage, + storage_settings, + minimum_pruning_distance: MINIMUM_PRUNING_DISTANCE, + } } } @@ -509,7 +526,15 @@ impl DatabaseProvider { storage: Arc, storage_settings: Arc>, ) -> Self { - Self { tx, chain_spec, static_file_provider, prune_modes, storage, storage_settings } + Self { + tx, + chain_spec, + static_file_provider, + prune_modes, + storage, + storage_settings, + minimum_pruning_distance: MINIMUM_PRUNING_DISTANCE, + } } /// Consume `DbTx` or `DbTxMut`. @@ -1630,7 +1655,7 @@ impl StateWriter self.static_file_provider() .get_highest_static_file_tx(StaticFileSegment::Receipts) .is_none()) && - PruneMode::Distance(MINIMUM_PRUNING_DISTANCE).should_prune(first_block, tip); + PruneMode::Distance(self.minimum_pruning_distance).should_prune(first_block, tip); // Prepare set of addresses which logs should not be pruned. let mut allowed_addresses: HashSet = HashSet::new(); @@ -3182,6 +3207,7 @@ mod tests { test_utils::{blocks::BlockchainTestData, create_test_provider_factory}, BlockWriter, }; + use reth_ethereum_primitives::Receipt; use reth_testing_utils::generators::{self, random_block, BlockParams}; #[test] @@ -4658,4 +4684,142 @@ mod tests { "storage_nibbles2 should have the value that was created and will be deleted" ); } + + #[test] + fn test_prunable_receipts_logic() { + let insert_blocks = + |provider_rw: &DatabaseProviderRW<_, _>, tip_block: u64, tx_count: u8| { + let mut rng = generators::rng(); + for block_num in 0..=tip_block { + let block = random_block( + &mut rng, + block_num, + BlockParams { tx_count: Some(tx_count), ..Default::default() }, + ); + provider_rw.insert_block(block.try_recover().unwrap()).unwrap(); + } + }; + + let write_receipts = |provider_rw: DatabaseProviderRW<_, _>, block: u64| { + let outcome = ExecutionOutcome { + first_block: block, + receipts: vec![vec![Receipt { + tx_type: Default::default(), + success: true, + cumulative_gas_used: block, // identifier to assert against + logs: vec![], + }]], + ..Default::default() + }; + provider_rw.write_state(&outcome, crate::OriginalValuesKnown::No).unwrap(); + provider_rw.commit().unwrap(); + }; + + // Legacy mode (receipts in DB) - should be prunable + { + let factory = create_test_provider_factory(); + let storage_settings = StorageSettings::legacy(); + factory.set_storage_settings_cache(storage_settings); + let factory = factory.with_prune_modes(PruneModes { + receipts: Some(PruneMode::Before(100)), + ..Default::default() + }); + + let tip_block = 200u64; + let first_block = 1u64; + + // create chain + let provider_rw = factory.provider_rw().unwrap(); + insert_blocks(&provider_rw, tip_block, 1); + provider_rw.commit().unwrap(); + + write_receipts( + factory.provider_rw().unwrap().with_minimum_pruning_distance(100), + first_block, + ); + write_receipts( + factory.provider_rw().unwrap().with_minimum_pruning_distance(100), + tip_block - 1, + ); + + let provider = factory.provider().unwrap(); + + for (block, num_receipts) in [(0, 0), (tip_block - 1, 1)] { + assert!(provider + .receipts_by_block(block.into()) + .unwrap() + .is_some_and(|r| r.len() == num_receipts)); + } + } + + // Static files mode + { + let factory = create_test_provider_factory(); + let storage_settings = StorageSettings::new().with_receipts_in_static_files(); + factory.set_storage_settings_cache(storage_settings); + let factory = factory.with_prune_modes(PruneModes { + receipts: Some(PruneMode::Before(2)), + ..Default::default() + }); + + let tip_block = 200u64; + + // create chain + let provider_rw = factory.provider_rw().unwrap(); + insert_blocks(&provider_rw, tip_block, 1); + provider_rw.commit().unwrap(); + + // Attempt to write receipts for block 0 and 1 (should be skipped) + write_receipts(factory.provider_rw().unwrap().with_minimum_pruning_distance(100), 0); + write_receipts(factory.provider_rw().unwrap().with_minimum_pruning_distance(100), 1); + + assert!(factory + .static_file_provider() + .get_highest_static_file_tx(StaticFileSegment::Receipts) + .is_none(),); + assert!(factory + .static_file_provider() + .get_highest_static_file_block(StaticFileSegment::Receipts) + .is_some_and(|b| b == 1),); + + // Since we have prune mode Before(2), the next receipt (block 2) should be written to + // static files. + write_receipts(factory.provider_rw().unwrap().with_minimum_pruning_distance(100), 2); + assert!(factory + .static_file_provider() + .get_highest_static_file_tx(StaticFileSegment::Receipts) + .is_some_and(|num| num == 2),); + + // After having a receipt already in static files, attempt to skip the next receipt by + // changing the prune mode. It should NOT skip it and should still write the receipt, + // since static files do not support gaps. + let factory = factory.with_prune_modes(PruneModes { + receipts: Some(PruneMode::Before(100)), + ..Default::default() + }); + let provider_rw = factory.provider_rw().unwrap().with_minimum_pruning_distance(1); + assert!(PruneMode::Distance(1).should_prune(3, tip_block)); + write_receipts(provider_rw, 3); + + // Ensure we can only fetch the 2 last receipts. + // + // Test setup only has 1 tx per block and each receipt has its cumulative_gas_used set + // to the block number it belongs to easily identify and assert. + let provider = factory.provider().unwrap(); + assert!(EitherWriter::receipts_destination(&provider).is_static_file()); + for (num, num_receipts) in [(0, 0), (1, 0), (2, 1), (3, 1)] { + assert!(provider + .receipts_by_block(num.into()) + .unwrap() + .is_some_and(|r| r.len() == num_receipts)); + + let receipt = provider.receipt(num).unwrap(); + if num_receipts > 0 { + assert!(receipt.is_some_and(|r| r.cumulative_gas_used == num)); + } else { + assert!(receipt.is_none()); + } + } + } + } } From b0d0eec2284e3f759099a1e72b3b88526f657c5d Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:58:38 +0000 Subject: [PATCH 13/18] clippy --- crates/storage/provider/src/providers/database/provider.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 00eb3963ce2..e0a207277be 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -134,7 +134,7 @@ impl DatabaseProviderRW { /// Override the minimum pruning distance for testing purposes. #[cfg(any(test, feature = "test-utils"))] - pub fn with_minimum_pruning_distance(mut self, distance: u64) -> Self { + pub const fn with_minimum_pruning_distance(mut self, distance: u64) -> Self { self.0.minimum_pruning_distance = distance; self } From 7686c77943d2a244552b010cb9e56b47e7e3f81e Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:26:50 +0000 Subject: [PATCH 14/18] init_genesis with settings + --static-files.receipts --- Cargo.lock | 1 + crates/cli/commands/src/common.rs | 12 ++++++---- crates/node/builder/src/launch/common.rs | 12 +++++++--- crates/node/core/Cargo.toml | 1 + crates/node/core/src/args/static_files.rs | 19 ++++++++++++++++ crates/storage/db-common/src/init.rs | 27 ++++++++++++++++++++++- 6 files changed, 64 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84adfdd9128..f896b883424 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9139,6 +9139,7 @@ dependencies = [ "reth-network-p2p", "reth-network-peers", "reth-primitives-traits", + "reth-provider", "reth-prune-types", "reth-rpc-convert", "reth-rpc-eth-types", diff --git a/crates/cli/commands/src/common.rs b/crates/cli/commands/src/common.rs index 6bb038d97c8..d2a28451b94 100644 --- a/crates/cli/commands/src/common.rs +++ b/crates/cli/commands/src/common.rs @@ -7,7 +7,7 @@ use reth_cli::chainspec::ChainSpecParser; use reth_config::{config::EtlConfig, Config}; use reth_consensus::noop::NoopConsensus; use reth_db::{init_db, open_db_read_only, DatabaseEnv}; -use reth_db_common::init::init_genesis; +use reth_db_common::init::init_genesis_with_settings; use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader}; use reth_eth_wire::NetPrimitivesFor; use reth_evm::{noop::NoopEvmConfig, ConfigureEvm}; @@ -17,12 +17,12 @@ use reth_node_builder::{ Node, NodeComponents, NodeComponentsBuilder, NodeTypes, NodeTypesWithDBAdapter, }; use reth_node_core::{ - args::{DatabaseArgs, DatadirArgs}, + args::{DatabaseArgs, DatadirArgs, StaticFilesArgs}, dirs::{ChainPath, DataDirPath}, }; use reth_provider::{ providers::{BlockchainProvider, NodeTypesForProvider, StaticFileProvider}, - ProviderFactory, StaticFileProviderFactory, + ProviderFactory, StaticFileProviderFactory, StorageSettings, }; use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget}; use reth_static_file::StaticFileProducer; @@ -57,6 +57,10 @@ pub struct EnvironmentArgs { /// All database related arguments #[command(flatten)] pub db: DatabaseArgs, + + /// All static files related arguments + #[command(flatten)] + pub static_files: StaticFilesArgs, } impl EnvironmentArgs { @@ -106,7 +110,7 @@ impl EnvironmentArgs { let provider_factory = self.create_provider_factory(&config, db, sfp)?; if access.is_read_write() { debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis"); - init_genesis(&provider_factory)?; + init_genesis_with_settings(&provider_factory, self.static_files.to_settings())?; } Ok(Environment { config, provider_factory, data_dir }) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index bbfbfbcf9f1..651fd3958be 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -42,7 +42,7 @@ use reth_chainspec::{Chain, EthChainSpec, EthereumHardforks}; use reth_config::{config::EtlConfig, PruneConfig}; use reth_consensus::noop::NoopConsensus; use reth_db_api::{database::Database, database_metrics::DatabaseMetrics}; -use reth_db_common::init::{init_genesis, InitStorageError}; +use reth_db_common::init::{init_genesis_with_settings, InitStorageError}; use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader}; use reth_engine_local::MiningMode; use reth_evm::{noop::NoopEvmConfig, ConfigureEvm}; @@ -624,13 +624,19 @@ where /// Convenience function to [`Self::init_genesis`] pub fn with_genesis(self) -> Result { - init_genesis(self.provider_factory())?; + init_genesis_with_settings( + self.provider_factory(), + self.node_config().static_files.to_settings(), + )?; Ok(self) } /// Write the genesis block and state if it has not already been written pub fn init_genesis(&self) -> Result { - init_genesis(self.provider_factory()) + init_genesis_with_settings( + self.provider_factory(), + self.node_config().static_files.to_settings(), + ) } /// Creates a new `WithMeteredProvider` container and attaches it to the diff --git a/crates/node/core/Cargo.toml b/crates/node/core/Cargo.toml index 1d767865793..1f66865cc01 100644 --- a/crates/node/core/Cargo.toml +++ b/crates/node/core/Cargo.toml @@ -19,6 +19,7 @@ reth-cli-util.workspace = true reth-db = { workspace = true, features = ["mdbx"] } reth-storage-errors.workspace = true reth-storage-api = { workspace = true, features = ["std", "db-api"] } +reth-provider.workspace = true reth-network = { workspace = true, features = ["serde"] } reth-network-p2p.workspace = true reth-rpc-eth-types.workspace = true diff --git a/crates/node/core/src/args/static_files.rs b/crates/node/core/src/args/static_files.rs index 59e7f7e3d8f..cbe219bed4d 100644 --- a/crates/node/core/src/args/static_files.rs +++ b/crates/node/core/src/args/static_files.rs @@ -2,6 +2,7 @@ use clap::Args; use reth_config::config::{BlocksPerFileConfig, StaticFilesConfig}; +use reth_provider::StorageSettings; /// Parameters for static files configuration #[derive(Debug, Args, PartialEq, Eq, Default, Clone, Copy)] @@ -18,6 +19,15 @@ pub struct StaticFilesArgs { /// Number of blocks per file for the receipts segment. #[arg(long = "static-files.blocks-per-file.receipts")] pub blocks_per_file_receipts: Option, + + /// Store receipts in static files instead of the database. + /// + /// When enabled, receipts will be written to static files on disk instead of the database. + /// + /// Note: This setting can only be configured at genesis initialization. Once + /// the node has been initialized, changing this flag requires re-syncing from scratch. + #[arg(long = "static-files.receipts")] + pub receipts: bool, } impl StaticFilesArgs { @@ -34,4 +44,13 @@ impl StaticFilesArgs { }, } } + + /// Converts the static files arguments into [`StorageSettings`]. + pub fn to_settings(&self) -> StorageSettings { + if self.receipts { + StorageSettings::new().with_receipts_in_static_files() + } else { + StorageSettings::legacy() + } + } } diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index a560470e1b1..4a9c67c6b55 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -85,6 +85,32 @@ impl From for InitStorageError { /// Write the genesis block if it has not already been written pub fn init_genesis(factory: &PF) -> Result +where + PF: DatabaseProviderFactory + + StaticFileProviderFactory> + + ChainSpecProvider + + StageCheckpointReader + + BlockHashReader + + StorageSettingsCache, + PF::ProviderRW: StaticFileProviderFactory + + StageCheckpointWriter + + HistoryWriter + + HeaderProvider + + HashingWriter + + StateWriter + + TrieWriter + + MetadataWriter + + AsRef, + PF::ChainSpec: EthChainSpec
::BlockHeader>, +{ + init_genesis_with_settings(factory, StorageSettings::legacy()) +} + +/// Write the genesis block if it has not already been written, with static files configuration. +pub fn init_genesis_with_settings( + factory: &PF, + storage_settings: StorageSettings, +) -> Result where PF: DatabaseProviderFactory + StaticFileProviderFactory> @@ -164,7 +190,6 @@ where static_file_provider.latest_writer(StaticFileSegment::Transactions)?.increment_block(0)?; // Behaviour reserved only for new nodes should be set here. - let storage_settings = StorageSettings::new().with_receipts_in_static_files(); provider_rw.write_storage_settings(storage_settings)?; // `commit_unwind`` will first commit the DB and then the static file provider, which is From e73918ba57913f4585dfe4f5b6c075ba7752737d Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:30:04 +0000 Subject: [PATCH 15/18] doc update --- crates/storage/db-common/src/init.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index 4a9c67c6b55..f2790dfe23f 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -106,7 +106,7 @@ where init_genesis_with_settings(factory, StorageSettings::legacy()) } -/// Write the genesis block if it has not already been written, with static files configuration. +/// Write the genesis block if it has not already been written with [`StorageSettings`]. pub fn init_genesis_with_settings( factory: &PF, storage_settings: StorageSettings, From cffb4662f6953c23a9d94d3fb64333b4bf258cb1 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:33:55 +0000 Subject: [PATCH 16/18] clippy & book --- crates/cli/commands/src/common.rs | 2 +- docs/vocs/docs/pages/cli/reth/db.mdx | 17 +++++++++++++++++ docs/vocs/docs/pages/cli/reth/download.mdx | 17 +++++++++++++++++ docs/vocs/docs/pages/cli/reth/export-era.mdx | 17 +++++++++++++++++ docs/vocs/docs/pages/cli/reth/import-era.mdx | 17 +++++++++++++++++ docs/vocs/docs/pages/cli/reth/import.mdx | 17 +++++++++++++++++ docs/vocs/docs/pages/cli/reth/init-state.mdx | 17 +++++++++++++++++ docs/vocs/docs/pages/cli/reth/init.mdx | 17 +++++++++++++++++ docs/vocs/docs/pages/cli/reth/node.mdx | 7 +++++++ docs/vocs/docs/pages/cli/reth/prune.mdx | 17 +++++++++++++++++ docs/vocs/docs/pages/cli/reth/re-execute.mdx | 17 +++++++++++++++++ docs/vocs/docs/pages/cli/reth/stage/drop.mdx | 17 +++++++++++++++++ docs/vocs/docs/pages/cli/reth/stage/dump.mdx | 17 +++++++++++++++++ docs/vocs/docs/pages/cli/reth/stage/run.mdx | 17 +++++++++++++++++ docs/vocs/docs/pages/cli/reth/stage/unwind.mdx | 17 +++++++++++++++++ 15 files changed, 229 insertions(+), 1 deletion(-) diff --git a/crates/cli/commands/src/common.rs b/crates/cli/commands/src/common.rs index d2a28451b94..81a5cad6d16 100644 --- a/crates/cli/commands/src/common.rs +++ b/crates/cli/commands/src/common.rs @@ -22,7 +22,7 @@ use reth_node_core::{ }; use reth_provider::{ providers::{BlockchainProvider, NodeTypesForProvider, StaticFileProvider}, - ProviderFactory, StaticFileProviderFactory, StorageSettings, + ProviderFactory, StaticFileProviderFactory, }; use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget}; use reth_static_file::StaticFileProducer; diff --git a/docs/vocs/docs/pages/cli/reth/db.mdx b/docs/vocs/docs/pages/cli/reth/db.mdx index 95e4bb20ca7..b9cfb992a61 100644 --- a/docs/vocs/docs/pages/cli/reth/db.mdx +++ b/docs/vocs/docs/pages/cli/reth/db.mdx @@ -99,6 +99,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + Logging: --log.stdout.format The format to use for logs written to stdout diff --git a/docs/vocs/docs/pages/cli/reth/download.mdx b/docs/vocs/docs/pages/cli/reth/download.mdx index f13aeb0bc97..63c8e507c1f 100644 --- a/docs/vocs/docs/pages/cli/reth/download.mdx +++ b/docs/vocs/docs/pages/cli/reth/download.mdx @@ -86,6 +86,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + -u, --url Specify a snapshot URL or let the command propose a default one. diff --git a/docs/vocs/docs/pages/cli/reth/export-era.mdx b/docs/vocs/docs/pages/cli/reth/export-era.mdx index 16ea002972c..7efbe064264 100644 --- a/docs/vocs/docs/pages/cli/reth/export-era.mdx +++ b/docs/vocs/docs/pages/cli/reth/export-era.mdx @@ -86,6 +86,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + --first-block-number Optional first block number to export from the db. It is by default 0. diff --git a/docs/vocs/docs/pages/cli/reth/import-era.mdx b/docs/vocs/docs/pages/cli/reth/import-era.mdx index 1eaa32a6fb0..a04bf89dcf6 100644 --- a/docs/vocs/docs/pages/cli/reth/import-era.mdx +++ b/docs/vocs/docs/pages/cli/reth/import-era.mdx @@ -86,6 +86,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + --path The path to a directory for import. diff --git a/docs/vocs/docs/pages/cli/reth/import.mdx b/docs/vocs/docs/pages/cli/reth/import.mdx index cc7ccc988c2..5699512754b 100644 --- a/docs/vocs/docs/pages/cli/reth/import.mdx +++ b/docs/vocs/docs/pages/cli/reth/import.mdx @@ -86,6 +86,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + --no-state Disables stages that require state. diff --git a/docs/vocs/docs/pages/cli/reth/init-state.mdx b/docs/vocs/docs/pages/cli/reth/init-state.mdx index 4a00e88f1ab..e37d06c63a3 100644 --- a/docs/vocs/docs/pages/cli/reth/init-state.mdx +++ b/docs/vocs/docs/pages/cli/reth/init-state.mdx @@ -86,6 +86,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + --without-evm Specifies whether to initialize the state without relying on EVM historical data. diff --git a/docs/vocs/docs/pages/cli/reth/init.mdx b/docs/vocs/docs/pages/cli/reth/init.mdx index c1cd24be238..f0eff64c6a6 100644 --- a/docs/vocs/docs/pages/cli/reth/init.mdx +++ b/docs/vocs/docs/pages/cli/reth/init.mdx @@ -86,6 +86,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + Logging: --log.stdout.format The format to use for logs written to stdout diff --git a/docs/vocs/docs/pages/cli/reth/node.mdx b/docs/vocs/docs/pages/cli/reth/node.mdx index b6f1dac2dcc..4289a8f199b 100644 --- a/docs/vocs/docs/pages/cli/reth/node.mdx +++ b/docs/vocs/docs/pages/cli/reth/node.mdx @@ -954,6 +954,13 @@ Static Files: --static-files.blocks-per-file.receipts Number of blocks per file for the receipts segment + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + Ress: --ress.enable Enable support for `ress` subprotocol diff --git a/docs/vocs/docs/pages/cli/reth/prune.mdx b/docs/vocs/docs/pages/cli/reth/prune.mdx index a24f297c6ce..d93c0d2302f 100644 --- a/docs/vocs/docs/pages/cli/reth/prune.mdx +++ b/docs/vocs/docs/pages/cli/reth/prune.mdx @@ -86,6 +86,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + Logging: --log.stdout.format The format to use for logs written to stdout diff --git a/docs/vocs/docs/pages/cli/reth/re-execute.mdx b/docs/vocs/docs/pages/cli/reth/re-execute.mdx index cce93bf9b62..d178a7f4c15 100644 --- a/docs/vocs/docs/pages/cli/reth/re-execute.mdx +++ b/docs/vocs/docs/pages/cli/reth/re-execute.mdx @@ -86,6 +86,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + --from The height to start at diff --git a/docs/vocs/docs/pages/cli/reth/stage/drop.mdx b/docs/vocs/docs/pages/cli/reth/stage/drop.mdx index 4a26cc909ec..0a055a6104b 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/drop.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/drop.mdx @@ -86,6 +86,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + Possible values: - headers: The headers stage within the pipeline diff --git a/docs/vocs/docs/pages/cli/reth/stage/dump.mdx b/docs/vocs/docs/pages/cli/reth/stage/dump.mdx index 874bb8d6288..4ac4cd5d9be 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/dump.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/dump.mdx @@ -93,6 +93,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + Logging: --log.stdout.format The format to use for logs written to stdout diff --git a/docs/vocs/docs/pages/cli/reth/stage/run.mdx b/docs/vocs/docs/pages/cli/reth/stage/run.mdx index 915faa93166..ba7514dc87d 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/run.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/run.mdx @@ -86,6 +86,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + --metrics Enable Prometheus metrics. diff --git a/docs/vocs/docs/pages/cli/reth/stage/unwind.mdx b/docs/vocs/docs/pages/cli/reth/stage/unwind.mdx index 7d8359d5dc3..fcba15254de 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/unwind.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/unwind.mdx @@ -91,6 +91,23 @@ Database: --db.sync-mode Controls how aggressively the database synchronizes data to disk +Static Files: + --static-files.blocks-per-file.headers + Number of blocks per file for the headers segment + + --static-files.blocks-per-file.transactions + Number of blocks per file for the transactions segment + + --static-files.blocks-per-file.receipts + Number of blocks per file for the receipts segment + + --static-files.receipts + Store receipts in static files instead of the database. + + When enabled, receipts will be written to static files on disk instead of the database. + + Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + --offline If this is enabled, then all stages except headers, bodies, and sender recovery will be unwound From ac8fcdb10b8880b7a5daeaa4df8c3cde96d620b0 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:40:43 +0000 Subject: [PATCH 17/18] make fn const --- crates/node/core/src/args/static_files.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/node/core/src/args/static_files.rs b/crates/node/core/src/args/static_files.rs index cbe219bed4d..9de5b146436 100644 --- a/crates/node/core/src/args/static_files.rs +++ b/crates/node/core/src/args/static_files.rs @@ -46,7 +46,7 @@ impl StaticFilesArgs { } /// Converts the static files arguments into [`StorageSettings`]. - pub fn to_settings(&self) -> StorageSettings { + pub const fn to_settings(&self) -> StorageSettings { if self.receipts { StorageSettings::new().with_receipts_in_static_files() } else { From 4f03c0478fa7ea7657c5abf1e596f31a4ba1a460 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:06:16 +0000 Subject: [PATCH 18/18] feat: prune receipts from static files (#19401) --- crates/prune/prune/src/builder.rs | 6 ++- crates/prune/prune/src/segments/mod.rs | 43 ++++++++++++++++++- crates/prune/prune/src/segments/receipts.rs | 33 +++++++++++--- crates/prune/prune/src/segments/set.rs | 5 ++- .../prune/prune/src/segments/user/bodies.rs | 27 ++---------- .../prune/prune/src/segments/user/receipts.rs | 4 +- crates/stages/stages/src/stages/prune.rs | 6 +-- 7 files changed, 84 insertions(+), 40 deletions(-) diff --git a/crates/prune/prune/src/builder.rs b/crates/prune/prune/src/builder.rs index 78283710e15..9e451ea49fb 100644 --- a/crates/prune/prune/src/builder.rs +++ b/crates/prune/prune/src/builder.rs @@ -7,7 +7,7 @@ use reth_primitives_traits::NodePrimitives; use reth_provider::{ providers::StaticFileProvider, BlockReader, ChainStateBlockReader, DBProvider, DatabaseProviderFactory, NodePrimitivesProvider, PruneCheckpointReader, PruneCheckpointWriter, - StaticFileProviderFactory, + StaticFileProviderFactory, StorageSettingsCache, }; use reth_prune_types::PruneModes; use std::time::Duration; @@ -80,6 +80,7 @@ impl PrunerBuilder { + PruneCheckpointReader + BlockReader + ChainStateBlockReader + + StorageSettingsCache + StaticFileProviderFactory< Primitives: NodePrimitives, >, @@ -112,7 +113,8 @@ impl PrunerBuilder { + BlockReader + ChainStateBlockReader + PruneCheckpointWriter - + PruneCheckpointReader, + + PruneCheckpointReader + + StorageSettingsCache, { let segments = SegmentSet::::from_components(static_file_provider, self.segments); diff --git a/crates/prune/prune/src/segments/mod.rs b/crates/prune/prune/src/segments/mod.rs index f917c78ea94..86e8597088e 100644 --- a/crates/prune/prune/src/segments/mod.rs +++ b/crates/prune/prune/src/segments/mod.rs @@ -4,8 +4,14 @@ mod user; use crate::{PruneLimiter, PrunerError}; use alloy_primitives::{BlockNumber, TxNumber}; -use reth_provider::{errors::provider::ProviderResult, BlockReader, PruneCheckpointWriter}; -use reth_prune_types::{PruneCheckpoint, PruneMode, PrunePurpose, PruneSegment, SegmentOutput}; +use reth_provider::{ + errors::provider::ProviderResult, BlockReader, PruneCheckpointWriter, StaticFileProviderFactory, +}; +use reth_prune_types::{ + PruneCheckpoint, PruneMode, PruneProgress, PrunePurpose, PruneSegment, SegmentOutput, + SegmentOutputCheckpoint, +}; +use reth_static_file_types::StaticFileSegment; pub use set::SegmentSet; use std::{fmt::Debug, ops::RangeInclusive}; use tracing::error; @@ -14,6 +20,39 @@ pub use user::{ SenderRecovery, StorageHistory, TransactionLookup, }; +/// Prunes data from static files for a given segment. +/// +/// This is a generic helper function used by both receipts and bodies pruning +/// when data is stored in static files. +pub(crate) fn prune_static_files( + provider: &Provider, + input: PruneInput, + segment: StaticFileSegment, +) -> Result +where + Provider: StaticFileProviderFactory, +{ + let deleted_headers = + provider.static_file_provider().delete_segment_below_block(segment, input.to_block + 1)?; + + if deleted_headers.is_empty() { + return Ok(SegmentOutput::done()) + } + + let tx_ranges = deleted_headers.iter().filter_map(|header| header.tx_range()); + + let pruned = tx_ranges.clone().map(|range| range.len()).sum::() as usize; + + Ok(SegmentOutput { + progress: PruneProgress::Finished, + pruned, + checkpoint: Some(SegmentOutputCheckpoint { + block_number: Some(input.to_block), + tx_number: tx_ranges.map(|range| range.end()).max(), + }), + }) +} + /// A segment represents a pruning of some portion of the data. /// /// Segments are called from [`Pruner`](crate::Pruner) with the following lifecycle: diff --git a/crates/prune/prune/src/segments/receipts.rs b/crates/prune/prune/src/segments/receipts.rs index 5e9d39aca13..30915f89b2f 100644 --- a/crates/prune/prune/src/segments/receipts.rs +++ b/crates/prune/prune/src/segments/receipts.rs @@ -3,15 +3,21 @@ //! - [`crate::segments::user::Receipts`] is responsible for pruning receipts according to the //! user-configured settings (for example, on a full node or with a custom prune config) -use crate::{db_ext::DbTxPruneExt, segments::PruneInput, PrunerError}; +use crate::{ + db_ext::DbTxPruneExt, + segments::{self, PruneInput}, + PrunerError, +}; use reth_db_api::{table::Value, tables, transaction::DbTxMut}; use reth_primitives_traits::NodePrimitives; use reth_provider::{ - errors::provider::ProviderResult, BlockReader, DBProvider, NodePrimitivesProvider, - PruneCheckpointWriter, TransactionsProvider, + errors::provider::ProviderResult, BlockReader, DBProvider, EitherWriter, + NodePrimitivesProvider, PruneCheckpointWriter, StaticFileProviderFactory, StorageSettingsCache, + TransactionsProvider, }; use reth_prune_types::{PruneCheckpoint, PruneSegment, SegmentOutput, SegmentOutputCheckpoint}; -use tracing::trace; +use reth_static_file_types::StaticFileSegment; +use tracing::{debug, trace}; pub(crate) fn prune( provider: &Provider, @@ -21,8 +27,17 @@ where Provider: DBProvider + TransactionsProvider + BlockReader + + StorageSettingsCache + + StaticFileProviderFactory + NodePrimitivesProvider>, { + if EitherWriter::receipts_destination(provider).is_static_file() { + debug!(target: "pruner", "Pruning receipts from static files."); + return segments::prune_static_files(provider, input, StaticFileSegment::Receipts) + } + debug!(target: "pruner", "Pruning receipts from database."); + + // Original database implementation for when receipts are not on static files (old nodes) let tx_range = match input.get_next_tx_num_range(provider)? { Some(range) => range, None => { @@ -98,8 +113,14 @@ mod tests { use std::ops::Sub; #[test] - fn prune() { - let db = TestStageDB::default(); + fn prune_legacy() { + let mut db = TestStageDB::default(); + // Configure the factory to use database for receipts by enabling receipt pruning. + // This ensures EitherWriter::receipts_destination returns Database instead of StaticFile. + db.factory = db.factory.with_prune_modes(reth_prune_types::PruneModes { + receipts: Some(PruneMode::Full), + ..Default::default() + }); let mut rng = generators::rng(); let blocks = random_block_range( diff --git a/crates/prune/prune/src/segments/set.rs b/crates/prune/prune/src/segments/set.rs index 7ae9e044e20..479ab4f25b0 100644 --- a/crates/prune/prune/src/segments/set.rs +++ b/crates/prune/prune/src/segments/set.rs @@ -7,7 +7,7 @@ use reth_db_api::{table::Value, transaction::DbTxMut}; use reth_primitives_traits::NodePrimitives; use reth_provider::{ providers::StaticFileProvider, BlockReader, ChainStateBlockReader, DBProvider, - PruneCheckpointReader, PruneCheckpointWriter, StaticFileProviderFactory, + PruneCheckpointReader, PruneCheckpointWriter, StaticFileProviderFactory, StorageSettingsCache, }; use reth_prune_types::PruneModes; @@ -51,7 +51,8 @@ where + PruneCheckpointWriter + PruneCheckpointReader + BlockReader - + ChainStateBlockReader, + + ChainStateBlockReader + + StorageSettingsCache, { /// Creates a [`SegmentSet`] from an existing components, such as [`StaticFileProvider`] and /// [`PruneModes`]. diff --git a/crates/prune/prune/src/segments/user/bodies.rs b/crates/prune/prune/src/segments/user/bodies.rs index 2623529b458..13c61450c2e 100644 --- a/crates/prune/prune/src/segments/user/bodies.rs +++ b/crates/prune/prune/src/segments/user/bodies.rs @@ -1,11 +1,9 @@ use crate::{ - segments::{PruneInput, Segment}, + segments::{self, PruneInput, Segment}, PrunerError, }; use reth_provider::{BlockReader, StaticFileProviderFactory}; -use reth_prune_types::{ - PruneMode, PruneProgress, PrunePurpose, PruneSegment, SegmentOutput, SegmentOutputCheckpoint, -}; +use reth_prune_types::{PruneMode, PrunePurpose, PruneSegment, SegmentOutput}; use reth_static_file_types::StaticFileSegment; /// Segment responsible for pruning transactions in static files. @@ -40,26 +38,7 @@ where } fn prune(&self, provider: &Provider, input: PruneInput) -> Result { - let deleted_headers = provider - .static_file_provider() - .delete_segment_below_block(StaticFileSegment::Transactions, input.to_block + 1)?; - - if deleted_headers.is_empty() { - return Ok(SegmentOutput::done()) - } - - let tx_ranges = deleted_headers.iter().filter_map(|header| header.tx_range()); - - let pruned = tx_ranges.clone().map(|range| range.len()).sum::() as usize; - - Ok(SegmentOutput { - progress: PruneProgress::Finished, - pruned, - checkpoint: Some(SegmentOutputCheckpoint { - block_number: Some(input.to_block), - tx_number: tx_ranges.map(|range| range.end()).max(), - }), - }) + segments::prune_static_files(provider, input, StaticFileSegment::Transactions) } } diff --git a/crates/prune/prune/src/segments/user/receipts.rs b/crates/prune/prune/src/segments/user/receipts.rs index 03faddc1d5b..9f193b4ca3d 100644 --- a/crates/prune/prune/src/segments/user/receipts.rs +++ b/crates/prune/prune/src/segments/user/receipts.rs @@ -6,7 +6,7 @@ use reth_db_api::{table::Value, transaction::DbTxMut}; use reth_primitives_traits::NodePrimitives; use reth_provider::{ errors::provider::ProviderResult, BlockReader, DBProvider, NodePrimitivesProvider, - PruneCheckpointWriter, TransactionsProvider, + PruneCheckpointWriter, StaticFileProviderFactory, StorageSettingsCache, TransactionsProvider, }; use reth_prune_types::{PruneCheckpoint, PruneMode, PrunePurpose, PruneSegment, SegmentOutput}; use tracing::instrument; @@ -28,6 +28,8 @@ where + PruneCheckpointWriter + TransactionsProvider + BlockReader + + StorageSettingsCache + + StaticFileProviderFactory + NodePrimitivesProvider>, { fn segment(&self) -> PruneSegment { diff --git a/crates/stages/stages/src/stages/prune.rs b/crates/stages/stages/src/stages/prune.rs index f6fb7f90ae1..11804075553 100644 --- a/crates/stages/stages/src/stages/prune.rs +++ b/crates/stages/stages/src/stages/prune.rs @@ -2,7 +2,7 @@ use reth_db_api::{table::Value, transaction::DbTxMut}; use reth_primitives_traits::NodePrimitives; use reth_provider::{ BlockReader, ChainStateBlockReader, DBProvider, PruneCheckpointReader, PruneCheckpointWriter, - StaticFileProviderFactory, + StaticFileProviderFactory, StorageSettingsCache, }; use reth_prune::{ PruneMode, PruneModes, PruneSegment, PrunerBuilder, SegmentOutput, SegmentOutputCheckpoint, @@ -45,7 +45,7 @@ where + ChainStateBlockReader + StaticFileProviderFactory< Primitives: NodePrimitives, - >, + > + StorageSettingsCache, { fn id(&self) -> StageId { StageId::Prune @@ -146,7 +146,7 @@ where + ChainStateBlockReader + StaticFileProviderFactory< Primitives: NodePrimitives, - >, + > + StorageSettingsCache, { fn id(&self) -> StageId { StageId::PruneSenderRecovery