diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index f0dfb4ac2bbb..40836b8c02c5 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -5,7 +5,7 @@ use reth_downloaders::{ headers::reverse_headers::ReverseHeadersDownloaderBuilder, }; use reth_network::{NetworkConfigBuilder, PeersConfig, SessionsConfig}; -use reth_primitives::PruneMode; +use reth_primitives::{serde_helper::deserialize_opt_prune_mode_with_min_distance, PruneMode}; use secp256k1::SecretKey; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -305,7 +305,10 @@ pub struct PruneParts { #[serde(skip_serializing_if = "Option::is_none")] pub transaction_lookup: Option, /// Receipts pruning configuration. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_opt_prune_mode_with_min_distance::<64, _>" + )] pub receipts: Option, /// Account History pruning configuration. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/primitives/src/serde_helper/mod.rs b/crates/primitives/src/serde_helper/mod.rs index c63e7f62756f..7cd85f892f98 100644 --- a/crates/primitives/src/serde_helper/mod.rs +++ b/crates/primitives/src/serde_helper/mod.rs @@ -10,6 +10,8 @@ use crate::H256; pub use jsonu256::*; pub mod num; +mod prune; +pub use prune::deserialize_opt_prune_mode_with_min_distance; /// serde functions for handling primitive `u64` as [U64](crate::U64) pub mod u64_hex { diff --git a/crates/primitives/src/serde_helper/prune.rs b/crates/primitives/src/serde_helper/prune.rs new file mode 100644 index 000000000000..7dffafdf8f7f --- /dev/null +++ b/crates/primitives/src/serde_helper/prune.rs @@ -0,0 +1,49 @@ +use crate::PruneMode; +use serde::{Deserialize, Deserializer}; + +/// Deserializes [`Option`] and validates that the value contained in +/// [PruneMode::Distance] (if any) is not less than the const generic parameter `MIN_DISTANCE`. +pub fn deserialize_opt_prune_mode_with_min_distance< + 'de, + const MIN_DISTANCE: u64, + D: Deserializer<'de>, +>( + deserializer: D, +) -> Result, D::Error> { + let prune_mode = Option::::deserialize(deserializer)?; + + match prune_mode { + Some(PruneMode::Distance(distance)) if distance < MIN_DISTANCE => { + Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Unsigned(distance), + // This message should have "expected" wording, so we say "not less than" + &format!("prune mode distance not less than {MIN_DISTANCE} blocks").as_str(), + )) + } + _ => Ok(prune_mode), + } +} + +#[cfg(test)] +mod test { + use crate::PruneMode; + use assert_matches::assert_matches; + use serde::Deserialize; + + #[test] + fn deserialize_opt_prune_mode_with_min_distance() { + #[derive(Debug, Deserialize, PartialEq, Eq)] + struct V( + #[serde( + deserialize_with = "super::deserialize_opt_prune_mode_with_min_distance::<10, _>" + )] + Option, + ); + + assert!(serde_json::from_str::(r#"{"distance": 10}"#).is_ok()); + assert_matches!( + serde_json::from_str::(r#"{"distance": 9}"#), + Err(err) if err.to_string() == "invalid value: integer `9`, expected prune mode distance not less than 10 blocks" + ); + } +}