1use alloy_primitives::BlockNumber;
2use derive_more::Display;
3use thiserror::Error;
4
5use crate::{PruneMode, ReceiptsLogPruneConfig};
6
7pub const MINIMUM_PRUNING_DISTANCE: u64 = 32 * 2 + 10_000;
13
14#[derive(Debug, Error, PartialEq, Eq, Clone)]
16pub enum UnwindTargetPrunedError {
17 #[error("Cannot unwind to block {target_block} as it is beyond the {history_type} limit. Latest block: {latest_block}, History limit: {limit}")]
19 TargetBeyondHistoryLimit {
20 latest_block: BlockNumber,
22 target_block: BlockNumber,
24 history_type: HistoryType,
26 limit: u64,
28 },
29}
30
31#[derive(Debug, Display, Clone, PartialEq, Eq)]
32pub enum HistoryType {
33 AccountHistory,
35 StorageHistory,
37}
38
39#[derive(Debug, Clone, Default, Eq, PartialEq)]
41#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
42#[cfg_attr(any(test, feature = "serde"), serde(default))]
43pub struct PruneModes {
44 #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none"))]
46 pub sender_recovery: Option<PruneMode>,
47 #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none"))]
49 pub transaction_lookup: Option<PruneMode>,
50 #[cfg_attr(
53 any(test, feature = "serde"),
54 serde(
55 skip_serializing_if = "Option::is_none",
56 deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
57 )
58 )]
59 pub receipts: Option<PruneMode>,
60 #[cfg_attr(
62 any(test, feature = "serde"),
63 serde(
64 skip_serializing_if = "Option::is_none",
65 deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
66 )
67 )]
68 pub account_history: Option<PruneMode>,
69 #[cfg_attr(
71 any(test, feature = "serde"),
72 serde(
73 skip_serializing_if = "Option::is_none",
74 deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
75 )
76 )]
77 pub storage_history: Option<PruneMode>,
78 #[cfg_attr(
80 any(test, feature = "serde"),
81 serde(
82 skip_serializing_if = "Option::is_none",
83 deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
84 )
85 )]
86 pub bodies_history: Option<PruneMode>,
87 pub receipts_log_filter: ReceiptsLogPruneConfig,
93}
94
95impl PruneModes {
96 pub fn none() -> Self {
98 Self::default()
99 }
100
101 pub fn all() -> Self {
103 Self {
104 sender_recovery: Some(PruneMode::Full),
105 transaction_lookup: Some(PruneMode::Full),
106 receipts: Some(PruneMode::Full),
107 account_history: Some(PruneMode::Full),
108 storage_history: Some(PruneMode::Full),
109 bodies_history: Some(PruneMode::Full),
110 receipts_log_filter: Default::default(),
111 }
112 }
113
114 pub fn has_receipts_pruning(&self) -> bool {
116 self.receipts.is_some() || !self.receipts_log_filter.is_empty()
117 }
118
119 pub fn is_empty(&self) -> bool {
121 self == &Self::none()
122 }
123
124 pub fn ensure_unwind_target_unpruned(
126 &self,
127 latest_block: u64,
128 target_block: u64,
129 ) -> Result<(), UnwindTargetPrunedError> {
130 let distance = latest_block.saturating_sub(target_block);
131 [
132 (self.account_history, HistoryType::AccountHistory),
133 (self.storage_history, HistoryType::StorageHistory),
134 ]
135 .iter()
136 .find_map(|(prune_mode, history_type)| {
137 if let Some(PruneMode::Distance(limit)) = prune_mode {
138 (distance > *limit).then_some(Err(
139 UnwindTargetPrunedError::TargetBeyondHistoryLimit {
140 latest_block,
141 target_block,
142 history_type: history_type.clone(),
143 limit: *limit,
144 },
145 ))
146 } else {
147 None
148 }
149 })
150 .unwrap_or(Ok(()))
151 }
152}
153
154#[cfg(any(test, feature = "serde"))]
163fn deserialize_opt_prune_mode_with_min_blocks<
164 'de,
165 const MIN_BLOCKS: u64,
166 D: serde::Deserializer<'de>,
167>(
168 deserializer: D,
169) -> Result<Option<PruneMode>, D::Error> {
170 use alloc::format;
171 use serde::Deserialize;
172 let prune_mode = Option::<PruneMode>::deserialize(deserializer)?;
173
174 match prune_mode {
175 Some(PruneMode::Full) if MIN_BLOCKS > 0 => {
176 Err(serde::de::Error::invalid_value(
177 serde::de::Unexpected::Str("full"),
178 &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database")
180 .as_str(),
181 ))
182 }
183 Some(PruneMode::Distance(distance)) if distance < MIN_BLOCKS => {
184 Err(serde::de::Error::invalid_value(
185 serde::de::Unexpected::Unsigned(distance),
186 &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database")
188 .as_str(),
189 ))
190 }
191 _ => Ok(prune_mode),
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use assert_matches::assert_matches;
199 use serde::Deserialize;
200
201 #[test]
202 fn test_deserialize_opt_prune_mode_with_min_blocks() {
203 #[derive(Debug, Deserialize, PartialEq, Eq)]
204 struct V(
205 #[serde(deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<10, _>")]
206 Option<PruneMode>,
207 );
208
209 assert!(serde_json::from_str::<V>(r#"{"distance": 10}"#).is_ok());
210 assert_matches!(
211 serde_json::from_str::<V>(r#"{"distance": 9}"#),
212 Err(err) if err.to_string() == "invalid value: integer `9`, expected prune mode that leaves at least 10 blocks in the database"
213 );
214
215 assert_matches!(
216 serde_json::from_str::<V>(r#""full""#),
217 Err(err) if err.to_string() == "invalid value: string \"full\", expected prune mode that leaves at least 10 blocks in the database"
218 );
219 }
220}