reth_prune_types/
target.rs

1use crate::{PruneMode, ReceiptsLogPruneConfig};
2
3/// Minimum distance from the tip necessary for the node to work correctly:
4/// 1. Minimum 2 epochs (32 blocks per epoch) required to handle any reorg according to the
5///    consensus protocol.
6/// 2. Another 10k blocks to have a room for maneuver in case when things go wrong and a manual
7///    unwind is required.
8pub const MINIMUM_PRUNING_DISTANCE: u64 = 32 * 2 + 10_000;
9
10/// Pruning configuration for every segment of the data that can be pruned.
11#[derive(Debug, Clone, Default, Eq, PartialEq)]
12#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
13#[cfg_attr(any(test, feature = "serde"), serde(default))]
14pub struct PruneModes {
15    /// Sender Recovery pruning configuration.
16    #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none"))]
17    pub sender_recovery: Option<PruneMode>,
18    /// Transaction Lookup pruning configuration.
19    #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none"))]
20    pub transaction_lookup: Option<PruneMode>,
21    /// Receipts pruning configuration. This setting overrides `receipts_log_filter`
22    /// and offers improved performance.
23    #[cfg_attr(
24        any(test, feature = "serde"),
25        serde(
26            skip_serializing_if = "Option::is_none",
27            deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
28        )
29    )]
30    pub receipts: Option<PruneMode>,
31    /// Account History pruning configuration.
32    #[cfg_attr(
33        any(test, feature = "serde"),
34        serde(
35            skip_serializing_if = "Option::is_none",
36            deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
37        )
38    )]
39    pub account_history: Option<PruneMode>,
40    /// Storage History pruning configuration.
41    #[cfg_attr(
42        any(test, feature = "serde"),
43        serde(
44            skip_serializing_if = "Option::is_none",
45            deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
46        )
47    )]
48    pub storage_history: Option<PruneMode>,
49    /// Receipts pruning configuration by retaining only those receipts that contain logs emitted
50    /// by the specified addresses, discarding others. This setting is overridden by `receipts`.
51    ///
52    /// The [`BlockNumber`](`crate::BlockNumber`) represents the starting block from which point
53    /// onwards the receipts are preserved.
54    pub receipts_log_filter: ReceiptsLogPruneConfig,
55}
56
57impl PruneModes {
58    /// Sets pruning to no target.
59    pub fn none() -> Self {
60        Self::default()
61    }
62
63    /// Sets pruning to all targets.
64    pub fn all() -> Self {
65        Self {
66            sender_recovery: Some(PruneMode::Full),
67            transaction_lookup: Some(PruneMode::Full),
68            receipts: Some(PruneMode::Full),
69            account_history: Some(PruneMode::Full),
70            storage_history: Some(PruneMode::Full),
71            receipts_log_filter: Default::default(),
72        }
73    }
74
75    /// Returns whether there is any kind of receipt pruning configuration.
76    pub fn has_receipts_pruning(&self) -> bool {
77        self.receipts.is_some() || !self.receipts_log_filter.is_empty()
78    }
79
80    /// Returns true if all prune modes are set to [`None`].
81    pub fn is_empty(&self) -> bool {
82        self == &Self::none()
83    }
84}
85
86/// Deserializes [`Option<PruneMode>`] and validates that the value is not less than the const
87/// generic parameter `MIN_BLOCKS`. This parameter represents the number of blocks that needs to be
88/// left in database after the pruning.
89///
90/// 1. For [`PruneMode::Full`], it fails if `MIN_BLOCKS > 0`.
91/// 2. For [`PruneMode::Distance(distance`)], it fails if `distance < MIN_BLOCKS + 1`. `+ 1` is
92///    needed because `PruneMode::Distance(0)` means that we leave zero blocks from the latest,
93///    meaning we have one block in the database.
94#[cfg(any(test, feature = "serde"))]
95fn deserialize_opt_prune_mode_with_min_blocks<
96    'de,
97    const MIN_BLOCKS: u64,
98    D: serde::Deserializer<'de>,
99>(
100    deserializer: D,
101) -> Result<Option<PruneMode>, D::Error> {
102    use alloc::format;
103    use serde::Deserialize;
104    let prune_mode = Option::<PruneMode>::deserialize(deserializer)?;
105
106    match prune_mode {
107        Some(PruneMode::Full) if MIN_BLOCKS > 0 => {
108            Err(serde::de::Error::invalid_value(
109                serde::de::Unexpected::Str("full"),
110                // This message should have "expected" wording
111                &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database")
112                    .as_str(),
113            ))
114        }
115        Some(PruneMode::Distance(distance)) if distance < MIN_BLOCKS => {
116            Err(serde::de::Error::invalid_value(
117                serde::de::Unexpected::Unsigned(distance),
118                // This message should have "expected" wording
119                &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database")
120                    .as_str(),
121            ))
122        }
123        _ => Ok(prune_mode),
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use assert_matches::assert_matches;
131    use serde::Deserialize;
132
133    #[test]
134    fn test_deserialize_opt_prune_mode_with_min_blocks() {
135        #[derive(Debug, Deserialize, PartialEq, Eq)]
136        struct V(
137            #[serde(deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<10, _>")]
138            Option<PruneMode>,
139        );
140
141        assert!(serde_json::from_str::<V>(r#"{"distance": 10}"#).is_ok());
142        assert_matches!(
143            serde_json::from_str::<V>(r#"{"distance": 9}"#),
144            Err(err) if err.to_string() == "invalid value: integer `9`, expected prune mode that leaves at least 10 blocks in the database"
145        );
146
147        assert_matches!(
148            serde_json::from_str::<V>(r#""full""#),
149            Err(err) if err.to_string() == "invalid value: string \"full\", expected prune mode that leaves at least 10 blocks in the database"
150        );
151    }
152}