1use alloy_primitives::BlockNumber;
2use derive_more::Display;
3use thiserror::Error;
4
5use crate::{PruneCheckpoint, PruneMode, PruneSegment, 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
39const fn default_merkle_changesets_mode() -> PruneMode {
41 PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)
42}
43
44#[derive(Debug, Clone, Eq, PartialEq)]
46#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
47#[cfg_attr(any(test, feature = "serde"), serde(default))]
48pub struct PruneModes {
49 #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none"))]
51 pub sender_recovery: Option<PruneMode>,
52 #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none"))]
54 pub transaction_lookup: Option<PruneMode>,
55 #[cfg_attr(
58 any(test, feature = "serde"),
59 serde(
60 skip_serializing_if = "Option::is_none",
61 deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
62 )
63 )]
64 pub receipts: Option<PruneMode>,
65 #[cfg_attr(
67 any(test, feature = "serde"),
68 serde(
69 skip_serializing_if = "Option::is_none",
70 deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
71 )
72 )]
73 pub account_history: Option<PruneMode>,
74 #[cfg_attr(
76 any(test, feature = "serde"),
77 serde(
78 skip_serializing_if = "Option::is_none",
79 deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
80 )
81 )]
82 pub storage_history: Option<PruneMode>,
83 #[cfg_attr(
85 any(test, feature = "serde"),
86 serde(
87 skip_serializing_if = "Option::is_none",
88 deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
89 )
90 )]
91 pub bodies_history: Option<PruneMode>,
92 #[cfg_attr(
95 any(test, feature = "serde"),
96 serde(
97 default = "default_merkle_changesets_mode",
98 deserialize_with = "deserialize_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
99 )
100 )]
101 pub merkle_changesets: PruneMode,
102 #[cfg_attr(
108 any(test, feature = "serde"),
109 serde(skip_serializing_if = "ReceiptsLogPruneConfig::is_empty")
110 )]
111 pub receipts_log_filter: ReceiptsLogPruneConfig,
112}
113
114impl Default for PruneModes {
115 fn default() -> Self {
116 Self {
117 sender_recovery: None,
118 transaction_lookup: None,
119 receipts: None,
120 account_history: None,
121 storage_history: None,
122 bodies_history: None,
123 merkle_changesets: default_merkle_changesets_mode(),
124 receipts_log_filter: ReceiptsLogPruneConfig::default(),
125 }
126 }
127}
128
129impl PruneModes {
130 pub fn all() -> Self {
132 Self {
133 sender_recovery: Some(PruneMode::Full),
134 transaction_lookup: Some(PruneMode::Full),
135 receipts: Some(PruneMode::Full),
136 account_history: Some(PruneMode::Full),
137 storage_history: Some(PruneMode::Full),
138 bodies_history: Some(PruneMode::Full),
139 merkle_changesets: PruneMode::Full,
140 receipts_log_filter: Default::default(),
141 }
142 }
143
144 pub fn has_receipts_pruning(&self) -> bool {
146 self.receipts.is_some() || !self.receipts_log_filter.is_empty()
147 }
148
149 pub fn ensure_unwind_target_unpruned(
156 &self,
157 latest_block: u64,
158 target_block: u64,
159 checkpoints: &[(PruneSegment, PruneCheckpoint)],
160 ) -> Result<(), UnwindTargetPrunedError> {
161 let distance = latest_block.saturating_sub(target_block);
162 for (prune_mode, history_type, checkpoint) in &[
163 (
164 self.account_history,
165 HistoryType::AccountHistory,
166 checkpoints.iter().find(|(segment, _)| segment.is_account_history()),
167 ),
168 (
169 self.storage_history,
170 HistoryType::StorageHistory,
171 checkpoints.iter().find(|(segment, _)| segment.is_storage_history()),
172 ),
173 ] {
174 if let Some(PruneMode::Distance(limit)) = prune_mode {
175 if distance > *limit {
177 let pruned_height = checkpoint
180 .and_then(|checkpoint| checkpoint.1.block_number)
181 .unwrap_or(latest_block);
182 if pruned_height >= target_block {
183 return Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit {
185 latest_block,
186 target_block,
187 history_type: history_type.clone(),
188 limit: *limit,
189 })
190 }
191 }
192 }
193 }
194 Ok(())
195 }
196}
197
198#[cfg(any(test, feature = "serde"))]
207fn deserialize_prune_mode_with_min_blocks<
208 'de,
209 const MIN_BLOCKS: u64,
210 D: serde::Deserializer<'de>,
211>(
212 deserializer: D,
213) -> Result<PruneMode, D::Error> {
214 use serde::Deserialize;
215 let prune_mode = PruneMode::deserialize(deserializer)?;
216 serde_deserialize_validate::<MIN_BLOCKS, D>(&prune_mode)?;
217 Ok(prune_mode)
218}
219
220#[cfg(any(test, feature = "serde"))]
229fn deserialize_opt_prune_mode_with_min_blocks<
230 'de,
231 const MIN_BLOCKS: u64,
232 D: serde::Deserializer<'de>,
233>(
234 deserializer: D,
235) -> Result<Option<PruneMode>, D::Error> {
236 use serde::Deserialize;
237 let prune_mode = Option::<PruneMode>::deserialize(deserializer)?;
238 if let Some(prune_mode) = prune_mode.as_ref() {
239 serde_deserialize_validate::<MIN_BLOCKS, D>(prune_mode)?;
240 }
241 Ok(prune_mode)
242}
243
244#[cfg(any(test, feature = "serde"))]
245fn serde_deserialize_validate<'a, 'de, const MIN_BLOCKS: u64, D: serde::Deserializer<'de>>(
246 prune_mode: &'a PruneMode,
247) -> Result<(), D::Error> {
248 use alloc::format;
249 match prune_mode {
250 PruneMode::Full if MIN_BLOCKS > 0 => {
251 Err(serde::de::Error::invalid_value(
252 serde::de::Unexpected::Str("full"),
253 &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database")
255 .as_str(),
256 ))
257 }
258 PruneMode::Distance(distance) if *distance < MIN_BLOCKS => {
259 Err(serde::de::Error::invalid_value(
260 serde::de::Unexpected::Unsigned(*distance),
261 &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database")
263 .as_str(),
264 ))
265 }
266 _ => Ok(()),
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use assert_matches::assert_matches;
274 use serde::Deserialize;
275
276 #[test]
277 fn test_deserialize_opt_prune_mode_with_min_blocks() {
278 #[derive(Debug, Deserialize, PartialEq, Eq)]
279 struct V(
280 #[serde(deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<10, _>")]
281 Option<PruneMode>,
282 );
283
284 assert!(serde_json::from_str::<V>(r#"{"distance": 10}"#).is_ok());
285 assert_matches!(
286 serde_json::from_str::<V>(r#"{"distance": 9}"#),
287 Err(err) if err.to_string() == "invalid value: integer `9`, expected prune mode that leaves at least 10 blocks in the database"
288 );
289
290 assert_matches!(
291 serde_json::from_str::<V>(r#""full""#),
292 Err(err) if err.to_string() == "invalid value: string \"full\", expected prune mode that leaves at least 10 blocks in the database"
293 );
294 }
295
296 #[test]
297 fn test_unwind_target_unpruned() {
298 let prune_modes = PruneModes::default();
300 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 500, &[]).is_ok());
301 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 0, &[]).is_ok());
302
303 let prune_modes = PruneModes {
305 account_history: Some(PruneMode::Distance(100)),
306 storage_history: Some(PruneMode::Distance(100)),
307 ..Default::default()
308 };
309 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 950, &[]).is_ok());
311
312 let prune_modes =
317 PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() };
318 let result = prune_modes.ensure_unwind_target_unpruned(1000, 800, &[]);
320 assert_matches!(
321 result,
322 Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit {
323 latest_block: 1000,
324 target_block: 800,
325 history_type: HistoryType::AccountHistory,
326 limit: 100
327 })
328 );
329
330 let prune_modes =
332 PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() };
333 let checkpoints = vec![(
334 PruneSegment::AccountHistory,
335 PruneCheckpoint {
336 block_number: Some(850),
337 tx_number: None,
338 prune_mode: PruneMode::Distance(100),
339 },
340 )];
341 let result = prune_modes.ensure_unwind_target_unpruned(1000, 800, &checkpoints);
343 assert_matches!(
344 result,
345 Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit {
346 latest_block: 1000,
347 target_block: 800,
348 history_type: HistoryType::AccountHistory,
349 limit: 100
350 })
351 );
352
353 let prune_modes =
355 PruneModes { storage_history: Some(PruneMode::Distance(50)), ..Default::default() };
356 let checkpoints = vec![(
357 PruneSegment::StorageHistory,
358 PruneCheckpoint {
359 block_number: Some(960),
360 tx_number: None,
361 prune_mode: PruneMode::Distance(50),
362 },
363 )];
364 let result = prune_modes.ensure_unwind_target_unpruned(1000, 900, &checkpoints);
366 assert_matches!(
367 result,
368 Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit {
369 latest_block: 1000,
370 target_block: 900,
371 history_type: HistoryType::StorageHistory,
372 limit: 50
373 })
374 );
375
376 let prune_modes =
378 PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() };
379 let checkpoints = vec![(
380 PruneSegment::AccountHistory,
381 PruneCheckpoint {
382 block_number: Some(700),
383 tx_number: None,
384 prune_mode: PruneMode::Distance(100),
385 },
386 )];
387 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 800, &checkpoints).is_ok());
390
391 let prune_modes = PruneModes {
393 account_history: Some(PruneMode::Distance(200)),
394 storage_history: Some(PruneMode::Distance(50)),
395 ..Default::default()
396 };
397 let checkpoints = vec![
398 (
399 PruneSegment::AccountHistory,
400 PruneCheckpoint {
401 block_number: Some(700),
402 tx_number: None,
403 prune_mode: PruneMode::Distance(200),
404 },
405 ),
406 (
407 PruneSegment::StorageHistory,
408 PruneCheckpoint {
409 block_number: Some(960),
410 tx_number: None,
411 prune_mode: PruneMode::Distance(50),
412 },
413 ),
414 ];
415 let result = prune_modes.ensure_unwind_target_unpruned(1000, 900, &checkpoints);
418 assert_matches!(
419 result,
420 Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit {
421 latest_block: 1000,
422 target_block: 900,
423 history_type: HistoryType::StorageHistory,
424 limit: 50
425 })
426 );
427
428 let prune_modes =
430 PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() };
431 let checkpoints = vec![(
432 PruneSegment::AccountHistory,
433 PruneCheckpoint {
434 block_number: Some(900),
435 tx_number: None,
436 prune_mode: PruneMode::Distance(100),
437 },
438 )];
439 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 900, &checkpoints).is_ok());
441
442 let prune_modes = PruneModes {
444 account_history: Some(PruneMode::Full),
445 storage_history: Some(PruneMode::Full),
446 ..Default::default()
447 };
448 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 0, &[]).is_ok());
449
450 let prune_modes =
452 PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() };
453 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 1500, &[]).is_ok());
455 }
456}