// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Polkadot is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . // //! Test configuration definition and helpers. use super::*; use keyring::Keyring; use std::{path::Path, time::Duration}; pub use crate::cli::TestObjective; use polkadot_primitives::{AuthorityDiscoveryId, ValidatorId}; use rand::{distributions::Uniform, prelude::Distribution, thread_rng}; use serde::{Deserialize, Serialize}; pub fn random_pov_size(min_pov_size: usize, max_pov_size: usize) -> usize { random_uniform_sample(min_pov_size, max_pov_size) } fn random_uniform_sample + From>(min_value: T, max_value: T) -> T { Uniform::from(min_value.into()..=max_value.into()) .sample(&mut thread_rng()) .into() } /// Peer response latency configuration. #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct PeerLatency { /// Min latency for `NetworkAction` completion. pub min_latency: Duration, /// Max latency or `NetworkAction` completion. pub max_latency: Duration, } // Default PoV size in KiB. fn default_pov_size() -> usize { 5120 } // Default bandwidth in bytes fn default_bandwidth() -> usize { 52428800 } // Default connectivity percentage fn default_connectivity() -> usize { 100 } /// The test input parameters #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TestConfiguration { /// The test objective pub objective: TestObjective, /// Number of validators pub n_validators: usize, /// Number of cores pub n_cores: usize, /// The min PoV size #[serde(default = "default_pov_size")] pub min_pov_size: usize, /// The max PoV size, #[serde(default = "default_pov_size")] pub max_pov_size: usize, /// Randomly sampled pov_sizes #[serde(skip)] pov_sizes: Vec, /// The amount of bandiwdth remote validators have. #[serde(default = "default_bandwidth")] pub peer_bandwidth: usize, /// The amount of bandiwdth our node has. #[serde(default = "default_bandwidth")] pub bandwidth: usize, /// Optional peer emulation latency #[serde(default)] pub latency: Option, /// Error probability, applies to sending messages to the emulated network peers #[serde(default)] pub error: usize, /// Connectivity ratio, the percentage of peers we are not connected to, but ar part of /// the topology. #[serde(default = "default_connectivity")] pub connectivity: usize, /// Number of blocks to run the test for pub num_blocks: usize, } fn generate_pov_sizes(count: usize, min_kib: usize, max_kib: usize) -> Vec { (0..count).map(|_| random_pov_size(min_kib * 1024, max_kib * 1024)).collect() } #[derive(Serialize, Deserialize)] pub struct TestSequence { #[serde(rename(serialize = "TestConfiguration", deserialize = "TestConfiguration"))] test_configurations: Vec, } impl TestSequence { pub fn into_vec(self) -> Vec { self.test_configurations .into_iter() .map(|mut config| { config.pov_sizes = generate_pov_sizes(config.n_cores, config.min_pov_size, config.max_pov_size); config }) .collect() } } impl TestSequence { pub fn new_from_file(path: &Path) -> std::io::Result { let string = String::from_utf8(std::fs::read(path)?).expect("File is valid UTF8"); Ok(serde_yaml::from_str(&string).expect("File is valid test sequence YA")) } } /// Helper struct for authority related state. #[derive(Clone)] pub struct TestAuthorities { pub keyrings: Vec, pub validator_public: Vec, pub validator_authority_id: Vec, } impl TestConfiguration { #[allow(unused)] pub fn write_to_disk(&self) { // Serialize a slice of configurations let yaml = serde_yaml::to_string(&TestSequence { test_configurations: vec![self.clone()] }) .unwrap(); std::fs::write("last_test.yaml", yaml).unwrap(); } pub fn pov_sizes(&self) -> &[usize] { &self.pov_sizes } /// Generates the authority keys we need for the network emulation. pub fn generate_authorities(&self) -> TestAuthorities { let keyrings = (0..self.n_validators) .map(|peer_index| Keyring::new(format!("Node{}", peer_index))) .collect::>(); // Generate `AuthorityDiscoveryId`` for each peer let validator_public: Vec = keyrings .iter() .map(|keyring: &Keyring| keyring.clone().public().into()) .collect::>(); let validator_authority_id: Vec = keyrings .iter() .map(|keyring| keyring.clone().public().into()) .collect::>(); TestAuthorities { keyrings, validator_public, validator_authority_id } } /// An unconstrained standard configuration matching Polkadot/Kusama pub fn ideal_network( objective: TestObjective, num_blocks: usize, n_validators: usize, n_cores: usize, min_pov_size: usize, max_pov_size: usize, ) -> TestConfiguration { Self { objective, n_cores, n_validators, pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size), bandwidth: 50 * 1024 * 1024, peer_bandwidth: 50 * 1024 * 1024, // No latency latency: None, error: 0, num_blocks, min_pov_size, max_pov_size, connectivity: 100, } } pub fn healthy_network( objective: TestObjective, num_blocks: usize, n_validators: usize, n_cores: usize, min_pov_size: usize, max_pov_size: usize, ) -> TestConfiguration { Self { objective, n_cores, n_validators, pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size), bandwidth: 50 * 1024 * 1024, peer_bandwidth: 50 * 1024 * 1024, latency: Some(PeerLatency { min_latency: Duration::from_millis(1), max_latency: Duration::from_millis(100), }), error: 3, num_blocks, min_pov_size, max_pov_size, connectivity: 95, } } pub fn degraded_network( objective: TestObjective, num_blocks: usize, n_validators: usize, n_cores: usize, min_pov_size: usize, max_pov_size: usize, ) -> TestConfiguration { Self { objective, n_cores, n_validators, pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size), bandwidth: 50 * 1024 * 1024, peer_bandwidth: 50 * 1024 * 1024, latency: Some(PeerLatency { min_latency: Duration::from_millis(10), max_latency: Duration::from_millis(500), }), error: 33, num_blocks, min_pov_size, max_pov_size, connectivity: 67, } } } /// Produce a randomized duration between `min` and `max`. pub fn random_latency(maybe_peer_latency: Option<&PeerLatency>) -> Option { maybe_peer_latency.map(|peer_latency| { Uniform::from(peer_latency.min_latency..=peer_latency.max_latency).sample(&mut thread_rng()) }) } /// Generate a random error based on `probability`. /// `probability` should be a number between 0 and 100. pub fn random_error(probability: usize) -> bool { Uniform::from(0..=99).sample(&mut thread_rng()) < probability }