use std::str::FromStr;
use datasize::DataSize;
use serde::{
de::{Deserializer, Error as SerdeError, Unexpected},
Deserialize, Serialize,
};
use tracing::error;
use casper_types::TimeDiff;
#[cfg(test)]
use super::error::Error;
const DEFAULT_INFECTION_TARGET: u8 = 3;
const DEFAULT_SATURATION_LIMIT_PERCENT: u8 = 80;
pub(super) const MAX_SATURATION_LIMIT_PERCENT: u8 = 99;
pub(super) const DEFAULT_FINISHED_ENTRY_DURATION: &str = "60sec";
const DEFAULT_GOSSIP_REQUEST_TIMEOUT: &str = "10sec";
const DEFAULT_GET_REMAINDER_TIMEOUT: &str = "60sec";
const DEFAULT_VALIDATE_AND_STORE_TIMEOUT: &str = "60sec";
#[cfg(test)]
const SMALL_TIMEOUTS_FINISHED_ENTRY_DURATION: &str = "2sec";
#[cfg(test)]
const SMALL_TIMEOUTS_GOSSIP_REQUEST_TIMEOUT: &str = "1sec";
#[cfg(test)]
const SMALL_TIMEOUTS_GET_REMAINDER_TIMEOUT: &str = "1sec";
#[cfg(test)]
const SMALL_TIMEOUTS_VALIDATE_AND_STORE_TIMEOUT: &str = "1sec";
#[derive(Copy, Clone, DataSize, Debug, Deserialize, Serialize)]
pub struct Config {
pub infection_target: u8,
#[serde(deserialize_with = "deserialize_saturation_limit_percent")]
pub saturation_limit_percent: u8,
pub finished_entry_duration: TimeDiff,
pub gossip_request_timeout: TimeDiff,
pub get_remainder_timeout: TimeDiff,
pub validate_and_store_timeout: TimeDiff,
}
impl Config {
#[cfg(test)]
pub(crate) fn new(
infection_target: u8,
saturation_limit_percent: u8,
finished_entry_duration: TimeDiff,
gossip_request_timeout: TimeDiff,
get_remainder_timeout: TimeDiff,
validate_and_store_timeout: TimeDiff,
) -> Result<Self, Error> {
if saturation_limit_percent > MAX_SATURATION_LIMIT_PERCENT {
return Err(Error::InvalidSaturationLimit);
}
Ok(Config {
infection_target,
saturation_limit_percent,
finished_entry_duration,
gossip_request_timeout,
get_remainder_timeout,
validate_and_store_timeout,
})
}
#[cfg(test)]
pub(crate) fn new_with_small_timeouts() -> Self {
Config {
finished_entry_duration: TimeDiff::from_str(SMALL_TIMEOUTS_FINISHED_ENTRY_DURATION)
.unwrap(),
gossip_request_timeout: TimeDiff::from_str(SMALL_TIMEOUTS_GOSSIP_REQUEST_TIMEOUT)
.unwrap(),
get_remainder_timeout: TimeDiff::from_str(SMALL_TIMEOUTS_GET_REMAINDER_TIMEOUT)
.unwrap(),
validate_and_store_timeout: TimeDiff::from_str(
SMALL_TIMEOUTS_VALIDATE_AND_STORE_TIMEOUT,
)
.unwrap(),
..Default::default()
}
}
pub(crate) fn infection_target(&self) -> u8 {
self.infection_target
}
pub(crate) fn saturation_limit_percent(&self) -> u8 {
self.saturation_limit_percent
}
pub(crate) fn finished_entry_duration(&self) -> TimeDiff {
self.finished_entry_duration
}
pub(crate) fn gossip_request_timeout(&self) -> TimeDiff {
self.gossip_request_timeout
}
pub(crate) fn get_remainder_timeout(&self) -> TimeDiff {
self.get_remainder_timeout
}
pub(crate) fn validate_and_store_timeout(&self) -> TimeDiff {
self.validate_and_store_timeout
}
}
impl Default for Config {
fn default() -> Self {
Config {
infection_target: DEFAULT_INFECTION_TARGET,
saturation_limit_percent: DEFAULT_SATURATION_LIMIT_PERCENT,
finished_entry_duration: TimeDiff::from_str(DEFAULT_FINISHED_ENTRY_DURATION).unwrap(),
gossip_request_timeout: TimeDiff::from_str(DEFAULT_GOSSIP_REQUEST_TIMEOUT).unwrap(),
get_remainder_timeout: TimeDiff::from_str(DEFAULT_GET_REMAINDER_TIMEOUT).unwrap(),
validate_and_store_timeout: TimeDiff::from_str(DEFAULT_VALIDATE_AND_STORE_TIMEOUT)
.unwrap(),
}
}
}
fn deserialize_saturation_limit_percent<'de, D>(deserializer: D) -> Result<u8, D::Error>
where
D: Deserializer<'de>,
{
let saturation_limit_percent = u8::deserialize(deserializer)?;
if saturation_limit_percent > MAX_SATURATION_LIMIT_PERCENT {
error!(
"saturation_limit_percent of {} is above {}",
saturation_limit_percent, MAX_SATURATION_LIMIT_PERCENT
);
return Err(SerdeError::invalid_value(
Unexpected::Unsigned(saturation_limit_percent as u64),
&"a value between 0 and 99 inclusive",
));
}
Ok(saturation_limit_percent)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invalid_config_should_fail() {
let invalid_config = Config {
infection_target: 3,
saturation_limit_percent: MAX_SATURATION_LIMIT_PERCENT + 1,
finished_entry_duration: TimeDiff::from_str(DEFAULT_FINISHED_ENTRY_DURATION).unwrap(),
gossip_request_timeout: TimeDiff::from_str(DEFAULT_GOSSIP_REQUEST_TIMEOUT).unwrap(),
get_remainder_timeout: TimeDiff::from_str(DEFAULT_GET_REMAINDER_TIMEOUT).unwrap(),
validate_and_store_timeout: TimeDiff::from_str(DEFAULT_VALIDATE_AND_STORE_TIMEOUT)
.unwrap(),
};
let config_as_json = serde_json::to_string(&invalid_config).unwrap();
assert!(serde_json::from_str::<Config>(&config_as_json).is_err());
assert!(Config::new(
3,
MAX_SATURATION_LIMIT_PERCENT + 1,
TimeDiff::from_str(DEFAULT_FINISHED_ENTRY_DURATION).unwrap(),
TimeDiff::from_str(DEFAULT_GOSSIP_REQUEST_TIMEOUT).unwrap(),
TimeDiff::from_str(DEFAULT_GET_REMAINDER_TIMEOUT).unwrap(),
TimeDiff::from_str(DEFAULT_VALIDATE_AND_STORE_TIMEOUT).unwrap()
)
.is_err())
}
}