From 718e93c329012d4b2717b385c4e76a161a67ec39 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 13 Oct 2025 11:26:55 +0200 Subject: [PATCH 1/7] WIP --- crates/config/src/config.rs | 3 + crates/node/core/src/args/pruning.rs | 1 + crates/prune/prune/src/builder.rs | 6 +- crates/prune/prune/src/segments/set.rs | 14 ++-- .../src/segments/user/merkle_change_sets.rs | 76 ++++++++++++++++--- crates/prune/types/src/target.rs | 76 ++++++++++++++++++- crates/stages/stages/src/stages/prune.rs | 4 +- 7 files changed, 161 insertions(+), 19 deletions(-) diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index c1c5ef96075..1763295b9c8 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -464,6 +464,7 @@ impl PruneConfig { account_history, storage_history, bodies_history, + merkle_changesets, receipts_log_filter, }, } = other; @@ -480,6 +481,8 @@ impl PruneConfig { self.segments.account_history = self.segments.account_history.or(account_history); self.segments.storage_history = self.segments.storage_history.or(storage_history); self.segments.bodies_history = self.segments.bodies_history.or(bodies_history); + // Merkle changesets is not optional, so we just replace it if provided + self.segments.merkle_changesets = merkle_changesets; if self.segments.receipts_log_filter.0.is_empty() && !receipts_log_filter.0.is_empty() { self.segments.receipts_log_filter = receipts_log_filter; diff --git a/crates/node/core/src/args/pruning.rs b/crates/node/core/src/args/pruning.rs index e96245350fd..64721e14601 100644 --- a/crates/node/core/src/args/pruning.rs +++ b/crates/node/core/src/args/pruning.rs @@ -126,6 +126,7 @@ impl PruningArgs { storage_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)), // TODO: set default to pre-merge block if available bodies_history: None, + merkle_changesets: PruneMode::Full, receipts_log_filter: Default::default(), }, } diff --git a/crates/prune/prune/src/builder.rs b/crates/prune/prune/src/builder.rs index 1987c500da7..202ba5e5bb2 100644 --- a/crates/prune/prune/src/builder.rs +++ b/crates/prune/prune/src/builder.rs @@ -6,8 +6,8 @@ use reth_db_api::{table::Value, transaction::DbTxMut}; use reth_exex_types::FinishedExExHeight; use reth_primitives_traits::NodePrimitives; use reth_provider::{ - providers::StaticFileProvider, BlockReader, DBProvider, DatabaseProviderFactory, - NodePrimitivesProvider, PruneCheckpointReader, PruneCheckpointWriter, + providers::StaticFileProvider, BlockReader, ChainStateBlockReader, DBProvider, + DatabaseProviderFactory, NodePrimitivesProvider, PruneCheckpointReader, PruneCheckpointWriter, StaticFileProviderFactory, }; use reth_prune_types::PruneModes; @@ -83,6 +83,7 @@ impl PrunerBuilder { ProviderRW: PruneCheckpointWriter + PruneCheckpointReader + BlockReader + + ChainStateBlockReader + StaticFileProviderFactory< Primitives: NodePrimitives, >, @@ -113,6 +114,7 @@ impl PrunerBuilder { Primitives: NodePrimitives, > + DBProvider + BlockReader + + ChainStateBlockReader + PruneCheckpointWriter + PruneCheckpointReader, { diff --git a/crates/prune/prune/src/segments/set.rs b/crates/prune/prune/src/segments/set.rs index 08e41bcdf75..72847219b09 100644 --- a/crates/prune/prune/src/segments/set.rs +++ b/crates/prune/prune/src/segments/set.rs @@ -1,13 +1,13 @@ use crate::segments::{ - AccountHistory, ReceiptsByLogs, Segment, SenderRecovery, StorageHistory, TransactionLookup, - UserReceipts, + AccountHistory, MerkleChangeSets, ReceiptsByLogs, Segment, SenderRecovery, StorageHistory, + TransactionLookup, UserReceipts, }; use alloy_eips::eip2718::Encodable2718; use reth_db_api::{table::Value, transaction::DbTxMut}; use reth_primitives_traits::NodePrimitives; use reth_provider::{ - providers::StaticFileProvider, BlockReader, DBProvider, PruneCheckpointReader, - PruneCheckpointWriter, StaticFileProviderFactory, + providers::StaticFileProvider, BlockReader, ChainStateBlockReader, DBProvider, + PruneCheckpointReader, PruneCheckpointWriter, StaticFileProviderFactory, }; use reth_prune_types::PruneModes; @@ -52,7 +52,8 @@ where > + DBProvider + PruneCheckpointWriter + PruneCheckpointReader - + BlockReader, + + BlockReader + + ChainStateBlockReader, { /// Creates a [`SegmentSet`] from an existing components, such as [`StaticFileProvider`] and /// [`PruneModes`]. @@ -67,6 +68,7 @@ where account_history, storage_history, bodies_history: _, + merkle_changesets, receipts_log_filter, } = prune_modes; @@ -77,6 +79,8 @@ where .segment(StaticFileTransactions::new(static_file_provider.clone())) // Static file receipts .segment(StaticFileReceipts::new(static_file_provider)) + // Merkle changesets + .segment(MerkleChangeSets::new(merkle_changesets)) // Account history .segment_opt(account_history.map(AccountHistory::new)) // Storage history diff --git a/crates/prune/prune/src/segments/user/merkle_change_sets.rs b/crates/prune/prune/src/segments/user/merkle_change_sets.rs index 2349615c28a..5399a06e4ac 100644 --- a/crates/prune/prune/src/segments/user/merkle_change_sets.rs +++ b/crates/prune/prune/src/segments/user/merkle_change_sets.rs @@ -3,17 +3,26 @@ use crate::{ segments::{PruneInput, Segment}, PrunerError, }; -use reth_db_api::{models::BlockNumberAddress, table::Value, tables, transaction::DbTxMut}; +use alloy_primitives::B256; +use reth_db_api::{models::BlockNumberHashedAddress, 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, ChainStateBlockReader, DBProvider, + NodePrimitivesProvider, PruneCheckpointWriter, TransactionsProvider, }; use reth_prune_types::{ PruneCheckpoint, PruneMode, PrunePurpose, PruneSegment, SegmentOutput, SegmentOutputCheckpoint, }; use tracing::{instrument, trace}; +/// Pruning segment for Merkle trie changesets (`AccountsTrieChangeSets` and +/// `StoragesTrieChangeSets`). +/// +/// The pruning behavior depends on the configured mode: +/// - `PruneMode::Full`: Aggressively prunes all changesets up to the finalized block, keeping only +/// changesets from the finalized block onwards (for potential reorgs) +/// - `PruneMode::Distance(n)`: Keeps exactly the last `n` blocks of changesets, regardless of the +/// finalized block position #[derive(Debug)] pub struct MerkleChangeSets { mode: PruneMode, @@ -31,6 +40,7 @@ where + PruneCheckpointWriter + TransactionsProvider + BlockReader + + ChainStateBlockReader + NodePrimitivesProvider>, { fn segment(&self) -> PruneSegment { @@ -47,21 +57,69 @@ where #[instrument(level = "trace", target = "pruner", skip(self, provider), ret)] fn prune(&self, provider: &Provider, input: PruneInput) -> Result { - let Some(block_range) = input.get_next_block_range() else { - trace!(target: "pruner", "No change sets to prune"); - return Ok(SegmentOutput::done()) + // Determine the prune target based on the configured mode + let prune_to_block = match self.mode { + PruneMode::Full => { + // For Full mode, aggressively prune up to the finalized block + + // Prune everything at and before the finalized block + match provider.last_finalized_block_number()? { + Some(num) => num, + None => { + trace!(target: "pruner", "No finalized block found, skipping merkle changesets pruning"); + return Ok(SegmentOutput::done()) + } + } + } + PruneMode::Distance(distance) => { + // For Distance mode, keep exactly the specified distance of blocks + // This respects the configured distance regardless of finalized block + input.to_block.saturating_sub(distance) + } + // For Before mode we prune up to, but not including, the specified block + PruneMode::Before(block_number) => block_number.saturating_sub(1), }; + // If there's nothing to prune (e.g., we're at genesis), return early + if prune_to_block == 0 { + trace!(target: "pruner", "Target block is at or near genesis, nothing to prune"); + return Ok(SegmentOutput::done()) + } + + // Get the range to prune based on checkpoint and our calculated prune target + let from_block = input.get_start_next_block_range(); + if from_block > prune_to_block { + trace!(target: "pruner", + from_block, + prune_to_block, + ?self.mode, + "Already pruned to target"); + return Ok(SegmentOutput::done()) + } + + let block_range = from_block..=prune_to_block; let block_range_end = *block_range.end(); + + trace!(target: "pruner", + ?block_range, + ?self.mode, + "Pruning merkle changesets based on configured mode"); let mut limiter = input.limiter; + // Create range for StoragesTrieChangeSets which uses BlockNumberHashedAddress as key + let storage_range_start: BlockNumberHashedAddress = + (*block_range.start(), B256::ZERO).into(); + let storage_range_end: BlockNumberHashedAddress = + (*block_range.end() + 1, B256::ZERO).into(); + let storage_range = storage_range_start..storage_range_end; + let mut last_storages_pruned_block = None; let (storages_pruned, done) = - provider.tx_ref().prune_table_with_range::( - BlockNumberAddress::range(block_range.clone()), + provider.tx_ref().prune_table_with_range::( + storage_range, &mut limiter, |_| false, - |(BlockNumberAddress((block_number, ..)), ..)| { + |(BlockNumberHashedAddress((block_number, _)), _)| { last_storages_pruned_block = Some(block_number); }, )?; diff --git a/crates/prune/types/src/target.rs b/crates/prune/types/src/target.rs index 574a0e2e555..755d56e377e 100644 --- a/crates/prune/types/src/target.rs +++ b/crates/prune/types/src/target.rs @@ -36,8 +36,13 @@ pub enum HistoryType { StorageHistory, } +/// Default pruning mode for merkle changesets - aggressively prune to finalized block +const fn default_merkle_changesets_mode() -> PruneMode { + PruneMode::Full +} + /// Pruning configuration for every segment of the data that can be pruned. -#[derive(Debug, Clone, Default, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(any(test, feature = "serde"), serde(default))] pub struct PruneModes { @@ -84,6 +89,16 @@ pub struct PruneModes { ) )] pub bodies_history: Option, + /// Merkle Changesets pruning configuration for `AccountsTrieChangeSets` and + /// `StoragesTrieChangeSets`. Defaults to `PruneMode::Full` to prune up to the finalized block. + #[cfg_attr( + any(test, feature = "serde"), + serde( + default = "default_merkle_changesets_mode", + deserialize_with = "deserialize_prune_mode_with_min_blocks::" + ) + )] + pub merkle_changesets: PruneMode, /// Receipts pruning configuration by retaining only those receipts that contain logs emitted /// by the specified addresses, discarding others. This setting is overridden by `receipts`. /// @@ -92,8 +107,23 @@ pub struct PruneModes { pub receipts_log_filter: ReceiptsLogPruneConfig, } +impl Default for PruneModes { + fn default() -> Self { + Self { + sender_recovery: None, + transaction_lookup: None, + receipts: None, + account_history: None, + storage_history: None, + bodies_history: None, + merkle_changesets: PruneMode::Full, + receipts_log_filter: ReceiptsLogPruneConfig::default(), + } + } +} + impl PruneModes { - /// Sets pruning to no target. + /// Sets pruning to no target except for merkle changesets which defaults to Full. pub fn none() -> Self { Self::default() } @@ -107,6 +137,7 @@ impl PruneModes { account_history: Some(PruneMode::Full), storage_history: Some(PruneMode::Full), bodies_history: Some(PruneMode::Full), + merkle_changesets: PruneMode::Full, receipts_log_filter: Default::default(), } } @@ -170,6 +201,47 @@ impl PruneModes { } } +/// Deserializes [`PruneMode`] and validates that the value is not less than the const +/// generic parameter `MIN_BLOCKS`. This parameter represents the number of blocks that needs to be +/// left in database after the pruning. +/// +/// 1. For [`PruneMode::Full`], it fails if `MIN_BLOCKS > 0`. +/// 2. For [`PruneMode::Distance`], it fails if `distance < MIN_BLOCKS + 1`. `+ 1` is needed because +/// `PruneMode::Distance(0)` means that we leave zero blocks from the latest, meaning we have one +/// block in the database. +#[cfg(any(test, feature = "serde"))] +fn deserialize_prune_mode_with_min_blocks< + 'de, + const MIN_BLOCKS: u64, + D: serde::Deserializer<'de>, +>( + deserializer: D, +) -> Result { + use alloc::format; + use serde::Deserialize; + let prune_mode = PruneMode::deserialize(deserializer)?; + + match prune_mode { + PruneMode::Full if MIN_BLOCKS > 0 => { + Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Str("full"), + // This message should have "expected" wording + &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database") + .as_str(), + )) + } + PruneMode::Distance(distance) if distance < MIN_BLOCKS + 1 => { + Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Unsigned(distance), + // This message should have "expected" wording + &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database") + .as_str(), + )) + } + _ => Ok(prune_mode), + } +} + /// Deserializes [`Option`] and validates that the value is not less than the const /// generic parameter `MIN_BLOCKS`. This parameter represents the number of blocks that needs to be /// left in database after the pruning. diff --git a/crates/stages/stages/src/stages/prune.rs b/crates/stages/stages/src/stages/prune.rs index f62259dcfdd..8158404f1c3 100644 --- a/crates/stages/stages/src/stages/prune.rs +++ b/crates/stages/stages/src/stages/prune.rs @@ -1,7 +1,7 @@ use reth_db_api::{table::Value, transaction::DbTxMut}; use reth_primitives_traits::NodePrimitives; use reth_provider::{ - BlockReader, DBProvider, PruneCheckpointReader, PruneCheckpointWriter, + BlockReader, ChainStateBlockReader, DBProvider, PruneCheckpointReader, PruneCheckpointWriter, StaticFileProviderFactory, }; use reth_prune::{ @@ -42,6 +42,7 @@ where + PruneCheckpointReader + PruneCheckpointWriter + BlockReader + + ChainStateBlockReader + StaticFileProviderFactory< Primitives: NodePrimitives, >, @@ -133,6 +134,7 @@ where + PruneCheckpointReader + PruneCheckpointWriter + BlockReader + + ChainStateBlockReader + StaticFileProviderFactory< Primitives: NodePrimitives, >, From a8b0a0bce94485f974ee9c7051f1fbf9762e74f3 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 13 Oct 2025 18:14:25 +0200 Subject: [PATCH 2/7] WIP: distance default --- crates/config/src/config.rs | 2 +- crates/exex/exex/src/backfill/factory.rs | 2 +- crates/prune/prune/src/builder.rs | 2 +- .../src/segments/user/merkle_change_sets.rs | 42 +------------- crates/prune/types/src/target.rs | 58 ++++++------------- crates/stages/stages/src/sets.rs | 14 ++--- crates/stages/stages/src/stages/prune.rs | 2 +- .../provider/src/providers/database/mod.rs | 4 +- crates/storage/storage-api/src/noop.rs | 4 +- 9 files changed, 37 insertions(+), 93 deletions(-) diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index 1763295b9c8..7aaf80618a0 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -440,7 +440,7 @@ pub struct PruneConfig { impl Default for PruneConfig { fn default() -> Self { - Self { block_interval: DEFAULT_BLOCK_INTERVAL, segments: PruneModes::none() } + Self { block_interval: DEFAULT_BLOCK_INTERVAL, segments: PruneModes::default() } } } diff --git a/crates/exex/exex/src/backfill/factory.rs b/crates/exex/exex/src/backfill/factory.rs index 789d63f84e2..d9a51bc47a7 100644 --- a/crates/exex/exex/src/backfill/factory.rs +++ b/crates/exex/exex/src/backfill/factory.rs @@ -24,7 +24,7 @@ impl BackfillJobFactory { Self { evm_config, provider, - prune_modes: PruneModes::none(), + prune_modes: PruneModes::default(), thresholds: ExecutionStageThresholds { // Default duration for a database transaction to be considered long-lived is // 60 seconds, so we limit the backfill job to the half of it to be sure we finish diff --git a/crates/prune/prune/src/builder.rs b/crates/prune/prune/src/builder.rs index 202ba5e5bb2..f21319bb458 100644 --- a/crates/prune/prune/src/builder.rs +++ b/crates/prune/prune/src/builder.rs @@ -134,7 +134,7 @@ impl Default for PrunerBuilder { fn default() -> Self { Self { block_interval: 5, - segments: PruneModes::none(), + segments: PruneModes::default(), delete_limit: MAINNET_PRUNE_DELETE_LIMIT, timeout: None, finished_exex_height: watch::channel(FinishedExExHeight::NoExExs).1, diff --git a/crates/prune/prune/src/segments/user/merkle_change_sets.rs b/crates/prune/prune/src/segments/user/merkle_change_sets.rs index 5399a06e4ac..af9bfe5dccd 100644 --- a/crates/prune/prune/src/segments/user/merkle_change_sets.rs +++ b/crates/prune/prune/src/segments/user/merkle_change_sets.rs @@ -57,47 +57,11 @@ where #[instrument(level = "trace", target = "pruner", skip(self, provider), ret)] fn prune(&self, provider: &Provider, input: PruneInput) -> Result { - // Determine the prune target based on the configured mode - let prune_to_block = match self.mode { - PruneMode::Full => { - // For Full mode, aggressively prune up to the finalized block - - // Prune everything at and before the finalized block - match provider.last_finalized_block_number()? { - Some(num) => num, - None => { - trace!(target: "pruner", "No finalized block found, skipping merkle changesets pruning"); - return Ok(SegmentOutput::done()) - } - } - } - PruneMode::Distance(distance) => { - // For Distance mode, keep exactly the specified distance of blocks - // This respects the configured distance regardless of finalized block - input.to_block.saturating_sub(distance) - } - // For Before mode we prune up to, but not including, the specified block - PruneMode::Before(block_number) => block_number.saturating_sub(1), - }; - - // If there's nothing to prune (e.g., we're at genesis), return early - if prune_to_block == 0 { - trace!(target: "pruner", "Target block is at or near genesis, nothing to prune"); + let Some(block_range) = input.get_next_block_range() else { + trace!(target: "pruner", "No change sets to prune"); return Ok(SegmentOutput::done()) - } - - // Get the range to prune based on checkpoint and our calculated prune target - let from_block = input.get_start_next_block_range(); - if from_block > prune_to_block { - trace!(target: "pruner", - from_block, - prune_to_block, - ?self.mode, - "Already pruned to target"); - return Ok(SegmentOutput::done()) - } + }; - let block_range = from_block..=prune_to_block; let block_range_end = *block_range.end(); trace!(target: "pruner", diff --git a/crates/prune/types/src/target.rs b/crates/prune/types/src/target.rs index 755d56e377e..4be5fa7fe55 100644 --- a/crates/prune/types/src/target.rs +++ b/crates/prune/types/src/target.rs @@ -38,7 +38,7 @@ pub enum HistoryType { /// Default pruning mode for merkle changesets - aggressively prune to finalized block const fn default_merkle_changesets_mode() -> PruneMode { - PruneMode::Full + PruneMode::Distance(MINIMUM_PRUNING_DISTANCE) } /// Pruning configuration for every segment of the data that can be pruned. @@ -90,7 +90,7 @@ pub struct PruneModes { )] pub bodies_history: Option, /// Merkle Changesets pruning configuration for `AccountsTrieChangeSets` and - /// `StoragesTrieChangeSets`. Defaults to `PruneMode::Full` to prune up to the finalized block. + /// `StoragesTrieChangeSets`. #[cfg_attr( any(test, feature = "serde"), serde( @@ -116,18 +116,13 @@ impl Default for PruneModes { account_history: None, storage_history: None, bodies_history: None, - merkle_changesets: PruneMode::Full, + merkle_changesets: default_merkle_changesets_mode(), receipts_log_filter: ReceiptsLogPruneConfig::default(), } } } impl PruneModes { - /// Sets pruning to no target except for merkle changesets which defaults to Full. - pub fn none() -> Self { - Self::default() - } - /// Sets pruning to all targets. pub fn all() -> Self { Self { @@ -147,11 +142,6 @@ impl PruneModes { self.receipts.is_some() || !self.receipts_log_filter.is_empty() } - /// Returns true if all prune modes are set to [`None`]. - pub fn is_empty(&self) -> bool { - self == &Self::none() - } - /// Returns an error if we can't unwind to the targeted block because the target block is /// outside the range. /// @@ -217,29 +207,10 @@ fn deserialize_prune_mode_with_min_blocks< >( deserializer: D, ) -> Result { - use alloc::format; use serde::Deserialize; let prune_mode = PruneMode::deserialize(deserializer)?; - - match prune_mode { - PruneMode::Full if MIN_BLOCKS > 0 => { - Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Str("full"), - // This message should have "expected" wording - &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database") - .as_str(), - )) - } - PruneMode::Distance(distance) if distance < MIN_BLOCKS + 1 => { - Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Unsigned(distance), - // This message should have "expected" wording - &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database") - .as_str(), - )) - } - _ => Ok(prune_mode), - } + serde_deserialize_validate::(&prune_mode)?; + Ok(prune_mode) } /// Deserializes [`Option`] and validates that the value is not less than the const @@ -258,12 +229,21 @@ fn deserialize_opt_prune_mode_with_min_blocks< >( deserializer: D, ) -> Result, D::Error> { - use alloc::format; use serde::Deserialize; let prune_mode = Option::::deserialize(deserializer)?; + if let Some(prune_mode) = prune_mode.as_ref() { + serde_deserialize_validate::(prune_mode)?; + } + Ok(prune_mode) +} +#[cfg(any(test, feature = "serde"))] +fn serde_deserialize_validate<'a, 'de, const MIN_BLOCKS: u64, D: serde::Deserializer<'de>>( + prune_mode: &'a PruneMode, +) -> Result<(), D::Error> { + use alloc::format; match prune_mode { - Some(PruneMode::Full) if MIN_BLOCKS > 0 => { + PruneMode::Full if MIN_BLOCKS > 0 => { Err(serde::de::Error::invalid_value( serde::de::Unexpected::Str("full"), // This message should have "expected" wording @@ -271,15 +251,15 @@ fn deserialize_opt_prune_mode_with_min_blocks< .as_str(), )) } - Some(PruneMode::Distance(distance)) if distance < MIN_BLOCKS => { + PruneMode::Distance(distance) if *distance < MIN_BLOCKS => { Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Unsigned(distance), + serde::de::Unexpected::Unsigned(*distance), // This message should have "expected" wording &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database") .as_str(), )) } - _ => Ok(prune_mode), + _ => Ok(()), } } diff --git a/crates/stages/stages/src/sets.rs b/crates/stages/stages/src/sets.rs index 6c4d5d55f58..015be507336 100644 --- a/crates/stages/stages/src/sets.rs +++ b/crates/stages/stages/src/sets.rs @@ -54,7 +54,7 @@ use reth_primitives_traits::{Block, NodePrimitives}; use reth_provider::HeaderSyncGapProvider; use reth_prune_types::PruneModes; use reth_stages_api::Stage; -use std::{ops::Not, sync::Arc}; +use std::sync::Arc; use tokio::sync::watch; /// A set containing all stages to run a fully syncing instance of reth. @@ -337,12 +337,12 @@ where stages_config: self.stages_config.clone(), prune_modes: self.prune_modes.clone(), }) - // If any prune modes are set, add the prune stage. - .add_stage_opt(self.prune_modes.is_empty().not().then(|| { - // Prune stage should be added after all hashing stages, because otherwise it will - // delete - PruneStage::new(self.prune_modes.clone(), self.stages_config.prune.commit_threshold) - })) + // Prune stage should be added after all hashing stages, because otherwise it will + // delete + .add_stage(PruneStage::new( + self.prune_modes.clone(), + self.stages_config.prune.commit_threshold, + )) } } diff --git a/crates/stages/stages/src/stages/prune.rs b/crates/stages/stages/src/stages/prune.rs index 8158404f1c3..3161d4b1412 100644 --- a/crates/stages/stages/src/stages/prune.rs +++ b/crates/stages/stages/src/stages/prune.rs @@ -122,7 +122,7 @@ impl PruneSenderRecoveryStage { /// Create new prune sender recovery stage with the given prune mode and commit threshold. pub fn new(prune_mode: PruneMode, commit_threshold: usize) -> Self { Self(PruneStage::new( - PruneModes { sender_recovery: Some(prune_mode), ..PruneModes::none() }, + PruneModes { sender_recovery: Some(prune_mode), ..PruneModes::default() }, commit_threshold, )) } diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 54642a94757..c9f695a6ded 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -84,7 +84,7 @@ impl ProviderFactory { db, chain_spec, static_file_provider, - prune_modes: PruneModes::none(), + prune_modes: PruneModes::default(), storage: Default::default(), } } @@ -126,7 +126,7 @@ impl>> ProviderFactory { db: Arc::new(init_db(path, args).map_err(RethError::msg)?), chain_spec, static_file_provider, - prune_modes: PruneModes::none(), + prune_modes: PruneModes::default(), storage: Default::default(), }) } diff --git a/crates/storage/storage-api/src/noop.rs b/crates/storage/storage-api/src/noop.rs index 96e26c9f596..6b70a5260a6 100644 --- a/crates/storage/storage-api/src/noop.rs +++ b/crates/storage/storage-api/src/noop.rs @@ -60,7 +60,7 @@ impl NoopProvider { #[cfg(feature = "db-api")] tx: TxMock::default(), #[cfg(feature = "db-api")] - prune_modes: PruneModes::none(), + prune_modes: PruneModes::default(), _phantom: Default::default(), } } @@ -74,7 +74,7 @@ impl NoopProvider { #[cfg(feature = "db-api")] tx: TxMock::default(), #[cfg(feature = "db-api")] - prune_modes: PruneModes::none(), + prune_modes: PruneModes::default(), _phantom: Default::default(), } } From 06ba22cd9bd5066e9589f34a065981c89cc83b30 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 13 Oct 2025 19:21:55 +0200 Subject: [PATCH 3/7] fix: Fixes to pruning of MerkleChangeSets --- crates/config/src/config.rs | 3 +++ crates/prune/types/src/target.rs | 2 +- crates/stages/stages/src/stages/execution.rs | 4 ++-- crates/stages/stages/src/stages/mod.rs | 2 +- crates/storage/provider/src/providers/database/mod.rs | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index 7aaf80618a0..7ea5569834c 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -1004,6 +1004,7 @@ receipts = 'full' account_history: None, storage_history: Some(PruneMode::Before(5000)), bodies_history: None, + merkle_changesets: PruneMode::Before(0), receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([( Address::random(), PruneMode::Full, @@ -1020,6 +1021,7 @@ receipts = 'full' account_history: Some(PruneMode::Distance(2000)), storage_history: Some(PruneMode::Distance(3000)), bodies_history: None, + merkle_changesets: PruneMode::Distance(10000), receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([ (Address::random(), PruneMode::Distance(1000)), (Address::random(), PruneMode::Before(2000)), @@ -1038,6 +1040,7 @@ receipts = 'full' assert_eq!(config1.segments.receipts, Some(PruneMode::Distance(1000))); assert_eq!(config1.segments.account_history, Some(PruneMode::Distance(2000))); assert_eq!(config1.segments.storage_history, Some(PruneMode::Before(5000))); + assert_eq!(config1.segments.merkle_changesets, PruneMode::Distance(10000)); assert_eq!(config1.segments.receipts_log_filter, original_filter); } diff --git a/crates/prune/types/src/target.rs b/crates/prune/types/src/target.rs index 4be5fa7fe55..b7ba165fd68 100644 --- a/crates/prune/types/src/target.rs +++ b/crates/prune/types/src/target.rs @@ -292,7 +292,7 @@ mod tests { #[test] fn test_unwind_target_unpruned() { // Test case 1: No pruning configured - should always succeed - let prune_modes = PruneModes::none(); + let prune_modes = PruneModes::default(); assert!(prune_modes.ensure_unwind_target_unpruned(1000, 500, &[]).is_ok()); assert!(prune_modes.ensure_unwind_target_unpruned(1000, 0, &[]).is_ok()); diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 3736fa523cb..ed50572d58b 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -896,7 +896,7 @@ mod tests { // If there is a pruning configuration, then it's forced to use the database. // This way we test both cases. - let modes = [None, Some(PruneModes::none())]; + let modes = [None, Some(PruneModes::default())]; let random_filter = ReceiptsLogPruneConfig(BTreeMap::from([( Address::random(), PruneMode::Distance(100000), @@ -1033,7 +1033,7 @@ mod tests { // If there is a pruning configuration, then it's forced to use the database. // This way we test both cases. - let modes = [None, Some(PruneModes::none())]; + let modes = [None, Some(PruneModes::default())]; let random_filter = ReceiptsLogPruneConfig(BTreeMap::from([( Address::random(), PruneMode::Before(100000), diff --git a/crates/stages/stages/src/stages/mod.rs b/crates/stages/stages/src/stages/mod.rs index 8f37e24d365..58fa7cfb324 100644 --- a/crates/stages/stages/src/stages/mod.rs +++ b/crates/stages/stages/src/stages/mod.rs @@ -228,7 +228,7 @@ mod tests { // In an unpruned configuration there is 1 receipt, 3 changed accounts and 1 changed // storage. - let mut prune = PruneModes::none(); + let mut prune = PruneModes::default(); check_pruning(test_db.factory.clone(), prune.clone(), 1, 3, 1).await; prune.receipts = Some(PruneMode::Full); diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index c9f695a6ded..df0bc33c461 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -665,7 +665,7 @@ mod tests { let prune_modes = PruneModes { sender_recovery: Some(PruneMode::Full), transaction_lookup: Some(PruneMode::Full), - ..PruneModes::none() + ..PruneModes::default() }; let factory = create_test_provider_factory(); let provider = factory.with_prune_modes(prune_modes).provider_rw().unwrap(); From cff27101cef45736c760533bdd2547de0d838993 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 13 Oct 2025 19:34:08 +0200 Subject: [PATCH 4/7] Small fixes --- crates/node/core/src/args/pruning.rs | 2 +- .../prune/src/segments/user/merkle_change_sets.rs | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/crates/node/core/src/args/pruning.rs b/crates/node/core/src/args/pruning.rs index 64721e14601..846e4e6b203 100644 --- a/crates/node/core/src/args/pruning.rs +++ b/crates/node/core/src/args/pruning.rs @@ -126,7 +126,7 @@ impl PruningArgs { storage_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)), // TODO: set default to pre-merge block if available bodies_history: None, - merkle_changesets: PruneMode::Full, + merkle_changesets: PruneMode::Distance(MINIMUM_PRUNING_DISTANCE), receipts_log_filter: Default::default(), }, } diff --git a/crates/prune/prune/src/segments/user/merkle_change_sets.rs b/crates/prune/prune/src/segments/user/merkle_change_sets.rs index af9bfe5dccd..89cc4567b7d 100644 --- a/crates/prune/prune/src/segments/user/merkle_change_sets.rs +++ b/crates/prune/prune/src/segments/user/merkle_change_sets.rs @@ -15,14 +15,6 @@ use reth_prune_types::{ }; use tracing::{instrument, trace}; -/// Pruning segment for Merkle trie changesets (`AccountsTrieChangeSets` and -/// `StoragesTrieChangeSets`). -/// -/// The pruning behavior depends on the configured mode: -/// - `PruneMode::Full`: Aggressively prunes all changesets up to the finalized block, keeping only -/// changesets from the finalized block onwards (for potential reorgs) -/// - `PruneMode::Distance(n)`: Keeps exactly the last `n` blocks of changesets, regardless of the -/// finalized block position #[derive(Debug)] pub struct MerkleChangeSets { mode: PruneMode, @@ -63,11 +55,6 @@ where }; let block_range_end = *block_range.end(); - - trace!(target: "pruner", - ?block_range, - ?self.mode, - "Pruning merkle changesets based on configured mode"); let mut limiter = input.limiter; // Create range for StoragesTrieChangeSets which uses BlockNumberHashedAddress as key From abc8fd50856f0e4bf6666e2f6c640746787b3f60 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 13 Oct 2025 21:24:50 +0200 Subject: [PATCH 5/7] Update crates/prune/types/src/target.rs --- crates/prune/types/src/target.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/prune/types/src/target.rs b/crates/prune/types/src/target.rs index b7ba165fd68..657cf6a37c5 100644 --- a/crates/prune/types/src/target.rs +++ b/crates/prune/types/src/target.rs @@ -36,7 +36,7 @@ pub enum HistoryType { StorageHistory, } -/// Default pruning mode for merkle changesets - aggressively prune to finalized block +/// Default pruning mode for merkle changesets const fn default_merkle_changesets_mode() -> PruneMode { PruneMode::Distance(MINIMUM_PRUNING_DISTANCE) } From 145f0d2b29cd8e575e4176771a307599d9d02ac9 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 13 Oct 2025 22:17:55 +0200 Subject: [PATCH 6/7] MerkleChangeSets variant order --- crates/prune/types/src/segment.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/prune/types/src/segment.rs b/crates/prune/types/src/segment.rs index f399f3da42a..7f2db62f10a 100644 --- a/crates/prune/types/src/segment.rs +++ b/crates/prune/types/src/segment.rs @@ -3,6 +3,9 @@ use derive_more::Display; use thiserror::Error; /// Segment of the data that can be pruned. +/// +/// NOTE new variants must be added to the end of this enum. The variant index is encoded directly +/// when writing to the P`runeCheckpoint` table, so changing the order here will corrupt the table. #[derive(Debug, Display, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr(test, derive(arbitrary::Arbitrary))] #[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))] @@ -15,9 +18,6 @@ pub enum PruneSegment { TransactionLookup, /// Prune segment responsible for all rows in `Receipts` table. Receipts, - /// Prune segment responsible for all rows in `AccountsTrieChangeSets` and - /// `StoragesTrieChangeSets` table. - MerkleChangeSets, /// Prune segment responsible for some rows in `Receipts` table filtered by logs. ContractLogs, /// Prune segment responsible for the `AccountChangeSets` and `AccountsHistory` tables. @@ -29,6 +29,9 @@ pub enum PruneSegment { Headers, /// Prune segment responsible for the `Transactions` table. Transactions, + /// Prune segment responsible for all rows in `AccountsTrieChangeSets` and + /// `StoragesTrieChangeSets` table. + MerkleChangeSets, } #[cfg(test)] From ee2e06d7c50f83af5559781bbad47501ba4d414d Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 14 Oct 2025 10:08:05 +0200 Subject: [PATCH 7/7] Update crates/prune/types/src/segment.rs Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> --- crates/prune/types/src/segment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/prune/types/src/segment.rs b/crates/prune/types/src/segment.rs index 7f2db62f10a..0d60d900137 100644 --- a/crates/prune/types/src/segment.rs +++ b/crates/prune/types/src/segment.rs @@ -5,7 +5,7 @@ use thiserror::Error; /// Segment of the data that can be pruned. /// /// NOTE new variants must be added to the end of this enum. The variant index is encoded directly -/// when writing to the P`runeCheckpoint` table, so changing the order here will corrupt the table. +/// when writing to the `PruneCheckpoint` table, so changing the order here will corrupt the table. #[derive(Debug, Display, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr(test, derive(arbitrary::Arbitrary))] #[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::Compact))]