From 575d68a65d65ad1bdc460a05768dc68aa3abe753 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bastian=20K=C3=B6cher?= <bkchr@users.noreply.github.com>
Date: Tue, 8 Dec 2020 16:05:00 +0100
Subject: [PATCH] Adds consistency checks for the `HostConfiguration` (#2087)

* Adds consistency checks for the `HostConfiguration`

Besides that it fixes the chain specs to make the consistency checks happy.

* Update runtime/parachains/src/configuration.rs

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* Review feedback and test fixes etc

* Update

* More

* I'm an idiot

* Fix tests...

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
---
 polkadot/node/service/src/chain_spec.rs       |  10 +-
 polkadot/node/test/service/src/chain_spec.rs  |   5 +-
 polkadot/primitives/src/v1.rs                 |  18 +--
 .../src/runtime-api/validator-groups.md       |   4 -
 .../runtime/parachains/src/configuration.rs   | 111 ++++++++++++++++--
 .../runtime/parachains/src/initializer.rs     |  15 ++-
 .../parachains/src/runtime_api_impl/v1.rs     |   5 -
 polkadot/runtime/parachains/src/scheduler.rs  | 109 +----------------
 8 files changed, 137 insertions(+), 140 deletions(-)

diff --git a/polkadot/node/service/src/chain_spec.rs b/polkadot/node/service/src/chain_spec.rs
index 03cb99c012d..0acc287feea 100644
--- a/polkadot/node/service/src/chain_spec.rs
+++ b/polkadot/node/service/src/chain_spec.rs
@@ -779,7 +779,10 @@ fn rococo_staging_testnet_config_genesis(wasm_binary: &[u8]) -> rococo_runtime::
 				max_code_size: 5 * 1024 * 1024,
 				max_pov_size: 50 * 1024 * 1024,
 				max_head_data_size: 32 * 1024,
-				group_rotation_frequency: 10,
+				group_rotation_frequency: 20,
+				chain_availability_period: 4,
+				thread_availability_period: 4,
+				no_show_slots: 10,
 				..Default::default()
 			},
 		}),
@@ -1232,7 +1235,10 @@ pub fn rococo_testnet_genesis(
 				max_code_size: 5 * 1024 * 1024,
 				max_pov_size: 50 * 1024 * 1024,
 				max_head_data_size: 32 * 1024,
-				group_rotation_frequency: 10,
+				group_rotation_frequency: 20,
+				chain_availability_period: 4,
+				thread_availability_period: 4,
+				no_show_slots: 10,
 				max_upward_queue_count: 8,
 				max_upward_queue_size: 8 * 1024,
 				max_downward_message_size: 1024,
diff --git a/polkadot/node/test/service/src/chain_spec.rs b/polkadot/node/test/service/src/chain_spec.rs
index da0b4d844eb..546467ad8cc 100644
--- a/polkadot/node/test/service/src/chain_spec.rs
+++ b/polkadot/node/test/service/src/chain_spec.rs
@@ -169,7 +169,10 @@ fn polkadot_testnet_genesis(
 				max_code_size: 5 * 1024 * 1024,
 				max_pov_size: 50 * 1024 * 1024,
 				max_head_data_size: 32 * 1024,
-				group_rotation_frequency: 10,
+				group_rotation_frequency: 20,
+				chain_availability_period: 4,
+				thread_availability_period: 4,
+				no_show_slots: 10,
 				..Default::default()
 			},
 		}),
diff --git a/polkadot/primitives/src/v1.rs b/polkadot/primitives/src/v1.rs
index 3985254028c..18a84a9b61d 100644
--- a/polkadot/primitives/src/v1.rs
+++ b/polkadot/primitives/src/v1.rs
@@ -24,7 +24,7 @@ use bitvec::vec::BitVec;
 use primitives::RuntimeDebug;
 use runtime_primitives::traits::AppVerify;
 use inherents::InherentIdentifier;
-use sp_arithmetic::traits::{BaseArithmetic, Saturating, Zero};
+use sp_arithmetic::traits::{BaseArithmetic, Saturating};
 use application_crypto::KeyTypeId;
 
 pub use runtime_primitives::traits::{BlakeTwo256, Hash as HashT};
@@ -573,11 +573,7 @@ impl GroupRotationInfo {
 impl<N: Saturating + BaseArithmetic + Copy> GroupRotationInfo<N> {
 	/// Returns the block number of the next rotation after the current block. If the current block
 	/// is 10 and the rotation frequency is 5, this should return 15.
-	///
-	/// If the group rotation frequency is 0, returns 0.
 	pub fn next_rotation_at(&self) -> N {
-		if self.group_rotation_frequency.is_zero() { return Zero::zero() }
-
 		let cycle_once = self.now + self.group_rotation_frequency;
 		cycle_once - (
 			cycle_once.saturating_sub(self.session_start_block) % self.group_rotation_frequency
@@ -586,10 +582,7 @@ impl<N: Saturating + BaseArithmetic + Copy> GroupRotationInfo<N> {
 
 	/// Returns the block number of the last rotation before or including the current block. If the
 	/// current block is 10 and the rotation frequency is 5, this should return 10.
-	///
-	/// If the group rotation frequency is 0, returns 0.
 	pub fn last_rotation_at(&self) -> N {
-		if self.group_rotation_frequency.is_zero() { return Zero::zero() }
 		self.now - (
 			self.now.saturating_sub(self.session_start_block) % self.group_rotation_frequency
 		)
@@ -837,15 +830,6 @@ mod tests {
 
 		assert_eq!(info.next_rotation_at(), 20);
 		assert_eq!(info.last_rotation_at(), 15);
-
-		let info = GroupRotationInfo {
-			session_start_block: 10u32,
-			now: 11,
-			group_rotation_frequency: 0,
-		};
-
-		assert_eq!(info.next_rotation_at(), 0);
-		assert_eq!(info.last_rotation_at(), 0);
 	}
 
 	#[test]
diff --git a/polkadot/roadmap/implementers-guide/src/runtime-api/validator-groups.md b/polkadot/roadmap/implementers-guide/src/runtime-api/validator-groups.md
index 42b39f976d1..75a94e23497 100644
--- a/polkadot/roadmap/implementers-guide/src/runtime-api/validator-groups.md
+++ b/polkadot/roadmap/implementers-guide/src/runtime-api/validator-groups.md
@@ -17,14 +17,10 @@ impl GroupRotationInfo {
 
     /// Returns the block number of the next rotation after the current block. If the current block
     /// is 10 and the rotation frequency is 5, this should return 15.
-    ///
-    /// If the group rotation frequency is 0, returns 0.
     fn next_rotation_at(&self) -> BlockNumber;
 
     /// Returns the block number of the last rotation before or including the current block. If the
     /// current block is 10 and the rotation frequency is 5, this should return 10.
-    ///
-    /// If the group rotation frequency is 0, returns 0.
     fn last_rotation_at(&self) -> BlockNumber;
 }
 
diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs
index a37234238cc..af693ed6762 100644
--- a/polkadot/runtime/parachains/src/configuration.rs
+++ b/polkadot/runtime/parachains/src/configuration.rs
@@ -28,9 +28,10 @@ use frame_support::{
 };
 use parity_scale_codec::{Encode, Decode};
 use frame_system::ensure_root;
+use sp_runtime::traits::Zero;
 
 /// All configuration of the runtime with respect to parachains and parathreads.
-#[derive(Clone, Encode, Decode, PartialEq, Default, sp_core::RuntimeDebug)]
+#[derive(Clone, Encode, Decode, PartialEq, sp_core::RuntimeDebug)]
 #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
 pub struct HostConfiguration<BlockNumber> {
 	/// The minimum frequency at which parachains can update their validation code.
@@ -50,23 +51,32 @@ pub struct HostConfiguration<BlockNumber> {
 	pub parathread_cores: u32,
 	/// The number of retries that a parathread author has to submit their block.
 	pub parathread_retries: u32,
-	/// How often parachain groups should be rotated across parachains. Must be non-zero.
+	/// How often parachain groups should be rotated across parachains.
+	///
+	/// Must be non-zero.
 	pub group_rotation_frequency: BlockNumber,
 	/// The availability period, in blocks, for parachains. This is the amount of blocks
 	/// after inclusion that validators have to make the block available and signal its availability to
-	/// the chain. Must be at least 1.
+	/// the chain.
+	///
+	/// Must be at least 1.
 	pub chain_availability_period: BlockNumber,
 	/// The availability period, in blocks, for parathreads. Same as the `chain_availability_period`,
-	/// but a differing timeout due to differing requirements. Must be at least 1.
+	/// but a differing timeout due to differing requirements.
+	///
+	/// Must be at least 1.
 	pub thread_availability_period: BlockNumber,
 	/// The amount of blocks ahead to schedule parachains and parathreads.
 	pub scheduling_lookahead: u32,
-	/// The maximum number of validators to have per core. `None` means no maximum.
+	/// The maximum number of validators to have per core.
+	///
+	/// `None` means no maximum.
 	pub max_validators_per_core: Option<u32>,
 	/// The amount of sessions to keep for disputes.
 	pub dispute_period: SessionIndex,
 	/// The amount of consensus slots that must pass between submitting an assignment and
 	/// submitting an approval vote before a validator is considered a no-show.
+	///
 	/// Must be at least 1.
 	pub no_show_slots: u32,
 	/// The number of delay tranches in total.
@@ -132,6 +142,74 @@ pub struct HostConfiguration<BlockNumber> {
 	pub hrmp_max_message_num_per_candidate: u32,
 }
 
+impl<BlockNumber: Default + From<u32>> Default for HostConfiguration<BlockNumber> {
+	fn default() -> Self {
+		Self {
+			group_rotation_frequency: 1u32.into(),
+			chain_availability_period: 1u32.into(),
+			thread_availability_period: 1u32.into(),
+			no_show_slots: 1u32.into(),
+			validation_upgrade_frequency: Default::default(),
+			validation_upgrade_delay: Default::default(),
+			acceptance_period: Default::default(),
+			max_code_size: Default::default(),
+			max_pov_size: Default::default(),
+			max_head_data_size: Default::default(),
+			parathread_cores: Default::default(),
+			parathread_retries: Default::default(),
+			scheduling_lookahead: Default::default(),
+			max_validators_per_core: Default::default(),
+			dispute_period: Default::default(),
+			n_delay_tranches: Default::default(),
+			zeroth_delay_tranche_width: Default::default(),
+			needed_approvals: Default::default(),
+			relay_vrf_modulo_samples: Default::default(),
+			max_upward_queue_count: Default::default(),
+			max_upward_queue_size: Default::default(),
+			max_downward_message_size: Default::default(),
+			preferred_dispatchable_upward_messages_step_weight: Default::default(),
+			max_upward_message_size: Default::default(),
+			max_upward_message_num_per_candidate: Default::default(),
+			hrmp_open_request_ttl: Default::default(),
+			hrmp_sender_deposit: Default::default(),
+			hrmp_recipient_deposit: Default::default(),
+			hrmp_channel_max_capacity: Default::default(),
+			hrmp_channel_max_total_size: Default::default(),
+			hrmp_max_parachain_inbound_channels: Default::default(),
+			hrmp_max_parathread_inbound_channels: Default::default(),
+			hrmp_channel_max_message_size: Default::default(),
+			hrmp_max_parachain_outbound_channels: Default::default(),
+			hrmp_max_parathread_outbound_channels: Default::default(),
+			hrmp_max_message_num_per_candidate: Default::default(),
+		}
+	}
+}
+
+impl<BlockNumber: Zero> HostConfiguration<BlockNumber> {
+	/// Checks that this instance is consistent with the requirements on each individual member.
+	///
+	/// # Panic
+	///
+	/// This function panics if any member is not set properly.
+	fn check_consistency(&self) {
+		if self.group_rotation_frequency.is_zero() {
+			panic!("`group_rotation_frequency` must be non-zero!")
+		}
+
+		if self.chain_availability_period.is_zero() {
+			panic!("`chain_availability_period` must be at least 1!")
+		}
+
+		if self.thread_availability_period.is_zero() {
+			panic!("`thread_availability_period` must be at least 1!")
+		}
+
+		if self.no_show_slots.is_zero() {
+			panic!("`no_show_slots` must be at least 1!")
+		}
+	}
+}
+
 pub trait Config: frame_system::Config { }
 
 decl_storage! {
@@ -141,10 +219,18 @@ decl_storage! {
 		/// Pending configuration (if any) for the next session.
 		PendingConfig: Option<HostConfiguration<T::BlockNumber>>;
 	}
+	add_extra_genesis {
+		build(|config: &Self| {
+			config.config.check_consistency();
+		})
+	}
 }
 
 decl_error! {
-	pub enum Error for Module<T: Config> { }
+	pub enum Error for Module<T: Config> {
+		/// The new value for a configuration parameter is invalid.
+		InvalidNewValue,
+	}
 }
 
 decl_module! {
@@ -237,6 +323,9 @@ decl_module! {
 		#[weight = (1_000, DispatchClass::Operational)]
 		pub fn set_group_rotation_frequency(origin, new: T::BlockNumber) -> DispatchResult {
 			ensure_root(origin)?;
+
+			ensure!(!new.is_zero(), Error::<T>::InvalidNewValue);
+
 			Self::update_config_member(|config| {
 				sp_std::mem::replace(&mut config.group_rotation_frequency, new) != new
 			});
@@ -247,6 +336,9 @@ decl_module! {
 		#[weight = (1_000, DispatchClass::Operational)]
 		pub fn set_chain_availability_period(origin, new: T::BlockNumber) -> DispatchResult {
 			ensure_root(origin)?;
+
+			ensure!(!new.is_zero(), Error::<T>::InvalidNewValue);
+
 			Self::update_config_member(|config| {
 				sp_std::mem::replace(&mut config.chain_availability_period, new) != new
 			});
@@ -257,6 +349,9 @@ decl_module! {
 		#[weight = (1_000, DispatchClass::Operational)]
 		pub fn set_thread_availability_period(origin, new: T::BlockNumber) -> DispatchResult {
 			ensure_root(origin)?;
+
+			ensure!(!new.is_zero(), Error::<T>::InvalidNewValue);
+
 			Self::update_config_member(|config| {
 				sp_std::mem::replace(&mut config.thread_availability_period, new) != new
 			});
@@ -298,7 +393,9 @@ decl_module! {
 		#[weight = (1_000, DispatchClass::Operational)]
 		pub fn set_no_show_slots(origin, new: u32) -> DispatchResult {
 			ensure_root(origin)?;
-			ensure!(new >= 1, "no_show_slots must be at least 1");
+
+			ensure!(!new.is_zero(), Error::<T>::InvalidNewValue);
+
 			Self::update_config_member(|config| {
 				sp_std::mem::replace(&mut config.no_show_slots, new) != new
 			});
diff --git a/polkadot/runtime/parachains/src/initializer.rs b/polkadot/runtime/parachains/src/initializer.rs
index 15484ed5fb3..409b52a2607 100644
--- a/polkadot/runtime/parachains/src/initializer.rs
+++ b/polkadot/runtime/parachains/src/initializer.rs
@@ -33,7 +33,7 @@ use crate::{
 };
 
 /// Information about a session change that has just occurred.
-#[derive(Default, Clone)]
+#[derive(Clone)]
 pub struct SessionChangeNotification<BlockNumber> {
 	/// The new validators in the session.
 	pub validators: Vec<ValidatorId>,
@@ -49,6 +49,19 @@ pub struct SessionChangeNotification<BlockNumber> {
 	pub session_index: sp_staking::SessionIndex,
 }
 
+impl<BlockNumber: Default + From<u32>> Default for SessionChangeNotification<BlockNumber> {
+	fn default() -> Self {
+		Self {
+			validators: Vec::new(),
+			queued: Vec::new(),
+			prev_config: HostConfiguration::default(),
+			new_config: HostConfiguration::default(),
+			random_seed: Default::default(),
+			session_index: Default::default(),
+		}
+	}
+}
+
 #[derive(Encode, Decode)]
 struct BufferedSessionChange<N> {
 	apply_at: N,
diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs
index a5733612716..f099c0db150 100644
--- a/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs
+++ b/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs
@@ -26,7 +26,6 @@ use primitives::v1::{
 	GroupIndex, CandidateEvent, PersistedValidationData, SessionInfo,
 	InboundDownwardMessage, InboundHrmpMessage,
 };
-use sp_runtime::traits::Zero;
 use frame_support::debug;
 use crate::{initializer, inclusion, scheduler, configuration, paras, session_info, dmp, hrmp};
 
@@ -57,10 +56,6 @@ pub fn availability_cores<T: initializer::Config>() -> Vec<CoreState<T::BlockNum
 	let time_out_at = |backed_in_number, availability_period| {
 		let time_out_at = backed_in_number + availability_period;
 
-		if rotation_info.group_rotation_frequency == Zero::zero() {
-			return time_out_at;
-		}
-
 		let current_window = rotation_info.last_rotation_at() + availability_period;
 		let next_rotation = rotation_info.next_rotation_at();
 
diff --git a/polkadot/runtime/parachains/src/scheduler.rs b/polkadot/runtime/parachains/src/scheduler.rs
index 139e2dfa6a7..7eaf8e6c136 100644
--- a/polkadot/runtime/parachains/src/scheduler.rs
+++ b/polkadot/runtime/parachains/src/scheduler.rs
@@ -46,7 +46,7 @@ use frame_support::{
 	weights::Weight,
 };
 use parity_scale_codec::{Encode, Decode};
-use sp_runtime::traits::{Saturating, Zero};
+use sp_runtime::traits::Saturating;
 
 use rand::{SeedableRng, seq::SliceRandom};
 use rand_chacha::ChaCha20Rng;
@@ -568,11 +568,6 @@ impl<T: Config> Module<T> {
 
 		if at < session_start_block { return None }
 
-		if config.group_rotation_frequency.is_zero() {
-			// interpret this as "no rotations"
-			return Some(GroupIndex(core.0));
-		}
-
 		let validator_groups = ValidatorGroups::get();
 
 		if core.0 as usize >= validator_groups.len() { return None }
@@ -599,9 +594,6 @@ impl<T: Config> Module<T> {
 	/// timeouts, i.e. only within `max(config.chain_availability_period, config.thread_availability_period)`
 	/// of the last rotation would this return `Some`, unless there are no rotations.
 	///
-	/// If there are no rotations (config.group_rotation_frequency == 0),
-	/// availability timeouts can occur at any block.
-	///
 	/// This really should not be a box, but is working around a compiler limitation filed here:
 	/// https://github.com/rust-lang/rust/issues/73226
 	/// which prevents us from testing the code if using `impl Trait`.
@@ -611,12 +603,7 @@ impl<T: Config> Module<T> {
 
 		let session_start = <SessionStartBlock<T>>::get();
 		let blocks_since_session_start = now.saturating_sub(session_start);
-		let no_rotation = config.group_rotation_frequency.is_zero();
-		let blocks_since_last_rotation = if no_rotation {
-			<T::BlockNumber>::zero()
-		} else {
-			blocks_since_session_start % config.group_rotation_frequency
-		};
+		let blocks_since_last_rotation = blocks_since_session_start % config.group_rotation_frequency;
 
 		let absolute_cutoff = sp_std::cmp::max(
 			config.chain_availability_period,
@@ -1658,8 +1645,10 @@ mod tests {
 		} = default_config();
 		let collator = CollatorId::from(Sr25519Keyring::Alice.public());
 
-		assert!(chain_availability_period < thread_availability_period &&
-			thread_availability_period < group_rotation_frequency);
+		assert!(
+			chain_availability_period < thread_availability_period
+				&& thread_availability_period < group_rotation_frequency
+		);
 
 		let chain_a = ParaId::from(1);
 		let thread_a = ParaId::from(2);
@@ -1749,92 +1738,6 @@ mod tests {
 		});
 	}
 
-	#[test]
-	fn availability_predicate_no_rotation() {
-		let genesis_config = MockGenesisConfig {
-			configuration: crate::configuration::GenesisConfig {
-				config: HostConfiguration {
-					group_rotation_frequency: 0, // no rotation
-					..default_config()
-				},
-				..Default::default()
-			},
-			..Default::default()
-		};
-		let HostConfiguration {
-			chain_availability_period,
-			thread_availability_period,
-			..
-		} = default_config();
-		let collator = CollatorId::from(Sr25519Keyring::Alice.public());
-
-		let chain_a = ParaId::from(1);
-		let thread_a = ParaId::from(2);
-
-		let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs {
-			genesis_head: Vec::new().into(),
-			validation_code: Vec::new().into(),
-			parachain: is_chain,
-		});
-
-		new_test_ext(genesis_config).execute_with(|| {
-			schedule_blank_para(chain_a, true);
-			schedule_blank_para(thread_a, false);
-
-			// start a new session with our chain & thread registered.
-			run_to_block(1, |number| match number {
-				1 => Some(SessionChangeNotification {
-					new_config: HostConfiguration{
-						// Note: the `group_rotation_frequency` config change
-						// is not accounted for on session change
-						// group_rotation_frequency: 0,
-						..default_config()
-					},
-					validators: vec![
-						ValidatorId::from(Sr25519Keyring::Alice.public()),
-						ValidatorId::from(Sr25519Keyring::Bob.public()),
-						ValidatorId::from(Sr25519Keyring::Charlie.public()),
-						ValidatorId::from(Sr25519Keyring::Dave.public()),
-						ValidatorId::from(Sr25519Keyring::Eve.public()),
-					],
-					..Default::default()
-				}),
-				_ => None,
-			});
-
-			// assign some availability cores.
-			{
-				AvailabilityCores::mutate(|cores| {
-					cores[0] = Some(CoreOccupied::Parachain);
-					cores[1] = Some(CoreOccupied::Parathread(ParathreadEntry {
-						claim: ParathreadClaim(thread_a, collator),
-						retries: 0,
-					}))
-				});
-			}
-			run_to_block(1 + 1, |_| None);
-			run_to_block(1 + 1 + 100500, |_| None);
-			{
-				let pred = Scheduler::availability_timeout_predicate()
-					.expect("predicate exists with no rotation");
-
-				let now = System::block_number();
-
-				assert!(!pred(CoreIndex(0), now)); // assigned: chain
-				assert!(!pred(CoreIndex(1), now)); // assigned: thread
-				assert!(pred(CoreIndex(2), now));
-
-				// check the tighter bound on chains vs threads.
-				assert!(pred(CoreIndex(0), now - chain_availability_period));
-				assert!(pred(CoreIndex(1), now - thread_availability_period));
-
-				// check the threshold is exact.
-				assert!(!pred(CoreIndex(0), now - chain_availability_period + 1));
-				assert!(!pred(CoreIndex(1), now - thread_availability_period + 1));
-			}
-		});
-	}
-
 	#[test]
 	fn next_up_on_available_uses_next_scheduled_or_none_for_thread() {
 		let mut config = default_config();
-- 
GitLab