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 <eskimor@no-such-url.com>
This commit is contained in:
eskimor
2023-09-06 18:19:21 +02:00
committed by GitHub
parent eeb368ed9c
commit 1e2a2f0c69
19 changed files with 489 additions and 643 deletions
Generated
+1
View File
@@ -12792,6 +12792,7 @@ dependencies = [
"pallet-timestamp",
"pallet-vesting",
"parity-scale-codec",
"polkadot-core-primitives",
"polkadot-parachain-primitives",
"polkadot-primitives",
"polkadot-primitives-test-helpers",
+2 -2
View File
@@ -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,
-54
View File
@@ -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<N = BlockNumber> {
/// 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<N> ParasEntry<N> {
/// 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<N> {
/// The core is not occupied.
Free,
/// A paras.
Paras(ParasEntry<N>),
}
impl<N> CoreOccupied<N> {
/// 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))]
+1
View File
@@ -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"
+2 -2
View File
@@ -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::*;
@@ -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<T: Config> AssignmentProvider<BlockNumberFor<T>> for Pallet<T> {
fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig<BlockNumberFor<T>> {
let config = <configuration::Pallet<T>>::config();
AssignmentProviderConfig {
availability_period: config.paras_availability_period,
max_availability_timeouts: config.on_demand_retries,
ttl: config.on_demand_ttl,
}
@@ -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) {
@@ -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<T: Config> AssignmentProvider<BlockNumberFor<T>> for Pallet<T> {
fn push_assignment_for_core(_: CoreIndex, _: Assignment) {}
fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig<BlockNumberFor<T>> {
let config = <configuration::Pallet<T>>::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
+8 -6
View File
@@ -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,
@@ -190,11 +190,20 @@ pub struct HostConfiguration<BlockNumber> {
///
/// 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,
+93 -113
View File
@@ -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::*;
@@ -597,7 +600,7 @@ impl<T: Config> Pallet<T> {
pub(crate) fn process_candidates<GV>(
allowed_relay_parents: &AllowedRelayParentsTracker<T::Hash, BlockNumberFor<T>>,
candidates: Vec<BackedCandidate<T::Hash>>,
scheduled: Vec<CoreAssignment<BlockNumberFor<T>>>,
scheduled: &BTreeMap<ParaId, CoreIndex>,
group_validators: GV,
) -> Result<ProcessedCandidates<T::Hash>, DispatchError>
where
@@ -620,20 +623,18 @@ impl<T: Config> Pallet<T> {
// 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<BlockNumberFor<T>>| -> DispatchResult {
ensure!(
last_core.map_or(true, |core| assignment.core > core),
Error::<T>::ScheduledOutOfOrder,
);
let mut check_assignment_in_order = |core_idx| -> DispatchResult {
ensure!(
last_core.map_or(true, |core| core_idx > core),
Error::<T>::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
@@ -645,9 +646,7 @@ impl<T: Config> Pallet<T> {
//
// 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;
@@ -681,108 +680,89 @@ impl<T: Config> Pallet<T> {
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(&para_id).ok_or(Error::<T>::UnscheduledCandidate)?;
check_assignment_in_order(core_idx)?;
ensure!(
<PendingAvailability<T>>::get(&para_id).is_none() &&
<PendingAvailabilityCommitments<T>>::get(&para_id).is_none(),
Error::<T>::CandidateScheduledBeforeParaFree,
);
if para_id == core_assignment.paras_entry.para_id() {
ensure!(
<PendingAvailability<T>>::get(&para_id).is_none() &&
<PendingAvailabilityCommitments<T>>::get(&para_id).is_none(),
Error::<T>::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 = <scheduler::Pallet<T>>::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::<T>::InvalidAssignment
})?;
let group_vals =
group_validators(group_idx).ok_or_else(|| Error::<T>::InvalidGroupIndex)?;
// account for already skipped, and then skip this one.
skip = i + skip + 1;
// 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())
},
);
// 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 = <scheduler::Pallet<T>>::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::<T>::InvalidAssignment
})?;
let group_vals = group_validators(group_idx)
.ok_or_else(|| Error::<T>::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::<T>::InsufficientBacking,
match maybe_amount_validated {
Ok(amount_validated) => ensure!(
amount_validated >=
effective_minimum_backing_votes(
group_vals.len(),
minimum_backing_votes
),
Err(()) => {
Err(Error::<T>::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::<T>::InsufficientBacking,
),
Err(()) => {
Err(Error::<T>::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));
}
// 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::<T>::UnscheduledCandidate);
}
// 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
@@ -1043,13 +1023,13 @@ impl<T: Config> Pallet<T> {
///
/// Returns a vector of cleaned-up core IDs.
pub(crate) fn collect_pending(
pred: impl Fn(CoreIndex, BlockNumberFor<T>) -> bool,
pred: impl Fn(BlockNumberFor<T>) -> AvailabilityTimeoutStatus<BlockNumberFor<T>>,
) -> Vec<CoreIndex> {
let mut cleaned_up_ids = Vec::new();
let mut cleaned_up_cores = Vec::new();
for (para_id, pending_record) in <PendingAvailability<T>>::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);
}
@@ -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();
<PendingAvailability<Test>>::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!(<PendingAvailabilityCommitments<Test>>::get(&chain_a).is_some());
assert!(<PendingAvailabilityCommitments<Test>>::get(&chain_b).is_some());
ParaInclusion::collect_pending(|core, _since| core == CoreIndex::from(0));
ParaInclusion::collect_pending(Scheduler::availability_timeout_predicate());
assert!(<PendingAvailability<Test>>::get(&chain_a).is_none());
assert!(<PendingAvailability<Test>>::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_a_assignment = (chain_a, CoreIndex::from(0));
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_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::<Test>::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::<Test>::UnscheduledCandidate
Error::<Test>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::CandidateScheduledBeforeParaFree
@@ -1272,7 +1262,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::<Test>::PrematureCodeUpgrade
@@ -1306,7 +1296,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::<Test>::ValidationDataHashMismatch.into()),
@@ -1341,7 +1331,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::<Test>::InvalidValidationCodeHash
@@ -1376,7 +1366,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::<Test>::ParaHeadMismatch
@@ -1446,21 +1436,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,
@@ -1548,11 +1526,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");
@@ -1738,12 +1714,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(),
@@ -1769,7 +1740,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");
@@ -1895,28 +1866,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,
@@ -1998,11 +1951,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");
@@ -2212,15 +2163,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(),
@@ -2246,7 +2189,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");
@@ -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 = <scheduler::Pallet<T>>::availability_timeout_predicate();
let freed_timeout = if let Some(pred) = availability_pred {
let freed_timeout = if <scheduler::Pallet<T>>::availability_timeout_check_required() {
let pred = <scheduler::Pallet<T>>::availability_timeout_predicate();
<inclusion::Pallet<T>>::collect_pending(pred)
} else {
Vec::new()
@@ -583,7 +580,10 @@ impl<T: Config> Pallet<T> {
let freed = collect_all_freed_cores::<T, _>(freed_concluded.iter().cloned());
let scheduled = <scheduler::Pallet<T>>::update_claimqueue(freed, now);
<scheduler::Pallet<T>>::update_claimqueue(freed, now);
let scheduled = <scheduler::Pallet<T>>::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<T: Config> Pallet<T> {
.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<T: Config> Pallet<T> {
} = <inclusion::Pallet<T>>::process_candidates(
&allowed_relay_parents,
backed_candidates.clone(),
scheduled,
&scheduled,
<scheduler::Pallet<T>>::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<BackedCandidate<T::Hash>>,
mut candidate_has_concluded_invalid_dispute_or_is_invalid: F,
scheduled: &[CoreAssignment<BlockNumberFor<T>>],
scheduled: &BTreeMap<ParaId, CoreIndex>,
) -> Vec<BackedCandidate<T::Hash>> {
// 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::<BTreeMap<ParaId, CoreIndex>>();
// 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
@@ -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::<Vec<_>>();
.map(|idx| (ParaId::from(1_u32 + idx as u32), CoreIndex::from(idx as u32)))
.collect::<BTreeMap<_, _>>();
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::<Test, _>(
backed_candidates.clone(),
has_concluded_invalid,
@@ -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<T: initializer::Config>(
/// Implementation for the `availability_cores` function of the runtime API.
pub fn availability_cores<T: initializer::Config>() -> Vec<CoreState<T::Hash, BlockNumberFor<T>>> {
let cores = <scheduler::Pallet<T>>::availability_cores();
let config = <configuration::Pallet<T>>::config();
let now = <frame_system::Pallet<T>>::block_number() + One::one();
let rotation_info = <scheduler::Pallet<T>>::group_rotation_info(now);
let time_out_at = |backed_in_number, availability_period| {
let time_out_at = backed_in_number + availability_period;
// 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.
<scheduler::Pallet<T>>::update_claimqueue(Vec::new(), now);
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
}
};
let time_out_for = <scheduler::Pallet<T>>::availability_timeout_predicate();
let group_responsible_for =
|backed_in_number, core_index| match <scheduler::Pallet<T>>::group_assigned_to_core(
@@ -93,7 +79,9 @@ pub fn availability_cores<T: initializer::Config>() -> Vec<CoreState<T::Hash, Bl
},
};
let mut core_states: Vec<_> = cores
let scheduled: BTreeMap<_, _> = <scheduler::Pallet<T>>::scheduled_paras().collect();
cores
.into_iter()
.enumerate()
.map(|(i, core)| match core {
@@ -108,7 +96,7 @@ pub fn availability_cores<T: initializer::Config>() -> Vec<CoreState<T::Hash, Bl
i as u32,
)),
occupied_since: backed_in_number,
time_out_at: time_out_at(backed_in_number, config.paras_availability_period),
time_out_at: time_out_for(backed_in_number).live_until,
next_up_on_time_out: <scheduler::Pallet<T>>::next_up_on_time_out(CoreIndex(
i as u32,
)),
@@ -121,19 +109,15 @@ pub fn availability_cores<T: initializer::Config>() -> Vec<CoreState<T::Hash, Bl
candidate_descriptor: pending_availability.candidate_descriptor().clone(),
})
},
CoreOccupied::Free => 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 <scheduler::Pallet<T>>::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.
+179 -100
View File
@@ -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<T: Config> =
StorageValue<_, Vec<CoreOccupied<BlockNumberFor<T>>>, 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<N> {
/// 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<N>),
}
impl<N> CoreOccupied<N> {
/// 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<CoreIndex, VecDeque<Option<ParasEntry<BlockNumberFor<T>>>>>,
ValueQuery,
>;
/// Assignments as tracked in the claim queue.
#[derive(Clone, Encode, Decode, TypeInfo, PartialEq, RuntimeDebug)]
pub struct ParasEntry<N = BlockNumber> {
/// 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<N> ParasEntry<N> {
/// 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<BlockNumber> {
/// 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<BlockNumber>,
}
impl<BlockNumber> CoreAssignment<BlockNumber> {
/// 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<BlockNumber> {
self.paras_entry
}
}
/// Availability timeout status of a core.
pub(crate) struct AvailabilityTimeoutStatus<BlockNumber> {
/// 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<T: Config> Pallet<T> {
Some(GroupIndex(group_idx as u32))
}
/// Returns an optional predicate that should be used for timing out occupied cores.
/// Returns a 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.
///
/// 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<impl Fn(CoreIndex, BlockNumberFor<T>) -> bool> {
let now = <frame_system::Pallet<T>>::block_number();
) -> impl Fn(BlockNumberFor<T>) -> AvailabilityTimeoutStatus<BlockNumberFor<T>> {
let config = <configuration::Pallet<T>>::config();
let session_start = <SessionStartBlock<T>>::get();
let now = <frame_system::Pallet<T>>::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::<T>::get();
let AssignmentProviderConfig { availability_period, .. } =
T::AssignmentProvider::get_provider_config(core_index);
let now = <frame_system::Pallet<T>>::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 = <configuration::Pallet<T>>::config();
let now = <frame_system::Pallet<T>>::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<T>,
@@ -508,7 +597,7 @@ impl<T: Config> Pallet<T> {
pub(crate) fn update_claimqueue(
just_freed_cores: impl IntoIterator<Item = (CoreIndex, FreedReason)>,
now: BlockNumberFor<T>,
) -> Vec<CoreAssignment<BlockNumberFor<T>>> {
) {
Self::move_claimqueue_forward();
Self::free_cores_and_fill_claimqueue(just_freed_cores, now)
}
@@ -534,61 +623,58 @@ impl<T: Config> Pallet<T> {
fn free_cores_and_fill_claimqueue(
just_freed_cores: impl IntoIterator<Item = (CoreIndex, FreedReason)>,
now: BlockNumberFor<T>,
) -> Vec<CoreAssignment<BlockNumberFor<T>>> {
) {
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::<T>::get().is_empty() {
vec![]
} else {
let n_lookahead = Self::claimqueue_lookahead();
let n_session_cores = T::AssignmentProvider::session_core_count();
let cq = ClaimQueue::<T>::get();
let ttl = <configuration::Pallet<T>>::config().on_demand_ttl;
return
}
let n_lookahead = Self::claimqueue_lookahead();
let n_session_cores = T::AssignmentProvider::session_core_count();
let cq = ClaimQueue::<T>::get();
let ttl = <configuration::Pallet<T>>::config().on_demand_ttl;
for core_idx in 0..n_session_cores {
let core_idx = CoreIndex::from(core_idx);
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));
}
// 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());
}
}
debug_assert!(timedout_paras.is_empty());
debug_assert!(concluded_paras.is_empty());
Self::scheduled_claimqueue()
// 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());
}
fn is_core_occupied(core_idx: CoreIndex) -> bool {
@@ -623,29 +709,22 @@ impl<T: Config> Pallet<T> {
.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<CoreAssignment<BlockNumberFor<T>>> {
/// Paras scheduled next in the claim queue.
pub(crate) fn scheduled_paras() -> impl Iterator<Item = (CoreIndex, ParaId)> {
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<Item = (CoreIndex, ParasEntry<BlockNumberFor<T>>)> {
let claimqueue = ClaimQueue::<T>::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))]
@@ -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<BlockNumber> {
/// 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<BlockNumber> {
/// Returns a set of variables needed by the scheduler
fn get_provider_config(core_idx: CoreIndex) -> AssignmentProviderConfig<BlockNumber>;
}
/// 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<BlockNumber> {
/// 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<BlockNumber>,
}
impl<BlockNumber> CoreAssignment<BlockNumber> {
/// 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<BlockNumber> {
self.paras_entry
}
}
@@ -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::*;
@@ -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::<Test>(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::<Vec<_>>().len(), 4);
// cores 0, 1, 2, and 3 should be occupied. mark them as such.
let mut occupied_map: BTreeMap<CoreIndex, ParaId> = 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::<Vec<_>>().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<Test>| {
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::<Test>(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::<Test>(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();
assert!(Scheduler::availability_timeout_check_required());
let pred = Scheduler::availability_timeout_predicate();
let last_rotation = Scheduler::group_rotation_info(now).last_rotation_at();
let would_be_timed_out = now - paras_availability_period;
for i in 0..AvailabilityCores::<Test>::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));
}
let should_not_be_timed_out = last_rotation;
assert!(!pred(CoreIndex(0), now));
assert!(!pred(CoreIndex(1), now));
assert!(pred(CoreIndex(2), now));
// 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);