diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index c1c5ef96075..7ea5569834c 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() } } } @@ -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; @@ -1001,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, @@ -1017,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)), @@ -1035,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/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/node/core/src/args/pruning.rs b/crates/node/core/src/args/pruning.rs index e96245350fd..846e4e6b203 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::Distance(MINIMUM_PRUNING_DISTANCE), receipts_log_filter: Default::default(), }, } diff --git a/crates/prune/prune/src/builder.rs b/crates/prune/prune/src/builder.rs index 1987c500da7..f21319bb458 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, { @@ -132,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/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..89cc4567b7d 100644 --- a/crates/prune/prune/src/segments/user/merkle_change_sets.rs +++ b/crates/prune/prune/src/segments/user/merkle_change_sets.rs @@ -3,11 +3,12 @@ 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, @@ -31,6 +32,7 @@ where + PruneCheckpointWriter + TransactionsProvider + BlockReader + + ChainStateBlockReader + NodePrimitivesProvider>, { fn segment(&self) -> PruneSegment { @@ -55,13 +57,20 @@ where let block_range_end = *block_range.end(); 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/segment.rs b/crates/prune/types/src/segment.rs index f399f3da42a..0d60d900137 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 `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))] @@ -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)] diff --git a/crates/prune/types/src/target.rs b/crates/prune/types/src/target.rs index 574a0e2e555..657cf6a37c5 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 +const fn default_merkle_changesets_mode() -> PruneMode { + PruneMode::Distance(MINIMUM_PRUNING_DISTANCE) +} + /// 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`. + #[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,12 +107,22 @@ pub struct PruneModes { pub receipts_log_filter: ReceiptsLogPruneConfig, } -impl PruneModes { - /// Sets pruning to no target. - pub fn none() -> Self { - Self::default() +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: default_merkle_changesets_mode(), + receipts_log_filter: ReceiptsLogPruneConfig::default(), + } } +} +impl PruneModes { /// Sets pruning to all targets. pub fn all() -> Self { Self { @@ -107,6 +132,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(), } } @@ -116,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. /// @@ -170,6 +191,28 @@ 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 serde::Deserialize; + let prune_mode = PruneMode::deserialize(deserializer)?; + serde_deserialize_validate::(&prune_mode)?; + 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. @@ -186,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 @@ -199,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(()), } } @@ -240,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/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/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/stages/stages/src/stages/prune.rs b/crates/stages/stages/src/stages/prune.rs index f62259dcfdd..3161d4b1412 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, >, @@ -121,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, )) } @@ -133,6 +134,7 @@ where + PruneCheckpointReader + PruneCheckpointWriter + BlockReader + + ChainStateBlockReader + StaticFileProviderFactory< Primitives: NodePrimitives, >, diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 54642a94757..df0bc33c461 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(), }) } @@ -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(); 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(), } }