1use crate::{PruneMode, ReceiptsLogPruneConfig};
23/// 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;
910/// 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"))]
17pub sender_recovery: Option<PruneMode>,
18/// Transaction Lookup pruning configuration.
19#[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none"))]
20pub 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 )]
30pub 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 )]
39pub 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 )]
48pub 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.
54pub receipts_log_filter: ReceiptsLogPruneConfig,
55}
5657impl PruneModes {
58/// Sets pruning to no target.
59pub fn none() -> Self {
60Self::default()
61 }
6263/// Sets pruning to all targets.
64pub fn all() -> Self {
65Self {
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 }
7475/// Returns whether there is any kind of receipt pruning configuration.
76pub fn has_receipts_pruning(&self) -> bool {
77self.receipts.is_some() || !self.receipts_log_filter.is_empty()
78 }
7980/// Returns true if all prune modes are set to [`None`].
81pub fn is_empty(&self) -> bool {
82self== &Self::none()
83 }
84}
8586/// 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,
97const MIN_BLOCKS: u64,
98 D: serde::Deserializer<'de>,
99>(
100 deserializer: D,
101) -> Result<Option<PruneMode>, D::Error> {
102use alloc::format;
103use serde::Deserialize;
104let prune_mode = Option::<PruneMode>::deserialize(deserializer)?;
105106match prune_mode {
107Some(PruneMode::Full) if MIN_BLOCKS > 0 => {
108Err(serde::de::Error::invalid_value(
109serde::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 }
115Some(PruneMode::Distance(distance)) if distance < MIN_BLOCKS => {
116Err(serde::de::Error::invalid_value(
117serde::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}
126127#[cfg(test)]
128mod tests {
129use super::*;
130use assert_matches::assert_matches;
131use serde::Deserialize;
132133#[test]
134fn test_deserialize_opt_prune_mode_with_min_blocks() {
135#[derive(Debug, Deserialize, PartialEq, Eq)]
136struct V(
137#[serde(deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<10, _>")]
138Option<PruneMode>,
139 );
140141assert!(serde_json::from_str::<V>(r#"{"distance": 10}"#).is_ok());
142assert_matches!(
143 serde_json::from_str::<V>(r#"{"distance": 9}"#),
144Err(err) if err.to_string() == "invalid value: integer `9`, expected prune mode that leaves at least 10 blocks in the database"
145);
146147assert_matches!(
148 serde_json::from_str::<V>(r#""full""#),
149Err(err) if err.to_string() == "invalid value: string \"full\", expected prune mode that leaves at least 10 blocks in the database"
150);
151 }
152}