diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index 2ebceb0b64..020e645390 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -1079,18 +1079,6 @@ transaction_lookup = 'full' receipts = { distance = 16384 } #"; let _conf: Config = toml::from_str(s).unwrap(); - - let s = r"# -[prune] -block_interval = 5 - -[prune.segments] -sender_recovery = { distance = 16384 } -transaction_lookup = 'full' -receipts = 'full' -#"; - let err = toml::from_str::(s).unwrap_err().to_string(); - assert!(err.contains("invalid value: string \"full\""), "{}", err); } #[test] diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 98f5c06d65..c2e33b73e9 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -170,7 +170,8 @@ impl LaunchContext { toml_config.peers.trusted_nodes_only = config.network.trusted_only; // Merge static file CLI arguments with config file, giving priority to CLI - toml_config.static_files = config.static_files.merge_with_config(toml_config.static_files); + toml_config.static_files = + config.static_files.merge_with_config(toml_config.static_files, config.pruning.minimal); Ok(toml_config) } @@ -1301,6 +1302,7 @@ mod tests { let node_config = NodeConfig { pruning: PruningArgs { full: true, + minimal: false, block_interval: None, sender_recovery_full: false, sender_recovery_distance: None, diff --git a/crates/node/core/src/args/mod.rs b/crates/node/core/src/args/mod.rs index 2872886950..d5c2aa4c66 100644 --- a/crates/node/core/src/args/mod.rs +++ b/crates/node/core/src/args/mod.rs @@ -78,7 +78,7 @@ pub use era::{DefaultEraHost, EraArgs, EraSourceArgs}; /// `StaticFilesArgs` for configuring static files. mod static_files; -pub use static_files::StaticFilesArgs; +pub use static_files::{StaticFilesArgs, MINIMAL_BLOCKS_PER_FILE}; mod error; pub mod types; diff --git a/crates/node/core/src/args/pruning.rs b/crates/node/core/src/args/pruning.rs index ed79ee529c..82a081228a 100644 --- a/crates/node/core/src/args/pruning.rs +++ b/crates/node/core/src/args/pruning.rs @@ -16,9 +16,18 @@ use std::{collections::BTreeMap, ops::Not}; #[command(next_help_heading = "Pruning")] pub struct PruningArgs { /// Run full node. Only the most recent [`MINIMUM_PRUNING_DISTANCE`] block states are stored. - #[arg(long, default_value_t = false)] + #[arg(long, default_value_t = false, conflicts_with = "minimal")] pub full: bool, + /// Run minimal storage mode with maximum pruning and smaller static files. + /// + /// This mode configures the node to use minimal disk space by: + /// - Fully pruning sender recovery, transaction lookup, receipts + /// - Leaving 10,064 blocks for account, storage history and block bodies + /// - Using 10,000 blocks per static file segment + #[arg(long, default_value_t = false, conflicts_with = "full")] + pub minimal: bool, + /// Minimum pruning interval measured in blocks. #[arg(long = "prune.block-interval", alias = "block-interval", value_parser = RangedU64ValueParser::::new().range(1..))] pub block_interval: Option, @@ -140,6 +149,23 @@ impl PruningArgs { } } + // If --minimal is set, use minimal storage mode with aggressive pruning. + if self.minimal { + config = PruneConfig { + block_interval: config.block_interval, + segments: PruneModes { + sender_recovery: Some(PruneMode::Full), + transaction_lookup: Some(PruneMode::Full), + receipts: Some(PruneMode::Full), + account_history: Some(PruneMode::Distance(10064)), + storage_history: Some(PruneMode::Distance(10064)), + bodies_history: Some(PruneMode::Distance(10064)), + merkle_changesets: PruneMode::Distance(MERKLE_CHANGESETS_RETENTION_BLOCKS), + receipts_log_filter: Default::default(), + }, + } + } + // Override with any explicitly set prune.* flags. if let Some(block_interval) = self.block_interval { config.block_interval = block_interval as usize; diff --git a/crates/node/core/src/args/static_files.rs b/crates/node/core/src/args/static_files.rs index c46657a9f8..44116dd84b 100644 --- a/crates/node/core/src/args/static_files.rs +++ b/crates/node/core/src/args/static_files.rs @@ -4,6 +4,11 @@ use clap::Args; use reth_config::config::{BlocksPerFileConfig, StaticFilesConfig}; use reth_provider::StorageSettings; +/// Blocks per static file when running in `--minimal` node. +/// +/// 10000 blocks per static file allows us to prune all history every 10k blocks. +pub const MINIMAL_BLOCKS_PER_FILE: u64 = 10000; + /// Parameters for static files configuration #[derive(Debug, Args, PartialEq, Eq, Default, Clone, Copy)] #[command(next_help_heading = "Static Files")] @@ -61,14 +66,25 @@ pub struct StaticFilesArgs { impl StaticFilesArgs { /// Merges the CLI arguments with an existing [`StaticFilesConfig`], giving priority to CLI /// args. - pub fn merge_with_config(&self, config: StaticFilesConfig) -> StaticFilesConfig { + /// + /// If `minimal` is true, uses [`MINIMAL_BLOCKS_PER_FILE`] blocks per file as the default for + /// headers, transactions, and receipts segments. + pub fn merge_with_config(&self, config: StaticFilesConfig, minimal: bool) -> StaticFilesConfig { + let minimal_blocks_per_file = minimal.then_some(MINIMAL_BLOCKS_PER_FILE); StaticFilesConfig { blocks_per_file: BlocksPerFileConfig { - headers: self.blocks_per_file_headers.or(config.blocks_per_file.headers), + headers: self + .blocks_per_file_headers + .or(minimal_blocks_per_file) + .or(config.blocks_per_file.headers), transactions: self .blocks_per_file_transactions + .or(minimal_blocks_per_file) .or(config.blocks_per_file.transactions), - receipts: self.blocks_per_file_receipts.or(config.blocks_per_file.receipts), + receipts: self + .blocks_per_file_receipts + .or(minimal_blocks_per_file) + .or(config.blocks_per_file.receipts), transaction_senders: self .blocks_per_file_transaction_senders .or(config.blocks_per_file.transaction_senders), diff --git a/crates/prune/types/src/mode.rs b/crates/prune/types/src/mode.rs index 0565087673..8f5eaac0d7 100644 --- a/crates/prune/types/src/mode.rs +++ b/crates/prune/types/src/mode.rs @@ -42,15 +42,15 @@ impl PruneMode { purpose: PrunePurpose, ) -> Result, PruneSegmentError> { let result = match self { - Self::Full if segment.min_blocks(purpose) == 0 => Some((tip, *self)), + Self::Full if segment.min_blocks() == 0 => Some((tip, *self)), Self::Distance(distance) if *distance > tip => None, // Nothing to prune yet - Self::Distance(distance) if *distance >= segment.min_blocks(purpose) => { + Self::Distance(distance) if *distance >= segment.min_blocks() => { Some((tip - distance, *self)) } Self::Before(n) if *n == tip + 1 && purpose.is_static_file() => Some((tip, *self)), Self::Before(n) if *n > tip => None, // Nothing to prune yet Self::Before(n) => { - (tip - n >= segment.min_blocks(purpose)).then(|| ((*n).saturating_sub(1), *self)) + (tip - n >= segment.min_blocks()).then(|| ((*n).saturating_sub(1), *self)) } _ => return Err(PruneSegmentError::Configuration(segment)), }; @@ -93,7 +93,7 @@ mod tests { #[test] fn test_prune_target_block() { let tip = 20000; - let segment = PruneSegment::Receipts; + let segment = PruneSegment::AccountHistory; let tests = vec![ // MINIMUM_PRUNING_DISTANCE makes this impossible @@ -101,8 +101,8 @@ mod tests { // Nothing to prune (PruneMode::Distance(tip + 1), Ok(None)), ( - PruneMode::Distance(segment.min_blocks(PrunePurpose::User) + 1), - Ok(Some(tip - (segment.min_blocks(PrunePurpose::User) + 1))), + PruneMode::Distance(segment.min_blocks() + 1), + Ok(Some(tip - (segment.min_blocks() + 1))), ), // Nothing to prune (PruneMode::Before(tip + 1), Ok(None)), diff --git a/crates/prune/types/src/segment.rs b/crates/prune/types/src/segment.rs index 7922f906c2..b731a7efa9 100644 --- a/crates/prune/types/src/segment.rs +++ b/crates/prune/types/src/segment.rs @@ -61,15 +61,12 @@ impl PruneSegment { } /// Returns minimum number of blocks to keep in the database for this segment. - pub const fn min_blocks(&self, purpose: PrunePurpose) -> u64 { + pub const fn min_blocks(&self) -> u64 { match self { - Self::SenderRecovery | Self::TransactionLookup => 0, - Self::Receipts if purpose.is_static_file() => 0, - Self::ContractLogs | - Self::AccountHistory | - Self::StorageHistory | - Self::Bodies | - Self::Receipts => MINIMUM_PRUNING_DISTANCE, + Self::SenderRecovery | Self::TransactionLookup | Self::Receipts | Self::Bodies => 0, + Self::ContractLogs | Self::AccountHistory | Self::StorageHistory => { + MINIMUM_PRUNING_DISTANCE + } Self::MerkleChangeSets => MERKLE_CHANGESETS_RETENTION_BLOCKS, #[expect(deprecated)] #[expect(clippy::match_same_arms)] diff --git a/crates/prune/types/src/target.rs b/crates/prune/types/src/target.rs index 0c98a9dfd4..5eee7e5aba 100644 --- a/crates/prune/types/src/target.rs +++ b/crates/prune/types/src/target.rs @@ -58,13 +58,7 @@ pub struct PruneModes { pub transaction_lookup: Option, /// Receipts pruning configuration. This setting overrides `receipts_log_filter` /// and offers improved performance. - #[cfg_attr( - any(test, feature = "serde"), - serde( - skip_serializing_if = "Option::is_none", - deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::" - ) - )] + #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none",))] pub receipts: Option, /// Account History pruning configuration. #[cfg_attr( @@ -85,13 +79,7 @@ pub struct PruneModes { )] pub storage_history: Option, /// Bodies History pruning configuration. - #[cfg_attr( - any(test, feature = "serde"), - serde( - skip_serializing_if = "Option::is_none", - deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::" - ) - )] + #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none",))] pub bodies_history: Option, /// Merkle Changesets pruning configuration for `AccountsTrieChangeSets` and /// `StoragesTrieChangeSets`. diff --git a/docs/vocs/docs/pages/cli/op-reth/node.mdx b/docs/vocs/docs/pages/cli/op-reth/node.mdx index 102e9c02fc..0c7f40e75f 100644 --- a/docs/vocs/docs/pages/cli/op-reth/node.mdx +++ b/docs/vocs/docs/pages/cli/op-reth/node.mdx @@ -824,6 +824,11 @@ Pruning: --full Run full node. Only the most recent [`MINIMUM_PRUNING_DISTANCE`] block states are stored + --minimal + Run minimal storage mode with maximum pruning and smaller static files. + + This mode configures the node to use minimal disk space by: - Fully pruning sender recovery, transaction lookup, receipts - Leaving 10,064 blocks for account, storage history and block bodies - Using 10,000 blocks per static file segment + --prune.block-interval Minimum pruning interval measured in blocks diff --git a/docs/vocs/docs/pages/cli/reth/node.mdx b/docs/vocs/docs/pages/cli/reth/node.mdx index 384c29169d..eaac479607 100644 --- a/docs/vocs/docs/pages/cli/reth/node.mdx +++ b/docs/vocs/docs/pages/cli/reth/node.mdx @@ -824,6 +824,11 @@ Pruning: --full Run full node. Only the most recent [`MINIMUM_PRUNING_DISTANCE`] block states are stored + --minimal + Run minimal storage mode with maximum pruning and smaller static files. + + This mode configures the node to use minimal disk space by: - Fully pruning sender recovery, transaction lookup, receipts - Leaving 10,064 blocks for account, storage history and block bodies - Using 10,000 blocks per static file segment + --prune.block-interval Minimum pruning interval measured in blocks