mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 10:31:03 +00:00
add additional assurances to create_inherent (#4349)
* minor: move checks into separate fn * add additional validity checks * simplify shuffling * Closes potential OOB weight * improve docs * fooo * remove obsolete comment * move filtering into the rollback-transaction Technically this is not necessary but avoids future footguns. * move check up and avoid duplicate checks * refactor: make sure backed candidates are sane, even more * doc wording Co-authored-by: Zeke Mostov <z.mostov@gmail.com> * refactor: avoid const generics for sake of wasm size `true` -> `FullCheck::Skip`, `false` -> `FullCheck::Yes`. * chore: unify `CandidateCheckContext` instance names * refactor: introduce `IndexedRetain` for `Vec<T>` * chore: make tests prefix free * doc: re-introduce removed comment * refactor: remove another const generic to save some wasm size Co-authored-by: Zeke Mostov <z.mostov@gmail.com>
This commit is contained in:
committed by
GitHub
parent
48f6c17e77
commit
10961815cb
@@ -51,13 +51,14 @@ All failed checks should lead to an unrecoverable error making the block invalid
|
|||||||
1. For each applied bit of each availability-bitfield, set the bit for the validator in the `CandidatePendingAvailability`'s `availability_votes` bitfield. Track all candidates that now have >2/3 of bits set in their `availability_votes`. These candidates are now available and can be enacted.
|
1. For each applied bit of each availability-bitfield, set the bit for the validator in the `CandidatePendingAvailability`'s `availability_votes` bitfield. Track all candidates that now have >2/3 of bits set in their `availability_votes`. These candidates are now available and can be enacted.
|
||||||
1. For all now-available candidates, invoke the `enact_candidate` routine with the candidate and relay-parent number.
|
1. For all now-available candidates, invoke the `enact_candidate` routine with the candidate and relay-parent number.
|
||||||
1. Return a list of `(CoreIndex, CandidateHash)` from freed cores consisting of the cores where candidates have become available.
|
1. Return a list of `(CoreIndex, CandidateHash)` from freed cores consisting of the cores where candidates have become available.
|
||||||
* `sanitize_bitfields<T: crate::inclusion::Config, const CHECK_SIGS: bool>(
|
* `sanitize_bitfields<T: crate::inclusion::Config>(
|
||||||
unchecked_bitfields: UncheckedSignedAvailabilityBitfields,
|
unchecked_bitfields: UncheckedSignedAvailabilityBitfields,
|
||||||
disputed_bitfield: DisputedBitfield,
|
disputed_bitfield: DisputedBitfield,
|
||||||
expected_bits: usize,
|
expected_bits: usize,
|
||||||
parent_hash: T::Hash,
|
parent_hash: T::Hash,
|
||||||
session_index: SessionIndex,
|
session_index: SessionIndex,
|
||||||
validators: &[ValidatorId],
|
validators: &[ValidatorId],
|
||||||
|
full_check: FullCheck,
|
||||||
)`:
|
)`:
|
||||||
1. check that `disputed_bitfield` has the same number of bits as the `expected_bits`, iff not return early with an empty vec.
|
1. check that `disputed_bitfield` has the same number of bits as the `expected_bits`, iff not return early with an empty vec.
|
||||||
1. each of the below checks is for each bitfield. If a check does not pass the bitfield will be skipped.
|
1. each of the below checks is for each bitfield. If a check does not pass the bitfield will be skipped.
|
||||||
@@ -65,7 +66,7 @@ All failed checks should lead to an unrecoverable error making the block invalid
|
|||||||
1. check that the number of bits is equal to `expected_bits`.
|
1. check that the number of bits is equal to `expected_bits`.
|
||||||
1. check that the validator index is strictly increasing (and thus also unique).
|
1. check that the validator index is strictly increasing (and thus also unique).
|
||||||
1. check that the validator bit index is not out of bounds.
|
1. check that the validator bit index is not out of bounds.
|
||||||
1. check the validators signature, iff `CHECK_SIGS=true`.
|
1. check the validators signature, iff `full_check=FullCheck::Yes`.
|
||||||
|
|
||||||
* `sanitize_backed_candidates<T: crate::inclusion::Config, F: Fn(CandidateHash) -> bool>(
|
* `sanitize_backed_candidates<T: crate::inclusion::Config, F: Fn(CandidateHash) -> bool>(
|
||||||
relay_parent: T::Hash,
|
relay_parent: T::Hash,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
configuration, disputes, dmp, hrmp, paras,
|
configuration, disputes, dmp, hrmp, paras,
|
||||||
paras_inherent::{sanitize_bitfields, DisputedBitfield, VERIFY_SIGS},
|
paras_inherent::{sanitize_bitfields, DisputedBitfield},
|
||||||
scheduler::CoreAssignment,
|
scheduler::CoreAssignment,
|
||||||
shared, ump,
|
shared, ump,
|
||||||
};
|
};
|
||||||
@@ -56,6 +56,19 @@ pub struct AvailabilityBitfieldRecord<N> {
|
|||||||
submitted_at: N, // for accounting, as meaning of bits may change over time.
|
submitted_at: N, // for accounting, as meaning of bits may change over time.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determines if all checks should be applied or if a subset was already completed
|
||||||
|
/// in a code path that will be executed afterwards or was already executed before.
|
||||||
|
#[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||||
|
pub(crate) enum FullCheck {
|
||||||
|
/// Yes, do a full check, skip nothing.
|
||||||
|
Yes,
|
||||||
|
/// Skip a subset of checks that are already completed before.
|
||||||
|
///
|
||||||
|
/// Attention: Should only be used when absolutely sure that the required
|
||||||
|
/// checks are completed before.
|
||||||
|
Skip,
|
||||||
|
}
|
||||||
|
|
||||||
/// A backed candidate pending availability.
|
/// A backed candidate pending availability.
|
||||||
#[derive(Encode, Decode, PartialEq, TypeInfo)]
|
#[derive(Encode, Decode, PartialEq, TypeInfo)]
|
||||||
#[cfg_attr(test, derive(Debug, Default))]
|
#[cfg_attr(test, derive(Debug, Default))]
|
||||||
@@ -403,13 +416,14 @@ impl<T: Config> Pallet<T> {
|
|||||||
let session_index = shared::Pallet::<T>::session_index();
|
let session_index = shared::Pallet::<T>::session_index();
|
||||||
let parent_hash = frame_system::Pallet::<T>::parent_hash();
|
let parent_hash = frame_system::Pallet::<T>::parent_hash();
|
||||||
|
|
||||||
let checked_bitfields = sanitize_bitfields::<T, VERIFY_SIGS>(
|
let checked_bitfields = sanitize_bitfields::<T>(
|
||||||
signed_bitfields,
|
signed_bitfields,
|
||||||
disputed_bitfield,
|
disputed_bitfield,
|
||||||
expected_bits,
|
expected_bits,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validators[..],
|
&validators[..],
|
||||||
|
FullCheck::Yes,
|
||||||
);
|
);
|
||||||
|
|
||||||
let freed_cores = Self::update_pending_availability_and_get_freed_cores::<_, true>(
|
let freed_cores = Self::update_pending_availability_and_get_freed_cores::<_, true>(
|
||||||
@@ -427,12 +441,16 @@ impl<T: Config> Pallet<T> {
|
|||||||
///
|
///
|
||||||
/// Both should be sorted ascending by core index, and the candidates should be a subset of
|
/// Both should be sorted ascending by core index, and the candidates should be a subset of
|
||||||
/// scheduled cores. If these conditions are not met, the execution of the function fails.
|
/// scheduled cores. If these conditions are not met, the execution of the function fails.
|
||||||
pub(crate) fn process_candidates(
|
pub(crate) fn process_candidates<GV>(
|
||||||
parent_storage_root: T::Hash,
|
parent_storage_root: T::Hash,
|
||||||
candidates: Vec<BackedCandidate<T::Hash>>,
|
candidates: Vec<BackedCandidate<T::Hash>>,
|
||||||
scheduled: Vec<CoreAssignment>,
|
scheduled: Vec<CoreAssignment>,
|
||||||
group_validators: impl Fn(GroupIndex) -> Option<Vec<ValidatorIndex>>,
|
group_validators: GV,
|
||||||
) -> Result<ProcessedCandidates<T::Hash>, DispatchError> {
|
full_check: FullCheck,
|
||||||
|
) -> Result<ProcessedCandidates<T::Hash>, DispatchError>
|
||||||
|
where
|
||||||
|
GV: Fn(GroupIndex) -> Option<Vec<ValidatorIndex>>,
|
||||||
|
{
|
||||||
ensure!(candidates.len() <= scheduled.len(), Error::<T>::UnscheduledCandidate);
|
ensure!(candidates.len() <= scheduled.len(), Error::<T>::UnscheduledCandidate);
|
||||||
|
|
||||||
if scheduled.is_empty() {
|
if scheduled.is_empty() {
|
||||||
@@ -446,7 +464,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
// before of the block where we include a candidate (i.e. this code path).
|
// before of the block where we include a candidate (i.e. this code path).
|
||||||
let now = <frame_system::Pallet<T>>::block_number();
|
let now = <frame_system::Pallet<T>>::block_number();
|
||||||
let relay_parent_number = now - One::one();
|
let relay_parent_number = now - One::one();
|
||||||
let check_cx = CandidateCheckContext::<T>::new(now, relay_parent_number);
|
let check_ctx = CandidateCheckContext::<T>::new(now, relay_parent_number);
|
||||||
|
|
||||||
// Collect candidate receipts with backers.
|
// Collect candidate receipts with backers.
|
||||||
let mut candidate_receipt_with_backing_validator_indices =
|
let mut candidate_receipt_with_backing_validator_indices =
|
||||||
@@ -481,54 +499,20 @@ impl<T: Config> Pallet<T> {
|
|||||||
//
|
//
|
||||||
// In the meantime, we do certain sanity checks on the candidates and on the scheduled
|
// In the meantime, we do certain sanity checks on the candidates and on the scheduled
|
||||||
// list.
|
// list.
|
||||||
'a: for (candidate_idx, backed_candidate) in candidates.iter().enumerate() {
|
'next_backed_candidate: for (candidate_idx, backed_candidate) in
|
||||||
|
candidates.iter().enumerate()
|
||||||
|
{
|
||||||
|
if let FullCheck::Yes = full_check {
|
||||||
|
check_ctx.verify_backed_candidate(
|
||||||
|
parent_hash,
|
||||||
|
candidate_idx,
|
||||||
|
backed_candidate,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
let para_id = backed_candidate.descriptor().para_id;
|
let para_id = backed_candidate.descriptor().para_id;
|
||||||
let mut backers = bitvec::bitvec![BitOrderLsb0, u8; 0; validators.len()];
|
let mut backers = bitvec::bitvec![BitOrderLsb0, u8; 0; validators.len()];
|
||||||
|
|
||||||
// we require that the candidate is in the context of the parent block.
|
|
||||||
ensure!(
|
|
||||||
backed_candidate.descriptor().relay_parent == parent_hash,
|
|
||||||
Error::<T>::CandidateNotInParentContext,
|
|
||||||
);
|
|
||||||
ensure!(
|
|
||||||
backed_candidate.descriptor().check_collator_signature().is_ok(),
|
|
||||||
Error::<T>::NotCollatorSigned,
|
|
||||||
);
|
|
||||||
|
|
||||||
let validation_code_hash =
|
|
||||||
<paras::Pallet<T>>::validation_code_hash_at(para_id, now, None)
|
|
||||||
// A candidate for a parachain without current validation code is not scheduled.
|
|
||||||
.ok_or_else(|| Error::<T>::UnscheduledCandidate)?;
|
|
||||||
ensure!(
|
|
||||||
backed_candidate.descriptor().validation_code_hash == validation_code_hash,
|
|
||||||
Error::<T>::InvalidValidationCodeHash,
|
|
||||||
);
|
|
||||||
|
|
||||||
ensure!(
|
|
||||||
backed_candidate.descriptor().para_head ==
|
|
||||||
backed_candidate.candidate.commitments.head_data.hash(),
|
|
||||||
Error::<T>::ParaHeadMismatch,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Err(err) = check_cx.check_validation_outputs(
|
|
||||||
para_id,
|
|
||||||
&backed_candidate.candidate.commitments.head_data,
|
|
||||||
&backed_candidate.candidate.commitments.new_validation_code,
|
|
||||||
backed_candidate.candidate.commitments.processed_downward_messages,
|
|
||||||
&backed_candidate.candidate.commitments.upward_messages,
|
|
||||||
T::BlockNumber::from(backed_candidate.candidate.commitments.hrmp_watermark),
|
|
||||||
&backed_candidate.candidate.commitments.horizontal_messages,
|
|
||||||
) {
|
|
||||||
log::debug!(
|
|
||||||
target: LOG_TARGET,
|
|
||||||
"Validation outputs checking during inclusion of a candidate {} for parachain `{}` failed: {:?}",
|
|
||||||
candidate_idx,
|
|
||||||
u32::from(para_id),
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
Err(err.strip_into_dispatch_err::<T>())?;
|
|
||||||
};
|
|
||||||
|
|
||||||
for (i, assignment) in scheduled[skip..].iter().enumerate() {
|
for (i, assignment) in scheduled[skip..].iter().enumerate() {
|
||||||
check_assignment_in_order(assignment)?;
|
check_assignment_in_order(assignment)?;
|
||||||
|
|
||||||
@@ -631,7 +615,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
backers,
|
backers,
|
||||||
assignment.group_idx,
|
assignment.group_idx,
|
||||||
));
|
));
|
||||||
continue 'a
|
continue 'next_backed_candidate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,7 +666,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
availability_votes,
|
availability_votes,
|
||||||
relay_parent_number,
|
relay_parent_number,
|
||||||
backers: backers.to_bitvec(),
|
backers: backers.to_bitvec(),
|
||||||
backed_in_number: check_cx.now,
|
backed_in_number: check_ctx.now,
|
||||||
backing_group: group,
|
backing_group: group,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -704,9 +688,9 @@ impl<T: Config> Pallet<T> {
|
|||||||
// `relay_parent_number` is equal to `now`.
|
// `relay_parent_number` is equal to `now`.
|
||||||
let now = <frame_system::Pallet<T>>::block_number();
|
let now = <frame_system::Pallet<T>>::block_number();
|
||||||
let relay_parent_number = now;
|
let relay_parent_number = now;
|
||||||
let check_cx = CandidateCheckContext::<T>::new(now, relay_parent_number);
|
let check_ctx = CandidateCheckContext::<T>::new(now, relay_parent_number);
|
||||||
|
|
||||||
if let Err(err) = check_cx.check_validation_outputs(
|
if let Err(err) = check_ctx.check_validation_outputs(
|
||||||
para_id,
|
para_id,
|
||||||
&validation_outputs.head_data,
|
&validation_outputs.head_data,
|
||||||
&validation_outputs.new_validation_code,
|
&validation_outputs.new_validation_code,
|
||||||
@@ -941,17 +925,78 @@ impl<BlockNumber> AcceptanceCheckErr<BlockNumber> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of data required for checking a candidate.
|
/// A collection of data required for checking a candidate.
|
||||||
struct CandidateCheckContext<T: Config> {
|
pub(crate) struct CandidateCheckContext<T: Config> {
|
||||||
config: configuration::HostConfiguration<T::BlockNumber>,
|
config: configuration::HostConfiguration<T::BlockNumber>,
|
||||||
now: T::BlockNumber,
|
now: T::BlockNumber,
|
||||||
relay_parent_number: T::BlockNumber,
|
relay_parent_number: T::BlockNumber,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> CandidateCheckContext<T> {
|
impl<T: Config> CandidateCheckContext<T> {
|
||||||
fn new(now: T::BlockNumber, relay_parent_number: T::BlockNumber) -> Self {
|
pub(crate) fn new(now: T::BlockNumber, relay_parent_number: T::BlockNumber) -> Self {
|
||||||
Self { config: <configuration::Pallet<T>>::config(), now, relay_parent_number }
|
Self { config: <configuration::Pallet<T>>::config(), now, relay_parent_number }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execute verification of the candidate.
|
||||||
|
///
|
||||||
|
/// Assures:
|
||||||
|
/// * correct expected relay parent reference
|
||||||
|
/// * collator signature check passes
|
||||||
|
/// * code hash of commitments matches current code hash
|
||||||
|
/// * para head in the descriptor and commitments match
|
||||||
|
pub(crate) fn verify_backed_candidate(
|
||||||
|
&self,
|
||||||
|
parent_hash: <T as frame_system::Config>::Hash,
|
||||||
|
candidate_idx: usize,
|
||||||
|
backed_candidate: &BackedCandidate<<T as frame_system::Config>::Hash>,
|
||||||
|
) -> Result<(), Error<T>> {
|
||||||
|
let para_id = backed_candidate.descriptor().para_id;
|
||||||
|
let now = self.now;
|
||||||
|
|
||||||
|
// we require that the candidate is in the context of the parent block.
|
||||||
|
ensure!(
|
||||||
|
backed_candidate.descriptor().relay_parent == parent_hash,
|
||||||
|
Error::<T>::CandidateNotInParentContext,
|
||||||
|
);
|
||||||
|
ensure!(
|
||||||
|
backed_candidate.descriptor().check_collator_signature().is_ok(),
|
||||||
|
Error::<T>::NotCollatorSigned,
|
||||||
|
);
|
||||||
|
|
||||||
|
let validation_code_hash = <paras::Pallet<T>>::validation_code_hash_at(para_id, now, None)
|
||||||
|
// A candidate for a parachain without current validation code is not scheduled.
|
||||||
|
.ok_or_else(|| Error::<T>::UnscheduledCandidate)?;
|
||||||
|
ensure!(
|
||||||
|
backed_candidate.descriptor().validation_code_hash == validation_code_hash,
|
||||||
|
Error::<T>::InvalidValidationCodeHash,
|
||||||
|
);
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
backed_candidate.descriptor().para_head ==
|
||||||
|
backed_candidate.candidate.commitments.head_data.hash(),
|
||||||
|
Error::<T>::ParaHeadMismatch,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(err) = self.check_validation_outputs(
|
||||||
|
para_id,
|
||||||
|
&backed_candidate.candidate.commitments.head_data,
|
||||||
|
&backed_candidate.candidate.commitments.new_validation_code,
|
||||||
|
backed_candidate.candidate.commitments.processed_downward_messages,
|
||||||
|
&backed_candidate.candidate.commitments.upward_messages,
|
||||||
|
T::BlockNumber::from(backed_candidate.candidate.commitments.hrmp_watermark),
|
||||||
|
&backed_candidate.candidate.commitments.horizontal_messages,
|
||||||
|
) {
|
||||||
|
log::debug!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
"Validation outputs checking during inclusion of a candidate {} for parachain `{}` failed: {:?}",
|
||||||
|
candidate_idx,
|
||||||
|
u32::from(para_id),
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
Err(err.strip_into_dispatch_err::<T>())?;
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Check the given outputs after candidate validation on whether it passes the acceptance
|
/// Check the given outputs after candidate validation on whether it passes the acceptance
|
||||||
/// criteria.
|
/// criteria.
|
||||||
fn check_validation_outputs(
|
fn check_validation_outputs(
|
||||||
@@ -1935,6 +1980,7 @@ pub(crate) mod tests {
|
|||||||
vec![backed],
|
vec![backed],
|
||||||
vec![chain_b_assignment.clone()],
|
vec![chain_b_assignment.clone()],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
),
|
),
|
||||||
Error::<Test>::UnscheduledCandidate
|
Error::<Test>::UnscheduledCandidate
|
||||||
);
|
);
|
||||||
@@ -1990,6 +2036,7 @@ pub(crate) mod tests {
|
|||||||
vec![backed_b, backed_a],
|
vec![backed_b, backed_a],
|
||||||
vec![chain_a_assignment.clone(), chain_b_assignment.clone()],
|
vec![chain_a_assignment.clone(), chain_b_assignment.clone()],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
),
|
),
|
||||||
Error::<Test>::UnscheduledCandidate
|
Error::<Test>::UnscheduledCandidate
|
||||||
);
|
);
|
||||||
@@ -2023,6 +2070,7 @@ pub(crate) mod tests {
|
|||||||
vec![backed],
|
vec![backed],
|
||||||
vec![chain_a_assignment.clone()],
|
vec![chain_a_assignment.clone()],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
),
|
),
|
||||||
Error::<Test>::InsufficientBacking
|
Error::<Test>::InsufficientBacking
|
||||||
);
|
);
|
||||||
@@ -2058,6 +2106,7 @@ pub(crate) mod tests {
|
|||||||
vec![backed],
|
vec![backed],
|
||||||
vec![chain_a_assignment.clone()],
|
vec![chain_a_assignment.clone()],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
),
|
),
|
||||||
Error::<Test>::CandidateNotInParentContext
|
Error::<Test>::CandidateNotInParentContext
|
||||||
);
|
);
|
||||||
@@ -2097,6 +2146,7 @@ pub(crate) mod tests {
|
|||||||
thread_a_assignment.clone(),
|
thread_a_assignment.clone(),
|
||||||
],
|
],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
),
|
),
|
||||||
Error::<Test>::WrongCollator,
|
Error::<Test>::WrongCollator,
|
||||||
);
|
);
|
||||||
@@ -2135,6 +2185,7 @@ pub(crate) mod tests {
|
|||||||
vec![backed],
|
vec![backed],
|
||||||
vec![thread_a_assignment.clone()],
|
vec![thread_a_assignment.clone()],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
),
|
),
|
||||||
Error::<Test>::NotCollatorSigned
|
Error::<Test>::NotCollatorSigned
|
||||||
);
|
);
|
||||||
@@ -2185,6 +2236,7 @@ pub(crate) mod tests {
|
|||||||
vec![backed],
|
vec![backed],
|
||||||
vec![chain_a_assignment.clone()],
|
vec![chain_a_assignment.clone()],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
),
|
),
|
||||||
Error::<Test>::CandidateScheduledBeforeParaFree
|
Error::<Test>::CandidateScheduledBeforeParaFree
|
||||||
);
|
);
|
||||||
@@ -2228,6 +2280,7 @@ pub(crate) mod tests {
|
|||||||
vec![backed],
|
vec![backed],
|
||||||
vec![chain_a_assignment.clone()],
|
vec![chain_a_assignment.clone()],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
),
|
),
|
||||||
Error::<Test>::CandidateScheduledBeforeParaFree
|
Error::<Test>::CandidateScheduledBeforeParaFree
|
||||||
);
|
);
|
||||||
@@ -2279,6 +2332,7 @@ pub(crate) mod tests {
|
|||||||
vec![backed],
|
vec![backed],
|
||||||
vec![chain_a_assignment.clone()],
|
vec![chain_a_assignment.clone()],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
),
|
),
|
||||||
Error::<Test>::PrematureCodeUpgrade
|
Error::<Test>::PrematureCodeUpgrade
|
||||||
);
|
);
|
||||||
@@ -2313,6 +2367,7 @@ pub(crate) mod tests {
|
|||||||
vec![backed],
|
vec![backed],
|
||||||
vec![chain_a_assignment.clone()],
|
vec![chain_a_assignment.clone()],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
),
|
),
|
||||||
Err(Error::<Test>::ValidationDataHashMismatch.into()),
|
Err(Error::<Test>::ValidationDataHashMismatch.into()),
|
||||||
);
|
);
|
||||||
@@ -2348,6 +2403,7 @@ pub(crate) mod tests {
|
|||||||
vec![backed],
|
vec![backed],
|
||||||
vec![chain_a_assignment.clone()],
|
vec![chain_a_assignment.clone()],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
),
|
),
|
||||||
Error::<Test>::InvalidValidationCodeHash
|
Error::<Test>::InvalidValidationCodeHash
|
||||||
);
|
);
|
||||||
@@ -2383,6 +2439,7 @@ pub(crate) mod tests {
|
|||||||
vec![backed],
|
vec![backed],
|
||||||
vec![chain_a_assignment.clone()],
|
vec![chain_a_assignment.clone()],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
),
|
),
|
||||||
Error::<Test>::ParaHeadMismatch
|
Error::<Test>::ParaHeadMismatch
|
||||||
);
|
);
|
||||||
@@ -2552,6 +2609,7 @@ pub(crate) mod tests {
|
|||||||
thread_a_assignment.clone(),
|
thread_a_assignment.clone(),
|
||||||
],
|
],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
)
|
)
|
||||||
.expect("candidates scheduled, in order, and backed");
|
.expect("candidates scheduled, in order, and backed");
|
||||||
|
|
||||||
@@ -2742,6 +2800,7 @@ pub(crate) mod tests {
|
|||||||
vec![backed_a],
|
vec![backed_a],
|
||||||
vec![chain_a_assignment.clone()],
|
vec![chain_a_assignment.clone()],
|
||||||
&group_validators,
|
&group_validators,
|
||||||
|
FullCheck::Yes,
|
||||||
)
|
)
|
||||||
.expect("candidates scheduled, in order, and backed");
|
.expect("candidates scheduled, in order, and backed");
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
disputes::DisputesHandler,
|
disputes::DisputesHandler,
|
||||||
inclusion, initializer,
|
inclusion,
|
||||||
|
inclusion::{CandidateCheckContext, FullCheck},
|
||||||
|
initializer,
|
||||||
scheduler::{self, CoreAssignment, FreedReason},
|
scheduler::{self, CoreAssignment, FreedReason},
|
||||||
shared, ump,
|
shared, ump,
|
||||||
};
|
};
|
||||||
@@ -42,21 +44,21 @@ use primitives::v1::{
|
|||||||
UncheckedSignedAvailabilityBitfields, ValidatorId, ValidatorIndex,
|
UncheckedSignedAvailabilityBitfields, ValidatorId, ValidatorIndex,
|
||||||
PARACHAINS_INHERENT_IDENTIFIER,
|
PARACHAINS_INHERENT_IDENTIFIER,
|
||||||
};
|
};
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{seq::SliceRandom, SeedableRng};
|
||||||
|
|
||||||
use scale_info::TypeInfo;
|
use scale_info::TypeInfo;
|
||||||
use sp_runtime::traits::Header as HeaderT;
|
use sp_runtime::traits::{Header as HeaderT, One};
|
||||||
use sp_std::{
|
use sp_std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
|
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "runtime-benchmarks")]
|
#[cfg(feature = "runtime-benchmarks")]
|
||||||
mod benchmarking;
|
mod benchmarking;
|
||||||
|
|
||||||
const LOG_TARGET: &str = "runtime::inclusion-inherent";
|
const LOG_TARGET: &str = "runtime::inclusion-inherent";
|
||||||
const SKIP_SIG_VERIFY: bool = false;
|
|
||||||
pub(crate) const VERIFY_SIGS: bool = true;
|
|
||||||
|
|
||||||
pub trait WeightInfo {
|
pub trait WeightInfo {
|
||||||
/// Variant over `v`, the count of dispute statements in a dispute statement set. This gives the
|
/// Variant over `v`, the count of dispute statements in a dispute statement set. This gives the
|
||||||
@@ -158,6 +160,29 @@ fn backed_candidates_weight<T: frame_system::Config + Config>(
|
|||||||
.fold(0, |acc, x| acc.saturating_add(x))
|
.fold(0, |acc, x| acc.saturating_add(x))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A helper trait to allow calling retain while getting access
|
||||||
|
/// to the index of the item in the `vec`.
|
||||||
|
trait IndexedRetain<T> {
|
||||||
|
/// Retains only the elements specified by the predicate.
|
||||||
|
///
|
||||||
|
/// In other words, remove all elements `e` residing at
|
||||||
|
/// index `i` such that `f(i, &e)` returns `false`. This method
|
||||||
|
/// operates in place, visiting each element exactly once in the
|
||||||
|
/// original order, and preserves the order of the retained elements.
|
||||||
|
fn indexed_retain(&mut self, f: impl FnMut(usize, &T) -> bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IndexedRetain<T> for Vec<T> {
|
||||||
|
fn indexed_retain(&mut self, mut f: impl FnMut(usize, &T) -> bool) {
|
||||||
|
let mut idx = 0_usize;
|
||||||
|
self.retain(move |item| {
|
||||||
|
let ret = f(idx, item);
|
||||||
|
idx += 1_usize;
|
||||||
|
ret
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A bitfield concerning concluded disputes for candidates
|
/// A bitfield concerning concluded disputes for candidates
|
||||||
/// associated to the core index equivalent to the bit position.
|
/// associated to the core index equivalent to the bit position.
|
||||||
#[derive(Default, PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
|
#[derive(Default, PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||||
@@ -252,8 +277,7 @@ pub mod pallet {
|
|||||||
// (`enter`) and the off-chain checks by the block author (this function). Once we are confident
|
// (`enter`) and the off-chain checks by the block author (this function). Once we are confident
|
||||||
// in all the logic in this module this check should be removed to optimize performance.
|
// in all the logic in this module this check should be removed to optimize performance.
|
||||||
|
|
||||||
let inherent_data =
|
let inherent_data = match Self::enter_inner(inherent_data.clone(), FullCheck::Skip) {
|
||||||
match Self::enter(frame_system::RawOrigin::None.into(), inherent_data.clone()) {
|
|
||||||
Ok(_) => inherent_data,
|
Ok(_) => inherent_data,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!(
|
log::error!(
|
||||||
@@ -329,6 +353,16 @@ pub mod pallet {
|
|||||||
ensure!(!Included::<T>::exists(), Error::<T>::TooManyInclusionInherents);
|
ensure!(!Included::<T>::exists(), Error::<T>::TooManyInclusionInherents);
|
||||||
Included::<T>::set(Some(()));
|
Included::<T>::set(Some(()));
|
||||||
|
|
||||||
|
Self::enter_inner(data, FullCheck::Yes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config> Pallet<T> {
|
||||||
|
pub(crate) fn enter_inner(
|
||||||
|
data: ParachainsInherentData<T::Header>,
|
||||||
|
full_check: FullCheck,
|
||||||
|
) -> DispatchResultWithPostInfo {
|
||||||
let ParachainsInherentData {
|
let ParachainsInherentData {
|
||||||
bitfields: mut signed_bitfields,
|
bitfields: mut signed_bitfields,
|
||||||
mut backed_candidates,
|
mut backed_candidates,
|
||||||
@@ -351,6 +385,8 @@ pub mod pallet {
|
|||||||
Error::<T>::InvalidParentHeader,
|
Error::<T>::InvalidParentHeader,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let now = <frame_system::Pallet<T>>::block_number();
|
||||||
|
|
||||||
let mut candidate_weight = backed_candidates_weight::<T>(&backed_candidates);
|
let mut candidate_weight = backed_candidates_weight::<T>(&backed_candidates);
|
||||||
let mut bitfields_weight = signed_bitfields_weight::<T>(signed_bitfields.len());
|
let mut bitfields_weight = signed_bitfields_weight::<T>(signed_bitfields.len());
|
||||||
let disputes_weight = dispute_statements_weight::<T>(&disputes);
|
let disputes_weight = dispute_statements_weight::<T>(&disputes);
|
||||||
@@ -454,7 +490,6 @@ pub mod pallet {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Inform the disputes module of all included candidates.
|
// Inform the disputes module of all included candidates.
|
||||||
let now = <frame_system::Pallet<T>>::block_number();
|
|
||||||
for (_, candidate_hash) in &freed_concluded {
|
for (_, candidate_hash) in &freed_concluded {
|
||||||
T::DisputesHandler::note_included(current_session, *candidate_hash, now);
|
T::DisputesHandler::note_included(current_session, *candidate_hash, now);
|
||||||
}
|
}
|
||||||
@@ -468,8 +503,9 @@ pub mod pallet {
|
|||||||
let backed_candidates = sanitize_backed_candidates::<T, _>(
|
let backed_candidates = sanitize_backed_candidates::<T, _>(
|
||||||
parent_hash,
|
parent_hash,
|
||||||
backed_candidates,
|
backed_candidates,
|
||||||
move |candidate_hash: CandidateHash| -> bool {
|
move |_candidate_index: usize, backed_candidate: &BackedCandidate<T::Hash>| -> bool {
|
||||||
<T>::DisputesHandler::concluded_invalid(current_session, candidate_hash)
|
<T>::DisputesHandler::concluded_invalid(current_session, backed_candidate.hash())
|
||||||
|
// `fn process_candidates` does the verification checks
|
||||||
},
|
},
|
||||||
&scheduled[..],
|
&scheduled[..],
|
||||||
);
|
);
|
||||||
@@ -484,6 +520,7 @@ pub mod pallet {
|
|||||||
backed_candidates,
|
backed_candidates,
|
||||||
scheduled,
|
scheduled,
|
||||||
<scheduler::Pallet<T>>::group_validators,
|
<scheduler::Pallet<T>>::group_validators,
|
||||||
|
full_check,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// The number of disputes included in a block is
|
// The number of disputes included in a block is
|
||||||
@@ -503,7 +540,6 @@ pub mod pallet {
|
|||||||
|
|
||||||
Ok(Some(total_weight).into())
|
Ok(Some(total_weight).into())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> Pallet<T> {
|
impl<T: Config> Pallet<T> {
|
||||||
@@ -548,7 +584,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
|
|
||||||
T::DisputesHandler::filter_multi_dispute_data(&mut disputes);
|
T::DisputesHandler::filter_multi_dispute_data(&mut disputes);
|
||||||
|
|
||||||
let (concluded_invalid_disputes, mut bitfields, scheduled) =
|
let (mut backed_candidates, mut bitfields) =
|
||||||
frame_support::storage::with_transaction(|| {
|
frame_support::storage::with_transaction(|| {
|
||||||
// we don't care about fresh or not disputes
|
// we don't care about fresh or not disputes
|
||||||
// this writes them to storage, so let's query it via those means
|
// this writes them to storage, so let's query it via those means
|
||||||
@@ -563,9 +599,13 @@ impl<T: Config> Pallet<T> {
|
|||||||
e
|
e
|
||||||
});
|
});
|
||||||
|
|
||||||
// current concluded invalid disputes, only including the current block's votes
|
// Contains the disputes that are concluded in the current session only,
|
||||||
// TODO why does this say "only including the current block's votes"? This can include
|
// since these are the only ones that are relevant for the occupied cores
|
||||||
// remote disputes, right?
|
// and lightens the load on `collect_disputed` significantly.
|
||||||
|
// Cores can't be occupied with candidates of the previous sessions, and only
|
||||||
|
// things with new votes can have just concluded. We only need to collect
|
||||||
|
// cores with disputes that conclude just now, because disputes that
|
||||||
|
// concluded longer ago have already had any corresponding cores cleaned up.
|
||||||
let current_concluded_invalid_disputes = disputes
|
let current_concluded_invalid_disputes = disputes
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|dss| dss.session == current_session)
|
.filter(|dss| dss.session == current_session)
|
||||||
@@ -576,8 +616,8 @@ impl<T: Config> Pallet<T> {
|
|||||||
.map(|(_session, candidate)| candidate)
|
.map(|(_session, candidate)| candidate)
|
||||||
.collect::<BTreeSet<CandidateHash>>();
|
.collect::<BTreeSet<CandidateHash>>();
|
||||||
|
|
||||||
// all concluded invalid disputes, that are relevant for the set of candidates
|
// All concluded invalid disputes, that are relevant for the set of candidates
|
||||||
// the inherent provided
|
// the inherent provided.
|
||||||
let concluded_invalid_disputes = backed_candidates
|
let concluded_invalid_disputes = backed_candidates
|
||||||
.iter()
|
.iter()
|
||||||
.map(|backed_candidate| backed_candidate.hash())
|
.map(|backed_candidate| backed_candidate.hash())
|
||||||
@@ -604,13 +644,14 @@ impl<T: Config> Pallet<T> {
|
|||||||
|
|
||||||
// The following 3 calls are equiv to a call to `process_bitfields`
|
// The following 3 calls are equiv to a call to `process_bitfields`
|
||||||
// but we can retain access to `bitfields`.
|
// but we can retain access to `bitfields`.
|
||||||
let bitfields = sanitize_bitfields::<T, SKIP_SIG_VERIFY>(
|
let bitfields = sanitize_bitfields::<T>(
|
||||||
bitfields,
|
bitfields,
|
||||||
disputed_bitfield,
|
disputed_bitfield,
|
||||||
expected_bits,
|
expected_bits,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
current_session,
|
current_session,
|
||||||
&validator_public[..],
|
&validator_public[..],
|
||||||
|
FullCheck::Skip,
|
||||||
);
|
);
|
||||||
|
|
||||||
let freed_concluded =
|
let freed_concluded =
|
||||||
@@ -627,31 +668,45 @@ impl<T: Config> Pallet<T> {
|
|||||||
let freed = collect_all_freed_cores::<T, _>(freed_concluded.iter().cloned());
|
let freed = collect_all_freed_cores::<T, _>(freed_concluded.iter().cloned());
|
||||||
|
|
||||||
<scheduler::Pallet<T>>::clear();
|
<scheduler::Pallet<T>>::clear();
|
||||||
<scheduler::Pallet<T>>::schedule(freed, <frame_system::Pallet<T>>::block_number());
|
let now = <frame_system::Pallet<T>>::block_number();
|
||||||
|
<scheduler::Pallet<T>>::schedule(freed, now);
|
||||||
|
|
||||||
let scheduled = <scheduler::Pallet<T>>::scheduled();
|
let scheduled = <scheduler::Pallet<T>>::scheduled();
|
||||||
|
|
||||||
frame_support::storage::TransactionOutcome::Rollback((
|
let relay_parent_number = now - One::one();
|
||||||
// concluded disputes for backed candidates in this block
|
|
||||||
concluded_invalid_disputes,
|
|
||||||
// filtered bitfields,
|
|
||||||
bitfields,
|
|
||||||
// updated schedule
|
|
||||||
scheduled,
|
|
||||||
))
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut backed_candidates = sanitize_backed_candidates::<T, _>(
|
let check_ctx = CandidateCheckContext::<T>::new(now, relay_parent_number);
|
||||||
|
let backed_candidates = sanitize_backed_candidates::<T, _>(
|
||||||
parent_hash,
|
parent_hash,
|
||||||
backed_candidates,
|
backed_candidates,
|
||||||
move |candidate_hash: CandidateHash| -> bool {
|
move |candidate_idx: usize,
|
||||||
concluded_invalid_disputes.contains(&candidate_hash)
|
backed_candidate: &BackedCandidate<<T as frame_system::Config>::Hash>|
|
||||||
|
-> bool {
|
||||||
|
// never include a concluded-invalid candidate
|
||||||
|
concluded_invalid_disputes.contains(&backed_candidate.hash()) ||
|
||||||
|
// Instead of checking the candidates with code upgrades twice
|
||||||
|
// move the checking up here and skip it in the training wheels fallback.
|
||||||
|
// That way we avoid possible duplicate checks while assuring all
|
||||||
|
// backed candidates fine to pass on.
|
||||||
|
check_ctx
|
||||||
|
.verify_backed_candidate(parent_hash, candidate_idx, backed_candidate)
|
||||||
|
.is_err()
|
||||||
},
|
},
|
||||||
&scheduled[..],
|
&scheduled[..],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
frame_support::storage::TransactionOutcome::Rollback((
|
||||||
|
// filtered backed candidates
|
||||||
|
backed_candidates,
|
||||||
|
// filtered bitfields
|
||||||
|
bitfields,
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
let entropy = compute_entropy::<T>(parent_hash);
|
let entropy = compute_entropy::<T>(parent_hash);
|
||||||
let mut rng = rand_chacha::ChaChaRng::from_seed(entropy.into());
|
let mut rng = rand_chacha::ChaChaRng::from_seed(entropy.into());
|
||||||
|
|
||||||
|
// Assure the maximum block weight is adhered.
|
||||||
let max_block_weight = <T as frame_system::Config>::BlockWeights::get().max_block;
|
let max_block_weight = <T as frame_system::Config>::BlockWeights::get().max_block;
|
||||||
let _consumed_weight = apply_weight_limit::<T>(
|
let _consumed_weight = apply_weight_limit::<T>(
|
||||||
&mut backed_candidates,
|
&mut backed_candidates,
|
||||||
@@ -714,15 +769,11 @@ fn random_sel<X, F: Fn(&X) -> Weight>(
|
|||||||
|
|
||||||
let mut weight_acc = 0 as Weight;
|
let mut weight_acc = 0 as Weight;
|
||||||
|
|
||||||
while !preferred_indices.is_empty() {
|
preferred_indices.shuffle(rng);
|
||||||
// randomly pick an index from the preferred set
|
for preferred_idx in preferred_indices {
|
||||||
let pick = rng.gen_range(0..preferred_indices.len());
|
|
||||||
// remove the index from the available set of preferred indices
|
|
||||||
let preferred_idx = preferred_indices.swap_remove(pick);
|
|
||||||
|
|
||||||
// preferred indices originate from outside
|
// preferred indices originate from outside
|
||||||
if let Some(item) = selectables.get(preferred_idx) {
|
if let Some(item) = selectables.get(preferred_idx) {
|
||||||
let updated = weight_acc + weight_fn(item);
|
let updated = weight_acc.saturating_add(weight_fn(item));
|
||||||
if updated > weight_limit {
|
if updated > weight_limit {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -731,14 +782,10 @@ fn random_sel<X, F: Fn(&X) -> Weight>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while !indices.is_empty() {
|
indices.shuffle(rng);
|
||||||
// randomly pick an index
|
for idx in indices {
|
||||||
let pick = rng.gen_range(0..indices.len());
|
|
||||||
// remove the index from the available set of indices
|
|
||||||
let idx = indices.swap_remove(pick);
|
|
||||||
|
|
||||||
let item = &selectables[idx];
|
let item = &selectables[idx];
|
||||||
let updated = weight_acc + weight_fn(item);
|
let updated = weight_acc.saturating_add(weight_fn(item));
|
||||||
|
|
||||||
if updated > weight_limit {
|
if updated > weight_limit {
|
||||||
continue
|
continue
|
||||||
@@ -754,14 +801,18 @@ fn random_sel<X, F: Fn(&X) -> Weight>(
|
|||||||
(weight_acc, picked_indices)
|
(weight_acc, picked_indices)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Considers an upper threshold that the candidates must not exceed.
|
/// Considers an upper threshold that the inherent data must not exceed.
|
||||||
///
|
///
|
||||||
/// If there is sufficient space, all bitfields and candidates will be included.
|
/// If there is sufficient space, all disputes, all bitfields and all candidates
|
||||||
|
/// will be included.
|
||||||
///
|
///
|
||||||
/// Otherwise tries to include all bitfields, and fills in the remaining weight with candidates.
|
/// Otherwise tries to include all disputes, and then tries to fill the remaining space with bitfields and then candidates.
|
||||||
///
|
///
|
||||||
/// If even the bitfields are too large to fit into the `max_weight` limit, bitfields are randomly
|
/// The selection process is random. For candidates, there is an exception for code upgrades as they are preferred.
|
||||||
/// picked and _no_ candidates will be included.
|
/// And for disputes, local and older disputes are preferred (see `limit_disputes`).
|
||||||
|
/// for backed candidates, since with a increasing number of parachains their chances of
|
||||||
|
/// inclusion become slim. All backed candidates are checked beforehands in `fn create_inherent_inner`
|
||||||
|
/// which guarantees sanity.
|
||||||
fn apply_weight_limit<T: Config + inclusion::Config>(
|
fn apply_weight_limit<T: Config + inclusion::Config>(
|
||||||
candidates: &mut Vec<BackedCandidate<<T>::Hash>>,
|
candidates: &mut Vec<BackedCandidate<<T>::Hash>>,
|
||||||
bitfields: &mut UncheckedSignedAvailabilityBitfields,
|
bitfields: &mut UncheckedSignedAvailabilityBitfields,
|
||||||
@@ -804,12 +855,7 @@ fn apply_weight_limit<T: Config + inclusion::Config>(
|
|||||||
|c| backed_candidate_weight::<T>(c),
|
|c| backed_candidate_weight::<T>(c),
|
||||||
remaining_weight,
|
remaining_weight,
|
||||||
);
|
);
|
||||||
let mut idx = 0_usize;
|
candidates.indexed_retain(|idx, _backed_candidate| indices.binary_search(&idx).is_ok());
|
||||||
candidates.retain(|_backed_candidate| {
|
|
||||||
let exists = indices.binary_search(&idx).is_ok();
|
|
||||||
idx += 1;
|
|
||||||
exists
|
|
||||||
});
|
|
||||||
// pick all bitfields, and
|
// pick all bitfields, and
|
||||||
// fill the remaining space with candidates
|
// fill the remaining space with candidates
|
||||||
let total = acc_candidate_weight.saturating_add(total_bitfields_weight);
|
let total = acc_candidate_weight.saturating_add(total_bitfields_weight);
|
||||||
@@ -828,12 +874,7 @@ fn apply_weight_limit<T: Config + inclusion::Config>(
|
|||||||
remaining_weight,
|
remaining_weight,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut idx = 0_usize;
|
bitfields.indexed_retain(|idx, _bitfield| indices.binary_search(&idx).is_ok());
|
||||||
bitfields.retain(|_bitfield| {
|
|
||||||
let exists = indices.binary_search(&idx).is_ok();
|
|
||||||
idx += 1;
|
|
||||||
exists
|
|
||||||
});
|
|
||||||
|
|
||||||
total
|
total
|
||||||
}
|
}
|
||||||
@@ -854,15 +895,16 @@ fn apply_weight_limit<T: Config + inclusion::Config>(
|
|||||||
/// they were actually checked and filtered to allow using it in both
|
/// they were actually checked and filtered to allow using it in both
|
||||||
/// cases, as `filtering` and `checking` stage.
|
/// cases, as `filtering` and `checking` stage.
|
||||||
///
|
///
|
||||||
/// `CHECK_SIGS` determines if validator signatures are checked. If true, bitfields that have an
|
/// `full_check` determines if validator signatures are checked. If `::Yes`,
|
||||||
/// invalid signature will be filtered out.
|
/// bitfields that have an invalid signature will be filtered out.
|
||||||
pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config, const CHECK_SIGS: bool>(
|
pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config>(
|
||||||
unchecked_bitfields: UncheckedSignedAvailabilityBitfields,
|
unchecked_bitfields: UncheckedSignedAvailabilityBitfields,
|
||||||
disputed_bitfield: DisputedBitfield,
|
disputed_bitfield: DisputedBitfield,
|
||||||
expected_bits: usize,
|
expected_bits: usize,
|
||||||
parent_hash: T::Hash,
|
parent_hash: T::Hash,
|
||||||
session_index: SessionIndex,
|
session_index: SessionIndex,
|
||||||
validators: &[ValidatorId],
|
validators: &[ValidatorId],
|
||||||
|
full_check: FullCheck,
|
||||||
) -> UncheckedSignedAvailabilityBitfields {
|
) -> UncheckedSignedAvailabilityBitfields {
|
||||||
let mut bitfields = Vec::with_capacity(unchecked_bitfields.len());
|
let mut bitfields = Vec::with_capacity(unchecked_bitfields.len());
|
||||||
|
|
||||||
@@ -882,8 +924,8 @@ pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config, const CHECK_SIGS:
|
|||||||
if unchecked_bitfield.unchecked_payload().0.len() != expected_bits {
|
if unchecked_bitfield.unchecked_payload().0.len() != expected_bits {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
"[CHECK_SIGS: {}] bad bitfield length: {} != {:?}",
|
"[{:?}] bad bitfield length: {} != {:?}",
|
||||||
CHECK_SIGS,
|
full_check,
|
||||||
unchecked_bitfield.unchecked_payload().0.len(),
|
unchecked_bitfield.unchecked_payload().0.len(),
|
||||||
expected_bits,
|
expected_bits,
|
||||||
);
|
);
|
||||||
@@ -895,8 +937,8 @@ pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config, const CHECK_SIGS:
|
|||||||
{
|
{
|
||||||
log::trace!(
|
log::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
"[CHECK_SIGS: {}] bitfield contains disputed cores: {:?}",
|
"[{:?}] bitfield contains disputed cores: {:?}",
|
||||||
CHECK_SIGS,
|
full_check,
|
||||||
unchecked_bitfield.unchecked_payload().0.clone() & disputed_bitfield.0.clone()
|
unchecked_bitfield.unchecked_payload().0.clone() & disputed_bitfield.0.clone()
|
||||||
);
|
);
|
||||||
continue
|
continue
|
||||||
@@ -907,8 +949,8 @@ pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config, const CHECK_SIGS:
|
|||||||
if !last_index.map_or(true, |last_index: ValidatorIndex| last_index < validator_index) {
|
if !last_index.map_or(true, |last_index: ValidatorIndex| last_index < validator_index) {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
"[CHECK_SIGS: {}] bitfield validator index is not greater than last: !({:?} < {})",
|
"[{:?}] bitfield validator index is not greater than last: !({:?} < {})",
|
||||||
CHECK_SIGS,
|
full_check,
|
||||||
last_index.as_ref().map(|x| x.0),
|
last_index.as_ref().map(|x| x.0),
|
||||||
validator_index.0
|
validator_index.0
|
||||||
);
|
);
|
||||||
@@ -918,8 +960,8 @@ pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config, const CHECK_SIGS:
|
|||||||
if unchecked_bitfield.unchecked_validator_index().0 as usize >= validators.len() {
|
if unchecked_bitfield.unchecked_validator_index().0 as usize >= validators.len() {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
"[CHECK_SIGS: {}] bitfield validator index is out of bounds: {} >= {}",
|
"[{:?}] bitfield validator index is out of bounds: {} >= {}",
|
||||||
CHECK_SIGS,
|
full_check,
|
||||||
validator_index.0,
|
validator_index.0,
|
||||||
validators.len(),
|
validators.len(),
|
||||||
);
|
);
|
||||||
@@ -928,7 +970,7 @@ pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config, const CHECK_SIGS:
|
|||||||
|
|
||||||
let validator_public = &validators[validator_index.0 as usize];
|
let validator_public = &validators[validator_index.0 as usize];
|
||||||
|
|
||||||
if CHECK_SIGS {
|
if let FullCheck::Yes = full_check {
|
||||||
if let Ok(signed_bitfield) =
|
if let Ok(signed_bitfield) =
|
||||||
unchecked_bitfield.try_into_checked(&signing_context, validator_public)
|
unchecked_bitfield.try_into_checked(&signing_context, validator_public)
|
||||||
{
|
{
|
||||||
@@ -954,15 +996,18 @@ pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config, const CHECK_SIGS:
|
|||||||
///
|
///
|
||||||
/// `candidate_has_concluded_invalid_dispute` must return `true` if the candidate
|
/// `candidate_has_concluded_invalid_dispute` must return `true` if the candidate
|
||||||
/// is disputed, false otherwise
|
/// is disputed, false otherwise
|
||||||
fn sanitize_backed_candidates<T: crate::inclusion::Config, F: Fn(CandidateHash) -> bool>(
|
fn sanitize_backed_candidates<
|
||||||
|
T: crate::inclusion::Config,
|
||||||
|
F: FnMut(usize, &BackedCandidate<T::Hash>) -> bool,
|
||||||
|
>(
|
||||||
relay_parent: T::Hash,
|
relay_parent: T::Hash,
|
||||||
mut backed_candidates: Vec<BackedCandidate<T::Hash>>,
|
mut backed_candidates: Vec<BackedCandidate<T::Hash>>,
|
||||||
candidate_has_concluded_invalid_dispute: F,
|
mut candidate_has_concluded_invalid_dispute_or_is_invalid: F,
|
||||||
scheduled: &[CoreAssignment],
|
scheduled: &[CoreAssignment],
|
||||||
) -> Vec<BackedCandidate<T::Hash>> {
|
) -> Vec<BackedCandidate<T::Hash>> {
|
||||||
// Remove any candidates that were concluded invalid.
|
// Remove any candidates that were concluded invalid.
|
||||||
backed_candidates.retain(|backed_candidate| {
|
backed_candidates.indexed_retain(move |idx, backed_candidate| {
|
||||||
!candidate_has_concluded_invalid_dispute(backed_candidate.candidate.hash())
|
!candidate_has_concluded_invalid_dispute_or_is_invalid(idx, backed_candidate)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assure the backed candidate's `ParaId`'s core is free.
|
// Assure the backed candidate's `ParaId`'s core is free.
|
||||||
@@ -1657,7 +1702,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// Ensure that when a block is over weight due to disputes and bitfields, we abort
|
// Ensure that when a block is over weight due to disputes and bitfields, we abort
|
||||||
fn limit_candidates_over_weight() {
|
fn limit_candidates_over_weight_1() {
|
||||||
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
|
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
|
||||||
// Create the inherent data for this block
|
// Create the inherent data for this block
|
||||||
let mut dispute_statements = BTreeMap::new();
|
let mut dispute_statements = BTreeMap::new();
|
||||||
@@ -1728,7 +1773,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// Ensure that when a block is over weight due to disputes and bitfields, we abort
|
// Ensure that when a block is over weight due to disputes and bitfields, we abort
|
||||||
fn limit_candidates_over_weight_overweight() {
|
fn limit_candidates_over_weight_0() {
|
||||||
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
|
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
|
||||||
// Create the inherent data for this block
|
// Create the inherent data for this block
|
||||||
let mut dispute_statements = BTreeMap::new();
|
let mut dispute_statements = BTreeMap::new();
|
||||||
@@ -1871,24 +1916,26 @@ mod tests {
|
|||||||
|
|
||||||
{
|
{
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sanitize_bitfields::<Test, VERIFY_SIGS>(
|
sanitize_bitfields::<Test>(
|
||||||
unchecked_bitfields.clone(),
|
unchecked_bitfields.clone(),
|
||||||
disputed_bitfield.clone(),
|
disputed_bitfield.clone(),
|
||||||
expected_bits,
|
expected_bits,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validator_public[..]
|
&validator_public[..],
|
||||||
|
FullCheck::Skip,
|
||||||
),
|
),
|
||||||
unchecked_bitfields.clone()
|
unchecked_bitfields.clone()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sanitize_bitfields::<Test, SKIP_SIG_VERIFY>(
|
sanitize_bitfields::<Test>(
|
||||||
unchecked_bitfields.clone(),
|
unchecked_bitfields.clone(),
|
||||||
disputed_bitfield.clone(),
|
disputed_bitfield.clone(),
|
||||||
expected_bits,
|
expected_bits,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validator_public[..]
|
&validator_public[..],
|
||||||
|
FullCheck::Yes
|
||||||
),
|
),
|
||||||
unchecked_bitfields.clone()
|
unchecked_bitfields.clone()
|
||||||
);
|
);
|
||||||
@@ -1902,25 +1949,27 @@ mod tests {
|
|||||||
disputed_bitfield.0.set(0, true);
|
disputed_bitfield.0.set(0, true);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sanitize_bitfields::<Test, VERIFY_SIGS>(
|
sanitize_bitfields::<Test>(
|
||||||
unchecked_bitfields.clone(),
|
unchecked_bitfields.clone(),
|
||||||
disputed_bitfield.clone(),
|
disputed_bitfield.clone(),
|
||||||
expected_bits,
|
expected_bits,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validator_public[..]
|
&validator_public[..],
|
||||||
|
FullCheck::Yes
|
||||||
)
|
)
|
||||||
.len(),
|
.len(),
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sanitize_bitfields::<Test, SKIP_SIG_VERIFY>(
|
sanitize_bitfields::<Test>(
|
||||||
unchecked_bitfields.clone(),
|
unchecked_bitfields.clone(),
|
||||||
disputed_bitfield.clone(),
|
disputed_bitfield.clone(),
|
||||||
expected_bits,
|
expected_bits,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validator_public[..]
|
&validator_public[..],
|
||||||
|
FullCheck::Skip
|
||||||
)
|
)
|
||||||
.len(),
|
.len(),
|
||||||
1
|
1
|
||||||
@@ -1929,22 +1978,24 @@ mod tests {
|
|||||||
|
|
||||||
// bitfield size mismatch
|
// bitfield size mismatch
|
||||||
{
|
{
|
||||||
assert!(sanitize_bitfields::<Test, VERIFY_SIGS>(
|
assert!(sanitize_bitfields::<Test>(
|
||||||
unchecked_bitfields.clone(),
|
unchecked_bitfields.clone(),
|
||||||
disputed_bitfield.clone(),
|
disputed_bitfield.clone(),
|
||||||
expected_bits + 1,
|
expected_bits + 1,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validator_public[..]
|
&validator_public[..],
|
||||||
|
FullCheck::Yes
|
||||||
)
|
)
|
||||||
.is_empty());
|
.is_empty());
|
||||||
assert!(sanitize_bitfields::<Test, SKIP_SIG_VERIFY>(
|
assert!(sanitize_bitfields::<Test>(
|
||||||
unchecked_bitfields.clone(),
|
unchecked_bitfields.clone(),
|
||||||
disputed_bitfield.clone(),
|
disputed_bitfield.clone(),
|
||||||
expected_bits + 1,
|
expected_bits + 1,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validator_public[..]
|
&validator_public[..],
|
||||||
|
FullCheck::Skip
|
||||||
)
|
)
|
||||||
.is_empty());
|
.is_empty());
|
||||||
}
|
}
|
||||||
@@ -1953,24 +2004,26 @@ mod tests {
|
|||||||
{
|
{
|
||||||
let shortened = validator_public.len() - 2;
|
let shortened = validator_public.len() - 2;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&sanitize_bitfields::<Test, VERIFY_SIGS>(
|
&sanitize_bitfields::<Test>(
|
||||||
unchecked_bitfields.clone(),
|
unchecked_bitfields.clone(),
|
||||||
disputed_bitfield.clone(),
|
disputed_bitfield.clone(),
|
||||||
expected_bits,
|
expected_bits,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validator_public[..shortened]
|
&validator_public[..shortened],
|
||||||
|
FullCheck::Yes,
|
||||||
)[..],
|
)[..],
|
||||||
&unchecked_bitfields[..shortened]
|
&unchecked_bitfields[..shortened]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&sanitize_bitfields::<Test, SKIP_SIG_VERIFY>(
|
&sanitize_bitfields::<Test>(
|
||||||
unchecked_bitfields.clone(),
|
unchecked_bitfields.clone(),
|
||||||
disputed_bitfield.clone(),
|
disputed_bitfield.clone(),
|
||||||
expected_bits,
|
expected_bits,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validator_public[..shortened]
|
&validator_public[..shortened],
|
||||||
|
FullCheck::Skip,
|
||||||
)[..],
|
)[..],
|
||||||
&unchecked_bitfields[..shortened]
|
&unchecked_bitfields[..shortened]
|
||||||
);
|
);
|
||||||
@@ -1982,24 +2035,26 @@ mod tests {
|
|||||||
let x = unchecked_bitfields.swap_remove(0);
|
let x = unchecked_bitfields.swap_remove(0);
|
||||||
unchecked_bitfields.push(x);
|
unchecked_bitfields.push(x);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&sanitize_bitfields::<Test, VERIFY_SIGS>(
|
&sanitize_bitfields::<Test>(
|
||||||
unchecked_bitfields.clone(),
|
unchecked_bitfields.clone(),
|
||||||
disputed_bitfield.clone(),
|
disputed_bitfield.clone(),
|
||||||
expected_bits,
|
expected_bits,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validator_public[..]
|
&validator_public[..],
|
||||||
|
FullCheck::Yes
|
||||||
)[..],
|
)[..],
|
||||||
&unchecked_bitfields[..(unchecked_bitfields.len() - 2)]
|
&unchecked_bitfields[..(unchecked_bitfields.len() - 2)]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&sanitize_bitfields::<Test, SKIP_SIG_VERIFY>(
|
&sanitize_bitfields::<Test>(
|
||||||
unchecked_bitfields.clone(),
|
unchecked_bitfields.clone(),
|
||||||
disputed_bitfield.clone(),
|
disputed_bitfield.clone(),
|
||||||
expected_bits,
|
expected_bits,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validator_public[..]
|
&validator_public[..],
|
||||||
|
FullCheck::Skip
|
||||||
)[..],
|
)[..],
|
||||||
&unchecked_bitfields[..(unchecked_bitfields.len() - 2)]
|
&unchecked_bitfields[..(unchecked_bitfields.len() - 2)]
|
||||||
);
|
);
|
||||||
@@ -2017,24 +2072,26 @@ mod tests {
|
|||||||
.and_then(|u| Some(u.set_signature(ValidatorSignature::default())))
|
.and_then(|u| Some(u.set_signature(ValidatorSignature::default())))
|
||||||
.expect("we are accessing a valid index");
|
.expect("we are accessing a valid index");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&sanitize_bitfields::<Test, VERIFY_SIGS>(
|
&sanitize_bitfields::<Test>(
|
||||||
unchecked_bitfields.clone(),
|
unchecked_bitfields.clone(),
|
||||||
disputed_bitfield.clone(),
|
disputed_bitfield.clone(),
|
||||||
expected_bits,
|
expected_bits,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validator_public[..]
|
&validator_public[..],
|
||||||
|
FullCheck::Yes
|
||||||
)[..],
|
)[..],
|
||||||
&unchecked_bitfields[..last_bit_idx]
|
&unchecked_bitfields[..last_bit_idx]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&sanitize_bitfields::<Test, SKIP_SIG_VERIFY>(
|
&sanitize_bitfields::<Test>(
|
||||||
unchecked_bitfields.clone(),
|
unchecked_bitfields.clone(),
|
||||||
disputed_bitfield.clone(),
|
disputed_bitfield.clone(),
|
||||||
expected_bits,
|
expected_bits,
|
||||||
parent_hash,
|
parent_hash,
|
||||||
session_index,
|
session_index,
|
||||||
&validator_public[..]
|
&validator_public[..],
|
||||||
|
FullCheck::Skip
|
||||||
)[..],
|
)[..],
|
||||||
&unchecked_bitfields[..]
|
&unchecked_bitfields[..]
|
||||||
);
|
);
|
||||||
@@ -2068,7 +2125,8 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let has_concluded_invalid = |_candidate: CandidateHash| -> bool { false };
|
let has_concluded_invalid =
|
||||||
|
|_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false };
|
||||||
|
|
||||||
let scheduled = (0_usize..2)
|
let scheduled = (0_usize..2)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -2168,7 +2226,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
set
|
set
|
||||||
};
|
};
|
||||||
let has_concluded_invalid = |candidate: CandidateHash| set.contains(&candidate);
|
let has_concluded_invalid =
|
||||||
|
|_idx: usize, candidate: &BackedCandidate| set.contains(&candidate.hash());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sanitize_backed_candidates::<Test, _>(
|
sanitize_backed_candidates::<Test, _>(
|
||||||
relay_parent,
|
relay_parent,
|
||||||
|
|||||||
Reference in New Issue
Block a user