feat(node): --minimal flag (#20960)

This commit is contained in:
Alexey Shekhirin
2026-01-13 12:54:26 +00:00
committed by GitHub
parent 61354e6c21
commit a5dd7d0106
10 changed files with 73 additions and 46 deletions

View File

@@ -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::<Config>(s).unwrap_err().to_string();
assert!(err.contains("invalid value: string \"full\""), "{}", err);
}
#[test]

View File

@@ -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,

View File

@@ -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;

View File

@@ -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::<u64>::new().range(1..))]
pub block_interval: Option<u64>,
@@ -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;

View File

@@ -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),

View File

@@ -42,15 +42,15 @@ impl PruneMode {
purpose: PrunePurpose,
) -> Result<Option<(BlockNumber, Self)>, 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)),

View File

@@ -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)]

View File

@@ -58,13 +58,7 @@ pub struct PruneModes {
pub transaction_lookup: Option<PruneMode>,
/// 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::<MINIMUM_PRUNING_DISTANCE, _>"
)
)]
#[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none",))]
pub receipts: Option<PruneMode>,
/// Account History pruning configuration.
#[cfg_attr(
@@ -85,13 +79,7 @@ pub struct PruneModes {
)]
pub storage_history: Option<PruneMode>,
/// 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::<MINIMUM_PRUNING_DISTANCE, _>"
)
)]
#[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none",))]
pub bodies_history: Option<PruneMode>,
/// Merkle Changesets pruning configuration for `AccountsTrieChangeSets` and
/// `StoragesTrieChangeSets`.