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
39pub const MERKLE_CHANGESETS_RETENTION_BLOCKS: u64 = 128;
42
43const fn default_merkle_changesets_mode() -> PruneMode {
45 PruneMode::Distance(MERKLE_CHANGESETS_RETENTION_BLOCKS)
46}
47
48#[derive(Debug, Clone, Eq, PartialEq)]
50#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
51#[cfg_attr(any(test, feature = "serde"), serde(default))]
52pub struct PruneModes {
53 #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none"))]
55 pub sender_recovery: Option<PruneMode>,
56 #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing_if = "Option::is_none"))]
58 pub transaction_lookup: Option<PruneMode>,
59 #[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 receipts: 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 account_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 storage_history: Option<PruneMode>,
87 #[cfg_attr(
89 any(test, feature = "serde"),
90 serde(
91 skip_serializing_if = "Option::is_none",
92 deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
93 )
94 )]
95 pub bodies_history: Option<PruneMode>,
96 #[cfg_attr(any(test, feature = "serde"), serde(default = "default_merkle_changesets_mode"))]
99 pub merkle_changesets: PruneMode,
100 #[cfg_attr(
106 any(test, feature = "serde"),
107 serde(skip_serializing_if = "ReceiptsLogPruneConfig::is_empty")
108 )]
109 pub receipts_log_filter: ReceiptsLogPruneConfig,
110}
111
112impl Default for PruneModes {
113 fn default() -> Self {
114 Self {
115 sender_recovery: None,
116 transaction_lookup: None,
117 receipts: None,
118 account_history: None,
119 storage_history: None,
120 bodies_history: None,
121 merkle_changesets: default_merkle_changesets_mode(),
122 receipts_log_filter: ReceiptsLogPruneConfig::default(),
123 }
124 }
125}
126
127impl PruneModes {
128 pub fn all() -> Self {
130 Self {
131 sender_recovery: Some(PruneMode::Full),
132 transaction_lookup: Some(PruneMode::Full),
133 receipts: Some(PruneMode::Full),
134 account_history: Some(PruneMode::Full),
135 storage_history: Some(PruneMode::Full),
136 bodies_history: Some(PruneMode::Full),
137 merkle_changesets: PruneMode::Full,
138 receipts_log_filter: Default::default(),
139 }
140 }
141
142 pub fn has_receipts_pruning(&self) -> bool {
144 self.receipts.is_some() || !self.receipts_log_filter.is_empty()
145 }
146
147 pub const fn migrate(&mut self) -> bool {
154 if let PruneMode::Distance(d) = self.merkle_changesets &&
155 (d < MERKLE_CHANGESETS_RETENTION_BLOCKS || d == MINIMUM_PRUNING_DISTANCE)
156 {
157 self.merkle_changesets = PruneMode::Distance(MERKLE_CHANGESETS_RETENTION_BLOCKS);
158 return true;
159 }
160 false
161 }
162
163 pub fn ensure_unwind_target_unpruned(
170 &self,
171 latest_block: u64,
172 target_block: u64,
173 checkpoints: &[(PruneSegment, PruneCheckpoint)],
174 ) -> Result<(), UnwindTargetPrunedError> {
175 let distance = latest_block.saturating_sub(target_block);
176 for (prune_mode, history_type, checkpoint) in &[
177 (
178 self.account_history,
179 HistoryType::AccountHistory,
180 checkpoints.iter().find(|(segment, _)| segment.is_account_history()),
181 ),
182 (
183 self.storage_history,
184 HistoryType::StorageHistory,
185 checkpoints.iter().find(|(segment, _)| segment.is_storage_history()),
186 ),
187 ] {
188 if let Some(PruneMode::Distance(limit)) = prune_mode {
189 if distance > *limit {
191 let pruned_height = checkpoint
194 .and_then(|checkpoint| checkpoint.1.block_number)
195 .unwrap_or(latest_block);
196 if pruned_height >= target_block {
197 return Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit {
199 latest_block,
200 target_block,
201 history_type: history_type.clone(),
202 limit: *limit,
203 })
204 }
205 }
206 }
207 }
208 Ok(())
209 }
210}
211
212#[cfg(any(test, feature = "serde"))]
221fn deserialize_opt_prune_mode_with_min_blocks<
222 'de,
223 const MIN_BLOCKS: u64,
224 D: serde::Deserializer<'de>,
225>(
226 deserializer: D,
227) -> Result<Option<PruneMode>, D::Error> {
228 use serde::Deserialize;
229 let prune_mode = Option::<PruneMode>::deserialize(deserializer)?;
230 if let Some(prune_mode) = prune_mode.as_ref() {
231 serde_deserialize_validate::<MIN_BLOCKS, D>(prune_mode)?;
232 }
233 Ok(prune_mode)
234}
235
236#[cfg(any(test, feature = "serde"))]
237fn serde_deserialize_validate<'a, 'de, const MIN_BLOCKS: u64, D: serde::Deserializer<'de>>(
238 prune_mode: &'a PruneMode,
239) -> Result<(), D::Error> {
240 use alloc::format;
241 match prune_mode {
242 PruneMode::Full if MIN_BLOCKS > 0 => {
243 Err(serde::de::Error::invalid_value(
244 serde::de::Unexpected::Str("full"),
245 &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database")
247 .as_str(),
248 ))
249 }
250 PruneMode::Distance(distance) if *distance < MIN_BLOCKS => {
251 Err(serde::de::Error::invalid_value(
252 serde::de::Unexpected::Unsigned(*distance),
253 &format!("prune mode that leaves at least {MIN_BLOCKS} blocks in the database")
255 .as_str(),
256 ))
257 }
258 _ => Ok(()),
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265 use assert_matches::assert_matches;
266 use serde::Deserialize;
267
268 #[test]
269 fn test_deserialize_opt_prune_mode_with_min_blocks() {
270 #[derive(Debug, Deserialize, PartialEq, Eq)]
271 struct V(
272 #[serde(deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<10, _>")]
273 Option<PruneMode>,
274 );
275
276 assert!(serde_json::from_str::<V>(r#"{"distance": 10}"#).is_ok());
277 assert_matches!(
278 serde_json::from_str::<V>(r#"{"distance": 9}"#),
279 Err(err) if err.to_string() == "invalid value: integer `9`, expected prune mode that leaves at least 10 blocks in the database"
280 );
281
282 assert_matches!(
283 serde_json::from_str::<V>(r#""full""#),
284 Err(err) if err.to_string() == "invalid value: string \"full\", expected prune mode that leaves at least 10 blocks in the database"
285 );
286 }
287
288 #[test]
289 fn test_unwind_target_unpruned() {
290 let prune_modes = PruneModes::default();
292 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 500, &[]).is_ok());
293 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 0, &[]).is_ok());
294
295 let prune_modes = PruneModes {
297 account_history: Some(PruneMode::Distance(100)),
298 storage_history: Some(PruneMode::Distance(100)),
299 ..Default::default()
300 };
301 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 950, &[]).is_ok());
303
304 let prune_modes =
309 PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() };
310 let result = prune_modes.ensure_unwind_target_unpruned(1000, 800, &[]);
312 assert_matches!(
313 result,
314 Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit {
315 latest_block: 1000,
316 target_block: 800,
317 history_type: HistoryType::AccountHistory,
318 limit: 100
319 })
320 );
321
322 let prune_modes =
324 PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() };
325 let checkpoints = vec![(
326 PruneSegment::AccountHistory,
327 PruneCheckpoint {
328 block_number: Some(850),
329 tx_number: None,
330 prune_mode: PruneMode::Distance(100),
331 },
332 )];
333 let result = prune_modes.ensure_unwind_target_unpruned(1000, 800, &checkpoints);
335 assert_matches!(
336 result,
337 Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit {
338 latest_block: 1000,
339 target_block: 800,
340 history_type: HistoryType::AccountHistory,
341 limit: 100
342 })
343 );
344
345 let prune_modes =
347 PruneModes { storage_history: Some(PruneMode::Distance(50)), ..Default::default() };
348 let checkpoints = vec![(
349 PruneSegment::StorageHistory,
350 PruneCheckpoint {
351 block_number: Some(960),
352 tx_number: None,
353 prune_mode: PruneMode::Distance(50),
354 },
355 )];
356 let result = prune_modes.ensure_unwind_target_unpruned(1000, 900, &checkpoints);
358 assert_matches!(
359 result,
360 Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit {
361 latest_block: 1000,
362 target_block: 900,
363 history_type: HistoryType::StorageHistory,
364 limit: 50
365 })
366 );
367
368 let prune_modes =
370 PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() };
371 let checkpoints = vec![(
372 PruneSegment::AccountHistory,
373 PruneCheckpoint {
374 block_number: Some(700),
375 tx_number: None,
376 prune_mode: PruneMode::Distance(100),
377 },
378 )];
379 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 800, &checkpoints).is_ok());
382
383 let prune_modes = PruneModes {
385 account_history: Some(PruneMode::Distance(200)),
386 storage_history: Some(PruneMode::Distance(50)),
387 ..Default::default()
388 };
389 let checkpoints = vec![
390 (
391 PruneSegment::AccountHistory,
392 PruneCheckpoint {
393 block_number: Some(700),
394 tx_number: None,
395 prune_mode: PruneMode::Distance(200),
396 },
397 ),
398 (
399 PruneSegment::StorageHistory,
400 PruneCheckpoint {
401 block_number: Some(960),
402 tx_number: None,
403 prune_mode: PruneMode::Distance(50),
404 },
405 ),
406 ];
407 let result = prune_modes.ensure_unwind_target_unpruned(1000, 900, &checkpoints);
410 assert_matches!(
411 result,
412 Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit {
413 latest_block: 1000,
414 target_block: 900,
415 history_type: HistoryType::StorageHistory,
416 limit: 50
417 })
418 );
419
420 let prune_modes =
422 PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() };
423 let checkpoints = vec![(
424 PruneSegment::AccountHistory,
425 PruneCheckpoint {
426 block_number: Some(900),
427 tx_number: None,
428 prune_mode: PruneMode::Distance(100),
429 },
430 )];
431 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 900, &checkpoints).is_ok());
433
434 let prune_modes = PruneModes {
436 account_history: Some(PruneMode::Full),
437 storage_history: Some(PruneMode::Full),
438 ..Default::default()
439 };
440 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 0, &[]).is_ok());
441
442 let prune_modes =
444 PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() };
445 assert!(prune_modes.ensure_unwind_target_unpruned(1000, 1500, &[]).is_ok());
447 }
448}