diff --git a/crates/node/core/src/args/pruning.rs b/crates/node/core/src/args/pruning.rs index 24575a8ff7..9eff92a2ab 100644 --- a/crates/node/core/src/args/pruning.rs +++ b/crates/node/core/src/args/pruning.rs @@ -5,7 +5,9 @@ use alloy_primitives::{Address, BlockNumber}; use clap::{builder::RangedU64ValueParser, Args}; use reth_chainspec::EthereumHardforks; use reth_config::config::PruneConfig; -use reth_prune_types::{PruneMode, PruneModes, ReceiptsLogPruneConfig, MINIMUM_PRUNING_DISTANCE}; +use reth_prune_types::{ + PruneMode, PruneModes, ReceiptsLogPruneConfig, MINIMUM_UNWIND_SAFE_DISTANCE, +}; use std::{collections::BTreeMap, ops::Not, sync::OnceLock}; /// Global static pruning defaults @@ -68,9 +70,9 @@ impl Default for DefaultPruningValues { full_prune_modes: PruneModes { sender_recovery: Some(PruneMode::Full), transaction_lookup: None, - receipts: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)), - account_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)), - storage_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)), + receipts: Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)), + account_history: Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)), + storage_history: Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)), // This field is ignored when full_bodies_history_use_pre_merge is true bodies_history: None, receipts_log_filter: Default::default(), @@ -80,9 +82,9 @@ impl Default for DefaultPruningValues { sender_recovery: Some(PruneMode::Full), transaction_lookup: Some(PruneMode::Full), receipts: Some(PruneMode::Full), - account_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)), - storage_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)), - bodies_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)), + account_history: Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)), + storage_history: Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)), + bodies_history: Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)), receipts_log_filter: Default::default(), }, } @@ -93,7 +95,8 @@ impl Default for DefaultPruningValues { #[derive(Debug, Clone, Args, PartialEq, Eq, Default)] #[command(next_help_heading = "Pruning")] pub struct PruningArgs { - /// Run full node. Only the most recent [`MINIMUM_PRUNING_DISTANCE`] block states are stored. + /// Run full node. Only the most recent [`MINIMUM_UNWIND_SAFE_DISTANCE`] block states are + /// stored. #[arg(long, default_value_t = false, conflicts_with = "minimal")] pub full: bool, diff --git a/crates/prune/prune/src/segments/user/receipts_by_logs.rs b/crates/prune/prune/src/segments/user/receipts_by_logs.rs index 9e57bd2411..591e77997e 100644 --- a/crates/prune/prune/src/segments/user/receipts_by_logs.rs +++ b/crates/prune/prune/src/segments/user/receipts_by_logs.rs @@ -11,7 +11,7 @@ use reth_provider::{ }; use reth_prune_types::{ PruneCheckpoint, PruneMode, PrunePurpose, PruneSegment, ReceiptsLogPruneConfig, SegmentOutput, - MINIMUM_PRUNING_DISTANCE, + MINIMUM_UNWIND_SAFE_DISTANCE, }; use tracing::{instrument, trace}; #[derive(Debug)] @@ -49,8 +49,8 @@ where fn prune(&self, provider: &Provider, input: PruneInput) -> Result { // Contract log filtering removes every receipt possible except the ones in the list. So, // for the other receipts it's as if they had a `PruneMode::Distance()` of - // `MINIMUM_PRUNING_DISTANCE`. - let to_block = PruneMode::Distance(MINIMUM_PRUNING_DISTANCE) + // `MINIMUM_UNWIND_SAFE_DISTANCE`. + let to_block = PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE) .prune_target_block(input.to_block, PruneSegment::ContractLogs, PrunePurpose::User)? .map(|(bn, _)| bn) .unwrap_or_default(); diff --git a/crates/prune/types/src/lib.rs b/crates/prune/types/src/lib.rs index 315063278b..88d2d6490e 100644 --- a/crates/prune/types/src/lib.rs +++ b/crates/prune/types/src/lib.rs @@ -30,7 +30,9 @@ pub use pruner::{ SegmentOutputCheckpoint, }; pub use segment::{PrunePurpose, PruneSegment, PruneSegmentError}; -pub use target::{PruneModes, UnwindTargetPrunedError, MINIMUM_PRUNING_DISTANCE}; +pub use target::{ + PruneModes, UnwindTargetPrunedError, MINIMUM_DISTANCE, MINIMUM_UNWIND_SAFE_DISTANCE, +}; /// Configuration for pruning receipts not associated with logs emitted by the specified contracts. #[derive(Debug, Clone, PartialEq, Eq, Default)] diff --git a/crates/prune/types/src/mode.rs b/crates/prune/types/src/mode.rs index 8f5eaac0d7..3706094b5f 100644 --- a/crates/prune/types/src/mode.rs +++ b/crates/prune/types/src/mode.rs @@ -41,8 +41,12 @@ impl PruneMode { segment: PruneSegment, purpose: PrunePurpose, ) -> Result, PruneSegmentError> { + let min_blocks = segment.min_blocks(); let result = match self { - Self::Full if segment.min_blocks() == 0 => Some((tip, *self)), + Self::Full if min_blocks == 0 => Some((tip, *self)), + // For segments with min_blocks > 0, Full mode behaves like Distance(min_blocks) + Self::Full if min_blocks <= tip => Some((tip - min_blocks, *self)), + Self::Full => None, // Nothing to prune yet Self::Distance(distance) if *distance > tip => None, // Nothing to prune yet Self::Distance(distance) if *distance >= segment.min_blocks() => { Some((tip - distance, *self)) @@ -84,9 +88,7 @@ impl PruneMode { #[cfg(test)] mod tests { - use crate::{ - PruneMode, PrunePurpose, PruneSegment, PruneSegmentError, MINIMUM_PRUNING_DISTANCE, - }; + use crate::{PruneMode, PrunePurpose, PruneSegment, MINIMUM_UNWIND_SAFE_DISTANCE}; use assert_matches::assert_matches; use serde::Deserialize; @@ -96,8 +98,8 @@ mod tests { let segment = PruneSegment::AccountHistory; let tests = vec![ - // MINIMUM_PRUNING_DISTANCE makes this impossible - (PruneMode::Full, Err(PruneSegmentError::Configuration(segment))), + // Full mode with min_blocks > 0 behaves like Distance(min_blocks) + (PruneMode::Full, Ok(Some(tip - segment.min_blocks()))), // Nothing to prune (PruneMode::Distance(tip + 1), Ok(None)), ( @@ -107,12 +109,12 @@ mod tests { // Nothing to prune (PruneMode::Before(tip + 1), Ok(None)), ( - PruneMode::Before(tip - MINIMUM_PRUNING_DISTANCE), - Ok(Some(tip - MINIMUM_PRUNING_DISTANCE - 1)), + PruneMode::Before(tip - MINIMUM_UNWIND_SAFE_DISTANCE), + Ok(Some(tip - MINIMUM_UNWIND_SAFE_DISTANCE - 1)), ), ( - PruneMode::Before(tip - MINIMUM_PRUNING_DISTANCE - 1), - Ok(Some(tip - MINIMUM_PRUNING_DISTANCE - 2)), + PruneMode::Before(tip - MINIMUM_UNWIND_SAFE_DISTANCE - 1), + Ok(Some(tip - MINIMUM_UNWIND_SAFE_DISTANCE - 2)), ), // Nothing to prune (PruneMode::Before(tip - 1), Ok(None)), @@ -146,13 +148,13 @@ mod tests { let tests = vec![ (PruneMode::Distance(tip + 1), 1, !should_prune), ( - PruneMode::Distance(MINIMUM_PRUNING_DISTANCE + 1), - tip - MINIMUM_PRUNING_DISTANCE - 1, + PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE + 1), + tip - MINIMUM_UNWIND_SAFE_DISTANCE - 1, !should_prune, ), ( - PruneMode::Distance(MINIMUM_PRUNING_DISTANCE + 1), - tip - MINIMUM_PRUNING_DISTANCE - 2, + PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE + 1), + tip - MINIMUM_UNWIND_SAFE_DISTANCE - 2, should_prune, ), (PruneMode::Before(tip + 1), 1, should_prune), diff --git a/crates/prune/types/src/segment.rs b/crates/prune/types/src/segment.rs index 0e3f4e1edc..5bc055f829 100644 --- a/crates/prune/types/src/segment.rs +++ b/crates/prune/types/src/segment.rs @@ -1,6 +1,6 @@ #![allow(deprecated)] // necessary to all defining deprecated `PruneSegment` variants -use crate::MINIMUM_PRUNING_DISTANCE; +use crate::{MINIMUM_DISTANCE, MINIMUM_UNWIND_SAFE_DISTANCE}; use derive_more::Display; use strum::{EnumIter, IntoEnumIterator}; use thiserror::Error; @@ -65,9 +65,10 @@ impl PruneSegment { /// Returns minimum number of blocks to keep in the database for this segment. pub const fn min_blocks(&self) -> u64 { match self { - Self::SenderRecovery | Self::TransactionLookup | Self::Receipts | Self::Bodies => 0, + Self::SenderRecovery | Self::TransactionLookup => 0, + Self::Receipts | Self::Bodies => MINIMUM_DISTANCE, Self::ContractLogs | Self::AccountHistory | Self::StorageHistory => { - MINIMUM_PRUNING_DISTANCE + MINIMUM_UNWIND_SAFE_DISTANCE } #[expect(deprecated)] #[expect(clippy::match_same_arms)] diff --git a/crates/prune/types/src/target.rs b/crates/prune/types/src/target.rs index 92a01fc2e5..7f5c383a65 100644 --- a/crates/prune/types/src/target.rs +++ b/crates/prune/types/src/target.rs @@ -9,7 +9,12 @@ use crate::{PruneCheckpoint, PruneMode, PruneSegment, ReceiptsLogPruneConfig}; /// consensus protocol. /// 2. Another 10k blocks to have a room for maneuver in case when things go wrong and a manual /// unwind is required. -pub const MINIMUM_PRUNING_DISTANCE: u64 = 32 * 2 + 10_000; +pub const MINIMUM_UNWIND_SAFE_DISTANCE: u64 = 32 * 2 + 10_000; + +/// Minimum blocks to retain for receipts and bodies to ensure reorg safety. +/// This prevents pruning data that may be needed when handling chain reorganizations, +/// specifically when `canonical_block_by_hash` needs to reconstruct `ExecutedBlock` from disk. +pub const MINIMUM_DISTANCE: u64 = 64; /// Type of history that can be pruned #[derive(Debug, Error, PartialEq, Eq, Clone)] @@ -56,7 +61,7 @@ pub struct PruneModes { any(test, feature = "serde"), serde( skip_serializing_if = "Option::is_none", - deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::" + deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::" ) )] pub account_history: Option, @@ -65,7 +70,7 @@ pub struct PruneModes { any(test, feature = "serde"), serde( skip_serializing_if = "Option::is_none", - deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::" + deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::" ) )] pub storage_history: Option, diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 89c3c65020..2ee377093f 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -54,7 +54,7 @@ use reth_primitives_traits::{ Account, Block as _, BlockBody as _, Bytecode, RecoveredBlock, SealedHeader, StorageEntry, }; use reth_prune_types::{ - PruneCheckpoint, PruneMode, PruneModes, PruneSegment, MINIMUM_PRUNING_DISTANCE, + PruneCheckpoint, PruneMode, PruneModes, PruneSegment, MINIMUM_UNWIND_SAFE_DISTANCE, }; use reth_stages_types::{StageCheckpoint, StageId}; use reth_static_file_types::StaticFileSegment; @@ -368,7 +368,7 @@ impl DatabaseProvider { changeset_cache, pending_rocksdb_batches: Default::default(), commit_order, - minimum_pruning_distance: MINIMUM_PRUNING_DISTANCE, + minimum_pruning_distance: MINIMUM_UNWIND_SAFE_DISTANCE, metrics: metrics::DatabaseProviderMetrics::default(), } } @@ -958,7 +958,7 @@ impl DatabaseProvider { changeset_cache, pending_rocksdb_batches: Default::default(), commit_order: CommitOrder::Normal, - minimum_pruning_distance: MINIMUM_PRUNING_DISTANCE, + minimum_pruning_distance: MINIMUM_UNWIND_SAFE_DISTANCE, metrics: metrics::DatabaseProviderMetrics::default(), } } diff --git a/docs/vocs/docs/pages/cli/op-reth/node.mdx b/docs/vocs/docs/pages/cli/op-reth/node.mdx index ad3c8eff2b..054fe93391 100644 --- a/docs/vocs/docs/pages/cli/op-reth/node.mdx +++ b/docs/vocs/docs/pages/cli/op-reth/node.mdx @@ -832,7 +832,7 @@ Dev testnet: Pruning: --full - Run full node. Only the most recent [`MINIMUM_PRUNING_DISTANCE`] block states are stored + Run full node. Only the most recent [`MINIMUM_UNWIND_SAFE_DISTANCE`] block states are stored --minimal Run minimal storage mode with maximum pruning and smaller static files. diff --git a/docs/vocs/docs/pages/cli/reth/node.mdx b/docs/vocs/docs/pages/cli/reth/node.mdx index 6105ff7f00..2f0c4e3697 100644 --- a/docs/vocs/docs/pages/cli/reth/node.mdx +++ b/docs/vocs/docs/pages/cli/reth/node.mdx @@ -832,7 +832,7 @@ Dev testnet: Pruning: --full - Run full node. Only the most recent [`MINIMUM_PRUNING_DISTANCE`] block states are stored + Run full node. Only the most recent [`MINIMUM_UNWIND_SAFE_DISTANCE`] block states are stored --minimal Run minimal storage mode with maximum pruning and smaller static files.