diff --git a/crates/primitives/src/prune/target.rs b/crates/primitives/src/prune/target.rs index 8e16235ff8..b314ae9d03 100644 --- a/crates/primitives/src/prune/target.rs +++ b/crates/primitives/src/prune/target.rs @@ -1,4 +1,4 @@ -use crate::{serde_helper::deserialize_opt_prune_mode_with_min_distance, BlockNumber, PruneMode}; +use crate::{serde_helper::deserialize_opt_prune_mode_with_min_blocks, BlockNumber, PruneMode}; use paste::paste; use serde::{Deserialize, Serialize}; @@ -15,7 +15,7 @@ pub struct PruneModes { /// Receipts pruning configuration. #[serde( skip_serializing_if = "Option::is_none", - deserialize_with = "deserialize_opt_prune_mode_with_min_distance::<64, _>" + deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<64, _>" )] pub receipts: Option, /// Account History pruning configuration. @@ -27,7 +27,7 @@ pub struct PruneModes { } macro_rules! impl_prune_parts { - ($(($part:ident, $human_part:expr)),+) => { + ($(($part:ident, $human_part:expr, $min_blocks:expr)),+) => { $( paste! { #[doc = concat!( @@ -51,8 +51,12 @@ macro_rules! impl_prune_parts { $human_part, " pruning needs to be done, inclusive, according to the provided tip." )] - pub fn [](&self, tip: BlockNumber) -> Option<(BlockNumber, PruneMode)> { - self.$part.as_ref().map(|mode| (self.prune_to_block(mode, tip), *mode)) + pub fn [](&self, tip: BlockNumber) -> Option<(BlockNumber, PruneMode)> { + self.$part.as_ref().and_then(|mode| { + self.prune_target_block(mode, tip, $min_blocks).map(|block| { + (block, *mode) + }) + }) } } )+ @@ -90,20 +94,30 @@ impl PruneModes { } /// Returns block up to which pruning needs to be done, inclusive, according to the provided - /// prune mode and tip. - pub fn prune_to_block(&self, mode: &PruneMode, tip: BlockNumber) -> BlockNumber { + /// prune mode, tip block number and minimum number of blocks allowed to be pruned. + pub fn prune_target_block( + &self, + mode: &PruneMode, + tip: BlockNumber, + min_blocks: Option, + ) -> Option { match mode { - PruneMode::Full => tip, - PruneMode::Distance(distance) => tip.saturating_sub(*distance), - PruneMode::Before(n) => *n, + PruneMode::Full if min_blocks.unwrap_or_default() == 0 => Some(tip), + PruneMode::Distance(distance) if *distance >= min_blocks.unwrap_or_default() => { + Some(tip.saturating_sub(*distance)) + } + PruneMode::Before(n) if tip.saturating_sub(*n) >= min_blocks.unwrap_or_default() => { + Some(*n) + } + _ => None, } } impl_prune_parts!( - (sender_recovery, "Sender Recovery"), - (transaction_lookup, "Transaction Lookup"), - (receipts, "Receipts"), - (account_history, "Account History"), - (storage_history, "Storage History") + (sender_recovery, "Sender Recovery", None), + (transaction_lookup, "Transaction Lookup", None), + (receipts, "Receipts", Some(64)), + (account_history, "Account History", None), + (storage_history, "Storage History", None) ); } diff --git a/crates/primitives/src/serde_helper/mod.rs b/crates/primitives/src/serde_helper/mod.rs index 7cd85f892f..23db0bde83 100644 --- a/crates/primitives/src/serde_helper/mod.rs +++ b/crates/primitives/src/serde_helper/mod.rs @@ -11,7 +11,7 @@ pub use jsonu256::*; pub mod num; mod prune; -pub use prune::deserialize_opt_prune_mode_with_min_distance; +pub use prune::deserialize_opt_prune_mode_with_min_blocks; /// serde functions for handling primitive `u64` as [U64](crate::U64) pub mod u64_hex { diff --git a/crates/primitives/src/serde_helper/prune.rs b/crates/primitives/src/serde_helper/prune.rs index 7dffafdf8f..5c305ae595 100644 --- a/crates/primitives/src/serde_helper/prune.rs +++ b/crates/primitives/src/serde_helper/prune.rs @@ -1,11 +1,17 @@ use crate::PruneMode; use serde::{Deserialize, Deserializer}; -/// Deserializes [`Option`] and validates that the value contained in -/// [PruneMode::Distance] (if any) is not less than the const generic parameter `MIN_DISTANCE`. -pub fn deserialize_opt_prune_mode_with_min_distance< +/// 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. +/// +/// 1. For [PruneMode::Full], it fails if `MIN_BLOCKS > 0`. +/// 2. For [PruneMode::Distance(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. +pub fn deserialize_opt_prune_mode_with_min_blocks< 'de, - const MIN_DISTANCE: u64, + const MIN_BLOCKS: u64, D: Deserializer<'de>, >( deserializer: D, @@ -13,11 +19,20 @@ pub fn deserialize_opt_prune_mode_with_min_distance< let prune_mode = Option::::deserialize(deserializer)?; match prune_mode { - Some(PruneMode::Distance(distance)) if distance < MIN_DISTANCE => { + Some(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(), + )) + } + Some(PruneMode::Distance(distance)) if distance < MIN_BLOCKS => { Err(serde::de::Error::invalid_value( serde::de::Unexpected::Unsigned(distance), - // This message should have "expected" wording, so we say "not less than" - &format!("prune mode distance not less than {MIN_DISTANCE} blocks").as_str(), + // This message should have "expected" wording + &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database") + .as_str(), )) } _ => Ok(prune_mode), @@ -31,11 +46,11 @@ mod test { use serde::Deserialize; #[test] - fn deserialize_opt_prune_mode_with_min_distance() { + fn deserialize_opt_prune_mode_with_min_blocks() { #[derive(Debug, Deserialize, PartialEq, Eq)] struct V( #[serde( - deserialize_with = "super::deserialize_opt_prune_mode_with_min_distance::<10, _>" + deserialize_with = "super::deserialize_opt_prune_mode_with_min_blocks::<10, _>" )] Option, ); @@ -43,7 +58,12 @@ mod test { assert!(serde_json::from_str::(r#"{"distance": 10}"#).is_ok()); assert_matches!( serde_json::from_str::(r#"{"distance": 9}"#), - Err(err) if err.to_string() == "invalid value: integer `9`, expected prune mode distance not less than 10 blocks" + Err(err) if err.to_string() == "invalid value: integer `9`, expected prune mode that leaves at least 10 blocks in the database" + ); + + assert_matches!( + serde_json::from_str::(r#""full""#), + Err(err) if err.to_string() == "invalid value: string \"full\", expected prune mode that leaves at least 10 blocks in the database" ); } } diff --git a/crates/prune/src/pruner.rs b/crates/prune/src/pruner.rs index 4cdde139d7..400d0bda22 100644 --- a/crates/prune/src/pruner.rs +++ b/crates/prune/src/pruner.rs @@ -64,7 +64,9 @@ impl Pruner { pub fn run(&mut self, tip_block_number: BlockNumber) -> PrunerResult { let provider = self.provider_factory.provider_rw()?; - if let Some((to_block, prune_mode)) = self.modes.prune_to_block_receipts(tip_block_number) { + if let Some((to_block, prune_mode)) = + self.modes.prune_target_block_receipts(tip_block_number) + { self.prune_receipts(&provider, to_block, prune_mode)?; }