use std::collections::{BTreeMap, HashSet};
use serde::de::Error;
use serde_json::{self, Error as JsonError};
use storage::StorageValue;
use crypto::{hash, CryptoHash, PublicKey, Hash};
use helpers::{Height, Milliseconds};
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ValidatorKeys {
#[doc(hidden)]
pub consensus_key: PublicKey,
pub service_key: PublicKey,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StoredConfiguration {
pub previous_cfg_hash: Hash,
pub actual_from: Height,
pub validator_keys: Vec<ValidatorKeys>,
pub consensus: ConsensusConfig,
pub services: BTreeMap<String, serde_json::Value>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ConsensusConfig {
pub round_timeout: Milliseconds,
pub status_timeout: Milliseconds,
pub peers_timeout: Milliseconds,
pub txs_block_limit: u32,
pub max_message_len: u32,
pub timeout_adjuster: TimeoutAdjusterConfig,
}
impl ConsensusConfig {
pub const DEFAULT_MESSAGE_MAX_LEN: u32 = 1024 * 1024; }
impl Default for ConsensusConfig {
fn default() -> Self {
ConsensusConfig {
round_timeout: 3000,
status_timeout: 5000,
peers_timeout: 10_000,
txs_block_limit: 1000,
max_message_len: Self::DEFAULT_MESSAGE_MAX_LEN,
timeout_adjuster: TimeoutAdjusterConfig::Constant { timeout: 500 },
}
}
}
impl StoredConfiguration {
pub fn try_serialize(&self) -> Result<Vec<u8>, JsonError> {
serde_json::to_vec(&self)
}
pub fn try_deserialize(serialized: &[u8]) -> Result<StoredConfiguration, JsonError> {
let config: StoredConfiguration = serde_json::from_slice(serialized)?;
{
let mut keys = HashSet::with_capacity(config.validator_keys.len() * 2);
for k in &config.validator_keys {
keys.insert(k.consensus_key);
keys.insert(k.service_key);
}
if keys.len() != config.validator_keys.len() * 2 {
return Err(JsonError::custom(
"Duplicated keys are found: each consensus and service key must be unique",
));
}
}
let propose_timeout = match config.consensus.timeout_adjuster {
TimeoutAdjusterConfig::Constant { timeout } => timeout,
TimeoutAdjusterConfig::Dynamic { min, max, .. } => {
if min >= max {
return Err(JsonError::custom(format!(
"Dynamic adjuster: minimal timeout should be less then maximal: \
min = {}, max = {}",
min,
max
)));
}
max
}
TimeoutAdjusterConfig::MovingAverage {
min,
max,
adjustment_speed,
optimal_block_load,
} => {
if min >= max {
return Err(JsonError::custom(format!(
"Moving average adjuster: minimal timeout must be less then maximal: \
min = {}, max = {}",
min,
max
)));
}
if adjustment_speed <= 0. || adjustment_speed > 1. {
return Err(JsonError::custom(format!(
"Moving average adjuster: adjustment speed must be in the (0..1] range: {}",
adjustment_speed,
)));
}
if optimal_block_load <= 0. || optimal_block_load > 1. {
return Err(JsonError::custom(format!(
"Moving average adjuster: block load must be in the (0..1] range: {}",
adjustment_speed,
)));
}
max
}
};
if config.consensus.round_timeout <= propose_timeout {
return Err(JsonError::custom(format!(
"round_timeout({}) must be strictly larger than propose_timeout({})",
config.consensus.round_timeout,
propose_timeout
)));
}
if config.consensus.round_timeout <= 2 * propose_timeout {
warn!(
"It is recommended that round_timeout({}) be at least twice as large \
as propose_timeout({})",
config.consensus.round_timeout,
propose_timeout
);
}
Ok(config)
}
}
impl CryptoHash for StoredConfiguration {
fn hash(&self) -> Hash {
let vec_bytes = self.try_serialize().unwrap();
hash(&vec_bytes)
}
}
impl StorageValue for StoredConfiguration {
fn into_bytes(self) -> Vec<u8> {
self.try_serialize().unwrap()
}
fn from_bytes(v: ::std::borrow::Cow<[u8]>) -> Self {
StoredConfiguration::try_deserialize(v.as_ref()).unwrap()
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(tag = "type")]
pub enum TimeoutAdjusterConfig {
Constant {
timeout: Milliseconds,
},
Dynamic {
min: Milliseconds,
max: Milliseconds,
threshold: u32,
},
MovingAverage {
min: Milliseconds,
max: Milliseconds,
adjustment_speed: f64,
optimal_block_load: f64,
},
}
#[cfg(test)]
mod tests {
use toml;
use serde::{Serialize, Deserialize};
use std::fmt::Debug;
use crypto::{Seed, gen_keypair_from_seed};
use super::*;
#[test]
fn stored_configuration_toml() {
let original = create_test_configuration();
let toml = toml::to_string(&original).unwrap();
let deserialized: StoredConfiguration = toml::from_str(&toml).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn stored_configuration_serialize_deserialize() {
let configuration = create_test_configuration();
assert_eq!(configuration, serialize_deserialize(&configuration));
}
#[test]
#[should_panic(expected = "Duplicated keys are found")]
fn stored_configuration_duplicated_keys() {
let mut configuration = create_test_configuration();
configuration.validator_keys.push(ValidatorKeys {
consensus_key: PublicKey::zero(),
service_key: PublicKey::zero(),
});
serialize_deserialize(&configuration);
}
#[test]
fn constant_adjuster_config_toml() {
let config = TimeoutAdjusterConfig::Constant { timeout: 500 };
check_toml_roundtrip(&config);
}
#[test]
fn dynamic_adjuster_config_toml() {
let config = TimeoutAdjusterConfig::Dynamic {
min: 1,
max: 1000,
threshold: 10,
};
check_toml_roundtrip(&config);
}
#[test]
fn moving_average_adjuster_config_toml() {
let config = TimeoutAdjusterConfig::MovingAverage {
min: 1,
max: 1000,
adjustment_speed: 0.5,
optimal_block_load: 0.75,
};
check_toml_roundtrip(&config);
}
#[test]
#[should_panic(expected = "Dynamic adjuster: minimal timeout should be less then maximal")]
fn dynamic_adjuster_min_max() {
let mut configuration = create_test_configuration();
configuration.consensus.timeout_adjuster = TimeoutAdjusterConfig::Dynamic {
min: 10,
max: 0,
threshold: 1,
};
serialize_deserialize(&configuration);
}
#[test]
#[should_panic(expected = "Moving average adjuster: minimal timeout must be less then maximal")]
fn moving_average_adjuster_min_max() {
let mut configuration = create_test_configuration();
configuration.consensus.timeout_adjuster = TimeoutAdjusterConfig::MovingAverage {
min: 10,
max: 0,
adjustment_speed: 0.7,
optimal_block_load: 0.5,
};
serialize_deserialize(&configuration);
}
#[cfg_attr(rustfmt, rustfmt_skip)]
#[test]
#[should_panic(expected = "Moving average adjuster: adjustment speed must be in the (0..1]")]
fn moving_average_adjuster_negative_adjustment_speed() {
let mut configuration = create_test_configuration();
configuration.consensus.timeout_adjuster = TimeoutAdjusterConfig::MovingAverage {
min: 1,
max: 20,
adjustment_speed: -0.7,
optimal_block_load: 0.5,
};
serialize_deserialize(&configuration);
}
#[cfg_attr(rustfmt, rustfmt_skip)]
#[test]
#[should_panic(expected = "Moving average adjuster: adjustment speed must be in the (0..1]")]
fn moving_average_adjuster_invalid_adjustment_speed() {
let mut configuration = create_test_configuration();
configuration.consensus.timeout_adjuster = TimeoutAdjusterConfig::MovingAverage {
min: 10,
max: 20,
adjustment_speed: 1.5,
optimal_block_load: 0.5,
};
serialize_deserialize(&configuration);
}
#[cfg_attr(rustfmt, rustfmt_skip)]
#[test]
#[should_panic(expected = "Moving average adjuster: block load must be in the (0..1] range")]
fn moving_average_adjuster_negative_block_load() {
let mut configuration = create_test_configuration();
configuration.consensus.timeout_adjuster = TimeoutAdjusterConfig::MovingAverage {
min: 10,
max: 20,
adjustment_speed: 0.7,
optimal_block_load: -0.5,
};
serialize_deserialize(&configuration);
}
#[cfg_attr(rustfmt, rustfmt_skip)]
#[test]
#[should_panic(expected = "Moving average adjuster: block load must be in the (0..1] range")]
fn moving_average_adjuster_invalid_block_load() {
let mut configuration = create_test_configuration();
configuration.consensus.timeout_adjuster = TimeoutAdjusterConfig::MovingAverage {
min: 10,
max: 20,
adjustment_speed: 0.7,
optimal_block_load: 2.0,
};
serialize_deserialize(&configuration);
}
#[test]
#[should_panic(expected = "round_timeout(50) must be strictly larger than propose_timeout(50)")]
fn constant_adjuster_invalid_timeout() {
let mut configuration = create_test_configuration();
configuration.consensus.round_timeout = 50;
configuration.consensus.timeout_adjuster = TimeoutAdjusterConfig::Constant { timeout: 50 };
serialize_deserialize(&configuration);
}
#[test]
#[should_panic(expected = "round_timeout(50) must be strictly larger than propose_timeout(50)")]
fn dynamic_adjuster_invalid_timeout() {
let mut configuration = create_test_configuration();
configuration.consensus.round_timeout = 50;
configuration.consensus.timeout_adjuster = TimeoutAdjusterConfig::Dynamic {
min: 10,
max: 50,
threshold: 1,
};
serialize_deserialize(&configuration);
}
#[test]
#[should_panic(expected = "round_timeout(50) must be strictly larger than propose_timeout(50)")]
fn moving_average_adjuster_invalid_timeout() {
let mut configuration = create_test_configuration();
configuration.consensus.round_timeout = 50;
configuration.consensus.timeout_adjuster = TimeoutAdjusterConfig::MovingAverage {
min: 10,
max: 50,
adjustment_speed: 0.7,
optimal_block_load: 0.2,
};
serialize_deserialize(&configuration);
}
fn create_test_configuration() -> StoredConfiguration {
let validator_keys = (1..4)
.map(|i| {
ValidatorKeys {
consensus_key: gen_keypair_from_seed(&Seed::new([i; 32])).0,
service_key: gen_keypair_from_seed(&Seed::new([i * 10; 32])).0,
}
})
.collect();
StoredConfiguration {
previous_cfg_hash: Hash::zero(),
actual_from: Height(42),
validator_keys,
consensus: ConsensusConfig::default(),
services: BTreeMap::new(),
}
}
fn serialize_deserialize(configuration: &StoredConfiguration) -> StoredConfiguration {
let serialized = configuration.try_serialize().unwrap();
StoredConfiguration::try_deserialize(&serialized).unwrap()
}
fn check_toml_roundtrip<T>(original: &T)
where
for<'de> T: Serialize + Deserialize<'de> + PartialEq + Debug,
{
let toml = toml::to_string(original).unwrap();
let deserialized: T = toml::from_str(&toml).unwrap();
assert_eq!(*original, deserialized);
}
}