Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Elastic scaling: runtime dependency tracking and enactment #3479

Merged
merged 53 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
442185e
Initial draft changes
alindima Feb 16, 2024
799fabe
bugfixes
alindima Feb 26, 2024
9f3ba62
filter descendants of disputed candidates
alindima Feb 26, 2024
5b13eb7
some simplifications
alindima Feb 27, 2024
ecc5088
assert that candidates of a para are sorted in chain dependency order…
alindima Feb 28, 2024
0fb7b8c
deduplicate some of the logic for freeing cores
alindima Feb 28, 2024
0ed6fc3
update some comments
alindima Feb 28, 2024
0a08fa0
unify dropped candidates errors
alindima Feb 28, 2024
9e51e20
add more logs
alindima Feb 28, 2024
4e154e6
remove some todos
alindima Feb 28, 2024
ccef35b
review comments
alindima Feb 29, 2024
3f67898
some more nits
alindima Feb 29, 2024
c4fedd7
Merge remote-tracking branch 'origin/master' into alindima/elastic-sc…
alindima Feb 29, 2024
58b4129
add runtime migration to inclusion storage
alindima Feb 29, 2024
23a8d34
add migration tests
alindima Mar 1, 2024
e0d9dff
don't allow candidate cycles
alindima Mar 1, 2024
0198fb3
fix bug
alindima Mar 1, 2024
267fa05
make tests compile and make paras_inherent tests pass
alindima Mar 1, 2024
7733b25
fix inclusion tests
alindima Mar 1, 2024
c32f925
fix some more tests
alindima Mar 1, 2024
40e2933
clippy
alindima Mar 1, 2024
e8f5d2c
Merge remote-tracking branch 'origin/master' into alindima/elastic-sc…
alindima Mar 1, 2024
630261e
some review comments
alindima Mar 11, 2024
7dc5358
simplify update_pending_availability_and_get_freed_cores
alindima Mar 11, 2024
3793f20
fix clippy
alindima Mar 11, 2024
6c0d062
add prdoc
alindima Mar 11, 2024
74acc46
fix availability_cores runtime API
alindima Mar 11, 2024
4dd28a2
Merge remote-tracking branch 'origin/master' into alindima/elastic-sc…
alindima Mar 11, 2024
5afb901
remove some unused errors and reintroduce ValidationDataHashMismatch
alindima Mar 11, 2024
a8c10be
add more unit tests to inclusion module
alindima Mar 12, 2024
d2309a7
more inclusion unit tests
alindima Mar 13, 2024
c59bce0
more tests to paras_inherent
alindima Mar 13, 2024
5e1a351
more paras_inherent tests
alindima Mar 14, 2024
37ff4c5
optimise update_pending_availability_and_get_freed_cores
alindima Mar 15, 2024
a92523b
make force-enact work on all cores
alindima Mar 15, 2024
b77806e
some review comments
alindima Mar 15, 2024
318b8da
call free_cores_and_fill_claimqueue only once
alindima Mar 15, 2024
376d19c
add removal of pendingbitfields storage to the migration and add more…
alindima Mar 15, 2024
8cb5ff6
Merge remote-tracking branch 'origin/master' into alindima/elastic-sc…
alindima Mar 15, 2024
98f4a0e
one more test for paras_inherent
alindima Mar 18, 2024
e537513
".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime…
Mar 18, 2024
f440217
".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime…
Mar 18, 2024
2999589
Merge branch 'master' into alindima/elastic-scaling-runtime
alindima Mar 18, 2024
a745a63
fix runtime API panic
alindima Mar 19, 2024
1b70b11
Merge remote-tracking branch 'origin/master' into alindima/elastic-sc…
alindima Mar 19, 2024
c3060bd
fix clippy
alindima Mar 19, 2024
9f52b8d
map_candidates_to_cores: check core index for single core if ElasticS…
alindima Mar 20, 2024
c27de6a
add a couple more test cases
alindima Mar 20, 2024
e05693c
review comment
alindima Mar 20, 2024
bb76778
Merge branch 'master' into alindima/elastic-scaling-runtime
alindima Mar 20, 2024
91f705c
improve test
alindima Mar 21, 2024
0c8dc20
Merge remote-tracking branch 'origin/master' into alindima/elastic-sc…
alindima Mar 21, 2024
b8d2212
Merge branch 'master' into alindima/elastic-scaling-runtime
eskimor Mar 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions polkadot/roadmap/implementers-guide/src/runtime/inclusion.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,16 @@ All failed checks should lead to an unrecoverable error making the block invalid
// return a vector of cleaned-up core IDs.
}
```
* `force_enact(ParaId)`: Forcibly enact the candidate with the given ID as though it had been deemed available by
bitfields. Is a no-op if there is no candidate pending availability for this para-id. This should generally not be
used but it is useful during execution of Runtime APIs, where the changes to the state are expected to be discarded
directly after.
* `force_enact(ParaId)`: Forcibly enact the pending candidates of the given paraid as though they had been deemed
available by bitfields. Is a no-op if there is no candidate pending availability for this para-id.
If there are multiple candidates pending availability for this para-id, it will enact all of
them. This should generally not be used but it is useful during execution of Runtime APIs,
where the changes to the state are expected to be discarded directly after.
* `candidate_pending_availability(ParaId) -> Option<CommittedCandidateReceipt>`: returns the `CommittedCandidateReceipt`
pending availability for the para provided, if any.
* `pending_availability(ParaId) -> Option<CandidatePendingAvailability>`: returns the metadata around the candidate
pending availability for the para, if any.
* `collect_disputed(disputed: Vec<CandidateHash>) -> Vec<CoreIndex>`: Sweeps through all paras pending availability. If
* `free_disputed(disputed: Vec<CandidateHash>) -> Vec<CoreIndex>`: Sweeps through all paras pending availability. If
the candidate hash is one of the disputed candidates, then clean up the corresponding storage for that candidate and
the commitments. Return a vector of cleaned-up core IDs.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ There are a couple of important notes to the operations in this inherent as they
this fork.
1. When disputes are initiated, we remove the block from pending availability. This allows us to roll back chains to the
block before blocks are included as opposed to backing. It's important to do this before processing bitfields.
1. `Inclusion::collect_disputed` is kind of expensive so it's important to gate this on whether there are actually any
1. `Inclusion::free_disputed` is kind of expensive so it's important to gate this on whether there are actually any
new disputes. Which should be never.
1. And we don't accept parablocks that have open disputes or disputes that have concluded against the candidate. It's
important to import dispute statements before backing, but this is already the case as disputes are imported before
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ No finalization routine runs for this module.
- This clears them from `Scheduled` and marks each corresponding `core` in the `AvailabilityCores` as occupied.
- Since both the availability cores and the newly-occupied cores lists are sorted ascending, this method can be
implemented efficiently.
- `core_para(CoreIndex) -> ParaId`: return the currently-scheduled or occupied ParaId for the given core.
- `group_validators(GroupIndex) -> Option<Vec<ValidatorIndex>>`: return all validators in a given group, if the group
index is valid for this session.
- `availability_timeout_predicate() -> Option<impl Fn(CoreIndex, BlockNumber) -> bool>`: returns an optional predicate
Expand Down
109 changes: 68 additions & 41 deletions polkadot/runtime/parachains/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use sp_runtime::{
RuntimeAppPublic,
};
use sp_std::{
collections::{btree_map::BTreeMap, vec_deque::VecDeque},
collections::{btree_map::BTreeMap, btree_set::BTreeSet, vec_deque::VecDeque},
prelude::Vec,
vec,
};
Expand Down Expand Up @@ -104,6 +104,8 @@ pub(crate) struct BenchBuilder<T: paras_inherent::Config> {
code_upgrade: Option<u32>,
/// Specifies whether the claimqueue should be filled.
fill_claimqueue: bool,
/// Cores which should not be available when being populated with pending candidates.
unavailable_cores: Vec<u32>,
_phantom: sp_std::marker::PhantomData<T>,
}

Expand Down Expand Up @@ -133,6 +135,7 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
elastic_paras: Default::default(),
code_upgrade: None,
fill_claimqueue: true,
unavailable_cores: vec![],
_phantom: sp_std::marker::PhantomData::<T>,
}
}
Expand All @@ -149,6 +152,12 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
self
}

/// Set the cores which should not be available when being populated with pending candidates.
pub(crate) fn set_unavailable_cores(mut self, unavailable_cores: Vec<u32>) -> Self {
self.unavailable_cores = unavailable_cores;
self
}

/// Set a map from para id seed to number of validity votes.
pub(crate) fn set_backed_and_concluding_paras(
mut self,
Expand All @@ -159,7 +168,6 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
}

/// Set a map from para id seed to number of cores assigned to it.
#[cfg(feature = "runtime-benchmarks")]
pub(crate) fn set_elastic_paras(mut self, elastic_paras: BTreeMap<u32, u8>) -> Self {
self.elastic_paras = elastic_paras;
self
Expand Down Expand Up @@ -284,11 +292,13 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
core_idx: CoreIndex,
candidate_hash: CandidateHash,
availability_votes: BitVec<u8, BitOrderLsb0>,
commitments: CandidateCommitments,
) -> inclusion::CandidatePendingAvailability<T::Hash, BlockNumberFor<T>> {
inclusion::CandidatePendingAvailability::<T::Hash, BlockNumberFor<T>>::new(
core_idx, // core
candidate_hash, // hash
Self::candidate_descriptor_mock(), // candidate descriptor
commitments, // commitments
availability_votes, // availability votes
Default::default(), // backers
Zero::zero(), // relay parent
Expand All @@ -309,12 +319,6 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
availability_votes: BitVec<u8, BitOrderLsb0>,
candidate_hash: CandidateHash,
) {
let candidate_availability = Self::candidate_availability_mock(
group_idx,
core_idx,
candidate_hash,
availability_votes,
);
let commitments = CandidateCommitments::<u32> {
upward_messages: Default::default(),
horizontal_messages: Default::default(),
Expand All @@ -323,16 +327,29 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
processed_downward_messages: 0,
hrmp_watermark: 0u32.into(),
};
inclusion::PendingAvailability::<T>::insert(para_id, candidate_availability);
inclusion::PendingAvailabilityCommitments::<T>::insert(&para_id, commitments);
let candidate_availability = Self::candidate_availability_mock(
group_idx,
core_idx,
candidate_hash,
availability_votes,
commitments,
);
inclusion::PendingAvailability::<T>::mutate(para_id, |maybe_andidates| {
if let Some(candidates) = maybe_andidates {
candidates.push_back(candidate_availability);
} else {
*maybe_andidates =
Some([candidate_availability].into_iter().collect::<VecDeque<_>>());
}
});
}

/// Create an `AvailabilityBitfield` where `concluding` is a map where each key is a core index
/// that is concluding and `cores` is the total number of cores in the system.
fn availability_bitvec(concluding: &BTreeMap<u32, u32>, cores: usize) -> AvailabilityBitfield {
fn availability_bitvec(concluding_cores: &BTreeSet<u32>, cores: usize) -> AvailabilityBitfield {
let mut bitfields = bitvec::bitvec![u8, bitvec::order::Lsb0; 0; 0];
for i in 0..cores {
if concluding.get(&(i as u32)).is_some() {
if concluding_cores.contains(&(i as u32)) {
bitfields.push(true);
} else {
bitfields.push(false)
Expand All @@ -356,13 +373,13 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
}
}

/// Register `cores` count of parachains.
/// Register `n_paras` count of parachains.
///
/// Note that this must be called at least 2 sessions before the target session as there is a
/// n+2 session delay for the scheduled actions to take effect.
fn setup_para_ids(cores: usize) {
fn setup_para_ids(n_paras: usize) {
// make sure parachains exist prior to session change.
for i in 0..cores {
for i in 0..n_paras {
let para_id = ParaId::from(i as u32);
let validation_code = mock_validation_code();

Expand Down Expand Up @@ -472,24 +489,8 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
let validators =
self.validators.as_ref().expect("must have some validators prior to calling");

let availability_bitvec = Self::availability_bitvec(concluding_paras, total_cores);

let bitfields: Vec<UncheckedSigned<AvailabilityBitfield>> = validators
.iter()
.enumerate()
.map(|(i, public)| {
let unchecked_signed = UncheckedSigned::<AvailabilityBitfield>::benchmark_sign(
public,
availability_bitvec.clone(),
&self.signing_context(),
ValidatorIndex(i as u32),
);

unchecked_signed
})
.collect();

let mut current_core_idx = 0u32;
let mut concluding_cores = BTreeSet::new();

for (seed, _) in concluding_paras.iter() {
// make sure the candidates that will be concluding are marked as pending availability.
Expand All @@ -505,13 +506,34 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
para_id,
core_idx,
group_idx,
Self::validator_availability_votes_yes(validators.len()),
// No validators have made this candidate available yet.
bitvec::bitvec![u8, bitvec::order::Lsb0; 0; validators.len()],
CandidateHash(H256::from(byte32_slice_from(current_core_idx))),
);
if !self.unavailable_cores.contains(&current_core_idx) {
concluding_cores.insert(current_core_idx);
}
current_core_idx += 1;
}
}

let availability_bitvec = Self::availability_bitvec(&concluding_cores, total_cores);

let bitfields: Vec<UncheckedSigned<AvailabilityBitfield>> = validators
.iter()
.enumerate()
.map(|(i, public)| {
let unchecked_signed = UncheckedSigned::<AvailabilityBitfield>::benchmark_sign(
public,
availability_bitvec.clone(),
&self.signing_context(),
ValidatorIndex(i as u32),
);

unchecked_signed
})
.collect();

bitfields
}

Expand All @@ -522,7 +544,7 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
/// validity votes.
fn create_backed_candidates(
&self,
cores_with_backed_candidates: &BTreeMap<u32, u32>,
paras_with_backed_candidates: &BTreeMap<u32, u32>,
elastic_paras: &BTreeMap<u32, u8>,
includes_code_upgrade: Option<u32>,
) -> Vec<BackedCandidate<T::Hash>> {
Expand All @@ -531,7 +553,7 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
let config = configuration::Pallet::<T>::config();

let mut current_core_idx = 0u32;
cores_with_backed_candidates
paras_with_backed_candidates
.iter()
.flat_map(|(seed, num_votes)| {
assert!(*num_votes <= validators.len() as u32);
Expand Down Expand Up @@ -760,7 +782,7 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {

// NOTE: there is an n+2 session delay for these actions to take effect.
// We are currently in Session 0, so these changes will take effect in Session 2.
Self::setup_para_ids(used_cores);
Self::setup_para_ids(used_cores - extra_cores);
configuration::ActiveConfig::<T>::mutate(|c| {
c.scheduler_params.num_cores = used_cores as u32;
});
Expand All @@ -782,19 +804,19 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {

let disputes = builder.create_disputes(
builder.backed_and_concluding_paras.len() as u32,
used_cores as u32,
(used_cores - extra_cores) as u32,
builder.dispute_sessions.as_slice(),
);
let mut disputed_cores = (builder.backed_and_concluding_paras.len() as u32..
used_cores as u32)
((used_cores - extra_cores) as u32))
.into_iter()
.map(|idx| (idx, 0))
.collect::<BTreeMap<_, _>>();

let mut all_cores = builder.backed_and_concluding_paras.clone();
all_cores.append(&mut disputed_cores);

assert_eq!(inclusion::PendingAvailability::<T>::iter().count(), used_cores as usize,);
assert_eq!(inclusion::PendingAvailability::<T>::iter().count(), used_cores - extra_cores);

// Mark all the used cores as occupied. We expect that there are
// `backed_and_concluding_paras` that are pending availability and that there are
Expand Down Expand Up @@ -831,7 +853,7 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
.keys()
.flat_map(|para_id| {
(0..elastic_paras.get(&para_id).cloned().unwrap_or(1))
.map(|_para_local_core_idx| {
.filter_map(|_para_local_core_idx| {
let ttl = configuration::Pallet::<T>::config().scheduler_params.ttl;
// Load an assignment into provider so that one is present to pop
let assignment =
Expand All @@ -844,8 +866,13 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
CoreIndex(core_idx),
[ParasEntry::new(assignment, now + ttl)].into(),
);
let res = if builder.unavailable_cores.contains(&core_idx) {
None
} else {
Some(entry)
};
core_idx += 1;
entry
res
})
.collect::<Vec<(CoreIndex, VecDeque<ParasEntry<_>>)>>()
})
Expand Down
Loading
Loading