From eb864e086cfb8e82d763f35da0c4e265b096cef9 Mon Sep 17 00:00:00 2001 From: alindima Date: Tue, 22 Aug 2023 11:33:41 +0300 Subject: [PATCH] add runtime migration --- node/core/backing/src/lib.rs | 1 + node/core/runtime-api/src/tests.rs | 4 + primitives/src/runtime_api.rs | 1 + runtime/kusama/src/lib.rs | 1 + runtime/parachains/src/configuration.rs | 17 +- .../parachains/src/configuration/migration.rs | 1 + .../src/configuration/migration/v8.rs | 105 +++++- .../src/configuration/migration/v9.rs | 321 ++++++++++++++++++ runtime/parachains/src/configuration/tests.rs | 6 + runtime/parachains/src/inclusion/tests.rs | 6 +- .../parachains/src/paras_inherent/tests.rs | 239 ++++++------- runtime/polkadot/src/lib.rs | 2 + runtime/rococo/src/lib.rs | 1 + runtime/westend/src/lib.rs | 1 + 14 files changed, 583 insertions(+), 123 deletions(-) create mode 100644 runtime/parachains/src/configuration/migration/v9.rs diff --git a/node/core/backing/src/lib.rs b/node/core/backing/src/lib.rs index fa11183a5cc0..9af0d6046b96 100644 --- a/node/core/backing/src/lib.rs +++ b/node/core/backing/src/lib.rs @@ -1000,6 +1000,7 @@ async fn construct_per_relay_parent_state( try_runtime_api!(runtime_info.get_session_index_for_child(ctx.sender(), parent).await); let minimum_backing_votes = runtime_info.get_min_backing_votes(ctx.sender(), session_index, parent).await; + // TODO: if this does not exist, fall back to the hardcoded 2 value. let (validators, groups, cores) = futures::try_join!( request_validators(parent, ctx.sender()).await, diff --git a/node/core/runtime-api/src/tests.rs b/node/core/runtime-api/src/tests.rs index c3f8108312be..f448fa942198 100644 --- a/node/core/runtime-api/src/tests.rs +++ b/node/core/runtime-api/src/tests.rs @@ -264,6 +264,10 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient { ) -> Result, ApiError> { todo!("Not required for tests") } + + async fn minimum_backing_votes(&self, _: Hash) -> Result { + todo!("Not required for tests") + } } #[test] diff --git a/primitives/src/runtime_api.rs b/primitives/src/runtime_api.rs index 0c338e85ef2e..d7eadc43ed69 100644 --- a/primitives/src/runtime_api.rs +++ b/primitives/src/runtime_api.rs @@ -241,6 +241,7 @@ sp_api::decl_runtime_apis! { ) -> Option<()>; /// Get the minimum number of backing votes for a parachain candidate. + /// TODO: bump api version here? fn minimum_backing_votes() -> u32; /***** Asynchronous backing *****/ diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index 41fce1721c76..4ec57758f2f1 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -1606,6 +1606,7 @@ pub mod migrations { frame_support::migrations::RemovePallet::DbWeight>, frame_support::migrations::RemovePallet::DbWeight>, + parachains_configuration::migration::v9::MigrateToV9, ); } diff --git a/runtime/parachains/src/configuration.rs b/runtime/parachains/src/configuration.rs index f3ee2dc80865..6d6fb53a447b 100644 --- a/runtime/parachains/src/configuration.rs +++ b/runtime/parachains/src/configuration.rs @@ -295,7 +295,7 @@ impl> Default for HostConfiguration (remove UMP dispatch queue) /// v6-v7: /// v7-v8: - const STORAGE_VERSION: StorageVersion = StorageVersion::new(8); + /// v8-v9: + const STORAGE_VERSION: StorageVersion = StorageVersion::new(9); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -1154,6 +1155,18 @@ pub mod pallet { config.on_demand_ttl = new; }) } + /// Set the minimum backing votes threshold. + #[pallet::call_index(52)] + #[pallet::weight(( + T::WeightInfo::set_config_with_u32(), + DispatchClass::Operational + ))] + pub fn set_minimum_backing_votes(origin: OriginFor, new: u32) -> DispatchResult { + ensure_root(origin)?; + Self::schedule_config_update(|config| { + config.minimum_backing_votes = new; + }) + } } #[pallet::hooks] diff --git a/runtime/parachains/src/configuration/migration.rs b/runtime/parachains/src/configuration/migration.rs index 4499b116462b..26f8a85b496d 100644 --- a/runtime/parachains/src/configuration/migration.rs +++ b/runtime/parachains/src/configuration/migration.rs @@ -19,3 +19,4 @@ pub mod v6; pub mod v7; pub mod v8; +pub mod v9; diff --git a/runtime/parachains/src/configuration/migration/v8.rs b/runtime/parachains/src/configuration/migration/v8.rs index 2fe23f37fa60..5c5b34821835 100644 --- a/runtime/parachains/src/configuration/migration/v8.rs +++ b/runtime/parachains/src/configuration/migration/v8.rs @@ -23,14 +23,114 @@ use frame_support::{ weights::Weight, }; use frame_system::pallet_prelude::BlockNumberFor; -use primitives::SessionIndex; +use primitives::{ + vstaging::AsyncBackingParams, Balance, ExecutorParams, SessionIndex, + ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, +}; use sp_runtime::Perbill; use sp_std::vec::Vec; use frame_support::traits::OnRuntimeUpgrade; use super::v7::V7HostConfiguration; -type V8HostConfiguration = configuration::HostConfiguration; +/// All configuration of the runtime with respect to paras. +#[derive(Clone, Encode, Decode, Debug)] +pub struct V8HostConfiguration { + pub max_code_size: u32, + pub max_head_data_size: u32, + pub max_upward_queue_count: u32, + pub max_upward_queue_size: u32, + pub max_upward_message_size: u32, + pub max_upward_message_num_per_candidate: u32, + pub hrmp_max_message_num_per_candidate: u32, + pub validation_upgrade_cooldown: BlockNumber, + pub validation_upgrade_delay: BlockNumber, + pub async_backing_params: AsyncBackingParams, + pub max_pov_size: u32, + pub max_downward_message_size: u32, + pub hrmp_max_parachain_outbound_channels: u32, + pub hrmp_sender_deposit: Balance, + pub hrmp_recipient_deposit: Balance, + pub hrmp_channel_max_capacity: u32, + pub hrmp_channel_max_total_size: u32, + pub hrmp_max_parachain_inbound_channels: u32, + pub hrmp_channel_max_message_size: u32, + pub executor_params: ExecutorParams, + pub code_retention_period: BlockNumber, + pub on_demand_cores: u32, + pub on_demand_retries: u32, + pub on_demand_queue_max_size: u32, + pub on_demand_target_queue_utilization: Perbill, + pub on_demand_fee_variability: Perbill, + pub on_demand_base_fee: Balance, + pub on_demand_ttl: BlockNumber, + pub group_rotation_frequency: BlockNumber, + pub paras_availability_period: BlockNumber, + pub scheduling_lookahead: u32, + pub max_validators_per_core: Option, + pub max_validators: Option, + pub dispute_period: SessionIndex, + pub dispute_post_conclusion_acceptance_period: BlockNumber, + pub no_show_slots: u32, + pub n_delay_tranches: u32, + pub zeroth_delay_tranche_width: u32, + pub needed_approvals: u32, + pub relay_vrf_modulo_samples: u32, + pub pvf_voting_ttl: SessionIndex, + pub minimum_validation_upgrade_delay: BlockNumber, +} + +impl> Default for V8HostConfiguration { + fn default() -> Self { + Self { + async_backing_params: AsyncBackingParams { + max_candidate_depth: 0, + allowed_ancestry_len: 0, + }, + group_rotation_frequency: 1u32.into(), + paras_availability_period: 1u32.into(), + no_show_slots: 1u32.into(), + validation_upgrade_cooldown: Default::default(), + validation_upgrade_delay: 2u32.into(), + code_retention_period: Default::default(), + max_code_size: Default::default(), + max_pov_size: Default::default(), + max_head_data_size: Default::default(), + on_demand_cores: Default::default(), + on_demand_retries: Default::default(), + scheduling_lookahead: 1, + max_validators_per_core: Default::default(), + max_validators: None, + dispute_period: 6, + dispute_post_conclusion_acceptance_period: 100.into(), + 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(), + max_upward_message_size: Default::default(), + max_upward_message_num_per_candidate: 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_channel_max_message_size: Default::default(), + hrmp_max_parachain_outbound_channels: Default::default(), + hrmp_max_message_num_per_candidate: Default::default(), + pvf_voting_ttl: 2u32.into(), + minimum_validation_upgrade_delay: 2.into(), + executor_params: Default::default(), + on_demand_queue_max_size: ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, + on_demand_base_fee: 10_000_000u128, + on_demand_fee_variability: Perbill::from_percent(3), + on_demand_target_queue_utilization: Perbill::from_percent(25), + on_demand_ttl: 5u32.into(), + } + } +} mod v7 { use super::*; @@ -150,7 +250,6 @@ on_demand_base_fee : 10_000_000u128, on_demand_fee_variability : Perbill::from_percent(3), on_demand_target_queue_utilization : Perbill::from_percent(25), on_demand_ttl : 5u32.into(), -minimum_backing_votes : 2 } }; diff --git a/runtime/parachains/src/configuration/migration/v9.rs b/runtime/parachains/src/configuration/migration/v9.rs new file mode 100644 index 000000000000..56d89959258a --- /dev/null +++ b/runtime/parachains/src/configuration/migration/v9.rs @@ -0,0 +1,321 @@ +// 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 . + +//! A module that is responsible for migration of storage. + +use crate::configuration::{self, Config, Pallet}; +use frame_support::{ + pallet_prelude::*, + traits::{Defensive, StorageVersion}, + weights::Weight, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use primitives::SessionIndex; +use sp_runtime::Perbill; +use sp_std::vec::Vec; + +use frame_support::traits::OnRuntimeUpgrade; + +use super::v8::V8HostConfiguration; +type V9HostConfiguration = configuration::HostConfiguration; + +mod v8 { + use super::*; + + #[frame_support::storage_alias] + pub(crate) type ActiveConfig = + StorageValue, V8HostConfiguration>, OptionQuery>; + + #[frame_support::storage_alias] + pub(crate) type PendingConfigs = StorageValue< + Pallet, + Vec<(SessionIndex, V8HostConfiguration>)>, + OptionQuery, + >; +} + +mod v9 { + use super::*; + + #[frame_support::storage_alias] + pub(crate) type ActiveConfig = + StorageValue, V9HostConfiguration>, OptionQuery>; + + #[frame_support::storage_alias] + pub(crate) type PendingConfigs = StorageValue< + Pallet, + Vec<(SessionIndex, V9HostConfiguration>)>, + OptionQuery, + >; +} + +pub struct MigrateToV9(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for MigrateToV9 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade() for HostConfiguration MigrateToV9"); + Ok(Vec::new()) + } + + fn on_runtime_upgrade() -> Weight { + log::info!(target: configuration::LOG_TARGET, "HostConfiguration MigrateToV9 started"); + if StorageVersion::get::>() == 8 { + let weight_consumed = migrate_to_v9::(); + + log::info!(target: configuration::LOG_TARGET, "HostConfiguration MigrateToV9 executed successfully"); + StorageVersion::new(9).put::>(); + + weight_consumed + } else { + log::warn!(target: configuration::LOG_TARGET, "HostConfiguration MigrateToV9 should be removed."); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + log::trace!(target: crate::configuration::LOG_TARGET, "Running post_upgrade() for HostConfiguration MigrateToV9"); + ensure!( + StorageVersion::get::>() >= 9, + "Storage version should be >= 9 after the migration" + ); + + Ok(()) + } +} + +fn migrate_to_v9() -> Weight { + // Unusual formatting is justified: + // - make it easier to verify that fields assign what they supposed to assign. + // - this code is transient and will be removed after all migrations are done. + // - this code is important enough to optimize for legibility sacrificing consistency. + #[rustfmt::skip] + let translate = + |pre: V8HostConfiguration>| -> + V9HostConfiguration> + { + V9HostConfiguration { +max_code_size : pre.max_code_size, +max_head_data_size : pre.max_head_data_size, +max_upward_queue_count : pre.max_upward_queue_count, +max_upward_queue_size : pre.max_upward_queue_size, +max_upward_message_size : pre.max_upward_message_size, +max_upward_message_num_per_candidate : pre.max_upward_message_num_per_candidate, +hrmp_max_message_num_per_candidate : pre.hrmp_max_message_num_per_candidate, +validation_upgrade_cooldown : pre.validation_upgrade_cooldown, +validation_upgrade_delay : pre.validation_upgrade_delay, +max_pov_size : pre.max_pov_size, +max_downward_message_size : pre.max_downward_message_size, +hrmp_sender_deposit : pre.hrmp_sender_deposit, +hrmp_recipient_deposit : pre.hrmp_recipient_deposit, +hrmp_channel_max_capacity : pre.hrmp_channel_max_capacity, +hrmp_channel_max_total_size : pre.hrmp_channel_max_total_size, +hrmp_max_parachain_inbound_channels : pre.hrmp_max_parachain_inbound_channels, +hrmp_max_parachain_outbound_channels : pre.hrmp_max_parachain_outbound_channels, +hrmp_channel_max_message_size : pre.hrmp_channel_max_message_size, +code_retention_period : pre.code_retention_period, +on_demand_cores : pre.on_demand_cores, +on_demand_retries : pre.on_demand_retries, +group_rotation_frequency : pre.group_rotation_frequency, +paras_availability_period : pre.paras_availability_period, +scheduling_lookahead : pre.scheduling_lookahead, +max_validators_per_core : pre.max_validators_per_core, +max_validators : pre.max_validators, +dispute_period : pre.dispute_period, +dispute_post_conclusion_acceptance_period: pre.dispute_post_conclusion_acceptance_period, +no_show_slots : pre.no_show_slots, +n_delay_tranches : pre.n_delay_tranches, +zeroth_delay_tranche_width : pre.zeroth_delay_tranche_width, +needed_approvals : pre.needed_approvals, +relay_vrf_modulo_samples : pre.relay_vrf_modulo_samples, +pvf_voting_ttl : pre.pvf_voting_ttl, +minimum_validation_upgrade_delay : pre.minimum_validation_upgrade_delay, +async_backing_params : pre.async_backing_params, +executor_params : pre.executor_params, +on_demand_queue_max_size : 10_000u32, +on_demand_base_fee : 10_000_000u128, +on_demand_fee_variability : Perbill::from_percent(3), +on_demand_target_queue_utilization : Perbill::from_percent(25), +on_demand_ttl : 5u32.into(), +minimum_backing_votes : 2 + } + }; + + let v8 = v8::ActiveConfig::::get() + .defensive_proof("Could not decode old config") + .unwrap_or_default(); + let v9 = translate(v8); + v9::ActiveConfig::::set(Some(v9)); + + // Allowed to be empty. + let pending_v8 = v8::PendingConfigs::::get().unwrap_or_default(); + let mut pending_v9 = Vec::new(); + + for (session, v8) in pending_v8.into_iter() { + let v9 = translate(v8); + pending_v9.push((session, v9)); + } + v9::PendingConfigs::::set(Some(pending_v9.clone())); + + let num_configs = (pending_v9.len() + 1) as u64; + T::DbWeight::get().reads_writes(num_configs, num_configs) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{new_test_ext, Test}; + + #[test] + fn v9_deserialized_from_actual_data() { + // Example how to get new `raw_config`: + // We'll obtain the raw_config at a specified a block + // Steps: + // 1. Go to Polkadot.js -> Developer -> Chain state -> Storage: https://polkadot.js.org/apps/#/chainstate + // 2. Set these parameters: + // 2.1. selected state query: configuration; activeConfig(): + // PolkadotRuntimeParachainsConfigurationHostConfiguration + // 2.2. blockhash to query at: + // 0xf89d3ab5312c5f70d396dc59612f0aa65806c798346f9db4b35278baed2e0e53 (the hash of + // the block) + // 2.3. Note the value of encoded storage key -> + // 0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385 for the + // referenced block. + // 2.4. You'll also need the decoded values to update the test. + // 3. Go to Polkadot.js -> Developer -> Chain state -> Raw storage + // 3.1 Enter the encoded storage key and you get the raw config. + + // This exceeds the maximal line width length, but that's fine, since this is not code and + // doesn't need to be read and also leaving it as one line allows to easily copy it. + let raw_config = + hex_literal::hex![" + 0000300000800000080000000000100000c8000005000000050000000200000002000000000000000000000000005000000010000400000000000000000000000000000000000000000000000000000000000000000000000800000000200000040000000000100000b004000000000000000000001027000080b2e60e80c3c901809698000000000000000000000000000500000014000000040000000100000001010000000006000000640000000200000019000000000000000300000002000000020000000500000002000000" + ]; + + let v9 = + V9HostConfiguration::::decode(&mut &raw_config[..]).unwrap(); + + // We check only a sample of the values here. If we missed any fields or messed up data + // types that would skew all the fields coming after. + assert_eq!(v9.max_code_size, 3_145_728); + assert_eq!(v9.validation_upgrade_cooldown, 2); + assert_eq!(v9.max_pov_size, 5_242_880); + assert_eq!(v9.hrmp_channel_max_message_size, 1_048_576); + assert_eq!(v9.n_delay_tranches, 25); + assert_eq!(v9.minimum_validation_upgrade_delay, 5); + assert_eq!(v9.group_rotation_frequency, 20); + assert_eq!(v9.on_demand_cores, 0); + assert_eq!(v9.on_demand_base_fee, 10_000_000); + assert_eq!(v9.minimum_backing_votes, 2); + } + + #[test] + fn test_migrate_to_v9() { + // Host configuration has lots of fields. However, in this migration we only add one + // field. The most important part to check are a couple of the last fields. We also pick + // extra fields to check arbitrarily, e.g. depending on their position (i.e. the middle) and + // also their type. + // + // We specify only the picked fields and the rest should be provided by the `Default` + // implementation. That implementation is copied over between the two types and should work + // fine. + let v8 = V8HostConfiguration:: { + needed_approvals: 69, + paras_availability_period: 55, + hrmp_recipient_deposit: 1337, + max_pov_size: 1111, + minimum_validation_upgrade_delay: 20, + ..Default::default() + }; + + let mut pending_configs = Vec::new(); + pending_configs.push((100, v8.clone())); + pending_configs.push((300, v8.clone())); + + new_test_ext(Default::default()).execute_with(|| { + // Implant the v8 version in the state. + v8::ActiveConfig::::set(Some(v8)); + v8::PendingConfigs::::set(Some(pending_configs)); + + migrate_to_v9::(); + + let v9 = v9::ActiveConfig::::get().unwrap(); + let mut configs_to_check = v9::PendingConfigs::::get().unwrap(); + configs_to_check.push((0, v9.clone())); + + for (_, v8) in configs_to_check { + #[rustfmt::skip] + { + assert_eq!(v8.max_code_size , v9.max_code_size); + assert_eq!(v8.max_head_data_size , v9.max_head_data_size); + assert_eq!(v8.max_upward_queue_count , v9.max_upward_queue_count); + assert_eq!(v8.max_upward_queue_size , v9.max_upward_queue_size); + assert_eq!(v8.max_upward_message_size , v9.max_upward_message_size); + assert_eq!(v8.max_upward_message_num_per_candidate , v9.max_upward_message_num_per_candidate); + assert_eq!(v8.hrmp_max_message_num_per_candidate , v9.hrmp_max_message_num_per_candidate); + assert_eq!(v8.validation_upgrade_cooldown , v9.validation_upgrade_cooldown); + assert_eq!(v8.validation_upgrade_delay , v9.validation_upgrade_delay); + assert_eq!(v8.max_pov_size , v9.max_pov_size); + assert_eq!(v8.max_downward_message_size , v9.max_downward_message_size); + assert_eq!(v8.hrmp_max_parachain_outbound_channels , v9.hrmp_max_parachain_outbound_channels); + assert_eq!(v8.hrmp_sender_deposit , v9.hrmp_sender_deposit); + assert_eq!(v8.hrmp_recipient_deposit , v9.hrmp_recipient_deposit); + assert_eq!(v8.hrmp_channel_max_capacity , v9.hrmp_channel_max_capacity); + assert_eq!(v8.hrmp_channel_max_total_size , v9.hrmp_channel_max_total_size); + assert_eq!(v8.hrmp_max_parachain_inbound_channels , v9.hrmp_max_parachain_inbound_channels); + assert_eq!(v8.hrmp_channel_max_message_size , v9.hrmp_channel_max_message_size); + assert_eq!(v8.code_retention_period , v9.code_retention_period); + assert_eq!(v8.on_demand_cores , v9.on_demand_cores); + assert_eq!(v8.on_demand_retries , v9.on_demand_retries); + assert_eq!(v8.group_rotation_frequency , v9.group_rotation_frequency); + assert_eq!(v8.paras_availability_period , v9.paras_availability_period); + assert_eq!(v8.scheduling_lookahead , v9.scheduling_lookahead); + assert_eq!(v8.max_validators_per_core , v9.max_validators_per_core); + assert_eq!(v8.max_validators , v9.max_validators); + assert_eq!(v8.dispute_period , v9.dispute_period); + assert_eq!(v8.no_show_slots , v9.no_show_slots); + assert_eq!(v8.n_delay_tranches , v9.n_delay_tranches); + assert_eq!(v8.zeroth_delay_tranche_width , v9.zeroth_delay_tranche_width); + assert_eq!(v8.needed_approvals , v9.needed_approvals); + assert_eq!(v8.relay_vrf_modulo_samples , v9.relay_vrf_modulo_samples); + assert_eq!(v8.pvf_voting_ttl , v9.pvf_voting_ttl); + assert_eq!(v8.minimum_validation_upgrade_delay , v9.minimum_validation_upgrade_delay); + assert_eq!(v8.async_backing_params.allowed_ancestry_len, v9.async_backing_params.allowed_ancestry_len); + assert_eq!(v8.async_backing_params.max_candidate_depth , v9.async_backing_params.max_candidate_depth); + assert_eq!(v8.executor_params , v9.executor_params); + assert_eq!(v8.minimum_backing_votes , v9.minimum_backing_votes); + }; // ; makes this a statement. `rustfmt::skip` cannot be put on an expression. + } + }); + } + + // Test that migration doesn't panic in case there're no pending configurations upgrades in + // pallet's storage. + #[test] + fn test_migrate_to_v9_no_pending() { + let v8 = V8HostConfiguration::::default(); + + new_test_ext(Default::default()).execute_with(|| { + // Implant the v8 version in the state. + v8::ActiveConfig::::set(Some(v8)); + // Ensure there're no pending configs. + v8::PendingConfigs::::set(None); + + // Shouldn't fail. + migrate_to_v9::(); + }); + } +} diff --git a/runtime/parachains/src/configuration/tests.rs b/runtime/parachains/src/configuration/tests.rs index 83de7db932b4..690bb43153f8 100644 --- a/runtime/parachains/src/configuration/tests.rs +++ b/runtime/parachains/src/configuration/tests.rs @@ -317,6 +317,7 @@ fn setting_pending_config_members() { on_demand_fee_variability: Perbill::from_percent(3), on_demand_target_queue_utilization: Perbill::from_percent(25), on_demand_ttl: 5u32, + minimum_backing_votes: 5, }; Configuration::set_validation_upgrade_cooldown( @@ -467,6 +468,11 @@ fn setting_pending_config_members() { .unwrap(); Configuration::set_pvf_voting_ttl(RuntimeOrigin::root(), new_config.pvf_voting_ttl) .unwrap(); + Configuration::set_minimum_backing_votes( + RuntimeOrigin::root(), + new_config.minimum_backing_votes, + ) + .unwrap(); assert_eq!(PendingConfigs::::get(), vec![(shared::SESSION_DELAY, new_config)],); }) diff --git a/runtime/parachains/src/inclusion/tests.rs b/runtime/parachains/src/inclusion/tests.rs index 7c22ac36a802..8df9befd5fa7 100644 --- a/runtime/parachains/src/inclusion/tests.rs +++ b/runtime/parachains/src/inclusion/tests.rs @@ -85,6 +85,10 @@ fn default_allowed_relay_parent_tracker() -> AllowedRelayParentsTracker usize { + std::cmp::min(group_len, configuration::Pallet::::config().minimum_backing_votes as usize) +} + #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum BackingKind { #[allow(unused)] @@ -124,7 +128,7 @@ pub(crate) fn back_candidate( let signing = match kind { BackingKind::Unanimous => group.len(), - BackingKind::Threshold => threshold, + BackingKind::Threshold => threshold as usize, BackingKind::Lacking => threshold.saturating_sub(1), }; diff --git a/runtime/parachains/src/paras_inherent/tests.rs b/runtime/parachains/src/paras_inherent/tests.rs index 1e5271909664..ab515cb37565 100644 --- a/runtime/parachains/src/paras_inherent/tests.rs +++ b/runtime/parachains/src/paras_inherent/tests.rs @@ -948,8 +948,11 @@ fn default_header() -> primitives::Header { mod sanitizers { use super::*; - use crate::inclusion::tests::{ - back_candidate, collator_sign_candidate, BackingKind, TestCandidateBuilder, + use crate::{ + inclusion::tests::{ + back_candidate, collator_sign_candidate, BackingKind, TestCandidateBuilder, + }, + mock::{new_test_ext, MockGenesisConfig}, }; use bitvec::order::Lsb0; use primitives::{ @@ -1207,131 +1210,133 @@ mod sanitizers { #[test] fn candidates() { - const RELAY_PARENT_NUM: u32 = 3; - - let header = default_header(); - let relay_parent = header.hash(); - let session_index = SessionIndex::from(0_u32); - - let keystore = LocalKeystore::in_memory(); - let keystore = Arc::new(keystore) as KeystorePtr; - let signing_context = SigningContext { parent_hash: relay_parent, session_index }; - - let validators = vec![ - keyring::Sr25519Keyring::Alice, - keyring::Sr25519Keyring::Bob, - keyring::Sr25519Keyring::Charlie, - keyring::Sr25519Keyring::Dave, - ]; - for validator in validators.iter() { - Keystore::sr25519_generate_new( - &*keystore, - PARACHAIN_KEY_TYPE_ID, - Some(&validator.to_seed()), - ) - .unwrap(); - } - - let has_concluded_invalid = - |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; - - let entry_ttl = 10_000; - let scheduled = (0_usize..2) - .into_iter() - .map(|idx| { - let core_idx = CoreIndex::from(idx as u32); - let ca = CoreAssignment { - paras_entry: ParasEntry::new( - Assignment::new(ParaId::from(1_u32 + idx as u32)), - entry_ttl, - ), - core: core_idx, - }; - ca - }) - .collect::>(); - - let group_validators = |group_index: GroupIndex| { - match group_index { - group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]), - group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]), - _ => panic!("Group index out of bounds for 2 parachains and 1 parathread core"), + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + const RELAY_PARENT_NUM: u32 = 3; + + let header = default_header(); + let relay_parent = header.hash(); + let session_index = SessionIndex::from(0_u32); + + let keystore = LocalKeystore::in_memory(); + let keystore = Arc::new(keystore) as KeystorePtr; + let signing_context = SigningContext { parent_hash: relay_parent, session_index }; + + let validators = vec![ + keyring::Sr25519Keyring::Alice, + keyring::Sr25519Keyring::Bob, + keyring::Sr25519Keyring::Charlie, + keyring::Sr25519Keyring::Dave, + ]; + for validator in validators.iter() { + Keystore::sr25519_generate_new( + &*keystore, + PARACHAIN_KEY_TYPE_ID, + Some(&validator.to_seed()), + ) + .unwrap(); } - .map(|m| m.into_iter().map(ValidatorIndex).collect::>()) - }; - - let backed_candidates = (0_usize..2) - .into_iter() - .map(|idx0| { - let idx1 = idx0 + 1; - let mut candidate = TestCandidateBuilder { - para_id: ParaId::from(idx1), - relay_parent, - pov_hash: Hash::repeat_byte(idx1 as u8), - persisted_validation_data_hash: [42u8; 32].into(), - hrmp_watermark: RELAY_PARENT_NUM, - ..Default::default() - } - .build(); - - collator_sign_candidate(Sr25519Keyring::One, &mut candidate); - let backed = back_candidate( - candidate, - &validators, - group_validators(GroupIndex::from(idx0 as u32)).unwrap().as_ref(), - &keystore, - &signing_context, - BackingKind::Threshold, - ); - backed - }) - .collect::>(); - - // happy path - assert_eq!( - sanitize_backed_candidates::( - backed_candidates.clone(), - has_concluded_invalid, - &scheduled - ), - backed_candidates - ); - - // nothing is scheduled, so no paraids match, thus all backed candidates are skipped - { - let scheduled = &Vec::new(); - assert!(sanitize_backed_candidates::( - backed_candidates.clone(), - has_concluded_invalid, - &scheduled - ) - .is_empty()); - } + let has_concluded_invalid = + |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; - // candidates that have concluded as invalid are filtered out - { - // mark every second one as concluded invalid - let set = { - let mut set = std::collections::HashSet::new(); - for (idx, backed_candidate) in backed_candidates.iter().enumerate() { - if idx & 0x01 == 0 { - set.insert(backed_candidate.hash()); - } + let entry_ttl = 10_000; + let scheduled = (0_usize..2) + .into_iter() + .map(|idx| { + let core_idx = CoreIndex::from(idx as u32); + let ca = CoreAssignment { + paras_entry: ParasEntry::new( + Assignment::new(ParaId::from(1_u32 + idx as u32)), + entry_ttl, + ), + core: core_idx, + }; + ca + }) + .collect::>(); + + let group_validators = |group_index: GroupIndex| { + match group_index { + group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]), + group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]), + _ => panic!("Group index out of bounds for 2 parachains and 1 parathread core"), } - set + .map(|m| m.into_iter().map(ValidatorIndex).collect::>()) }; - let has_concluded_invalid = - |_idx: usize, candidate: &BackedCandidate| set.contains(&candidate.hash()); + + let backed_candidates = (0_usize..2) + .into_iter() + .map(|idx0| { + let idx1 = idx0 + 1; + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(idx1), + relay_parent, + pov_hash: Hash::repeat_byte(idx1 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(idx0 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + ); + backed + }) + .collect::>(); + + // happy path assert_eq!( sanitize_backed_candidates::( backed_candidates.clone(), has_concluded_invalid, &scheduled - ) - .len(), - backed_candidates.len() / 2 + ), + backed_candidates ); - } + + // nothing is scheduled, so no paraids match, thus all backed candidates are skipped + { + let scheduled = &Vec::new(); + assert!(sanitize_backed_candidates::( + backed_candidates.clone(), + has_concluded_invalid, + &scheduled + ) + .is_empty()); + } + + // candidates that have concluded as invalid are filtered out + { + // mark every second one as concluded invalid + let set = { + let mut set = std::collections::HashSet::new(); + for (idx, backed_candidate) in backed_candidates.iter().enumerate() { + if idx & 0x01 == 0 { + set.insert(backed_candidate.hash()); + } + } + set + }; + let has_concluded_invalid = + |_idx: usize, candidate: &BackedCandidate| set.contains(&candidate.hash()); + assert_eq!( + sanitize_backed_candidates::( + backed_candidates.clone(), + has_concluded_invalid, + &scheduled + ) + .len(), + backed_candidates.len() / 2 + ); + } + }); } } diff --git a/runtime/polkadot/src/lib.rs b/runtime/polkadot/src/lib.rs index 88b1d593aaab..cb53b33ba959 100644 --- a/runtime/polkadot/src/lib.rs +++ b/runtime/polkadot/src/lib.rs @@ -1525,6 +1525,8 @@ pub mod migrations { frame_support::migrations::RemovePallet::DbWeight>, frame_support::migrations::RemovePallet::DbWeight>, frame_support::migrations::RemovePallet::DbWeight>, + + parachains_configuration::migration::v9::MigrateToV9, ); } diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index 997f65454f04..cb799325471c 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -1553,6 +1553,7 @@ pub mod migrations { assigned_slots::migration::v1::VersionCheckedMigrateToV1, parachains_scheduler::migration::v1::MigrateToV1, parachains_configuration::migration::v8::MigrateToV8, + parachains_configuration::migration::v9::MigrateToV9, ); } diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 67594220a13a..4a217da8695b 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -1291,6 +1291,7 @@ pub mod migrations { assigned_slots::migration::v1::VersionCheckedMigrateToV1, parachains_scheduler::migration::v1::MigrateToV1, parachains_configuration::migration::v8::MigrateToV8, + parachains_configuration::migration::v9::MigrateToV9, ); }