reth_prune_types/
target.rs

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