Elastic scaling: runtime dependency tracking and enactment (#3479)

Changes needed to implement the runtime part of elastic scaling:
https://github.com/paritytech/polkadot-sdk/issues/3131,
https://github.com/paritytech/polkadot-sdk/issues/3132,
https://github.com/paritytech/polkadot-sdk/issues/3202

Also fixes https://github.com/paritytech/polkadot-sdk/issues/3675

TODOs:

- [x] storage migration
- [x] optimise process_candidates from O(N^2)
- [x] drop backable candidates which form cycles
- [x] fix unit tests
- [x] add more unit tests
- [x] check the runtime APIs which use the pending availability storage.
We need to expose all of them, see
https://github.com/paritytech/polkadot-sdk/issues/3576
- [x] optimise the candidate selection. we're currently picking randomly
until we satisfy the weight limit. we need to be smart about not
breaking candidate chains while being fair to all paras -
https://github.com/paritytech/polkadot-sdk/pull/3573

Relies on the changes made in
https://github.com/paritytech/polkadot-sdk/pull/3233 in terms of the
inclusion policy and the candidate ordering

---------

Signed-off-by: alindima <alin@parity.io>
Co-authored-by: command-bot <>
Co-authored-by: eskimor <eskimor@users.noreply.github.com>
This commit is contained in:
Alin Dima
2024-03-21 12:10:45 +02:00
committed by GitHub
parent 75074952a8
commit 4842faf65d
18 changed files with 4061 additions and 1734 deletions
@@ -22,6 +22,7 @@ use crate::{
scheduler::{self, CoreOccupied},
session_info, shared,
};
use frame_support::traits::{GetStorageVersion, StorageVersion};
use frame_system::pallet_prelude::*;
use primitives::{
async_backing::{
@@ -92,16 +93,41 @@ pub fn availability_cores<T: initializer::Config>() -> Vec<CoreState<T::Hash, Bl
.enumerate()
.map(|(i, core)| match core {
CoreOccupied::Paras(entry) => {
let pending_availability =
<inclusion::Pallet<T>>::pending_availability(entry.para_id())
.expect("Occupied core always has pending availability; qed");
// Due to https://github.com/paritytech/polkadot-sdk/issues/64, using the new storage types would cause
// this runtime API to panic. We explicitly handle the storage for version 0 to
// prevent that. When removing the inclusion v0 -> v1 migration, this bit of code
// can also be removed.
let pending_availability = if <inclusion::Pallet<T>>::on_chain_storage_version() ==
StorageVersion::new(0)
{
inclusion::migration::v0::PendingAvailability::<T>::get(entry.para_id())
.expect("Occupied core always has pending availability; qed")
} else {
let candidate = <inclusion::Pallet<T>>::pending_availability_with_core(
entry.para_id(),
CoreIndex(i as u32),
)
.expect("Occupied core always has pending availability; qed");
let backed_in_number = *pending_availability.backed_in_number();
// Translate to the old candidate format, as we don't need the commitments now.
inclusion::migration::v0::CandidatePendingAvailability {
core: candidate.core_occupied(),
hash: candidate.candidate_hash(),
descriptor: candidate.candidate_descriptor().clone(),
availability_votes: candidate.availability_votes().clone(),
backers: candidate.backers().clone(),
relay_parent_number: candidate.relay_parent_number(),
backed_in_number: candidate.backed_in_number(),
backing_group: candidate.backing_group(),
}
};
let backed_in_number = pending_availability.backed_in_number;
// Use the same block number for determining the responsible group as what the
// backing subsystem would use when it calls validator_groups api.
let backing_group_allocation_time =
pending_availability.relay_parent_number() + One::one();
pending_availability.relay_parent_number + One::one();
CoreState::Occupied(OccupiedCore {
next_up_on_available: <scheduler::Pallet<T>>::next_up_on_available(CoreIndex(
i as u32,
@@ -111,13 +137,13 @@ pub fn availability_cores<T: initializer::Config>() -> Vec<CoreState<T::Hash, Bl
next_up_on_time_out: <scheduler::Pallet<T>>::next_up_on_time_out(CoreIndex(
i as u32,
)),
availability: pending_availability.availability_votes().clone(),
availability: pending_availability.availability_votes.clone(),
group_responsible: group_responsible_for(
backing_group_allocation_time,
pending_availability.core_occupied(),
pending_availability.core,
),
candidate_hash: pending_availability.candidate_hash(),
candidate_descriptor: pending_availability.candidate_descriptor().clone(),
candidate_hash: pending_availability.hash,
candidate_descriptor: pending_availability.descriptor,
})
},
CoreOccupied::Free => {
@@ -200,8 +226,8 @@ pub fn assumed_validation_data<T: initializer::Config>(
};
let persisted_validation_data = make_validation_data().or_else(|| {
// Try again with force enacting the core. This check only makes sense if
// the core is occupied.
// Try again with force enacting the pending candidates. This check only makes sense if
// there are any pending candidates.
<inclusion::Pallet<T>>::pending_availability(para_id).and_then(|_| {
<inclusion::Pallet<T>>::force_enact(para_id);
make_validation_data()
@@ -465,27 +491,23 @@ pub fn backing_state<T: initializer::Config>(
};
let pending_availability = {
// Note: the API deals with a `Vec` as it is future-proof for cases
// where there may be multiple candidates pending availability at a time.
// But at the moment only one candidate can be pending availability per
// parachain.
crate::inclusion::PendingAvailability::<T>::get(&para_id)
.and_then(|pending| {
let commitments =
crate::inclusion::PendingAvailabilityCommitments::<T>::get(&para_id);
commitments.map(move |c| (pending, c))
.map(|pending_candidates| {
pending_candidates
.into_iter()
.map(|candidate| {
CandidatePendingAvailability {
candidate_hash: candidate.candidate_hash(),
descriptor: candidate.candidate_descriptor().clone(),
commitments: candidate.candidate_commitments().clone(),
relay_parent_number: candidate.relay_parent_number(),
max_pov_size: constraints.max_pov_size, /* assume always same in
* session. */
}
})
.collect()
})
.map(|(pending, commitments)| {
CandidatePendingAvailability {
candidate_hash: pending.candidate_hash(),
descriptor: pending.candidate_descriptor().clone(),
commitments,
relay_parent_number: pending.relay_parent_number(),
max_pov_size: constraints.max_pov_size, // assume always same in session.
}
})
.into_iter()
.collect()
.unwrap_or_else(|| vec![])
};
Some(BackingState { constraints, pending_availability })