diff --git a/pallets/collator-assignment/src/lib.rs b/pallets/collator-assignment/src/lib.rs index f2eca9f31..f8f29d783 100644 --- a/pallets/collator-assignment/src/lib.rs +++ b/pallets/collator-assignment/src/lib.rs @@ -45,6 +45,7 @@ pub use pallet::*; use { crate::weights::WeightInfo, frame_support::pallet_prelude::*, + frame_system::pallet_prelude::BlockNumberFor, rand::{seq::SliceRandom, SeedableRng}, rand_chacha::ChaCha20Rng, sp_runtime::{ @@ -93,6 +94,7 @@ pub mod pallet { type HostConfiguration: GetHostConfiguration; type ContainerChains: GetSessionContainerChains; type ShouldRotateAllCollators: ShouldRotateAllCollators; + type GetRandomnessForNextBlock: GetRandomnessForNextBlock>; /// The weight information of this pallet. type WeightInfo: WeightInfo; } @@ -124,6 +126,13 @@ pub mod pallet { pub(crate) type PendingCollatorContainerChain = StorageValue<_, Option>, ValueQuery>; + /// Randomness from previous block. Used to shuffle collators on session change. + /// Should only be set on the last block of each session and should be killed on the on_initialize of the next block. + /// The default value of [0; 32] disables randomness in the pallet. + #[pallet::storage] + #[pallet::getter(fn randomness)] + pub(crate) type Randomness = StorageValue<_, [u8; 32], ValueQuery>; + #[pallet::call] impl Pallet {} @@ -371,9 +380,9 @@ pub mod pallet { pub fn initializer_on_new_session( session_index: &T::SessionIndex, - random_seed: [u8; 32], collators: Vec, ) -> SessionChangeOutcome { + let random_seed = Randomness::::take(); let num_collators = collators.len(); let assigned_collators = Self::assign_collators(session_index, random_seed, collators); let num_parachains = assigned_collators.next_assignment.container_chains.len(); @@ -406,6 +415,28 @@ pub mod pallet { CollatorContainerChain::::put(assigned_collators); } } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(n: BlockNumberFor) -> Weight { + let mut weight = Weight::zero(); + + // Account reads and writes for on_finalize + if T::GetRandomnessForNextBlock::should_end_session(n.saturating_add(One::one())) { + weight += T::DbWeight::get().reads_writes(1, 1); + } + + weight + } + + fn on_finalize(n: BlockNumberFor) { + // If the next block is a session change, read randomness and store in pallet storage + if T::GetRandomnessForNextBlock::should_end_session(n.saturating_add(One::one())) { + let random_seed = T::GetRandomnessForNextBlock::get_randomness(); + Randomness::::put(random_seed); + } + } + } } pub struct RotateCollatorsEveryNSessions(PhantomData); @@ -418,3 +449,8 @@ where session_index % Period::get() == 0 } } + +pub trait GetRandomnessForNextBlock { + fn should_end_session(block_number: BlockNumber) -> bool; + fn get_randomness() -> [u8; 32]; +} diff --git a/pallets/collator-assignment/src/mock.rs b/pallets/collator-assignment/src/mock.rs index 9aac5a0a9..b6c9b721f 100644 --- a/pallets/collator-assignment/src/mock.rs +++ b/pallets/collator-assignment/src/mock.rs @@ -14,8 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Tanssi. If not, see +use frame_support::traits::Hooks; + use { - crate::{self as pallet_collator_assignment, RotateCollatorsEveryNSessions}, + crate::{ + self as pallet_collator_assignment, GetRandomnessForNextBlock, + RotateCollatorsEveryNSessions, + }, frame_support::{ parameter_types, traits::{ConstU16, ConstU64}, @@ -161,6 +166,18 @@ impl tp_traits::GetSessionContainerChains for ContainerChainsGetter { } } +pub struct MockGetRandomnessForNextBlock; + +impl GetRandomnessForNextBlock for MockGetRandomnessForNextBlock { + fn should_end_session(n: u64) -> bool { + n % 5 == 0 + } + + fn get_randomness() -> [u8; 32] { + MockData::mock().random_seed + } +} + parameter_types! { pub const CollatorRotationSessionPeriod: u32 = 5; } @@ -171,6 +188,7 @@ impl pallet_collator_assignment::Config for Test { type HostConfiguration = HostConfigurationGetter; type ContainerChains = ContainerChainsGetter; type ShouldRotateAllCollators = RotateCollatorsEveryNSessions; + type GetRandomnessForNextBlock = MockGetRandomnessForNextBlock; type WeightInfo = (); } @@ -193,15 +211,16 @@ pub fn run_to_block(n: u64) { for x in (old_block_number + 1)..=n { System::reset_events(); System::set_block_number(x); - let randomness = MockData::mock().random_seed; + CollatorAssignment::on_initialize(x); if x % session_len == 1 { let session_index = (x / session_len) as u32; CollatorAssignment::initializer_on_new_session( &session_index, - randomness, CollatorsGetter::collators(session_index), ); } + + CollatorAssignment::on_finalize(x); } } diff --git a/runtime/dancebox/src/lib.rs b/runtime/dancebox/src/lib.rs index 14a50f513..54f3f9b88 100644 --- a/runtime/dancebox/src/lib.rs +++ b/runtime/dancebox/src/lib.rs @@ -63,7 +63,7 @@ use { EnsureRoot, }, nimbus_primitives::NimbusId, - pallet_collator_assignment::RotateCollatorsEveryNSessions, + pallet_collator_assignment::{GetRandomnessForNextBlock, RotateCollatorsEveryNSessions}, pallet_pooled_staking::traits::{IsCandidateEligible, Timer}, pallet_registrar_runtime_api::ContainerChainGenesisData, pallet_session::{SessionManager, ShouldEndSession}, @@ -507,26 +507,6 @@ impl pallet_initializer::ApplyNewSession for OwnApplySession { all_validators: Vec<(AccountId, NimbusId)>, queued: Vec<(AccountId, NimbusId)>, ) { - let random_seed = if session_index != 0 { - if let Some(random_hash) = - BabeCurrentBlockRandomnessGetter::get_block_randomness_mixed(b"CollatorAssignment") - { - // Return random_hash as a [u8; 32] instead of a Hash - let mut buf = [0u8; 32]; - let len = sp_std::cmp::min(32, random_hash.as_ref().len()); - buf[..len].copy_from_slice(&random_hash.as_ref()[..len]); - - buf - } else { - // If there is no randomness (e.g when running in dev mode), return [0; 32] - // TODO: smoke test to ensure this never happens in a live network - [0; 32] - } - } else { - // In session 0 (genesis) there is randomness - [0; 32] - }; - // We first initialize Configuration Configuration::initializer_on_new_session(&session_index); // Next: Registrar @@ -537,11 +517,8 @@ impl pallet_initializer::ApplyNewSession for OwnApplySession { let next_collators = queued.iter().map(|(k, _)| k.clone()).collect(); // Next: CollatorAssignment - let assignments = CollatorAssignment::initializer_on_new_session( - &session_index, - random_seed, - next_collators, - ); + let assignments = + CollatorAssignment::initializer_on_new_session(&session_index, next_collators); let queued_id_to_nimbus_map = queued.iter().cloned().collect(); AuthorityAssignment::initializer_on_new_session( @@ -640,6 +617,39 @@ impl Get for ConfigurationCollatorRotationSessionPeriod { } } +pub struct BabeGetRandomnessForNextBlock; + +impl GetRandomnessForNextBlock for BabeGetRandomnessForNextBlock { + fn should_end_session(n: u32) -> bool { + ::ShouldEndSession::should_end_session(n) + } + + fn get_randomness() -> [u8; 32] { + let block_number = System::block_number(); + let random_seed = if block_number != 0 { + if let Some(random_hash) = + BabeCurrentBlockRandomnessGetter::get_block_randomness_mixed(b"CollatorAssignment") + { + // Return random_hash as a [u8; 32] instead of a Hash + let mut buf = [0u8; 32]; + let len = sp_std::cmp::min(32, random_hash.as_ref().len()); + buf[..len].copy_from_slice(&random_hash.as_ref()[..len]); + + buf + } else { + // If there is no randomness (e.g when running in dev mode), return [0; 32] + // TODO: smoke test to ensure this never happens in a live network + [0; 32] + } + } else { + // In block 0 (genesis) there is randomness + [0; 32] + }; + + random_seed + } +} + impl pallet_collator_assignment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type HostConfiguration = Configuration; @@ -647,6 +657,7 @@ impl pallet_collator_assignment::Config for Runtime { type SessionIndex = u32; type ShouldRotateAllCollators = RotateCollatorsEveryNSessions; + type GetRandomnessForNextBlock = BabeGetRandomnessForNextBlock; type WeightInfo = pallet_collator_assignment::weights::SubstrateWeight; } diff --git a/runtime/dancebox/tests/common/mod.rs b/runtime/dancebox/tests/common/mod.rs index 768078711..79b2428ea 100644 --- a/runtime/dancebox/tests/common/mod.rs +++ b/runtime/dancebox/tests/common/mod.rs @@ -17,7 +17,9 @@ use { cumulus_primitives_core::{ParaId, PersistedValidationData}, cumulus_primitives_parachain_inherent::ParachainInherentData, - dancebox_runtime::{AuthorInherent, AuthorityAssignment, MaxLengthTokenSymbol}, + dancebox_runtime::{ + AuthorInherent, AuthorityAssignment, CollatorAssignment, MaxLengthTokenSymbol, + }, frame_support::{ assert_ok, traits::{OnFinalize, OnInitialize}, @@ -78,16 +80,18 @@ pub fn run_to_block(n: u32) { ); // Initialize the new block - Session::on_initialize(System::block_number()); + CollatorAssignment::on_initialize(System::block_number()); Initializer::on_initialize(System::block_number()); + Session::on_initialize(System::block_number()); AuthorInherent::on_initialize(System::block_number()); pallet_author_inherent::Pallet::::kick_off_authorship_validation(None.into()) .expect("author inherent to dispatch correctly"); // Finalize the block - Session::on_finalize(System::block_number()); + CollatorAssignment::on_finalize(System::block_number()); Initializer::on_finalize(System::block_number()); + Session::on_finalize(System::block_number()); AuthorInherent::on_finalize(System::block_number()); } }