From c99c00d9d9adfe6d2250dd1c0ff36e2fa9d75bb8 Mon Sep 17 00:00:00 2001 From: eskimor Date: Wed, 6 Sep 2023 18:19:21 +0200 Subject: [PATCH] Fix nothing scheduled on session boundary (#1403) * Fix scheduled state at session boundaries. * Cleanup + better docs. * More cleanup and fixes. * Remove 12s hack. * Add dep. * Make clippy happy --------- Co-authored-by: eskimor --- Cargo.lock | 1 + polkadot/primitives/src/lib.rs | 4 +- polkadot/primitives/src/v5/mod.rs | 54 ---- polkadot/runtime/parachains/Cargo.toml | 1 + polkadot/runtime/parachains/src/assigner.rs | 4 +- .../parachains/src/assigner_on_demand/mod.rs | 5 +- .../src/assigner_on_demand/tests.rs | 6 +- .../parachains/src/assigner_parachains.rs | 6 +- polkadot/runtime/parachains/src/builder.rs | 14 +- .../runtime/parachains/src/configuration.rs | 17 +- .../runtime/parachains/src/inclusion/mod.rs | 208 ++++++------- .../runtime/parachains/src/inclusion/tests.rs | 129 +++----- .../parachains/src/paras_inherent/mod.rs | 32 +- .../parachains/src/paras_inherent/tests.rs | 22 +- .../parachains/src/runtime_api_impl/v5.rs | 66 ++-- polkadot/runtime/parachains/src/scheduler.rs | 281 +++++++++++------- .../parachains/src/scheduler/common.rs | 50 +--- .../parachains/src/scheduler/migration.rs | 1 - .../runtime/parachains/src/scheduler/tests.rs | 237 ++++++--------- 19 files changed, 492 insertions(+), 646 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d08c175bde5f..2fff578798912 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12792,6 +12792,7 @@ dependencies = [ "pallet-timestamp", "pallet-vesting", "parity-scale-codec", + "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-primitives-test-helpers", diff --git a/polkadot/primitives/src/lib.rs b/polkadot/primitives/src/lib.rs index 729908cc12ba0..9121b37908583 100644 --- a/polkadot/primitives/src/lib.rs +++ b/polkadot/primitives/src/lib.rs @@ -41,8 +41,8 @@ pub use v5::{ BackedCandidate, Balance, BlakeTwo256, Block, BlockId, BlockNumber, CandidateCommitments, CandidateDescriptor, CandidateEvent, CandidateHash, CandidateIndex, CandidateReceipt, CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, CollatorId, CollatorSignature, - CommittedCandidateReceipt, CompactStatement, ConsensusLog, CoreIndex, CoreOccupied, CoreState, - DisputeState, DisputeStatement, DisputeStatementSet, DownwardMessage, EncodeAs, ExecutorParam, + CommittedCandidateReceipt, CompactStatement, ConsensusLog, CoreIndex, CoreState, DisputeState, + DisputeStatement, DisputeStatementSet, DownwardMessage, EncodeAs, ExecutorParam, ExecutorParams, ExecutorParamsHash, ExplicitDisputeStatement, GroupIndex, GroupRotationInfo, Hash, HashT, HeadData, Header, HrmpChannelId, Id, InboundDownwardMessage, InboundHrmpMessage, IndexedVec, InherentData, InvalidDisputeStatementKind, Moment, MultiDisputeStatementSet, Nonce, diff --git a/polkadot/primitives/src/v5/mod.rs b/polkadot/primitives/src/v5/mod.rs index eed4cc2b36ba6..30782f95611f8 100644 --- a/polkadot/primitives/src/v5/mod.rs +++ b/polkadot/primitives/src/v5/mod.rs @@ -830,60 +830,6 @@ pub struct ParathreadEntry { pub retries: u32, } -/// An assignment for a parachain scheduled to be backed and included in a relay chain block. -#[derive(Clone, Encode, Decode, PartialEq, TypeInfo, RuntimeDebug)] -pub struct Assignment { - /// Assignment's ParaId - pub para_id: Id, -} - -impl Assignment { - /// Create a new `Assignment`. - pub fn new(para_id: Id) -> Self { - Self { para_id } - } -} - -/// An entry tracking a paras -#[derive(Clone, Encode, Decode, TypeInfo, PartialEq, RuntimeDebug)] -pub struct ParasEntry { - /// The `Assignment` - pub assignment: Assignment, - /// The number of times the entry has timed out in availability. - pub availability_timeouts: u32, - /// The block height where this entry becomes invalid. - pub ttl: N, -} - -impl ParasEntry { - /// Return `Id` from the underlying `Assignment`. - pub fn para_id(&self) -> Id { - self.assignment.para_id - } - - /// Create a new `ParasEntry`. - pub fn new(assignment: Assignment, now: N) -> Self { - ParasEntry { assignment, availability_timeouts: 0, ttl: now } - } -} - -/// What is occupying a specific availability core. -#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(PartialEq))] -pub enum CoreOccupied { - /// The core is not occupied. - Free, - /// A paras. - Paras(ParasEntry), -} - -impl CoreOccupied { - /// Is core free? - pub fn is_free(&self) -> bool { - matches!(self, Self::Free) - } -} - /// A helper data-type for tracking validator-group rotations. #[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)] #[cfg_attr(feature = "std", derive(PartialEq))] diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index 0e2f6aa1aa245..77eba0bc10baf 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -50,6 +50,7 @@ rand_chacha = { version = "0.3.1", default-features = false } static_assertions = { version = "1.1.0", optional = true } polkadot-parachain-primitives = { path = "../../parachain", default-features = false } polkadot-runtime-metrics = { path = "../metrics", default-features = false} +polkadot-core-primitives = { path = "../../core-primitives", default-features = false } [dev-dependencies] futures = "0.3.21" diff --git a/polkadot/runtime/parachains/src/assigner.rs b/polkadot/runtime/parachains/src/assigner.rs index 55434da11f307..b21e857a47137 100644 --- a/polkadot/runtime/parachains/src/assigner.rs +++ b/polkadot/runtime/parachains/src/assigner.rs @@ -17,11 +17,11 @@ //! The Polkadot multiplexing assignment provider. //! Provides blockspace assignments for both bulk and on demand parachains. use frame_system::pallet_prelude::BlockNumberFor; -use primitives::{v5::Assignment, CoreIndex, Id as ParaId}; +use primitives::{CoreIndex, Id as ParaId}; use crate::{ configuration, paras, - scheduler::common::{AssignmentProvider, AssignmentProviderConfig}, + scheduler::common::{Assignment, AssignmentProvider, AssignmentProviderConfig}, }; pub use pallet::*; diff --git a/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs b/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs index 0c9813d144f34..75c29bd6fbe4f 100644 --- a/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs +++ b/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs @@ -34,7 +34,7 @@ mod tests; use crate::{ configuration, paras, - scheduler::common::{AssignmentProvider, AssignmentProviderConfig}, + scheduler::common::{Assignment, AssignmentProvider, AssignmentProviderConfig}, }; use frame_support::{ @@ -46,7 +46,7 @@ use frame_support::{ }, }; use frame_system::pallet_prelude::*; -use primitives::{v5::Assignment, CoreIndex, Id as ParaId}; +use primitives::{CoreIndex, Id as ParaId}; use sp_runtime::{ traits::{One, SaturatedConversion}, FixedPointNumber, FixedPointOperand, FixedU128, Perbill, Saturating, @@ -606,7 +606,6 @@ impl AssignmentProvider> for Pallet { fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig> { let config = >::config(); AssignmentProviderConfig { - availability_period: config.paras_availability_period, max_availability_timeouts: config.on_demand_retries, ttl: config.on_demand_ttl, } diff --git a/polkadot/runtime/parachains/src/assigner_on_demand/tests.rs b/polkadot/runtime/parachains/src/assigner_on_demand/tests.rs index 8041179cd90c5..fe9a4e52bd076 100644 --- a/polkadot/runtime/parachains/src/assigner_on_demand/tests.rs +++ b/polkadot/runtime/parachains/src/assigner_on_demand/tests.rs @@ -24,13 +24,11 @@ use crate::{ System, Test, }, paras::{ParaGenesisArgs, ParaKind}, + scheduler::common::Assignment, }; use frame_support::{assert_noop, assert_ok, error::BadOrigin}; use pallet_balances::Error as BalancesError; -use primitives::{ - v5::{Assignment, ValidationCode}, - BlockNumber, SessionIndex, -}; +use primitives::{v5::ValidationCode, BlockNumber, SessionIndex}; use sp_std::collections::btree_map::BTreeMap; fn schedule_blank_para(id: ParaId, parakind: ParaKind) { diff --git a/polkadot/runtime/parachains/src/assigner_parachains.rs b/polkadot/runtime/parachains/src/assigner_parachains.rs index 9a6b970597d52..d605d86605151 100644 --- a/polkadot/runtime/parachains/src/assigner_parachains.rs +++ b/polkadot/runtime/parachains/src/assigner_parachains.rs @@ -19,11 +19,11 @@ use crate::{ configuration, paras, - scheduler::common::{AssignmentProvider, AssignmentProviderConfig}, + scheduler::common::{Assignment, AssignmentProvider, AssignmentProviderConfig}, }; use frame_system::pallet_prelude::BlockNumberFor; pub use pallet::*; -use primitives::{v5::Assignment, CoreIndex, Id as ParaId}; +use primitives::{CoreIndex, Id as ParaId}; #[frame_support::pallet] pub mod pallet { @@ -57,9 +57,7 @@ impl AssignmentProvider> for Pallet { fn push_assignment_for_core(_: CoreIndex, _: Assignment) {} fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig> { - let config = >::config(); AssignmentProviderConfig { - availability_period: config.paras_availability_period, // The next assignment already goes to the same [`ParaId`], no timeout tracking needed. max_availability_timeouts: 0, // The next assignment already goes to the same [`ParaId`], this can be any number diff --git a/polkadot/runtime/parachains/src/builder.rs b/polkadot/runtime/parachains/src/builder.rs index 4921af5bedda7..dced24df0aec8 100644 --- a/polkadot/runtime/parachains/src/builder.rs +++ b/polkadot/runtime/parachains/src/builder.rs @@ -18,18 +18,20 @@ use crate::{ configuration, inclusion, initializer, paras, paras::ParaKind, paras_inherent, - scheduler::{self, common::AssignmentProviderConfig}, + scheduler::{ + self, + common::{Assignment, AssignmentProviderConfig}, + CoreOccupied, ParasEntry, + }, session_info, shared, }; use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use primitives::{ - collator_signature_payload, - v5::{Assignment, ParasEntry}, - AvailabilityBitfield, BackedCandidate, CandidateCommitments, CandidateDescriptor, - CandidateHash, CollatorId, CollatorSignature, CommittedCandidateReceipt, CompactStatement, - CoreIndex, CoreOccupied, DisputeStatement, DisputeStatementSet, GroupIndex, HeadData, + collator_signature_payload, AvailabilityBitfield, BackedCandidate, CandidateCommitments, + CandidateDescriptor, CandidateHash, CollatorId, CollatorSignature, CommittedCandidateReceipt, + CompactStatement, CoreIndex, DisputeStatement, DisputeStatementSet, GroupIndex, HeadData, Id as ParaId, IndexedVec, InherentData as ParachainsInherentData, InvalidDisputeStatementKind, PersistedValidationData, SessionIndex, SigningContext, UncheckedSigned, ValidDisputeStatementKind, ValidationCode, ValidatorId, ValidatorIndex, ValidityAttestation, diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index 0c66bb5bdf961..33039cd08ca4b 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -190,11 +190,20 @@ pub struct HostConfiguration { /// /// Must be non-zero. pub group_rotation_frequency: BlockNumber, - /// The availability period, in blocks. This is the amount of blocks - /// after inclusion that validators have to make the block available and signal its - /// availability to the chain. + /// The minimum availability period, in blocks. /// - /// Must be at least 1. + /// This is the minimum amount of blocks after a core became occupied that validators have time + /// to make the block available. + /// + /// This value only has effect on group rotations. If backers backed something at the end of + /// their rotation, the occupied core affects the backing group that comes afterwards. We limit + /// the effect one backing group can have on the next to `paras_availability_period` blocks. + /// + /// Within a group rotation there is no timeout as backers are only affecting themselves. + /// + /// Must be at least 1. With a value of 1, the previous group will not be able to negatively + /// affect the following group at the expense of a tight availability timeline at group + /// rotation boundaries. pub paras_availability_period: BlockNumber, /// The amount of blocks ahead to schedule paras. pub scheduling_lookahead: u32, diff --git a/polkadot/runtime/parachains/src/inclusion/mod.rs b/polkadot/runtime/parachains/src/inclusion/mod.rs index 19e56be492c75..bdf3788b12e5a 100644 --- a/polkadot/runtime/parachains/src/inclusion/mod.rs +++ b/polkadot/runtime/parachains/src/inclusion/mod.rs @@ -22,7 +22,7 @@ use crate::{ configuration::{self, HostConfiguration}, disputes, dmp, hrmp, paras, - scheduler::{self, common::CoreAssignment}, + scheduler::{self, AvailabilityTimeoutStatus}, shared::{self, AllowedRelayParentsTracker}, }; use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; @@ -46,7 +46,10 @@ use scale_info::TypeInfo; use sp_runtime::{traits::One, DispatchError, SaturatedConversion, Saturating}; #[cfg(feature = "std")] use sp_std::fmt; -use sp_std::{collections::btree_set::BTreeSet, prelude::*}; +use sp_std::{ + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + prelude::*, +}; pub use pallet::*; @@ -598,7 +601,7 @@ impl Pallet { pub(crate) fn process_candidates( allowed_relay_parents: &AllowedRelayParentsTracker>, candidates: Vec>, - scheduled: Vec>>, + scheduled: &BTreeMap, group_validators: GV, ) -> Result, DispatchError> where @@ -621,20 +624,18 @@ impl Pallet { // Do all checks before writing storage. let core_indices_and_backers = { - let mut skip = 0; let mut core_indices_and_backers = Vec::with_capacity(candidates.len()); let mut last_core = None; - let mut check_assignment_in_order = - |assignment: &CoreAssignment>| -> DispatchResult { - ensure!( - last_core.map_or(true, |core| assignment.core > core), - Error::::ScheduledOutOfOrder, - ); + let mut check_assignment_in_order = |core_idx| -> DispatchResult { + ensure!( + last_core.map_or(true, |core| core_idx > core), + Error::::ScheduledOutOfOrder, + ); - last_core = Some(assignment.core); - Ok(()) - }; + last_core = Some(core_idx); + Ok(()) + }; // We combine an outer loop over candidates with an inner loop over the scheduled, // where each iteration of the outer loop picks up at the position @@ -646,9 +647,7 @@ impl Pallet { // // In the meantime, we do certain sanity checks on the candidates and on the scheduled // list. - 'next_backed_candidate: for (candidate_idx, backed_candidate) in - candidates.iter().enumerate() - { + for (candidate_idx, backed_candidate) in candidates.iter().enumerate() { let relay_parent_hash = backed_candidate.descriptor().relay_parent; let para_id = backed_candidate.descriptor().para_id; @@ -682,108 +681,89 @@ impl Pallet { let para_id = backed_candidate.descriptor().para_id; let mut backers = bitvec::bitvec![u8, BitOrderLsb0; 0; validators.len()]; - for (i, core_assignment) in scheduled[skip..].iter().enumerate() { - check_assignment_in_order(core_assignment)?; + let core_idx = *scheduled.get(¶_id).ok_or(Error::::UnscheduledCandidate)?; + check_assignment_in_order(core_idx)?; + ensure!( + >::get(¶_id).is_none() && + >::get(¶_id).is_none(), + Error::::CandidateScheduledBeforeParaFree, + ); - if para_id == core_assignment.paras_entry.para_id() { - ensure!( - >::get(¶_id).is_none() && - >::get(¶_id).is_none(), - Error::::CandidateScheduledBeforeParaFree, - ); + // The candidate based upon relay parent `N` should be backed by a group + // assigned to core at block `N + 1`. Thus, `relay_parent_number + 1` + // will always land in the current session. + let group_idx = >::group_assigned_to_core( + core_idx, + relay_parent_number + One::one(), + ) + .ok_or_else(|| { + log::warn!( + target: LOG_TARGET, + "Failed to compute group index for candidate {}", + candidate_idx + ); + Error::::InvalidAssignment + })?; + let group_vals = + group_validators(group_idx).ok_or_else(|| Error::::InvalidGroupIndex)?; - // account for already skipped, and then skip this one. - skip = i + skip + 1; - - // The candidate based upon relay parent `N` should be backed by a group - // assigned to core at block `N + 1`. Thus, `relay_parent_number + 1` - // will always land in the current session. - let group_idx = >::group_assigned_to_core( - core_assignment.core, - relay_parent_number + One::one(), - ) - .ok_or_else(|| { - log::warn!( - target: LOG_TARGET, - "Failed to compute group index for candidate {}", - candidate_idx - ); - Error::::InvalidAssignment - })?; - let group_vals = group_validators(group_idx) - .ok_or_else(|| Error::::InvalidGroupIndex)?; - - // check the signatures in the backing and that it is a majority. - { - let maybe_amount_validated = primitives::check_candidate_backing( - &backed_candidate, - &signing_context, - group_vals.len(), - |intra_group_vi| { - group_vals - .get(intra_group_vi) - .and_then(|vi| validators.get(vi.0 as usize)) - .map(|v| v.clone()) - }, - ); - - match maybe_amount_validated { - Ok(amount_validated) => ensure!( - amount_validated >= - effective_minimum_backing_votes( - group_vals.len(), - minimum_backing_votes - ), - Error::::InsufficientBacking, + // check the signatures in the backing and that it is a majority. + { + let maybe_amount_validated = primitives::check_candidate_backing( + &backed_candidate, + &signing_context, + group_vals.len(), + |intra_group_vi| { + group_vals + .get(intra_group_vi) + .and_then(|vi| validators.get(vi.0 as usize)) + .map(|v| v.clone()) + }, + ); + + match maybe_amount_validated { + Ok(amount_validated) => ensure!( + amount_validated >= + effective_minimum_backing_votes( + group_vals.len(), + minimum_backing_votes ), - Err(()) => { - Err(Error::::InvalidBacking)?; - }, - } - - let mut backer_idx_and_attestation = - Vec::<(ValidatorIndex, ValidityAttestation)>::with_capacity( - backed_candidate.validator_indices.count_ones(), - ); - let candidate_receipt = backed_candidate.receipt(); - - for ((bit_idx, _), attestation) in backed_candidate - .validator_indices - .iter() - .enumerate() - .filter(|(_, signed)| **signed) - .zip(backed_candidate.validity_votes.iter().cloned()) - { - let val_idx = group_vals - .get(bit_idx) - .expect("this query succeeded above; qed"); - backer_idx_and_attestation.push((*val_idx, attestation)); - - backers.set(val_idx.0 as _, true); - } - candidate_receipt_with_backing_validator_indices - .push((candidate_receipt, backer_idx_and_attestation)); - } - - core_indices_and_backers.push(( - (core_assignment.core, core_assignment.paras_entry.para_id()), - backers, - group_idx, - relay_parent_number, - )); - continue 'next_backed_candidate + Error::::InsufficientBacking, + ), + Err(()) => { + Err(Error::::InvalidBacking)?; + }, } - } - // end of loop reached means that the candidate didn't appear in the non-traversed - // section of the `scheduled` slice. either it was not scheduled or didn't appear in - // `candidates` in the correct order. - ensure!(false, Error::::UnscheduledCandidate); - } + let mut backer_idx_and_attestation = + Vec::<(ValidatorIndex, ValidityAttestation)>::with_capacity( + backed_candidate.validator_indices.count_ones(), + ); + let candidate_receipt = backed_candidate.receipt(); + + for ((bit_idx, _), attestation) in backed_candidate + .validator_indices + .iter() + .enumerate() + .filter(|(_, signed)| **signed) + .zip(backed_candidate.validity_votes.iter().cloned()) + { + let val_idx = + group_vals.get(bit_idx).expect("this query succeeded above; qed"); + backer_idx_and_attestation.push((*val_idx, attestation)); + + backers.set(val_idx.0 as _, true); + } + candidate_receipt_with_backing_validator_indices + .push((candidate_receipt, backer_idx_and_attestation)); + } - // check remainder of scheduled cores, if any. - for assignment in scheduled[skip..].iter() { - check_assignment_in_order(assignment)?; + core_indices_and_backers.push(( + (core_idx, para_id), + backers, + group_idx, + relay_parent_number, + )); } core_indices_and_backers @@ -1045,13 +1025,13 @@ impl Pallet { /// /// Returns a vector of cleaned-up core IDs. pub(crate) fn collect_pending( - pred: impl Fn(CoreIndex, BlockNumberFor) -> bool, + pred: impl Fn(BlockNumberFor) -> AvailabilityTimeoutStatus>, ) -> Vec { let mut cleaned_up_ids = Vec::new(); let mut cleaned_up_cores = Vec::new(); for (para_id, pending_record) in >::iter() { - if pred(pending_record.core, pending_record.backed_in_number) { + if pred(pending_record.backed_in_number).timed_out { cleaned_up_ids.push(para_id); cleaned_up_cores.push(pending_record.core); } diff --git a/polkadot/runtime/parachains/src/inclusion/tests.rs b/polkadot/runtime/parachains/src/inclusion/tests.rs index 89fdc6eec6796..b944575c5504e 100644 --- a/polkadot/runtime/parachains/src/inclusion/tests.rs +++ b/polkadot/runtime/parachains/src/inclusion/tests.rs @@ -36,7 +36,6 @@ use frame_support::assert_noop; use keyring::Sr25519Keyring; use parity_scale_codec::DecodeAll; use primitives::{ - v5::{Assignment, ParasEntry}, BlockNumber, CandidateCommitments, CandidateDescriptor, CollatorId, CompactStatement as Statement, Hash, SignedAvailabilityBitfield, SignedStatement, ValidationCode, ValidatorId, ValidityAttestation, PARACHAIN_KEY_TYPE_ID, @@ -380,7 +379,9 @@ fn collect_pending_cleans_up_pending() { (chain_b, ParaKind::Parachain), (thread_a, ParaKind::Parathread), ]; - new_test_ext(genesis_config(paras)).execute_with(|| { + let mut config = genesis_config(paras); + config.configuration.config.group_rotation_frequency = 3; + new_test_ext(config).execute_with(|| { let default_candidate = TestCandidateBuilder::default().build(); >::insert( chain_a, @@ -408,7 +409,7 @@ fn collect_pending_cleans_up_pending() { descriptor: default_candidate.descriptor, availability_votes: default_availability_votes(), relay_parent_number: 0, - backed_in_number: 0, + backed_in_number: 5, backers: default_backing_bitfield(), backing_group: GroupIndex::from(1), }, @@ -422,7 +423,7 @@ fn collect_pending_cleans_up_pending() { assert!(>::get(&chain_a).is_some()); assert!(>::get(&chain_b).is_some()); - ParaInclusion::collect_pending(|core, _since| core == CoreIndex::from(0)); + ParaInclusion::collect_pending(Scheduler::availability_timeout_predicate()); assert!(>::get(&chain_a).is_none()); assert!(>::get(&chain_b).is_some()); @@ -910,23 +911,12 @@ fn candidate_checks() { ]; Scheduler::set_validator_groups(validator_groups); - let entry_ttl = 10_000; let thread_collator: CollatorId = Sr25519Keyring::Two.public().into(); - let chain_a_assignment = CoreAssignment { - core: CoreIndex::from(0), - paras_entry: ParasEntry::new(Assignment::new(chain_a), entry_ttl), - }; - - let chain_b_assignment = CoreAssignment { - core: CoreIndex::from(1), - paras_entry: ParasEntry::new(Assignment::new(chain_b), entry_ttl), - }; + let chain_a_assignment = (chain_a, CoreIndex::from(0)); - let thread_a_assignment = CoreAssignment { - core: CoreIndex::from(2), - paras_entry: ParasEntry::new(Assignment::new(thread_a), entry_ttl), - }; + let chain_b_assignment = (chain_b, CoreIndex::from(1)); + let thread_a_assignment = (thread_a, CoreIndex::from(2)); let allowed_relay_parents = default_allowed_relay_parent_tracker(); // unscheduled candidate. @@ -955,7 +945,7 @@ fn candidate_checks() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed], - vec![chain_b_assignment.clone()], + &[chain_b_assignment].into_iter().collect(), &group_validators, ), Error::::UnscheduledCandidate @@ -1010,10 +1000,10 @@ fn candidate_checks() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed_b, backed_a], - vec![chain_a_assignment.clone(), chain_b_assignment.clone()], + &[chain_a_assignment, chain_b_assignment].into_iter().collect(), &group_validators, ), - Error::::UnscheduledCandidate + Error::::ScheduledOutOfOrder ); } @@ -1043,7 +1033,7 @@ fn candidate_checks() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed], - vec![chain_a_assignment.clone()], + &[chain_a_assignment].into_iter().collect(), &group_validators, ), Error::::InsufficientBacking @@ -1100,7 +1090,7 @@ fn candidate_checks() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed_b, backed_a], - vec![chain_a_assignment.clone(), chain_b_assignment.clone()], + &[chain_a_assignment, chain_b_assignment].into_iter().collect(), &group_validators, ), Error::::DisallowedRelayParent @@ -1138,7 +1128,7 @@ fn candidate_checks() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed], - vec![thread_a_assignment.clone()], + &[thread_a_assignment].into_iter().collect(), &group_validators, ), Error::::NotCollatorSigned @@ -1188,7 +1178,7 @@ fn candidate_checks() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed], - vec![chain_a_assignment.clone()], + &[chain_a_assignment].into_iter().collect(), &group_validators, ), Error::::CandidateScheduledBeforeParaFree @@ -1228,7 +1218,7 @@ fn candidate_checks() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed], - vec![chain_a_assignment.clone()], + &[chain_a_assignment].into_iter().collect(), &group_validators, ), Error::::CandidateScheduledBeforeParaFree @@ -1278,7 +1268,7 @@ fn candidate_checks() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed], - vec![chain_a_assignment.clone()], + &[chain_a_assignment].into_iter().collect(), &group_validators, ), Error::::PrematureCodeUpgrade @@ -1312,7 +1302,7 @@ fn candidate_checks() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed], - vec![chain_a_assignment.clone()], + &[chain_a_assignment].into_iter().collect(), &group_validators, ), Err(Error::::ValidationDataHashMismatch.into()), @@ -1347,7 +1337,7 @@ fn candidate_checks() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed], - vec![chain_a_assignment.clone()], + &[chain_a_assignment].into_iter().collect(), &group_validators, ), Error::::InvalidValidationCodeHash @@ -1382,7 +1372,7 @@ fn candidate_checks() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed], - vec![chain_a_assignment.clone()], + &[chain_a_assignment].into_iter().collect(), &group_validators, ), Error::::ParaHeadMismatch @@ -1452,21 +1442,9 @@ fn backing_works() { let allowed_relay_parents = default_allowed_relay_parent_tracker(); - let entry_ttl = 10_000; - let chain_a_assignment = CoreAssignment { - core: CoreIndex::from(0), - paras_entry: ParasEntry::new(Assignment::new(chain_a), entry_ttl), - }; - - let chain_b_assignment = CoreAssignment { - core: CoreIndex::from(1), - paras_entry: ParasEntry::new(Assignment::new(chain_b), entry_ttl), - }; - - let thread_a_assignment = CoreAssignment { - core: CoreIndex::from(2), - paras_entry: ParasEntry::new(Assignment::new(thread_a), entry_ttl), - }; + let chain_a_assignment = (chain_a, CoreIndex::from(0)); + let chain_b_assignment = (chain_b, CoreIndex::from(1)); + let thread_a_assignment = (thread_a, CoreIndex::from(2)); let mut candidate_a = TestCandidateBuilder { para_id: chain_a, @@ -1554,11 +1532,9 @@ fn backing_works() { } = ParaInclusion::process_candidates( &allowed_relay_parents, backed_candidates.clone(), - vec![ - chain_a_assignment.clone(), - chain_b_assignment.clone(), - thread_a_assignment.clone(), - ], + &[chain_a_assignment, chain_b_assignment, thread_a_assignment] + .into_iter() + .collect(), &group_validators, ) .expect("candidates scheduled, in order, and backed"); @@ -1744,12 +1720,7 @@ fn can_include_candidate_with_ok_code_upgrade() { Scheduler::set_validator_groups(validator_groups); let allowed_relay_parents = default_allowed_relay_parent_tracker(); - let entry_ttl = 10_000; - let chain_a_assignment = CoreAssignment { - core: CoreIndex::from(0), - paras_entry: ParasEntry::new(Assignment::new(chain_a), entry_ttl), - }; - + let chain_a_assignment = (chain_a, CoreIndex::from(0)); let mut candidate_a = TestCandidateBuilder { para_id: chain_a, relay_parent: System::parent_hash(), @@ -1775,7 +1746,7 @@ fn can_include_candidate_with_ok_code_upgrade() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed_a], - vec![chain_a_assignment.clone()], + &[chain_a_assignment].into_iter().collect(), &group_validators, ) .expect("candidates scheduled, in order, and backed"); @@ -1901,28 +1872,10 @@ fn check_allowed_relay_parents() { max_ancestry_len, ); - let chain_a_assignment = CoreAssignment { - core: CoreIndex::from(0), - paras_entry: ParasEntry { - assignment: Assignment { para_id: chain_a }, - availability_timeouts: 0, - ttl: 5, - }, - }; + let chain_a_assignment = (chain_a, CoreIndex::from(0)); - let chain_b_assignment = CoreAssignment { - core: CoreIndex::from(1), - paras_entry: ParasEntry { - assignment: Assignment { para_id: chain_b }, - availability_timeouts: 0, - ttl: 5, - }, - }; - - let thread_a_assignment = CoreAssignment { - core: CoreIndex::from(2), - paras_entry: ParasEntry::new(Assignment::new(thread_a), 5), - }; + let chain_b_assignment = (chain_b, CoreIndex::from(1)); + let thread_a_assignment = (thread_a, CoreIndex::from(2)); let mut candidate_a = TestCandidateBuilder { para_id: chain_a, @@ -2004,11 +1957,9 @@ fn check_allowed_relay_parents() { ParaInclusion::process_candidates( &allowed_relay_parents, backed_candidates.clone(), - vec![ - chain_a_assignment.clone(), - chain_b_assignment.clone(), - thread_a_assignment.clone(), - ], + &[chain_a_assignment, chain_b_assignment, thread_a_assignment] + .into_iter() + .collect(), &group_validators, ) .expect("candidates scheduled, in order, and backed"); @@ -2218,15 +2169,7 @@ fn para_upgrade_delay_scheduled_from_inclusion() { let allowed_relay_parents = default_allowed_relay_parent_tracker(); - let chain_a_assignment = CoreAssignment { - core: CoreIndex::from(0), - paras_entry: ParasEntry { - assignment: Assignment { para_id: chain_a }, - availability_timeouts: 0, - ttl: 5, - }, - }; - + let chain_a_assignment = (chain_a, CoreIndex::from(0)); let mut candidate_a = TestCandidateBuilder { para_id: chain_a, relay_parent: System::parent_hash(), @@ -2252,7 +2195,7 @@ fn para_upgrade_delay_scheduled_from_inclusion() { ParaInclusion::process_candidates( &allowed_relay_parents, vec![backed_a], - vec![chain_a_assignment.clone()], + &[chain_a_assignment].into_iter().collect(), &group_validators, ) .expect("candidates scheduled, in order, and backed"); diff --git a/polkadot/runtime/parachains/src/paras_inherent/mod.rs b/polkadot/runtime/parachains/src/paras_inherent/mod.rs index 6244f44e434b0..8e918d35d5ff0 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/mod.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/mod.rs @@ -29,10 +29,7 @@ use crate::{ initializer, metrics::METRICS, paras, - scheduler::{ - self, - common::{CoreAssignment, FreedReason}, - }, + scheduler::{self, FreedReason}, shared, ParaId, }; use bitvec::prelude::BitVec; @@ -245,8 +242,8 @@ pub mod pallet { T: Config, { // Handle timeouts for any availability core work. - let availability_pred = >::availability_timeout_predicate(); - let freed_timeout = if let Some(pred) = availability_pred { + let freed_timeout = if >::availability_timeout_check_required() { + let pred = >::availability_timeout_predicate(); >::collect_pending(pred) } else { Vec::new() @@ -320,7 +317,7 @@ impl Pallet { /// /// When called from `create_inherent` the `context` must be set to /// `ProcessInherentDataContext::ProvideInherent` so it guarantees the invariant that inherent - /// is not overweight. + /// is not overweight. /// It is **mandatory** that calls from `enter` set `context` to /// `ProcessInherentDataContext::Enter` to ensure the weight invariant is checked. /// @@ -583,7 +580,10 @@ impl Pallet { let freed = collect_all_freed_cores::(freed_concluded.iter().cloned()); - let scheduled = >::update_claimqueue(freed, now); + >::update_claimqueue(freed, now); + let scheduled = >::scheduled_paras() + .map(|(core_idx, para_id)| (para_id, core_idx)) + .collect(); METRICS.on_candidates_processed_total(backed_candidates.len() as u64); @@ -608,7 +608,7 @@ impl Pallet { .verify_backed_candidate(&allowed_relay_parents, candidate_idx, backed_candidate) .is_err() }, - &scheduled[..], + &scheduled, ); METRICS.on_candidates_sanitized(backed_candidates.len() as u64); @@ -620,7 +620,7 @@ impl Pallet { } = >::process_candidates( &allowed_relay_parents, backed_candidates.clone(), - scheduled, + &scheduled, >::group_validators, )?; // Note which of the scheduled cores were actually occupied by a backed candidate. @@ -917,7 +917,7 @@ fn sanitize_backed_candidates< >( mut backed_candidates: Vec>, mut candidate_has_concluded_invalid_dispute_or_is_invalid: F, - scheduled: &[CoreAssignment>], + scheduled: &BTreeMap, ) -> Vec> { // Remove any candidates that were concluded invalid. // This does not assume sorting. @@ -925,11 +925,6 @@ fn sanitize_backed_candidates< !candidate_has_concluded_invalid_dispute_or_is_invalid(candidate_idx, backed_candidate) }); - let scheduled_paras_to_core_idx = scheduled - .into_iter() - .map(|core_assignment| (core_assignment.paras_entry.para_id(), core_assignment.core)) - .collect::>(); - // Assure the backed candidate's `ParaId`'s core is free. // This holds under the assumption that `Scheduler::schedule` is called _before_. // We don't check the relay-parent because this is done in the closure when @@ -938,7 +933,7 @@ fn sanitize_backed_candidates< backed_candidates.retain(|backed_candidate| { let desc = backed_candidate.descriptor(); - scheduled_paras_to_core_idx.get(&desc.para_id).is_some() + scheduled.get(&desc.para_id).is_some() }); // Sort the `Vec` last, once there is a guarantee that these @@ -948,8 +943,7 @@ fn sanitize_backed_candidates< // but also allows this to be done in place. backed_candidates.sort_by(|x, y| { // Never panics, since we filtered all panic arguments out in the previous `fn retain`. - scheduled_paras_to_core_idx[&x.descriptor().para_id] - .cmp(&scheduled_paras_to_core_idx[&y.descriptor().para_id]) + scheduled[&x.descriptor().para_id].cmp(&scheduled[&y.descriptor().para_id]) }); backed_candidates diff --git a/polkadot/runtime/parachains/src/paras_inherent/tests.rs b/polkadot/runtime/parachains/src/paras_inherent/tests.rs index ab515cb37565b..7c70fcea19434 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/tests.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/tests.rs @@ -963,10 +963,7 @@ mod sanitizers { use crate::mock::Test; use keyring::Sr25519Keyring; - use primitives::{ - v5::{Assignment, ParasEntry}, - PARACHAIN_KEY_TYPE_ID, - }; + use primitives::PARACHAIN_KEY_TYPE_ID; use sc_keystore::LocalKeystore; use sp_keystore::{Keystore, KeystorePtr}; use std::sync::Arc; @@ -1239,21 +1236,10 @@ mod sanitizers { 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::>(); + .map(|idx| (ParaId::from(1_u32 + idx as u32), CoreIndex::from(idx as u32))) + .collect::>(); let group_validators = |group_index: GroupIndex| { match group_index { @@ -1304,7 +1290,7 @@ mod sanitizers { // nothing is scheduled, so no paraids match, thus all backed candidates are skipped { - let scheduled = &Vec::new(); + let scheduled = &BTreeMap::new(); assert!(sanitize_backed_candidates::( backed_candidates.clone(), has_concluded_invalid, diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v5.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v5.rs index bac1268f53bd3..46a609e0368dd 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v5.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v5.rs @@ -18,17 +18,17 @@ //! functions. use crate::{ - configuration, disputes, dmp, hrmp, inclusion, initializer, paras, paras_inherent, scheduler, + disputes, dmp, hrmp, inclusion, initializer, paras, paras_inherent, + scheduler::{self, CoreOccupied}, session_info, shared, }; use frame_system::pallet_prelude::*; use primitives::{ slashing, AuthorityDiscoveryId, CandidateEvent, CandidateHash, CommittedCandidateReceipt, - CoreIndex, CoreOccupied, CoreState, DisputeState, ExecutorParams, GroupIndex, - GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, - OccupiedCore, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, - ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash, - ValidatorId, ValidatorIndex, ValidatorSignature, + CoreIndex, CoreState, DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, + Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCore, OccupiedCoreAssumption, + PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo, + ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, }; use sp_runtime::traits::One; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; @@ -52,29 +52,15 @@ pub fn validator_groups( /// Implementation for the `availability_cores` function of the runtime API. pub fn availability_cores() -> Vec>> { let cores = >::availability_cores(); - let config = >::config(); let now = >::block_number() + One::one(); - let rotation_info = >::group_rotation_info(now); - let time_out_at = |backed_in_number, availability_period| { - let time_out_at = backed_in_number + availability_period; - - let current_window = rotation_info.last_rotation_at() + availability_period; - let next_rotation = rotation_info.next_rotation_at(); - - // If we are within `period` blocks of rotation, timeouts are being checked - // actively. We could even time out this block. - if time_out_at < current_window { - time_out_at - } else if time_out_at <= next_rotation { - // Otherwise, it will time out at the sooner of the next rotation - next_rotation - } else { - // or the scheduled time-out. This is by definition within `period` blocks - // of `next_rotation` and is thus a valid timeout block. - time_out_at - } - }; + // This explicit update is only strictly required for session boundaries: + // + // At the end of a session we clear the claim queues: Without this update call, nothing would be + // scheduled to the client. + >::update_claimqueue(Vec::new(), now); + + let time_out_for = >::availability_timeout_predicate(); let group_responsible_for = |backed_in_number, core_index| match >::group_assigned_to_core( @@ -93,7 +79,9 @@ pub fn availability_cores() -> Vec = cores + let scheduled: BTreeMap<_, _> = >::scheduled_paras().collect(); + + cores .into_iter() .enumerate() .map(|(i, core)| match core { @@ -108,7 +96,7 @@ pub fn availability_cores() -> Vec>::next_up_on_time_out(CoreIndex( i as u32, )), @@ -121,19 +109,15 @@ pub fn availability_cores() -> Vec CoreState::Free, + CoreOccupied::Free => { + if let Some(para_id) = scheduled.get(&CoreIndex(i as _)).cloned() { + CoreState::Scheduled(primitives::ScheduledCore { para_id, collator: None }) + } else { + CoreState::Free + } + }, }) - .collect(); - - // This will overwrite only `Free` cores if the scheduler module is working as intended. - for scheduled in >::scheduled_claimqueue() { - core_states[scheduled.core.0 as usize] = CoreState::Scheduled(primitives::ScheduledCore { - para_id: scheduled.paras_entry.para_id(), - collator: None, - }); - } - - core_states + .collect() } /// Returns current block number being processed and the corresponding root hash. diff --git a/polkadot/runtime/parachains/src/scheduler.rs b/polkadot/runtime/parachains/src/scheduler.rs index 577bcd153b5b0..60b2a9254600e 100644 --- a/polkadot/runtime/parachains/src/scheduler.rs +++ b/polkadot/runtime/parachains/src/scheduler.rs @@ -39,11 +39,11 @@ use crate::{configuration, initializer::SessionChangeNotification, paras}; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::BlockNumberFor; +pub use polkadot_core_primitives::v2::BlockNumber; use primitives::{ - v5::ParasEntry, CoreIndex, CoreOccupied, GroupIndex, GroupRotationInfo, Id as ParaId, - ScheduledCore, ValidatorIndex, + CoreIndex, GroupIndex, GroupRotationInfo, Id as ParaId, ScheduledCore, ValidatorIndex, }; -use sp_runtime::traits::{One, Saturating}; +use sp_runtime::traits::One; use sp_std::{ collections::{btree_map::BTreeMap, vec_deque::VecDeque}, prelude::*, @@ -51,7 +51,7 @@ use sp_std::{ pub mod common; -use common::{AssignmentProvider, AssignmentProviderConfig, CoreAssignment, FreedReason}; +use common::{Assignment, AssignmentProvider, AssignmentProviderConfig}; pub use pallet::*; @@ -101,6 +101,36 @@ pub mod pallet { pub(crate) type AvailabilityCores = StorageValue<_, Vec>>, ValueQuery>; + /// Representation of a core in `AvailabilityCores`. + /// + /// This is not to be confused with `CoreState` which is an enriched variant of this and exposed + /// to the node side. It also provides information about scheduled/upcoming assignments for + /// example and is computed on the fly in the `availability_cores` runtime call. + #[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)] + #[cfg_attr(feature = "std", derive(PartialEq))] + pub enum CoreOccupied { + /// No candidate is waiting availability on this core right now (the core is not occupied). + Free, + /// A para is currently waiting for availability/inclusion on this core. + Paras(ParasEntry), + } + + impl CoreOccupied { + /// Is core free? + pub fn is_free(&self) -> bool { + matches!(self, Self::Free) + } + } + + /// Reasons a core might be freed. + #[derive(Clone, Copy)] + pub enum FreedReason { + /// The core's work concluded and the parablock assigned to it is considered available. + Concluded, + /// The core's work timed out. + TimedOut, + } + /// The block number where the session start occurred. Used to track how many group rotations /// have occurred. /// @@ -124,6 +154,68 @@ pub mod pallet { BTreeMap>>>>, ValueQuery, >; + + /// Assignments as tracked in the claim queue. + #[derive(Clone, Encode, Decode, TypeInfo, PartialEq, RuntimeDebug)] + pub struct ParasEntry { + /// The underlying `Assignment` + pub assignment: Assignment, + /// The number of times the entry has timed out in availability already. + pub availability_timeouts: u32, + /// The block height until this entry needs to be backed. + /// + /// If missed the entry will be removed from the claim queue without ever having occupied + /// the core. + pub ttl: N, + } + + impl ParasEntry { + /// Return `Id` from the underlying `Assignment`. + pub fn para_id(&self) -> ParaId { + self.assignment.para_id + } + + /// Create a new `ParasEntry`. + pub fn new(assignment: Assignment, now: N) -> Self { + ParasEntry { assignment, availability_timeouts: 0, ttl: now } + } + } + + /// How a core is mapped to a backing group and a `ParaId` + #[derive(Clone, Encode, Decode, PartialEq, TypeInfo)] + #[cfg_attr(feature = "std", derive(Debug))] + pub struct CoreAssignment { + /// The core that is assigned. + pub core: CoreIndex, + /// The para id and accompanying information needed to collate and back a parablock. + pub paras_entry: ParasEntry, + } + + impl CoreAssignment { + /// Returns the [`ParaId`] of the assignment. + pub fn para_id(&self) -> ParaId { + self.paras_entry.para_id() + } + + /// Returns the inner [`ParasEntry`] of the assignment. + pub fn to_paras_entry(self) -> ParasEntry { + self.paras_entry + } + } + + /// Availability timeout status of a core. + pub(crate) struct AvailabilityTimeoutStatus { + /// Is the core already timed out? + /// + /// If this is true the core will be freed at this block. + pub timed_out: bool, + + /// When does this core timeout. + /// + /// The block number the core times out. If `timed_out` is true, this will correspond to + /// now (current block number). + pub live_until: BlockNumber, + } } type PositionInClaimqueue = u32; @@ -368,50 +460,47 @@ impl Pallet { Some(GroupIndex(group_idx as u32)) } - /// Returns an optional predicate that should be used for timing out occupied cores. - /// - /// If `None`, no timing-out should be done. The predicate accepts the index of the core, and - /// the block number since which it has been occupied, and the respective parachain timeouts, - /// i.e. only within `config.paras_availability_period` of the last rotation would this return - /// `Some`, unless there are no rotations. + /// Returns a predicate that should be used for timing out occupied cores. /// - /// The timeout used to depend, but does not depend any more on group rotations. First of all - /// it only matters if a para got another chance (a retry). If there is a retry and it happens - /// still within the same group rotation a censoring backing group would need to censor again - /// and lose out again on backing rewards. This is bad for the censoring backing group, it does - /// not matter for the parachain as long as it is retried often enough (so it eventually gets a - /// try on another backing group) - the effect is similar to having a prolonged timeout. It - /// should also be noted that for both malicious and offline backing groups it is actually more - /// realistic that the candidate will not be backed to begin with, instead of getting backed - /// and then not made available. + /// This only ever times out cores that have been occupied across a group rotation boundary. pub(crate) fn availability_timeout_predicate( - ) -> Option) -> bool> { - let now = >::block_number(); + ) -> impl Fn(BlockNumberFor) -> AvailabilityTimeoutStatus> { let config = >::config(); - let session_start = >::get(); + let now = >::block_number(); + let rotation_info = Self::group_rotation_info(now); - let blocks_since_session_start = now.saturating_sub(session_start); - let blocks_since_last_rotation = - blocks_since_session_start % config.group_rotation_frequency.max(1u8.into()); + let next_rotation = rotation_info.next_rotation_at(); - if blocks_since_last_rotation >= config.paras_availability_period { - None - } else { - Some(|core_index: CoreIndex, pending_since| { - let availability_cores = AvailabilityCores::::get(); - let AssignmentProviderConfig { availability_period, .. } = - T::AssignmentProvider::get_provider_config(core_index); - let now = >::block_number(); - match availability_cores.get(core_index.0 as usize) { - None => true, // out-of-bounds, doesn't really matter what is returned. - Some(CoreOccupied::Free) => true, // core free, still doesn't matter. - Some(CoreOccupied::Paras(_)) => - now.saturating_sub(pending_since) >= availability_period, - } - }) + let times_out = Self::availability_timeout_check_required(); + + move |pending_since| { + let time_out_at = if times_out { + // We are at the beginning of the rotation, here availability period is relevant. + // Note: blocks backed in this rotation will never time out here as backed_in + + // config.paras_availability_period will always be > now for these blocks, as + // otherwise above condition would not be true. + pending_since + config.paras_availability_period + } else { + next_rotation + config.paras_availability_period + }; + + AvailabilityTimeoutStatus { timed_out: time_out_at <= now, live_until: time_out_at } } } + /// Is evaluation of `availability_timeout_predicate` necessary at the current block? + /// + /// This can be used to avoid calling `availability_timeout_predicate` for each core in case + /// this function returns false. + pub(crate) fn availability_timeout_check_required() -> bool { + let config = >::config(); + let now = >::block_number() + One::one(); + let rotation_info = Self::group_rotation_info(now); + + let current_window = rotation_info.last_rotation_at() + config.paras_availability_period; + now < current_window + } + /// Returns a helper for determining group rotation. pub(crate) fn group_rotation_info( now: BlockNumberFor, @@ -508,7 +597,7 @@ impl Pallet { pub(crate) fn update_claimqueue( just_freed_cores: impl IntoIterator, now: BlockNumberFor, - ) -> Vec>> { + ) { Self::move_claimqueue_forward(); Self::free_cores_and_fill_claimqueue(just_freed_cores, now) } @@ -534,61 +623,58 @@ impl Pallet { fn free_cores_and_fill_claimqueue( just_freed_cores: impl IntoIterator, now: BlockNumberFor, - ) -> Vec>> { + ) { let (mut concluded_paras, mut timedout_paras) = Self::free_cores(just_freed_cores); // This can only happen on new sessions at which we move all assignments back to the // provider. Hence, there's nothing we need to do here. if ValidatorGroups::::get().is_empty() { - vec![] - } else { - let n_lookahead = Self::claimqueue_lookahead(); - let n_session_cores = T::AssignmentProvider::session_core_count(); - let cq = ClaimQueue::::get(); - let ttl = >::config().on_demand_ttl; - - for core_idx in 0..n_session_cores { - let core_idx = CoreIndex::from(core_idx); - - // add previously timedout paras back into the queue - if let Some(mut entry) = timedout_paras.remove(&core_idx) { - let AssignmentProviderConfig { max_availability_timeouts, .. } = - T::AssignmentProvider::get_provider_config(core_idx); - if entry.availability_timeouts < max_availability_timeouts { - // Increment the timeout counter. - entry.availability_timeouts += 1; - // Reset the ttl so that a timed out assignment. - entry.ttl = now + ttl; - Self::add_to_claimqueue(core_idx, entry); - // The claim has been added back into the claimqueue. - // Do not pop another assignment for the core. - continue - } else { - // Consider timed out assignments for on demand parachains as concluded for - // the assignment provider - let ret = concluded_paras.insert(core_idx, entry.para_id()); - debug_assert!(ret.is_none()); - } + return + } + let n_lookahead = Self::claimqueue_lookahead(); + let n_session_cores = T::AssignmentProvider::session_core_count(); + let cq = ClaimQueue::::get(); + let ttl = >::config().on_demand_ttl; + + for core_idx in 0..n_session_cores { + let core_idx = CoreIndex::from(core_idx); + + // add previously timedout paras back into the queue + if let Some(mut entry) = timedout_paras.remove(&core_idx) { + let AssignmentProviderConfig { max_availability_timeouts, .. } = + T::AssignmentProvider::get_provider_config(core_idx); + if entry.availability_timeouts < max_availability_timeouts { + // Increment the timeout counter. + entry.availability_timeouts += 1; + // Reset the ttl so that a timed out assignment. + entry.ttl = now + ttl; + Self::add_to_claimqueue(core_idx, entry); + // The claim has been added back into the claimqueue. + // Do not pop another assignment for the core. + continue + } else { + // Consider timed out assignments for on demand parachains as concluded for + // the assignment provider + let ret = concluded_paras.insert(core_idx, entry.para_id()); + debug_assert!(ret.is_none()); } + } - // We consider occupied cores to be part of the claimqueue - let n_lookahead_used = cq.get(&core_idx).map_or(0, |v| v.len() as u32) + - if Self::is_core_occupied(core_idx) { 1 } else { 0 }; - for _ in n_lookahead_used..n_lookahead { - let concluded_para = concluded_paras.remove(&core_idx); - if let Some(assignment) = - T::AssignmentProvider::pop_assignment_for_core(core_idx, concluded_para) - { - Self::add_to_claimqueue(core_idx, ParasEntry::new(assignment, now + ttl)); - } + // We consider occupied cores to be part of the claimqueue + let n_lookahead_used = cq.get(&core_idx).map_or(0, |v| v.len() as u32) + + if Self::is_core_occupied(core_idx) { 1 } else { 0 }; + for _ in n_lookahead_used..n_lookahead { + let concluded_para = concluded_paras.remove(&core_idx); + if let Some(assignment) = + T::AssignmentProvider::pop_assignment_for_core(core_idx, concluded_para) + { + Self::add_to_claimqueue(core_idx, ParasEntry::new(assignment, now + ttl)); } } - - debug_assert!(timedout_paras.is_empty()); - debug_assert!(concluded_paras.is_empty()); - - Self::scheduled_claimqueue() } + + debug_assert!(timedout_paras.is_empty()); + debug_assert!(concluded_paras.is_empty()); } fn is_core_occupied(core_idx: CoreIndex) -> bool { @@ -623,29 +709,22 @@ impl Pallet { .ok_or("remove returned None")? .ok_or("Element in Claimqueue was None.")?; - // Since the core is now occupied, the next entry in the claimqueue in order to achieve - // 12 second block times needs to be None - if core_claims.front() != Some(&None) { - core_claims.push_front(None); - } Ok((pos as u32, pe)) }) } - // TODO: Temporary to imitate the old schedule() call. Will be adjusted when we make the - // scheduler AB ready - pub(crate) fn scheduled_claimqueue() -> Vec>> { + /// Paras scheduled next in the claim queue. + pub(crate) fn scheduled_paras() -> impl Iterator { + Self::scheduled_entries().map(|(core_idx, e)| (core_idx, e.assignment.para_id)) + } + + /// Internal access to entries at the top of the claim queue. + fn scheduled_entries() -> impl Iterator>)> { let claimqueue = ClaimQueue::::get(); claimqueue .into_iter() - .flat_map(|(core_idx, v)| { - v.front() - .cloned() - .flatten() - .map(|pe| CoreAssignment { core: core_idx, paras_entry: pe }) - }) - .collect() + .filter_map(|(core_idx, v)| v.front().cloned().flatten().map(|e| (core_idx, e))) } #[cfg(any(feature = "runtime-benchmarks", test))] diff --git a/polkadot/runtime/parachains/src/scheduler/common.rs b/polkadot/runtime/parachains/src/scheduler/common.rs index 0e8e8338b17b7..316e8e3b760cc 100644 --- a/polkadot/runtime/parachains/src/scheduler/common.rs +++ b/polkadot/runtime/parachains/src/scheduler/common.rs @@ -17,10 +17,7 @@ //! Common traits and types used by the scheduler and assignment providers. use frame_support::pallet_prelude::*; -use primitives::{ - v5::{Assignment, ParasEntry}, - CoreIndex, Id as ParaId, -}; +use primitives::{CoreIndex, Id as ParaId}; use scale_info::TypeInfo; use sp_std::prelude::*; @@ -28,21 +25,22 @@ use sp_std::prelude::*; #[allow(unused)] use crate::configuration::HostConfiguration; -/// Reasons a core might be freed -#[derive(Clone, Copy)] -pub enum FreedReason { - /// The core's work concluded and the parablock assigned to it is considered available. - Concluded, - /// The core's work timed out. - TimedOut, +/// An assignment for a parachain scheduled to be backed and included in a relay chain block. +#[derive(Clone, Encode, Decode, PartialEq, TypeInfo, RuntimeDebug)] +pub struct Assignment { + /// Assignment's ParaId + pub para_id: ParaId, +} + +impl Assignment { + /// Create a new `Assignment`. + pub fn new(para_id: ParaId) -> Self { + Self { para_id } + } } /// A set of variables required by the scheduler in order to operate. pub struct AssignmentProviderConfig { - /// The availability period specified by the implementation. - /// See [`HostConfiguration::paras_availability_period`] for more information. - pub availability_period: BlockNumber, - /// How many times a collation can time out on availability. /// Zero timeouts still means that a collation can be provided as per the slot auction /// assignment provider. @@ -72,25 +70,3 @@ pub trait AssignmentProvider { /// Returns a set of variables needed by the scheduler fn get_provider_config(core_idx: CoreIndex) -> AssignmentProviderConfig; } - -/// How a core is mapped to a backing group and a `ParaId` -#[derive(Clone, Encode, Decode, PartialEq, TypeInfo)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct CoreAssignment { - /// The core that is assigned. - pub core: CoreIndex, - /// The para id and accompanying information needed to collate and back a parablock. - pub paras_entry: ParasEntry, -} - -impl CoreAssignment { - /// Returns the [`ParaId`] of the assignment. - pub fn para_id(&self) -> ParaId { - self.paras_entry.para_id() - } - - /// Returns the inner [`ParasEntry`] of the assignment. - pub fn to_paras_entry(self) -> ParasEntry { - self.paras_entry - } -} diff --git a/polkadot/runtime/parachains/src/scheduler/migration.rs b/polkadot/runtime/parachains/src/scheduler/migration.rs index 32ac9deaf68f2..accff7016ed1e 100644 --- a/polkadot/runtime/parachains/src/scheduler/migration.rs +++ b/polkadot/runtime/parachains/src/scheduler/migration.rs @@ -20,7 +20,6 @@ use super::*; use frame_support::{ pallet_prelude::ValueQuery, storage_alias, traits::OnRuntimeUpgrade, weights::Weight, }; -use primitives::vstaging::Assignment; mod v0 { use super::*; diff --git a/polkadot/runtime/parachains/src/scheduler/tests.rs b/polkadot/runtime/parachains/src/scheduler/tests.rs index e203531ca49d2..108f365d6b5c3 100644 --- a/polkadot/runtime/parachains/src/scheduler/tests.rs +++ b/polkadot/runtime/parachains/src/scheduler/tests.rs @@ -18,7 +18,7 @@ use super::*; use frame_support::assert_ok; use keyring::Sr25519Keyring; -use primitives::{v5::Assignment, BlockNumber, SessionIndex, ValidationCode, ValidatorId}; +use primitives::{BlockNumber, SessionIndex, ValidationCode, ValidatorId}; use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; use crate::{ @@ -427,33 +427,27 @@ fn fill_claimqueue_fills() { { assert_eq!(Scheduler::claimqueue_len(), 2 * lookahead); - let scheduled = Scheduler::scheduled_claimqueue(); + let scheduled: BTreeMap<_, _> = Scheduler::scheduled_entries().collect(); // Cannot assert on indices anymore as they depend on the assignment providers assert!(claimqueue_contains_para_ids::(vec![chain_a, chain_b])); assert_eq!( - scheduled[0], - CoreAssignment { - core: CoreIndex(0), - paras_entry: ParasEntry { - assignment: Assignment { para_id: chain_a }, - availability_timeouts: 0, - ttl: 6 - }, - } + scheduled.get(&CoreIndex(0)).unwrap(), + &ParasEntry { + assignment: Assignment { para_id: chain_a }, + availability_timeouts: 0, + ttl: 6 + }, ); assert_eq!( - scheduled[1], - CoreAssignment { - core: CoreIndex(1), - paras_entry: ParasEntry { - assignment: Assignment { para_id: chain_b }, - availability_timeouts: 0, - ttl: 6 - }, - } + scheduled.get(&CoreIndex(1)).unwrap(), + &ParasEntry { + assignment: Assignment { para_id: chain_b }, + availability_timeouts: 0, + ttl: 6 + }, ); } @@ -481,42 +475,33 @@ fn fill_claimqueue_fills() { { assert_eq!(Scheduler::claimqueue_len(), 5); - let scheduled = Scheduler::scheduled_claimqueue(); + let scheduled: BTreeMap<_, _> = Scheduler::scheduled_entries().collect(); assert_eq!( - scheduled[0], - CoreAssignment { - core: CoreIndex(0), - paras_entry: ParasEntry { - assignment: Assignment { para_id: chain_a }, - availability_timeouts: 0, - ttl: 6 - }, - } + scheduled.get(&CoreIndex(0)).unwrap(), + &ParasEntry { + assignment: Assignment { para_id: chain_a }, + availability_timeouts: 0, + ttl: 6 + }, ); assert_eq!( - scheduled[1], - CoreAssignment { - core: CoreIndex(1), - paras_entry: ParasEntry { - assignment: Assignment { para_id: chain_b }, - availability_timeouts: 0, - ttl: 6 - }, - } + scheduled.get(&CoreIndex(1)).unwrap(), + &ParasEntry { + assignment: Assignment { para_id: chain_b }, + availability_timeouts: 0, + ttl: 6 + }, ); // Was added a block later, note the TTL. assert_eq!( - scheduled[2], - CoreAssignment { - core: CoreIndex(2), - paras_entry: ParasEntry { - assignment: Assignment { para_id: thread_a }, - availability_timeouts: 0, - ttl: 7 - }, - } + scheduled.get(&CoreIndex(2)).unwrap(), + &ParasEntry { + assignment: Assignment { para_id: thread_a }, + availability_timeouts: 0, + ttl: 7 + }, ); // Sits on the same core as `thread_a` assert_eq!( @@ -528,15 +513,12 @@ fn fill_claimqueue_fills() { }) ); assert_eq!( - scheduled[3], - CoreAssignment { - core: CoreIndex(3), - paras_entry: ParasEntry { - assignment: Assignment { para_id: thread_c }, - availability_timeouts: 0, - ttl: 7 - }, - } + scheduled.get(&CoreIndex(3)).unwrap(), + &ParasEntry { + assignment: Assignment { para_id: thread_c }, + availability_timeouts: 0, + ttl: 7 + }, ); } }); @@ -608,7 +590,7 @@ fn schedule_schedules_including_just_freed() { let mut now = 2; run_to_block(now, |_| None); - assert_eq!(Scheduler::scheduled_claimqueue().len(), 4); + assert_eq!(Scheduler::scheduled_paras().collect::>().len(), 4); // cores 0, 1, 2, and 3 should be occupied. mark them as such. let mut occupied_map: BTreeMap = BTreeMap::new(); @@ -630,7 +612,7 @@ fn schedule_schedules_including_just_freed() { // core 4 is free assert!(cores[4] == CoreOccupied::Free); - assert!(Scheduler::scheduled_claimqueue().is_empty()); + assert!(Scheduler::scheduled_paras().collect::>().is_empty()); // All core index entries in the claimqueue should have `None` in them. Scheduler::claimqueue().iter().for_each(|(_core_idx, core_queue)| { @@ -657,21 +639,18 @@ fn schedule_schedules_including_just_freed() { run_to_block(now, |_| None); { - let scheduled = Scheduler::scheduled_claimqueue(); + let scheduled: BTreeMap<_, _> = Scheduler::scheduled_entries().collect(); // cores 0 and 1 are occupied by lease holding parachains. cores 2 and 3 are occupied by // on-demand parachain claims. core 4 was free. assert_eq!(scheduled.len(), 1); assert_eq!( - scheduled[0], - CoreAssignment { - core: CoreIndex(4), - paras_entry: ParasEntry { - assignment: Assignment { para_id: thread_b }, - availability_timeouts: 0, - ttl: 8 - }, - } + scheduled.get(&CoreIndex(4)).unwrap(), + &ParasEntry { + assignment: Assignment { para_id: thread_b }, + availability_timeouts: 0, + ttl: 8 + }, ); } @@ -686,54 +665,42 @@ fn schedule_schedules_including_just_freed() { Scheduler::update_claimqueue(just_updated, now); { - let scheduled = Scheduler::scheduled_claimqueue(); + let scheduled: BTreeMap<_, _> = Scheduler::scheduled_entries().collect(); // 1 thing scheduled before, + 3 cores freed. assert_eq!(scheduled.len(), 4); assert_eq!( - scheduled[0], - CoreAssignment { - core: CoreIndex(0), - paras_entry: ParasEntry { - assignment: Assignment { para_id: chain_a }, - availability_timeouts: 0, - ttl: 8 - }, - } + scheduled.get(&CoreIndex(0)).unwrap(), + &ParasEntry { + assignment: Assignment { para_id: chain_a }, + availability_timeouts: 0, + ttl: 8 + }, ); assert_eq!( - scheduled[1], - CoreAssignment { - core: CoreIndex(2), - paras_entry: ParasEntry { - assignment: Assignment { para_id: thread_d }, - availability_timeouts: 0, - ttl: 8 - }, - } + scheduled.get(&CoreIndex(2)).unwrap(), + &ParasEntry { + assignment: Assignment { para_id: thread_d }, + availability_timeouts: 0, + ttl: 8 + }, ); // Although C was descheduled, the core `4` was occupied so C goes back to the queue. assert_eq!( - scheduled[2], - CoreAssignment { - core: CoreIndex(3), - paras_entry: ParasEntry { - assignment: Assignment { para_id: thread_c }, - availability_timeouts: 1, - ttl: 8 - }, - } + scheduled.get(&CoreIndex(3)).unwrap(), + &ParasEntry { + assignment: Assignment { para_id: thread_c }, + availability_timeouts: 1, + ttl: 8 + }, ); assert_eq!( - scheduled[3], - CoreAssignment { - core: CoreIndex(4), - paras_entry: ParasEntry { - assignment: Assignment { para_id: thread_b }, - availability_timeouts: 0, - ttl: 8 - }, - } + scheduled.get(&CoreIndex(4)).unwrap(), + &ParasEntry { + assignment: Assignment { para_id: thread_b }, + availability_timeouts: 0, + ttl: 8 + }, ); // The only assignment yet to be popped on to the claim queue is `thread_e`. @@ -900,14 +867,14 @@ fn schedule_rotates_groups() { run_to_block(now, |_| None); let assert_groups_rotated = |rotations: u32, now: &BlockNumberFor| { - let scheduled = Scheduler::scheduled_claimqueue(); + let scheduled: BTreeMap<_, _> = Scheduler::scheduled_paras().collect(); assert_eq!(scheduled.len(), 2); assert_eq!( - Scheduler::group_assigned_to_core(scheduled[0].core, *now).unwrap(), + Scheduler::group_assigned_to_core(CoreIndex(0), *now).unwrap(), GroupIndex((0u32 + rotations) % on_demand_cores) ); assert_eq!( - Scheduler::group_assigned_to_core(scheduled[1].core, *now).unwrap(), + Scheduler::group_assigned_to_core(CoreIndex(1), *now).unwrap(), GroupIndex((1u32 + rotations) % on_demand_cores) ); }; @@ -999,7 +966,7 @@ fn on_demand_claims_are_pruned_after_timing_out() { ] .into_iter() .collect(); - let core_assignments = Scheduler::update_claimqueue(just_updated, now); + Scheduler::update_claimqueue(just_updated, now); // ParaId a exists in the claim queue until max_retries is reached. if n < max_retries + now { @@ -1008,13 +975,9 @@ fn on_demand_claims_are_pruned_after_timing_out() { assert!(!claimqueue_contains_para_ids::(vec![thread_a])); } - // Occupy the cores based on the output of update_claimqueue. - Scheduler::occupied( - core_assignments - .iter() - .map(|core_assignment| (core_assignment.core, core_assignment.para_id())) - .collect(), - ); + let core_assignments = Scheduler::scheduled_paras().collect(); + // Occupy the cores based on the result of update_claimqueue. + Scheduler::occupied(core_assignments); } // ParaId a does not exist in the claimqueue/availability_cores after @@ -1054,7 +1017,7 @@ fn on_demand_claims_are_pruned_after_timing_out() { } } - let core_assignments = Scheduler::update_claimqueue(just_updated, now); + Scheduler::update_claimqueue(just_updated, now); // ParaId a exists in the claim queue until groups are rotated. if n < 31 { @@ -1063,13 +1026,9 @@ fn on_demand_claims_are_pruned_after_timing_out() { assert!(!claimqueue_contains_para_ids::(vec![thread_a])); } - // Occupy the cores based on the output of update_claimqueue. - Scheduler::occupied( - core_assignments - .iter() - .map(|core_assignment| (core_assignment.core, core_assignment.para_id())) - .collect(), - ); + let core_assignments = Scheduler::scheduled_paras().collect(); + // Occupy the cores based on the result of update_claimqueue. + Scheduler::occupied(core_assignments); } // ParaId a does not exist in the claimqueue/availability_cores after @@ -1124,33 +1083,25 @@ fn availability_predicate_works() { run_to_block(1 + paras_availability_period, |_| None); - assert!(Scheduler::availability_timeout_predicate().is_none()); + assert!(!Scheduler::availability_timeout_check_required()); run_to_block(1 + group_rotation_frequency, |_| None); { - let pred = Scheduler::availability_timeout_predicate() - .expect("predicate exists recently after rotation"); - let now = System::block_number(); - let would_be_timed_out = now - paras_availability_period; - for i in 0..AvailabilityCores::::get().len() { - // returns true for unoccupied cores. - // And can time out paras at this stage. - assert!(pred(CoreIndex(i as u32), would_be_timed_out)); - } + assert!(Scheduler::availability_timeout_check_required()); + let pred = Scheduler::availability_timeout_predicate(); + let last_rotation = Scheduler::group_rotation_info(now).last_rotation_at(); - assert!(!pred(CoreIndex(0), now)); - assert!(!pred(CoreIndex(1), now)); - assert!(pred(CoreIndex(2), now)); + let would_be_timed_out = now - paras_availability_period; + let should_not_be_timed_out = last_rotation; - // check the tight bound. - assert!(pred(CoreIndex(0), now - paras_availability_period)); - assert!(pred(CoreIndex(1), now - paras_availability_period)); + assert!(pred(would_be_timed_out).timed_out); + assert!(!pred(should_not_be_timed_out).timed_out); + assert!(!pred(now).timed_out); // check the threshold is exact. - assert!(!pred(CoreIndex(0), now - paras_availability_period + 1)); - assert!(!pred(CoreIndex(1), now - paras_availability_period + 1)); + assert!(!pred(would_be_timed_out + 1).timed_out); } run_to_block(1 + group_rotation_frequency + paras_availability_period, |_| None);