mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 06:21:02 +00:00
Unify code paths of create_inherent and enter (#7137)
* Remove redundant enter call. * Remove optionality in dispute signature checking * Make enter_inner and create_inherent the same. * Remove redundant metric. * Unification: enter and create_inherent. * Remove `enter_inner` function. * Remove dead code. * Remove redundant import. * Remove dead code in disputes. * ".git/.scripts/commands/bench/bench.sh" runtime polkadot runtime_parachains::paras_inherent * ".git/.scripts/commands/bench/bench.sh" runtime kusama runtime_parachains::paras_inherent * ".git/.scripts/commands/bench/bench.sh" runtime westend runtime_parachains::paras_inherent * Merge fix. * Fix tests. * Remove obsolete comment. * ".git/.scripts/commands/bench/bench.sh" runtime polkadot runtime_parachains::paras_inherent * ".git/.scripts/commands/bench/bench.sh" runtime westend runtime_parachains::paras_inherent * ".git/.scripts/commands/bench/bench.sh" runtime kusama runtime_parachains::paras_inherent * Review remarks, fixes. * Guide updates. * Fmt. * ".git/.scripts/commands/bench/bench.sh" runtime polkadot runtime_parachains::paras_inherent --------- Co-authored-by: eskimor <eskimor@no-such-url.com> Co-authored-by: command-bot <>
This commit is contained in:
@@ -136,19 +136,6 @@ impl<BlockNumber> SlashingHandler<BlockNumber> for () {
|
||||
fn initializer_on_new_session(_: SessionIndex) {}
|
||||
}
|
||||
|
||||
/// Binary discriminator to determine if the expensive signature
|
||||
/// checks are necessary.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum VerifyDisputeSignatures {
|
||||
/// Yes, verify the signatures.
|
||||
Yes,
|
||||
/// No, skip the signature verification.
|
||||
///
|
||||
/// Only done if there exists an invariant that
|
||||
/// can guaranteed the signature was checked before.
|
||||
Skip,
|
||||
}
|
||||
|
||||
/// Provide a `Ordering` for the two provided dispute statement sets according to the
|
||||
/// following prioritization:
|
||||
/// 1. Prioritize local disputes over remote disputes
|
||||
@@ -184,36 +171,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
use super::paras_inherent::IsSortedBy;
|
||||
|
||||
/// Returns `true` if duplicate items were found, otherwise `false`.
|
||||
///
|
||||
/// `check_equal(a: &T, b: &T)` _must_ return `true`, iff `a` and `b` are equal, otherwise `false.
|
||||
/// The definition of _equal_ is to be defined by the user.
|
||||
///
|
||||
/// Attention: Requires the input `iter` to be sorted, such that _equals_
|
||||
/// would be adjacent in respect whatever `check_equal` defines as equality!
|
||||
fn contains_duplicates_in_sorted_iter<
|
||||
'a,
|
||||
T: 'a,
|
||||
I: 'a + IntoIterator<Item = &'a T>,
|
||||
C: 'static + FnMut(&T, &T) -> bool,
|
||||
>(
|
||||
iter: I,
|
||||
mut check_equal: C,
|
||||
) -> bool {
|
||||
let mut iter = iter.into_iter();
|
||||
if let Some(mut previous) = iter.next() {
|
||||
while let Some(current) = iter.next() {
|
||||
if check_equal(previous, current) {
|
||||
return true
|
||||
}
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// Hook into disputes handling.
|
||||
///
|
||||
/// Allows decoupling parachains handling from disputes so that it can
|
||||
@@ -223,23 +180,6 @@ pub trait DisputesHandler<BlockNumber: Ord> {
|
||||
/// any new parachain blocks for backing or inclusion.
|
||||
fn is_frozen() -> bool;
|
||||
|
||||
/// Assure sanity
|
||||
fn assure_deduplicated_and_sorted(statement_sets: &MultiDisputeStatementSet) -> Result<(), ()> {
|
||||
if !IsSortedBy::is_sorted_by(
|
||||
statement_sets.as_slice(),
|
||||
dispute_ordering_compare::<Self, BlockNumber>,
|
||||
) {
|
||||
return Err(())
|
||||
}
|
||||
// Sorted, so according to session and candidate hash, this will detect duplicates.
|
||||
if contains_duplicates_in_sorted_iter(statement_sets, |previous, current| {
|
||||
current.session == previous.session && current.candidate_hash == previous.candidate_hash
|
||||
}) {
|
||||
return Err(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove dispute statement duplicates and sort the non-duplicates based on
|
||||
/// local (lower indicies) vs remotes (higher indices) and age (older with lower indices).
|
||||
///
|
||||
@@ -274,7 +214,6 @@ pub trait DisputesHandler<BlockNumber: Ord> {
|
||||
fn filter_dispute_data(
|
||||
statement_set: DisputeStatementSet,
|
||||
post_conclusion_acceptance_period: BlockNumber,
|
||||
verify_sigs: VerifyDisputeSignatures,
|
||||
) -> Option<CheckedDisputeStatementSet>;
|
||||
|
||||
/// Handle sets of dispute statements corresponding to 0 or more candidates.
|
||||
@@ -322,7 +261,6 @@ impl<BlockNumber: Ord> DisputesHandler<BlockNumber> for () {
|
||||
fn filter_dispute_data(
|
||||
_set: DisputeStatementSet,
|
||||
_post_conclusion_acceptance_period: BlockNumber,
|
||||
_verify_sigs: VerifyDisputeSignatures,
|
||||
) -> Option<CheckedDisputeStatementSet> {
|
||||
None
|
||||
}
|
||||
@@ -371,14 +309,9 @@ where
|
||||
fn filter_dispute_data(
|
||||
set: DisputeStatementSet,
|
||||
post_conclusion_acceptance_period: T::BlockNumber,
|
||||
verify_sigs: VerifyDisputeSignatures,
|
||||
) -> Option<CheckedDisputeStatementSet> {
|
||||
pallet::Pallet::<T>::filter_dispute_data(
|
||||
&set,
|
||||
post_conclusion_acceptance_period,
|
||||
verify_sigs,
|
||||
)
|
||||
.filter_statement_set(set)
|
||||
pallet::Pallet::<T>::filter_dispute_data(&set, post_conclusion_acceptance_period)
|
||||
.filter_statement_set(set)
|
||||
}
|
||||
|
||||
fn process_checked_multi_dispute_data(
|
||||
@@ -1005,7 +938,6 @@ impl<T: Config> Pallet<T> {
|
||||
fn filter_dispute_data(
|
||||
set: &DisputeStatementSet,
|
||||
post_conclusion_acceptance_period: <T as frame_system::Config>::BlockNumber,
|
||||
verify_sigs: VerifyDisputeSignatures,
|
||||
) -> StatementSetFilter {
|
||||
let mut filter = StatementSetFilter::RemoveIndices(Vec::new());
|
||||
|
||||
@@ -1068,29 +1000,26 @@ impl<T: Config> Pallet<T> {
|
||||
},
|
||||
};
|
||||
|
||||
// Avoid checking signatures repeatedly.
|
||||
if let VerifyDisputeSignatures::Yes = verify_sigs {
|
||||
// Check signature after attempting import.
|
||||
//
|
||||
// Since we expect that this filter will be applied to
|
||||
// disputes long after they're concluded, 99% of the time,
|
||||
// the duplicate filter above will catch them before needing
|
||||
// to do a heavy signature check.
|
||||
//
|
||||
// This is only really important until the post-conclusion acceptance threshold
|
||||
// is reached, and then no part of this loop will be hit.
|
||||
if let Err(()) = check_signature(
|
||||
&validator_public,
|
||||
set.candidate_hash,
|
||||
set.session,
|
||||
statement,
|
||||
signature,
|
||||
) {
|
||||
importer.undo(undo);
|
||||
filter.remove_index(i);
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Check signature after attempting import.
|
||||
//
|
||||
// Since we expect that this filter will be applied to
|
||||
// disputes long after they're concluded, 99% of the time,
|
||||
// the duplicate filter above will catch them before needing
|
||||
// to do a heavy signature check.
|
||||
//
|
||||
// This is only really important until the post-conclusion acceptance threshold
|
||||
// is reached, and then no part of this loop will be hit.
|
||||
if let Err(()) = check_signature(
|
||||
&validator_public,
|
||||
set.candidate_hash,
|
||||
set.session,
|
||||
statement,
|
||||
signature,
|
||||
) {
|
||||
importer.undo(undo);
|
||||
filter.remove_index(i);
|
||||
continue
|
||||
};
|
||||
}
|
||||
|
||||
importer.finish()
|
||||
|
||||
@@ -42,16 +42,41 @@ fn filter_dispute_set(stmts: MultiDisputeStatementSet) -> CheckedMultiDisputeSta
|
||||
stmts
|
||||
.into_iter()
|
||||
.filter_map(|set| {
|
||||
let filter = Pallet::<Test>::filter_dispute_data(
|
||||
&set,
|
||||
post_conclusion_acceptance_period,
|
||||
VerifyDisputeSignatures::Skip,
|
||||
);
|
||||
let filter =
|
||||
Pallet::<Test>::filter_dispute_data(&set, post_conclusion_acceptance_period);
|
||||
filter.filter_statement_set(set)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Returns `true` if duplicate items were found, otherwise `false`.
|
||||
///
|
||||
/// `check_equal(a: &T, b: &T)` _must_ return `true`, iff `a` and `b` are equal, otherwise `false.
|
||||
/// The definition of _equal_ is to be defined by the user.
|
||||
///
|
||||
/// Attention: Requires the input `iter` to be sorted, such that _equals_
|
||||
/// would be adjacent in respect whatever `check_equal` defines as equality!
|
||||
fn contains_duplicates_in_sorted_iter<
|
||||
'a,
|
||||
T: 'a,
|
||||
I: 'a + IntoIterator<Item = &'a T>,
|
||||
C: 'static + FnMut(&T, &T) -> bool,
|
||||
>(
|
||||
iter: I,
|
||||
mut check_equal: C,
|
||||
) -> bool {
|
||||
let mut iter = iter.into_iter();
|
||||
if let Some(mut previous) = iter.next() {
|
||||
while let Some(current) = iter.next() {
|
||||
if check_equal(previous, current) {
|
||||
return true
|
||||
}
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// All arguments for `initializer::on_new_session`
|
||||
type NewSession<'a> = (
|
||||
bool,
|
||||
@@ -2061,7 +2086,6 @@ fn apply_filter_all<T: Config, I: IntoIterator<Item = DisputeStatementSet>>(
|
||||
if let Some(checked) = <Pallet<T> as DisputesHandler<<T>::BlockNumber>>::filter_dispute_data(
|
||||
dispute_statement,
|
||||
post_conclusion_acceptance_period,
|
||||
VerifyDisputeSignatures::Yes,
|
||||
) {
|
||||
acc.push(checked);
|
||||
}
|
||||
@@ -2136,11 +2160,7 @@ fn filter_removes_duplicates_within_set() {
|
||||
let post_conclusion_acceptance_period = 10;
|
||||
let statements = <Pallet<Test> as DisputesHandler<
|
||||
<Test as frame_system::Config>::BlockNumber,
|
||||
>>::filter_dispute_data(
|
||||
statements,
|
||||
post_conclusion_acceptance_period,
|
||||
VerifyDisputeSignatures::Yes,
|
||||
);
|
||||
>>::filter_dispute_data(statements, post_conclusion_acceptance_period);
|
||||
|
||||
assert_eq!(
|
||||
statements,
|
||||
@@ -2441,144 +2461,6 @@ fn filter_removes_duplicate_statements_sets() {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assure_no_duplicate_statements_sets_are_fine() {
|
||||
new_test_ext(Default::default()).execute_with(|| {
|
||||
let v0 = <ValidatorId as CryptoType>::Pair::generate().0;
|
||||
let v1 = <ValidatorId as CryptoType>::Pair::generate().0;
|
||||
|
||||
run_to_block(3, |b| {
|
||||
// a new session at each block
|
||||
Some((
|
||||
true,
|
||||
b,
|
||||
vec![(&0, v0.public()), (&1, v1.public())],
|
||||
Some(vec![(&0, v0.public()), (&1, v1.public())]),
|
||||
))
|
||||
});
|
||||
|
||||
let candidate_hash_a = CandidateHash(sp_core::H256::repeat_byte(1));
|
||||
|
||||
let payload = ExplicitDisputeStatement {
|
||||
valid: true,
|
||||
candidate_hash: candidate_hash_a.clone(),
|
||||
session: 1,
|
||||
}
|
||||
.signing_payload();
|
||||
|
||||
let payload_against = ExplicitDisputeStatement {
|
||||
valid: false,
|
||||
candidate_hash: candidate_hash_a.clone(),
|
||||
session: 1,
|
||||
}
|
||||
.signing_payload();
|
||||
|
||||
let sig_a = v0.sign(&payload);
|
||||
let sig_a_against = v1.sign(&payload_against);
|
||||
|
||||
let statements = vec![
|
||||
(
|
||||
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit),
|
||||
ValidatorIndex(0),
|
||||
sig_a.clone(),
|
||||
),
|
||||
(
|
||||
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit),
|
||||
ValidatorIndex(1),
|
||||
sig_a_against.clone(),
|
||||
),
|
||||
];
|
||||
|
||||
let sets = vec![
|
||||
DisputeStatementSet {
|
||||
candidate_hash: candidate_hash_a.clone(),
|
||||
session: 1,
|
||||
statements: statements.clone(),
|
||||
},
|
||||
DisputeStatementSet {
|
||||
candidate_hash: candidate_hash_a.clone(),
|
||||
session: 2,
|
||||
statements: statements.clone(),
|
||||
},
|
||||
];
|
||||
|
||||
// `Err(())` indicates presence of duplicates
|
||||
assert!(<Pallet::<Test> as DisputesHandler<
|
||||
<Test as frame_system::Config>::BlockNumber,
|
||||
>>::assure_deduplicated_and_sorted(&sets)
|
||||
.is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assure_detects_duplicate_statements_sets() {
|
||||
new_test_ext(Default::default()).execute_with(|| {
|
||||
let v0 = <ValidatorId as CryptoType>::Pair::generate().0;
|
||||
let v1 = <ValidatorId as CryptoType>::Pair::generate().0;
|
||||
|
||||
run_to_block(3, |b| {
|
||||
// a new session at each block
|
||||
Some((
|
||||
true,
|
||||
b,
|
||||
vec![(&0, v0.public()), (&1, v1.public())],
|
||||
Some(vec![(&0, v0.public()), (&1, v1.public())]),
|
||||
))
|
||||
});
|
||||
|
||||
let candidate_hash_a = CandidateHash(sp_core::H256::repeat_byte(1));
|
||||
|
||||
let payload = ExplicitDisputeStatement {
|
||||
valid: true,
|
||||
candidate_hash: candidate_hash_a.clone(),
|
||||
session: 1,
|
||||
}
|
||||
.signing_payload();
|
||||
|
||||
let payload_against = ExplicitDisputeStatement {
|
||||
valid: false,
|
||||
candidate_hash: candidate_hash_a.clone(),
|
||||
session: 1,
|
||||
}
|
||||
.signing_payload();
|
||||
|
||||
let sig_a = v0.sign(&payload);
|
||||
let sig_a_against = v1.sign(&payload_against);
|
||||
|
||||
let statements = vec![
|
||||
(
|
||||
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit),
|
||||
ValidatorIndex(0),
|
||||
sig_a.clone(),
|
||||
),
|
||||
(
|
||||
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit),
|
||||
ValidatorIndex(1),
|
||||
sig_a_against.clone(),
|
||||
),
|
||||
];
|
||||
|
||||
let sets = vec![
|
||||
DisputeStatementSet {
|
||||
candidate_hash: candidate_hash_a.clone(),
|
||||
session: 1,
|
||||
statements: statements.clone(),
|
||||
},
|
||||
DisputeStatementSet {
|
||||
candidate_hash: candidate_hash_a.clone(),
|
||||
session: 1,
|
||||
statements: statements.clone(),
|
||||
},
|
||||
];
|
||||
|
||||
// `Err(())` indicates presence of duplicates
|
||||
assert!(<Pallet::<Test> as DisputesHandler<
|
||||
<Test as frame_system::Config>::BlockNumber,
|
||||
>>::assure_deduplicated_and_sorted(&sets)
|
||||
.is_err());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_ignores_single_sided() {
|
||||
new_test_ext(Default::default()).execute_with(|| {
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
use crate::{
|
||||
configuration::{self, HostConfiguration},
|
||||
disputes, dmp, hrmp, paras,
|
||||
paras_inherent::DisputedBitfield,
|
||||
scheduler::CoreAssignment,
|
||||
shared,
|
||||
};
|
||||
@@ -39,8 +38,8 @@ use parity_scale_codec::{Decode, Encode};
|
||||
use primitives::{
|
||||
supermajority_threshold, well_known_keys, AvailabilityBitfield, BackedCandidate,
|
||||
CandidateCommitments, CandidateDescriptor, CandidateHash, CandidateReceipt,
|
||||
CommittedCandidateReceipt, CoreIndex, GroupIndex, Hash, HeadData, Id as ParaId, SigningContext,
|
||||
UncheckedSignedAvailabilityBitfields, UpwardMessage, ValidatorId, ValidatorIndex,
|
||||
CommittedCandidateReceipt, CoreIndex, GroupIndex, Hash, HeadData, Id as ParaId,
|
||||
SignedAvailabilityBitfields, SigningContext, UpwardMessage, ValidatorId, ValidatorIndex,
|
||||
ValidityAttestation,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
@@ -92,19 +91,6 @@ pub struct AvailabilityBitfieldRecord<N> {
|
||||
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(Clone, Copy, 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.
|
||||
#[derive(Encode, Decode, PartialEq, TypeInfo)]
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
@@ -497,13 +483,17 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
/// Extract the freed cores based on cores that became available.
|
||||
///
|
||||
/// Bitfields are expected to have been sanitized already. E.g. via `sanitize_bitfields`!
|
||||
///
|
||||
/// Updates storage items `PendingAvailability` and `AvailabilityBitfields`.
|
||||
///
|
||||
/// Returns a `Vec` of `CandidateHash`es and their respective `AvailabilityCore`s that became available,
|
||||
/// and cores free.
|
||||
pub(crate) fn update_pending_availability_and_get_freed_cores<F>(
|
||||
expected_bits: usize,
|
||||
validators: &[ValidatorId],
|
||||
signed_bitfields: UncheckedSignedAvailabilityBitfields,
|
||||
signed_bitfields: SignedAvailabilityBitfields,
|
||||
core_lookup: F,
|
||||
enact_candidate: bool,
|
||||
) -> Vec<(CoreIndex, CandidateHash)>
|
||||
where
|
||||
F: Fn(CoreIndex) -> Option<ParaId>,
|
||||
@@ -518,9 +508,8 @@ impl<T: Config> Pallet<T> {
|
||||
let now = <frame_system::Pallet<T>>::block_number();
|
||||
for (checked_bitfield, validator_index) in
|
||||
signed_bitfields.into_iter().map(|signed_bitfield| {
|
||||
// extracting unchecked data, since it's checked in `fn sanitize_bitfields` already.
|
||||
let validator_idx = signed_bitfield.unchecked_validator_index();
|
||||
let checked_bitfield = signed_bitfield.unchecked_into_payload();
|
||||
let validator_idx = signed_bitfield.validator_index();
|
||||
let checked_bitfield = signed_bitfield.into_payload();
|
||||
(checked_bitfield, validator_idx)
|
||||
}) {
|
||||
for (bit_idx, _) in checked_bitfield.0.iter().enumerate().filter(|(_, is_av)| **is_av) {
|
||||
@@ -575,20 +564,18 @@ impl<T: Config> Pallet<T> {
|
||||
},
|
||||
};
|
||||
|
||||
if enact_candidate {
|
||||
let receipt = CommittedCandidateReceipt {
|
||||
descriptor: pending_availability.descriptor,
|
||||
commitments,
|
||||
};
|
||||
let _weight = Self::enact_candidate(
|
||||
pending_availability.relay_parent_number,
|
||||
receipt,
|
||||
pending_availability.backers,
|
||||
pending_availability.availability_votes,
|
||||
pending_availability.core,
|
||||
pending_availability.backing_group,
|
||||
);
|
||||
}
|
||||
let receipt = CommittedCandidateReceipt {
|
||||
descriptor: pending_availability.descriptor,
|
||||
commitments,
|
||||
};
|
||||
let _weight = Self::enact_candidate(
|
||||
pending_availability.relay_parent_number,
|
||||
receipt,
|
||||
pending_availability.backers,
|
||||
pending_availability.availability_votes,
|
||||
pending_availability.core,
|
||||
pending_availability.backing_group,
|
||||
);
|
||||
|
||||
freed_cores.push((pending_availability.core, pending_availability.hash));
|
||||
} else {
|
||||
@@ -599,42 +586,6 @@ impl<T: Config> Pallet<T> {
|
||||
freed_cores
|
||||
}
|
||||
|
||||
/// Process a set of incoming bitfields.
|
||||
///
|
||||
/// Returns a `Vec` of `CandidateHash`es and their respective `AvailabilityCore`s that became available,
|
||||
/// and cores free.
|
||||
pub(crate) fn process_bitfields(
|
||||
expected_bits: usize,
|
||||
signed_bitfields: UncheckedSignedAvailabilityBitfields,
|
||||
disputed_bitfield: DisputedBitfield,
|
||||
core_lookup: impl Fn(CoreIndex) -> Option<ParaId>,
|
||||
full_check: FullCheck,
|
||||
) -> Result<Vec<(CoreIndex, CandidateHash)>, crate::inclusion::Error<T>> {
|
||||
let validators = shared::Pallet::<T>::active_validator_keys();
|
||||
let session_index = shared::Pallet::<T>::session_index();
|
||||
let parent_hash = frame_system::Pallet::<T>::parent_hash();
|
||||
|
||||
let checked_bitfields = crate::paras_inherent::assure_sanity_bitfields::<T>(
|
||||
signed_bitfields,
|
||||
disputed_bitfield,
|
||||
expected_bits,
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validators[..],
|
||||
full_check,
|
||||
)?;
|
||||
|
||||
let freed_cores = Self::update_pending_availability_and_get_freed_cores::<_>(
|
||||
expected_bits,
|
||||
&validators[..],
|
||||
checked_bitfields,
|
||||
core_lookup,
|
||||
true,
|
||||
);
|
||||
|
||||
Ok(freed_cores)
|
||||
}
|
||||
|
||||
/// Process candidates that have been backed. Provide the relay storage root, a set of candidates
|
||||
/// and scheduled cores.
|
||||
///
|
||||
|
||||
@@ -26,23 +26,20 @@ use crate::{
|
||||
paras_inherent::DisputedBitfield,
|
||||
scheduler::AssignmentKind,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use primitives::{SignedAvailabilityBitfields, UncheckedSignedAvailabilityBitfields};
|
||||
|
||||
use frame_support::assert_noop;
|
||||
use keyring::Sr25519Keyring;
|
||||
use parity_scale_codec::DecodeAll;
|
||||
use primitives::{
|
||||
BlockNumber, CandidateCommitments, CandidateDescriptor, CollatorId,
|
||||
CompactStatement as Statement, Hash, SignedAvailabilityBitfield, SignedStatement,
|
||||
UncheckedSignedAvailabilityBitfield, ValidationCode, ValidatorId, ValidityAttestation,
|
||||
PARACHAIN_KEY_TYPE_ID,
|
||||
ValidationCode, ValidatorId, ValidityAttestation, PARACHAIN_KEY_TYPE_ID,
|
||||
};
|
||||
use sc_keystore::LocalKeystore;
|
||||
use sp_keystore::{Keystore, KeystorePtr};
|
||||
use std::sync::Arc;
|
||||
use test_helpers::{
|
||||
dummy_candidate_receipt, dummy_collator, dummy_collator_signature, dummy_hash,
|
||||
dummy_validation_code,
|
||||
};
|
||||
use test_helpers::{dummy_collator, dummy_collator_signature, dummy_validation_code};
|
||||
|
||||
fn default_config() -> HostConfiguration<BlockNumber> {
|
||||
let mut config = HostConfiguration::default();
|
||||
@@ -313,6 +310,41 @@ pub(crate) fn make_vdata_hash(para_id: ParaId) -> Option<Hash> {
|
||||
Some(persisted_validation_data.hash())
|
||||
}
|
||||
|
||||
/// Wrapper around `sanitize_bitfields` with less parameters.
|
||||
fn simple_sanitize_bitfields(
|
||||
unchecked_bitfields: UncheckedSignedAvailabilityBitfields,
|
||||
disputed_bitfield: DisputedBitfield,
|
||||
expected_bits: usize,
|
||||
) -> SignedAvailabilityBitfields {
|
||||
let parent_hash = frame_system::Pallet::<Test>::parent_hash();
|
||||
let session_index = shared::Pallet::<Test>::session_index();
|
||||
let validators = shared::Pallet::<Test>::active_validator_keys();
|
||||
|
||||
crate::paras_inherent::sanitize_bitfields::<Test>(
|
||||
unchecked_bitfields,
|
||||
disputed_bitfield,
|
||||
expected_bits,
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validators,
|
||||
)
|
||||
}
|
||||
/// Process a set of already sanitized bitfields.
|
||||
pub(crate) fn process_bitfields(
|
||||
expected_bits: usize,
|
||||
signed_bitfields: SignedAvailabilityBitfields,
|
||||
core_lookup: impl Fn(CoreIndex) -> Option<ParaId>,
|
||||
) -> Vec<(CoreIndex, CandidateHash)> {
|
||||
let validators = shared::Pallet::<Test>::active_validator_keys();
|
||||
|
||||
ParaInclusion::update_pending_availability_and_get_freed_cores::<_>(
|
||||
expected_bits,
|
||||
&validators[..],
|
||||
signed_bitfields,
|
||||
core_lookup,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_pending_cleans_up_pending() {
|
||||
let chain_a = ParaId::from(1_u32);
|
||||
@@ -419,26 +451,6 @@ fn bitfield_checks() {
|
||||
_ => panic!("out of bounds for testing"),
|
||||
};
|
||||
|
||||
// mark all candidates as pending availability
|
||||
let set_pending_av = || {
|
||||
for (p_id, _) in paras {
|
||||
let receipt = dummy_candidate_receipt(dummy_hash());
|
||||
PendingAvailability::<Test>::insert(
|
||||
p_id,
|
||||
CandidatePendingAvailability {
|
||||
availability_votes: default_availability_votes(),
|
||||
core: CoreIndex(0),
|
||||
hash: receipt.hash(),
|
||||
descriptor: receipt.descriptor,
|
||||
backers: BitVec::default(),
|
||||
relay_parent_number: BlockNumber::from(0_u32),
|
||||
backed_in_number: BlockNumber::from(0_u32),
|
||||
backing_group: GroupIndex(0),
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// too many bits in bitfield
|
||||
{
|
||||
let mut bare_bitfield = default_bitfield();
|
||||
@@ -451,15 +463,15 @@ fn bitfield_checks() {
|
||||
&signing_context,
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
ParaInclusion::process_bitfields(
|
||||
expected_bits(),
|
||||
vec![signed.into()],
|
||||
DisputedBitfield::zeros(expected_bits()),
|
||||
&core_lookup,
|
||||
FullCheck::Yes,
|
||||
),
|
||||
Err(Error::<Test>::WrongBitfieldSize)
|
||||
let checked_bitfields = simple_sanitize_bitfields(
|
||||
vec![signed.into()],
|
||||
DisputedBitfield::zeros(expected_bits()),
|
||||
expected_bits(),
|
||||
);
|
||||
assert_eq!(
|
||||
checked_bitfields.len(),
|
||||
0,
|
||||
"Bitfield has wrong size, it should have been filtered."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -474,126 +486,16 @@ fn bitfield_checks() {
|
||||
&signing_context,
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
ParaInclusion::process_bitfields(
|
||||
expected_bits() + 1,
|
||||
vec![signed.into()],
|
||||
DisputedBitfield::zeros(expected_bits()),
|
||||
&core_lookup,
|
||||
FullCheck::Yes,
|
||||
),
|
||||
Err(Error::<Test>::WrongBitfieldSize)
|
||||
let checked_bitfields = simple_sanitize_bitfields(
|
||||
vec![signed.into()],
|
||||
DisputedBitfield::zeros(expected_bits()),
|
||||
expected_bits() + 1,
|
||||
);
|
||||
}
|
||||
|
||||
// duplicate.
|
||||
{
|
||||
set_pending_av.clone()();
|
||||
let back_core_0_bitfield = {
|
||||
let mut b = default_bitfield();
|
||||
b.0.set(0, true);
|
||||
b
|
||||
};
|
||||
let signed: UncheckedSignedAvailabilityBitfield = sign_bitfield(
|
||||
&keystore,
|
||||
&validators[0],
|
||||
ValidatorIndex(0),
|
||||
back_core_0_bitfield,
|
||||
&signing_context,
|
||||
)
|
||||
.into();
|
||||
|
||||
assert_eq!(
|
||||
<PendingAvailability<Test>>::get(chain_a)
|
||||
.unwrap()
|
||||
.availability_votes
|
||||
.count_ones(),
|
||||
0
|
||||
checked_bitfields.len(),
|
||||
0,
|
||||
"Bitfield has wrong size, it should have been filtered."
|
||||
);
|
||||
|
||||
// the threshold to free a core is 4 availability votes, but we only expect 1 valid
|
||||
// valid bitfield.
|
||||
assert_matches!(
|
||||
ParaInclusion::process_bitfields(
|
||||
expected_bits(),
|
||||
vec![signed.clone(), signed],
|
||||
DisputedBitfield::zeros(expected_bits()),
|
||||
&core_lookup,
|
||||
FullCheck::Yes,
|
||||
),
|
||||
Err(Error::<Test>::UnsortedOrDuplicateValidatorIndices)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
<PendingAvailability<Test>>::get(chain_a)
|
||||
.unwrap()
|
||||
.availability_votes
|
||||
.count_ones(),
|
||||
0
|
||||
);
|
||||
|
||||
// clean up
|
||||
#[allow(deprecated)]
|
||||
PendingAvailability::<Test>::remove_all(None);
|
||||
}
|
||||
|
||||
// out of order.
|
||||
{
|
||||
set_pending_av.clone()();
|
||||
let back_core_0_bitfield = {
|
||||
let mut b = default_bitfield();
|
||||
b.0.set(0, true);
|
||||
b
|
||||
};
|
||||
let signed_0 = sign_bitfield(
|
||||
&keystore,
|
||||
&validators[0],
|
||||
ValidatorIndex(0),
|
||||
back_core_0_bitfield.clone(),
|
||||
&signing_context,
|
||||
)
|
||||
.into();
|
||||
|
||||
let signed_1 = sign_bitfield(
|
||||
&keystore,
|
||||
&validators[1],
|
||||
ValidatorIndex(1),
|
||||
back_core_0_bitfield,
|
||||
&signing_context,
|
||||
)
|
||||
.into();
|
||||
|
||||
assert_eq!(
|
||||
<PendingAvailability<Test>>::get(chain_a)
|
||||
.unwrap()
|
||||
.availability_votes
|
||||
.count_ones(),
|
||||
0
|
||||
);
|
||||
|
||||
// the threshold to free a core is 4 availability votes, but we only expect 1 valid
|
||||
// valid bitfield because `signed_0` will get skipped for being out of order.
|
||||
assert_matches!(
|
||||
ParaInclusion::process_bitfields(
|
||||
expected_bits(),
|
||||
vec![signed_1, signed_0],
|
||||
DisputedBitfield::zeros(expected_bits()),
|
||||
&core_lookup,
|
||||
FullCheck::Yes,
|
||||
),
|
||||
Err(Error::<Test>::UnsortedOrDuplicateValidatorIndices)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
<PendingAvailability<Test>>::get(chain_a)
|
||||
.unwrap()
|
||||
.availability_votes
|
||||
.count_ones(),
|
||||
0
|
||||
);
|
||||
|
||||
#[allow(deprecated)]
|
||||
PendingAvailability::<Test>::remove_all(None);
|
||||
}
|
||||
|
||||
// non-pending bit set.
|
||||
@@ -608,13 +510,17 @@ fn bitfield_checks() {
|
||||
&signing_context,
|
||||
);
|
||||
|
||||
assert_matches!(ParaInclusion::process_bitfields(
|
||||
expected_bits(),
|
||||
// the threshold to free a core is 4 availability votes, but we only expect 1 valid
|
||||
// valid bitfield because `signed_0` will get skipped for being out of order.
|
||||
let checked_bitfields = simple_sanitize_bitfields(
|
||||
vec![signed.into()],
|
||||
DisputedBitfield::zeros(expected_bits()),
|
||||
&core_lookup,
|
||||
FullCheck::Yes,
|
||||
), Ok(x) => { assert!(x.is_empty())});
|
||||
expected_bits(),
|
||||
);
|
||||
assert_eq!(checked_bitfields.len(), 1, "No bitfields should have been filtered!");
|
||||
|
||||
let x = process_bitfields(expected_bits(), checked_bitfields, core_lookup);
|
||||
assert!(x.is_empty(), "No core should be freed.");
|
||||
}
|
||||
|
||||
// empty bitfield signed: always ok, but kind of useless.
|
||||
@@ -627,14 +533,15 @@ fn bitfield_checks() {
|
||||
bare_bitfield,
|
||||
&signing_context,
|
||||
);
|
||||
|
||||
assert_matches!(ParaInclusion::process_bitfields(
|
||||
expected_bits(),
|
||||
let checked_bitfields = simple_sanitize_bitfields(
|
||||
vec![signed.into()],
|
||||
DisputedBitfield::zeros(expected_bits()),
|
||||
&core_lookup,
|
||||
FullCheck::Yes,
|
||||
), Ok(x) => { assert!(x.is_empty())});
|
||||
expected_bits(),
|
||||
);
|
||||
assert_eq!(checked_bitfields.len(), 1, "No bitfields should have been filtered!");
|
||||
|
||||
let x = process_bitfields(expected_bits(), checked_bitfields, core_lookup);
|
||||
assert!(x.is_empty(), "No core should be freed.");
|
||||
}
|
||||
|
||||
// bitfield signed with pending bit signed.
|
||||
@@ -668,13 +575,15 @@ fn bitfield_checks() {
|
||||
&signing_context,
|
||||
);
|
||||
|
||||
assert_matches!(ParaInclusion::process_bitfields(
|
||||
expected_bits(),
|
||||
let checked_bitfields = simple_sanitize_bitfields(
|
||||
vec![signed.into()],
|
||||
DisputedBitfield::zeros(expected_bits()),
|
||||
&core_lookup,
|
||||
FullCheck::Yes,
|
||||
), Ok(v) => { assert!(v.is_empty())} );
|
||||
expected_bits(),
|
||||
);
|
||||
assert_eq!(checked_bitfields.len(), 1, "No bitfields should have been filtered!");
|
||||
|
||||
let x = process_bitfields(expected_bits(), checked_bitfields, core_lookup);
|
||||
assert!(x.is_empty(), "No core should be freed.");
|
||||
|
||||
<PendingAvailability<Test>>::remove(chain_a);
|
||||
PendingAvailabilityCommitments::<Test>::remove(chain_a);
|
||||
@@ -710,14 +619,16 @@ fn bitfield_checks() {
|
||||
&signing_context,
|
||||
);
|
||||
|
||||
// no core is freed
|
||||
assert_matches!(ParaInclusion::process_bitfields(
|
||||
expected_bits(),
|
||||
let checked_bitfields = simple_sanitize_bitfields(
|
||||
vec![signed.into()],
|
||||
DisputedBitfield::zeros(expected_bits()),
|
||||
&core_lookup,
|
||||
FullCheck::Yes,
|
||||
), Ok(v) => { assert!(v.is_empty()) });
|
||||
expected_bits(),
|
||||
);
|
||||
assert_eq!(checked_bitfields.len(), 1, "No bitfields should have been filtered!");
|
||||
|
||||
let x = process_bitfields(expected_bits(), checked_bitfields, core_lookup);
|
||||
// no core is freed
|
||||
assert!(x.is_empty(), "No core should be freed.");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -862,21 +773,19 @@ fn supermajority_bitfields_trigger_availability() {
|
||||
.into(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let old_len = signed_bitfields.len();
|
||||
let checked_bitfields = simple_sanitize_bitfields(
|
||||
signed_bitfields,
|
||||
DisputedBitfield::zeros(expected_bits()),
|
||||
expected_bits(),
|
||||
);
|
||||
assert_eq!(checked_bitfields.len(), old_len, "No bitfields should have been filtered!");
|
||||
|
||||
// only chain A's core is freed.
|
||||
assert_matches!(
|
||||
ParaInclusion::process_bitfields(
|
||||
expected_bits(),
|
||||
signed_bitfields,
|
||||
DisputedBitfield::zeros(expected_bits()),
|
||||
&core_lookup,
|
||||
FullCheck::Yes,
|
||||
),
|
||||
Ok(v) => {
|
||||
assert_eq!(vec![(CoreIndex(0), candidate_a.hash())], v);
|
||||
}
|
||||
);
|
||||
let v = process_bitfields(expected_bits(), checked_bitfields, core_lookup);
|
||||
assert_eq!(vec![(CoreIndex(0), candidate_a.hash())], v);
|
||||
|
||||
// chain A had 4 signing off, which is >= threshold.
|
||||
// chain B has 3 signing off, which is < threshold.
|
||||
|
||||
@@ -20,22 +20,20 @@ use polkadot_runtime_metrics::{Counter, CounterVec, Histogram};
|
||||
use primitives::metric_definitions::{
|
||||
PARACHAIN_CREATE_INHERENT_BITFIELDS_SIGNATURE_CHECKS,
|
||||
PARACHAIN_INHERENT_DATA_BITFIELDS_PROCESSED, PARACHAIN_INHERENT_DATA_CANDIDATES_PROCESSED,
|
||||
PARACHAIN_INHERENT_DATA_DISPUTE_SETS_INCLUDED, PARACHAIN_INHERENT_DATA_DISPUTE_SETS_PROCESSED,
|
||||
PARACHAIN_INHERENT_DATA_WEIGHT, PARACHAIN_VERIFY_DISPUTE_SIGNATURE,
|
||||
PARACHAIN_INHERENT_DATA_DISPUTE_SETS_PROCESSED, PARACHAIN_INHERENT_DATA_WEIGHT,
|
||||
PARACHAIN_VERIFY_DISPUTE_SIGNATURE,
|
||||
};
|
||||
|
||||
pub struct Metrics {
|
||||
/// Samples inherent data weight.
|
||||
inherent_data_weight: CounterVec,
|
||||
/// Counts how many inherent bitfields processed in `enter_inner`.
|
||||
/// Counts how many inherent bitfields processed in `process_inherent_data`.
|
||||
bitfields_processed: Counter,
|
||||
/// Counts how many parachain candidates processed in `enter_inner`.
|
||||
/// Counts how many parachain candidates processed in `process_inherent_data`.
|
||||
candidates_processed: CounterVec,
|
||||
/// Counts dispute statements sets processed in `enter_inner`.
|
||||
/// Counts dispute statements sets processed in `process_inherent_data`.
|
||||
dispute_sets_processed: CounterVec,
|
||||
/// Counts dispute statements sets included in `enter_inner`.
|
||||
disputes_included: Counter,
|
||||
/// Counts bitfield signature checks in `enter_inner`.
|
||||
/// Counts bitfield signature checks in `process_inherent_data`.
|
||||
bitfields_signature_checks: CounterVec,
|
||||
|
||||
/// Histogram with the time spent checking a validator signature of a dispute statement
|
||||
@@ -68,22 +66,17 @@ impl Metrics {
|
||||
self.candidates_processed.with_label_values(&["sanitized"]).inc_by(value);
|
||||
}
|
||||
|
||||
/// Increment the total number of parachain candidates received in `enter_inner`.
|
||||
/// Increment the total number of parachain candidates received in `process_inherent_data`.
|
||||
pub fn on_candidates_processed_total(&self, value: u64) {
|
||||
self.candidates_processed.with_label_values(&["total"]).inc_by(value);
|
||||
}
|
||||
|
||||
/// Sample the relay chain freeze events causing runtime to not process candidates in
|
||||
/// `enter_inner`.
|
||||
/// `process_inherent_data`.
|
||||
pub fn on_relay_chain_freeze(&self) {
|
||||
self.dispute_sets_processed.with_label_values(&["frozen"]).inc();
|
||||
}
|
||||
|
||||
/// Sample the number of dispute sets processed from the current session.
|
||||
pub fn on_current_session_disputes_processed(&self, value: u64) {
|
||||
self.dispute_sets_processed.with_label_values(&["current"]).inc_by(value);
|
||||
}
|
||||
|
||||
/// Increment the number of disputes that have concluded as invalid.
|
||||
pub fn on_disputes_concluded_invalid(&self, value: u64) {
|
||||
self.dispute_sets_processed
|
||||
@@ -96,10 +89,6 @@ impl Metrics {
|
||||
self.dispute_sets_processed.with_label_values(&["imported"]).inc_by(value);
|
||||
}
|
||||
|
||||
pub fn on_disputes_included(&self, value: u64) {
|
||||
self.disputes_included.inc_by(value);
|
||||
}
|
||||
|
||||
pub fn on_valid_bitfield_signature(&self) {
|
||||
self.bitfields_signature_checks.with_label_values(&["valid"]).inc_by(1);
|
||||
}
|
||||
@@ -118,7 +107,6 @@ pub const METRICS: Metrics = Metrics {
|
||||
bitfields_processed: Counter::new(PARACHAIN_INHERENT_DATA_BITFIELDS_PROCESSED),
|
||||
candidates_processed: CounterVec::new(PARACHAIN_INHERENT_DATA_CANDIDATES_PROCESSED),
|
||||
dispute_sets_processed: CounterVec::new(PARACHAIN_INHERENT_DATA_DISPUTE_SETS_PROCESSED),
|
||||
disputes_included: Counter::new(PARACHAIN_INHERENT_DATA_DISPUTE_SETS_INCLUDED),
|
||||
bitfields_signature_checks: CounterVec::new(
|
||||
PARACHAIN_CREATE_INHERENT_BITFIELDS_SIGNATURE_CHECKS,
|
||||
),
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
use crate::{
|
||||
configuration,
|
||||
disputes::{DisputesHandler, VerifyDisputeSignatures},
|
||||
disputes::DisputesHandler,
|
||||
inclusion,
|
||||
inclusion::{CandidateCheckContext, FullCheck},
|
||||
inclusion::CandidateCheckContext,
|
||||
initializer,
|
||||
metrics::METRICS,
|
||||
scheduler::{self, CoreAssignment, FreedReason},
|
||||
@@ -33,6 +33,7 @@ use crate::{
|
||||
};
|
||||
use bitvec::prelude::BitVec;
|
||||
use frame_support::{
|
||||
dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo},
|
||||
inherent::{InherentData, InherentIdentifier, MakeFatalError, ProvideInherent},
|
||||
pallet_prelude::*,
|
||||
traits::Randomness,
|
||||
@@ -43,7 +44,7 @@ use primitives::{
|
||||
BackedCandidate, CandidateHash, CandidateReceipt, CheckedDisputeStatementSet,
|
||||
CheckedMultiDisputeStatementSet, CoreIndex, DisputeStatementSet,
|
||||
InherentData as ParachainsInherentData, MultiDisputeStatementSet, ScrapedOnChainVotes,
|
||||
SessionIndex, SigningContext, UncheckedSignedAvailabilityBitfield,
|
||||
SessionIndex, SignedAvailabilityBitfields, SigningContext, UncheckedSignedAvailabilityBitfield,
|
||||
UncheckedSignedAvailabilityBitfields, ValidatorId, ValidatorIndex, ValidityAttestation,
|
||||
PARACHAINS_INHERENT_IDENTIFIER,
|
||||
};
|
||||
@@ -209,32 +210,6 @@ pub mod pallet {
|
||||
|
||||
fn create_inherent(data: &InherentData) -> Option<Self::Call> {
|
||||
let inherent_data = Self::create_inherent_inner(data)?;
|
||||
// Sanity check: session changes can invalidate an inherent,
|
||||
// and we _really_ don't want that to happen.
|
||||
// See <https://github.com/paritytech/polkadot/issues/1327>
|
||||
|
||||
// Calling `Self::enter` here is a safe-guard, to avoid any discrepancy between on-chain checks
|
||||
// (`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.
|
||||
|
||||
let inherent_data = match Self::enter_inner(inherent_data.clone(), FullCheck::Skip) {
|
||||
Ok(_) => inherent_data,
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"dropping paras inherent data because they produced \
|
||||
an invalid paras inherent: {:?}",
|
||||
err.error,
|
||||
);
|
||||
|
||||
ParachainsInherentData {
|
||||
bitfields: Vec::new(),
|
||||
backed_candidates: Vec::new(),
|
||||
disputes: Vec::new(),
|
||||
parent_header: inherent_data.parent_header,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Some(Call::enter { data: inherent_data })
|
||||
}
|
||||
@@ -294,259 +269,16 @@ pub mod pallet {
|
||||
ensure!(!Included::<T>::exists(), Error::<T>::TooManyInclusionInherents);
|
||||
Included::<T>::set(Some(()));
|
||||
|
||||
Self::enter_inner(data, FullCheck::Yes)
|
||||
Self::process_inherent_data(data).map(|(_processed, post_info)| post_info)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
pub(crate) fn enter_inner(
|
||||
data: ParachainsInherentData<T::Header>,
|
||||
full_check: FullCheck,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let ParachainsInherentData {
|
||||
bitfields: mut signed_bitfields,
|
||||
mut backed_candidates,
|
||||
parent_header,
|
||||
mut disputes,
|
||||
} = data;
|
||||
#[cfg(feature = "runtime-metrics")]
|
||||
sp_io::init_tracing();
|
||||
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"[enter_inner] parent_header={:?} bitfields.len(): {}, backed_candidates.len(): {}, disputes.len(): {}",
|
||||
parent_header.hash(),
|
||||
signed_bitfields.len(),
|
||||
backed_candidates.len(),
|
||||
disputes.len()
|
||||
);
|
||||
|
||||
// Check that the submitted parent header indeed corresponds to the previous block hash.
|
||||
let parent_hash = <frame_system::Pallet<T>>::parent_hash();
|
||||
ensure!(
|
||||
parent_header.hash().as_ref() == parent_hash.as_ref(),
|
||||
Error::<T>::InvalidParentHeader,
|
||||
);
|
||||
|
||||
let now = <frame_system::Pallet<T>>::block_number();
|
||||
|
||||
let mut candidates_weight = backed_candidates_weight::<T>(&backed_candidates);
|
||||
let mut bitfields_weight = signed_bitfields_weight::<T>(signed_bitfields.len());
|
||||
let disputes_weight = multi_dispute_statement_sets_weight::<T, _, _>(&disputes);
|
||||
|
||||
let current_session = <shared::Pallet<T>>::session_index();
|
||||
|
||||
let max_block_weight = <T as frame_system::Config>::BlockWeights::get().max_block;
|
||||
|
||||
METRICS
|
||||
.on_before_filter((candidates_weight + bitfields_weight + disputes_weight).ref_time());
|
||||
|
||||
T::DisputesHandler::assure_deduplicated_and_sorted(&mut disputes)
|
||||
.map_err(|_e| Error::<T>::DisputeStatementsUnsortedOrDuplicates)?;
|
||||
|
||||
let (checked_disputes, total_consumed_weight) = {
|
||||
// Obtain config params..
|
||||
let config = <configuration::Pallet<T>>::config();
|
||||
let post_conclusion_acceptance_period =
|
||||
config.dispute_post_conclusion_acceptance_period;
|
||||
|
||||
let verify_dispute_sigs = if let FullCheck::Yes = full_check {
|
||||
VerifyDisputeSignatures::Yes
|
||||
} else {
|
||||
VerifyDisputeSignatures::Skip
|
||||
};
|
||||
|
||||
// .. and prepare a helper closure.
|
||||
let dispute_set_validity_check = move |set| {
|
||||
T::DisputesHandler::filter_dispute_data(
|
||||
set,
|
||||
post_conclusion_acceptance_period,
|
||||
verify_dispute_sigs,
|
||||
)
|
||||
};
|
||||
|
||||
// In case of an overweight block, consume up to the entire block weight
|
||||
// in disputes, since we will never process anything else, but invalidate
|
||||
// the block. It's still reasonable to protect against a massive amount of disputes.
|
||||
if candidates_weight
|
||||
.saturating_add(bitfields_weight)
|
||||
.saturating_add(disputes_weight)
|
||||
.any_gt(max_block_weight)
|
||||
{
|
||||
log::warn!("Overweight para inherent data reached the runtime {:?}", parent_hash);
|
||||
backed_candidates.clear();
|
||||
candidates_weight = Weight::zero();
|
||||
signed_bitfields.clear();
|
||||
bitfields_weight = Weight::zero();
|
||||
}
|
||||
|
||||
let entropy = compute_entropy::<T>(parent_hash);
|
||||
let mut rng = rand_chacha::ChaChaRng::from_seed(entropy.into());
|
||||
|
||||
let (checked_disputes, checked_disputes_weight) = limit_and_sanitize_disputes::<T, _>(
|
||||
disputes,
|
||||
&dispute_set_validity_check,
|
||||
max_block_weight,
|
||||
&mut rng,
|
||||
);
|
||||
(
|
||||
checked_disputes,
|
||||
checked_disputes_weight
|
||||
.saturating_add(candidates_weight)
|
||||
.saturating_add(bitfields_weight),
|
||||
)
|
||||
};
|
||||
|
||||
let expected_bits = <scheduler::Pallet<T>>::availability_cores().len();
|
||||
|
||||
// Handle disputes logic.
|
||||
let disputed_bitfield = {
|
||||
let new_current_dispute_sets: Vec<_> = checked_disputes
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.filter(|s| s.session == current_session)
|
||||
.map(|s| (s.session, s.candidate_hash))
|
||||
.collect();
|
||||
|
||||
// Note that `process_checked_multi_dispute_data` will iterate and import each
|
||||
// dispute; so the input here must be reasonably bounded,
|
||||
// which is guaranteed by the checks and weight limitation above.
|
||||
let _ = T::DisputesHandler::process_checked_multi_dispute_data(&checked_disputes)?;
|
||||
METRICS.on_disputes_imported(checked_disputes.len() as u64);
|
||||
|
||||
if T::DisputesHandler::is_frozen() {
|
||||
// Relay chain freeze, at this point we will not include any parachain blocks.
|
||||
METRICS.on_relay_chain_freeze();
|
||||
|
||||
// The relay chain we are currently on is invalid. Proceed no further on parachains.
|
||||
return Ok(Some(total_consumed_weight).into())
|
||||
}
|
||||
|
||||
// Process the dispute sets of the current session.
|
||||
METRICS.on_current_session_disputes_processed(new_current_dispute_sets.len() as u64);
|
||||
|
||||
let mut freed_disputed = if !new_current_dispute_sets.is_empty() {
|
||||
let concluded_invalid_disputes = new_current_dispute_sets
|
||||
.iter()
|
||||
.filter(|(session, candidate)| {
|
||||
T::DisputesHandler::concluded_invalid(*session, *candidate)
|
||||
})
|
||||
.map(|(_, candidate)| *candidate)
|
||||
.collect::<BTreeSet<CandidateHash>>();
|
||||
|
||||
// Count invalid dispute sets.
|
||||
METRICS.on_disputes_concluded_invalid(concluded_invalid_disputes.len() as u64);
|
||||
|
||||
let freed_disputed: Vec<_> =
|
||||
<inclusion::Pallet<T>>::collect_disputed(&concluded_invalid_disputes)
|
||||
.into_iter()
|
||||
.map(|core| (core, FreedReason::Concluded))
|
||||
.collect();
|
||||
|
||||
freed_disputed
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// Create a bit index from the set of core indices where each index corresponds to
|
||||
// a core index that was freed due to a dispute.
|
||||
//
|
||||
// I.e. 010100 would indicate, the candidates on Core 1 and 3 would be disputed.
|
||||
let disputed_bitfield = create_disputed_bitfield(
|
||||
expected_bits,
|
||||
freed_disputed.iter().map(|(core_index, _)| core_index),
|
||||
);
|
||||
|
||||
if !freed_disputed.is_empty() {
|
||||
// unstable sort is fine, because core indices are unique
|
||||
// i.e. the same candidate can't occupy 2 cores at once.
|
||||
freed_disputed.sort_unstable_by_key(|pair| pair.0); // sort by core index
|
||||
<scheduler::Pallet<T>>::free_cores(freed_disputed);
|
||||
}
|
||||
|
||||
disputed_bitfield
|
||||
};
|
||||
|
||||
METRICS.on_bitfields_processed(signed_bitfields.len() as u64);
|
||||
|
||||
// Process new availability bitfields, yielding any availability cores whose
|
||||
// work has now concluded.
|
||||
let freed_concluded = <inclusion::Pallet<T>>::process_bitfields(
|
||||
expected_bits,
|
||||
signed_bitfields,
|
||||
disputed_bitfield,
|
||||
<scheduler::Pallet<T>>::core_para,
|
||||
full_check,
|
||||
)?;
|
||||
// any error in the previous function will cause an invalid block and not include
|
||||
// the `DisputeState` to be written to the storage, hence this is ok.
|
||||
set_scrapable_on_chain_disputes::<T>(current_session, checked_disputes.clone());
|
||||
|
||||
// Inform the disputes module of all included candidates.
|
||||
for (_, candidate_hash) in &freed_concluded {
|
||||
T::DisputesHandler::note_included(current_session, *candidate_hash, now);
|
||||
}
|
||||
|
||||
METRICS.on_candidates_included(freed_concluded.len() as u64);
|
||||
let freed = collect_all_freed_cores::<T, _>(freed_concluded.iter().cloned());
|
||||
|
||||
<scheduler::Pallet<T>>::clear();
|
||||
<scheduler::Pallet<T>>::schedule(freed, now);
|
||||
|
||||
METRICS.on_candidates_processed_total(backed_candidates.len() as u64);
|
||||
|
||||
let scheduled = <scheduler::Pallet<T>>::scheduled();
|
||||
assure_sanity_backed_candidates::<T, _>(
|
||||
parent_hash,
|
||||
&backed_candidates,
|
||||
move |_candidate_index: usize, backed_candidate: &BackedCandidate<T::Hash>| -> bool {
|
||||
<T>::DisputesHandler::concluded_invalid(current_session, backed_candidate.hash())
|
||||
// `fn process_candidates` does the verification checks
|
||||
},
|
||||
&scheduled[..],
|
||||
)?;
|
||||
|
||||
METRICS.on_candidates_sanitized(backed_candidates.len() as u64);
|
||||
|
||||
// Process backed candidates according to scheduled cores.
|
||||
let parent_storage_root = *parent_header.state_root();
|
||||
let inclusion::ProcessedCandidates::<<T::Header as HeaderT>::Hash> {
|
||||
core_indices: occupied,
|
||||
candidate_receipt_with_backing_validator_indices,
|
||||
} = <inclusion::Pallet<T>>::process_candidates(
|
||||
parent_storage_root,
|
||||
backed_candidates,
|
||||
scheduled,
|
||||
<scheduler::Pallet<T>>::group_validators,
|
||||
)?;
|
||||
|
||||
METRICS.on_disputes_included(checked_disputes.len() as u64);
|
||||
|
||||
set_scrapable_on_chain_backings::<T>(
|
||||
current_session,
|
||||
candidate_receipt_with_backing_validator_indices,
|
||||
);
|
||||
|
||||
// Note which of the scheduled cores were actually occupied by a backed candidate.
|
||||
<scheduler::Pallet<T>>::occupied(&occupied);
|
||||
|
||||
METRICS.on_after_filter(total_consumed_weight.ref_time());
|
||||
|
||||
Ok(Some(total_consumed_weight).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Create the `ParachainsInherentData` that gets passed to [`Self::enter`] in [`Self::create_inherent`].
|
||||
/// This code is pulled out of [`Self::create_inherent`] so it can be unit tested.
|
||||
fn create_inherent_inner(data: &InherentData) -> Option<ParachainsInherentData<T::Header>> {
|
||||
let ParachainsInherentData::<T::Header> {
|
||||
bitfields,
|
||||
backed_candidates,
|
||||
mut disputes,
|
||||
parent_header,
|
||||
} = match data.get_data(&Self::INHERENT_IDENTIFIER) {
|
||||
let parachains_inherent_data = match data.get_data(&Self::INHERENT_IDENTIFIER) {
|
||||
Ok(Some(d)) => d,
|
||||
Ok(None) => return None,
|
||||
Err(_) => {
|
||||
@@ -554,10 +286,45 @@ impl<T: Config> Pallet<T> {
|
||||
return None
|
||||
},
|
||||
};
|
||||
match Self::process_inherent_data(parachains_inherent_data) {
|
||||
Ok((processed, _)) => Some(processed),
|
||||
Err(err) => {
|
||||
log::warn!(target: LOG_TARGET, "Processing inherent data failed: {:?}", err);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Process inherent data.
|
||||
///
|
||||
/// The given inherent data is processed and state is altered accordingly. If any data could
|
||||
/// not be applied (inconsitencies, weight limit, ...) it is removed.
|
||||
///
|
||||
/// This function can both be called on block creation in `create_inherent` and on block import
|
||||
/// in `enter`. The mutation of `data` is only useful in the `create_inherent` case as it
|
||||
/// avoids overweight blocks for example.
|
||||
///
|
||||
/// Returns: Result containing processed inherent data and weight, the processed inherent would
|
||||
/// consume.
|
||||
fn process_inherent_data(
|
||||
data: ParachainsInherentData<T::Header>,
|
||||
) -> sp_std::result::Result<
|
||||
(ParachainsInherentData<T::Header>, PostDispatchInfo),
|
||||
DispatchErrorWithPostInfo,
|
||||
> {
|
||||
#[cfg(feature = "runtime-metrics")]
|
||||
sp_io::init_tracing();
|
||||
|
||||
let ParachainsInherentData {
|
||||
mut bitfields,
|
||||
mut backed_candidates,
|
||||
parent_header,
|
||||
mut disputes,
|
||||
} = data;
|
||||
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"[create_inherent_inner] bitfields.len(): {}, backed_candidates.len(): {}, disputes.len() {}",
|
||||
"[process_inherent_data] bitfields.len(): {}, backed_candidates.len(): {}, disputes.len() {}",
|
||||
bitfields.len(),
|
||||
backed_candidates.len(),
|
||||
disputes.len()
|
||||
@@ -565,13 +332,19 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
let parent_hash = <frame_system::Pallet<T>>::parent_hash();
|
||||
|
||||
if parent_hash != parent_header.hash() {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"ParachainsInherentData references a different parent header hash than frame"
|
||||
);
|
||||
return None
|
||||
}
|
||||
ensure!(
|
||||
parent_header.hash().as_ref() == parent_hash.as_ref(),
|
||||
Error::<T>::InvalidParentHeader,
|
||||
);
|
||||
|
||||
let now = <frame_system::Pallet<T>>::block_number();
|
||||
|
||||
let candidates_weight = backed_candidates_weight::<T>(&backed_candidates);
|
||||
let bitfields_weight = signed_bitfields_weight::<T>(bitfields.len());
|
||||
let disputes_weight = multi_dispute_statement_sets_weight::<T, _, _>(&disputes);
|
||||
|
||||
METRICS
|
||||
.on_before_filter((candidates_weight + bitfields_weight + disputes_weight).ref_time());
|
||||
|
||||
let current_session = <shared::Pallet<T>>::session_index();
|
||||
let expected_bits = <scheduler::Pallet<T>>::availability_cores().len();
|
||||
@@ -582,181 +355,208 @@ impl<T: Config> Pallet<T> {
|
||||
let mut rng = rand_chacha::ChaChaRng::from_seed(entropy.into());
|
||||
|
||||
// Filter out duplicates and continue.
|
||||
if let Err(_) = T::DisputesHandler::deduplicate_and_sort_dispute_data(&mut disputes) {
|
||||
if let Err(()) = T::DisputesHandler::deduplicate_and_sort_dispute_data(&mut disputes) {
|
||||
log::debug!(target: LOG_TARGET, "Found duplicate statement sets, retaining the first");
|
||||
}
|
||||
|
||||
let config = <configuration::Pallet<T>>::config();
|
||||
let post_conclusion_acceptance_period = config.dispute_post_conclusion_acceptance_period;
|
||||
|
||||
// TODO: Better if we can convert this to `with_transactional` and handle an error if
|
||||
// too many transactional layers are spawned.
|
||||
let (
|
||||
mut backed_candidates,
|
||||
mut bitfields,
|
||||
checked_disputes_sets,
|
||||
checked_disputes_sets_consumed_weight,
|
||||
) = frame_support::storage::with_transaction_unchecked(|| {
|
||||
let dispute_statement_set_valid = move |set: DisputeStatementSet| {
|
||||
T::DisputesHandler::filter_dispute_data(
|
||||
set,
|
||||
post_conclusion_acceptance_period,
|
||||
// `DisputeCoordinator` on the node side only forwards
|
||||
// valid dispute statement sets and hence this does not
|
||||
// need to be checked.
|
||||
VerifyDisputeSignatures::Skip,
|
||||
)
|
||||
};
|
||||
let dispute_statement_set_valid = move |set: DisputeStatementSet| {
|
||||
T::DisputesHandler::filter_dispute_data(set, post_conclusion_acceptance_period)
|
||||
};
|
||||
|
||||
// Limit the disputes first, since the following statements depend on the votes include here.
|
||||
let (checked_disputes_sets, checked_disputes_sets_consumed_weight) =
|
||||
limit_and_sanitize_disputes::<T, _>(
|
||||
disputes,
|
||||
dispute_statement_set_valid,
|
||||
max_block_weight,
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
// we don't care about fresh or not disputes
|
||||
// this writes them to storage, so let's query it via those means
|
||||
// if this fails for whatever reason, that's ok
|
||||
let _ = T::DisputesHandler::process_checked_multi_dispute_data(&checked_disputes_sets)
|
||||
.map_err(|e| {
|
||||
log::warn!(target: LOG_TARGET, "MultiDisputesData failed to update: {:?}", e);
|
||||
e
|
||||
});
|
||||
|
||||
// Contains the disputes that are concluded in the current session only,
|
||||
// since these are the only ones that are relevant for the occupied cores
|
||||
// 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 = checked_disputes_sets
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.filter(|dss| dss.session == current_session)
|
||||
.map(|dss| (dss.session, dss.candidate_hash))
|
||||
.filter(|(session, candidate)| {
|
||||
<T>::DisputesHandler::concluded_invalid(*session, *candidate)
|
||||
})
|
||||
.map(|(_session, candidate)| candidate)
|
||||
.collect::<BTreeSet<CandidateHash>>();
|
||||
|
||||
// All concluded invalid disputes, that are relevant for the set of candidates
|
||||
// the inherent provided.
|
||||
let concluded_invalid_disputes = backed_candidates
|
||||
.iter()
|
||||
.map(|backed_candidate| backed_candidate.hash())
|
||||
.filter(|candidate| {
|
||||
<T>::DisputesHandler::concluded_invalid(current_session, *candidate)
|
||||
})
|
||||
.collect::<BTreeSet<CandidateHash>>();
|
||||
|
||||
let mut freed_disputed: Vec<_> =
|
||||
<inclusion::Pallet<T>>::collect_disputed(¤t_concluded_invalid_disputes)
|
||||
.into_iter()
|
||||
.map(|core| (core, FreedReason::Concluded))
|
||||
.collect();
|
||||
|
||||
let disputed_bitfield =
|
||||
create_disputed_bitfield(expected_bits, freed_disputed.iter().map(|(x, _)| x));
|
||||
|
||||
if !freed_disputed.is_empty() {
|
||||
// unstable sort is fine, because core indices are unique
|
||||
// i.e. the same candidate can't occupy 2 cores at once.
|
||||
freed_disputed.sort_unstable_by_key(|pair| pair.0); // sort by core index
|
||||
<scheduler::Pallet<T>>::free_cores(freed_disputed.clone());
|
||||
}
|
||||
|
||||
// The following 3 calls are equiv to a call to `process_bitfields`
|
||||
// but we can retain access to `bitfields`.
|
||||
let bitfields = sanitize_bitfields::<T>(
|
||||
bitfields,
|
||||
disputed_bitfield,
|
||||
expected_bits,
|
||||
parent_hash,
|
||||
current_session,
|
||||
&validator_public[..],
|
||||
FullCheck::Yes,
|
||||
// Limit the disputes first, since the following statements depend on the votes include here.
|
||||
let (checked_disputes_sets, checked_disputes_sets_consumed_weight) =
|
||||
limit_and_sanitize_disputes::<T, _>(
|
||||
disputes,
|
||||
dispute_statement_set_valid,
|
||||
max_block_weight,
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
let freed_concluded =
|
||||
<inclusion::Pallet<T>>::update_pending_availability_and_get_freed_cores::<_>(
|
||||
expected_bits,
|
||||
&validator_public[..],
|
||||
bitfields.clone(),
|
||||
<scheduler::Pallet<T>>::core_para,
|
||||
false,
|
||||
);
|
||||
|
||||
let freed = collect_all_freed_cores::<T, _>(freed_concluded.iter().cloned());
|
||||
|
||||
<scheduler::Pallet<T>>::clear();
|
||||
let now = <frame_system::Pallet<T>>::block_number();
|
||||
<scheduler::Pallet<T>>::schedule(freed, now);
|
||||
|
||||
let scheduled = <scheduler::Pallet<T>>::scheduled();
|
||||
|
||||
let relay_parent_number = now - One::one();
|
||||
let parent_storage_root = *parent_header.state_root();
|
||||
|
||||
let check_ctx = CandidateCheckContext::<T>::new(now, relay_parent_number);
|
||||
let backed_candidates = sanitize_backed_candidates::<T, _>(
|
||||
parent_hash,
|
||||
backed_candidates,
|
||||
move |candidate_idx: usize,
|
||||
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, parent_storage_root, candidate_idx, backed_candidate)
|
||||
.is_err()
|
||||
},
|
||||
&scheduled[..],
|
||||
);
|
||||
|
||||
frame_support::storage::TransactionOutcome::Rollback((
|
||||
// filtered backed candidates
|
||||
backed_candidates,
|
||||
// filtered bitfields
|
||||
bitfields,
|
||||
// checked disputes sets
|
||||
checked_disputes_sets,
|
||||
checked_disputes_sets_consumed_weight,
|
||||
))
|
||||
});
|
||||
|
||||
// Assure the maximum block weight is adhered, by limiting bitfields and backed
|
||||
// candidates. Dispute statement sets were already limited before.
|
||||
let actual_weight = apply_weight_limit::<T>(
|
||||
let non_disputes_weight = apply_weight_limit::<T>(
|
||||
&mut backed_candidates,
|
||||
&mut bitfields,
|
||||
max_block_weight.saturating_sub(checked_disputes_sets_consumed_weight),
|
||||
&mut rng,
|
||||
);
|
||||
|
||||
if actual_weight.any_gt(max_block_weight) {
|
||||
let full_weight = non_disputes_weight.saturating_add(checked_disputes_sets_consumed_weight);
|
||||
|
||||
METRICS.on_after_filter(full_weight.ref_time());
|
||||
|
||||
if full_weight.any_gt(max_block_weight) {
|
||||
log::warn!(target: LOG_TARGET, "Post weight limiting weight is still too large.");
|
||||
}
|
||||
|
||||
// Note that `process_checked_multi_dispute_data` will iterate and import each
|
||||
// dispute; so the input here must be reasonably bounded,
|
||||
// which is guaranteed by the checks and weight limitation above.
|
||||
// We don't care about fresh or not disputes
|
||||
// this writes them to storage, so let's query it via those means
|
||||
// if this fails for whatever reason, that's ok.
|
||||
if let Err(e) =
|
||||
T::DisputesHandler::process_checked_multi_dispute_data(&checked_disputes_sets)
|
||||
{
|
||||
log::warn!(target: LOG_TARGET, "MultiDisputesData failed to update: {:?}", e);
|
||||
};
|
||||
METRICS.on_disputes_imported(checked_disputes_sets.len() as u64);
|
||||
|
||||
set_scrapable_on_chain_disputes::<T>(current_session, checked_disputes_sets.clone());
|
||||
|
||||
if T::DisputesHandler::is_frozen() {
|
||||
// Relay chain freeze, at this point we will not include any parachain blocks.
|
||||
METRICS.on_relay_chain_freeze();
|
||||
|
||||
let disputes = checked_disputes_sets
|
||||
.into_iter()
|
||||
.map(|checked| checked.into())
|
||||
.collect::<Vec<_>>();
|
||||
let processed = ParachainsInherentData {
|
||||
bitfields: Vec::new(),
|
||||
backed_candidates: Vec::new(),
|
||||
disputes,
|
||||
parent_header,
|
||||
};
|
||||
|
||||
// The relay chain we are currently on is invalid. Proceed no further on parachains.
|
||||
return Ok((processed, Some(checked_disputes_sets_consumed_weight).into()))
|
||||
}
|
||||
|
||||
// Contains the disputes that are concluded in the current session only,
|
||||
// since these are the only ones that are relevant for the occupied cores
|
||||
// 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 = checked_disputes_sets
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.filter(|dss| dss.session == current_session)
|
||||
.map(|dss| (dss.session, dss.candidate_hash))
|
||||
.filter(|(session, candidate)| {
|
||||
<T>::DisputesHandler::concluded_invalid(*session, *candidate)
|
||||
})
|
||||
.map(|(_session, candidate)| candidate)
|
||||
.collect::<BTreeSet<CandidateHash>>();
|
||||
|
||||
let mut freed_disputed: Vec<_> =
|
||||
<inclusion::Pallet<T>>::collect_disputed(¤t_concluded_invalid_disputes)
|
||||
.into_iter()
|
||||
.map(|core| (core, FreedReason::Concluded))
|
||||
.collect();
|
||||
|
||||
// Create a bit index from the set of core indices where each index corresponds to
|
||||
// a core index that was freed due to a dispute.
|
||||
//
|
||||
// I.e. 010100 would indicate, the candidates on Core 1 and 3 would be disputed.
|
||||
let disputed_bitfield = create_disputed_bitfield(
|
||||
expected_bits,
|
||||
freed_disputed.iter().map(|(core_index, _)| core_index),
|
||||
);
|
||||
|
||||
if !freed_disputed.is_empty() {
|
||||
// unstable sort is fine, because core indices are unique
|
||||
// i.e. the same candidate can't occupy 2 cores at once.
|
||||
freed_disputed.sort_unstable_by_key(|pair| pair.0); // sort by core index
|
||||
<scheduler::Pallet<T>>::free_cores(freed_disputed.clone());
|
||||
}
|
||||
|
||||
let bitfields = sanitize_bitfields::<T>(
|
||||
bitfields,
|
||||
disputed_bitfield,
|
||||
expected_bits,
|
||||
parent_hash,
|
||||
current_session,
|
||||
&validator_public[..],
|
||||
);
|
||||
METRICS.on_bitfields_processed(bitfields.len() as u64);
|
||||
|
||||
// Process new availability bitfields, yielding any availability cores whose
|
||||
// work has now concluded.
|
||||
let freed_concluded =
|
||||
<inclusion::Pallet<T>>::update_pending_availability_and_get_freed_cores::<_>(
|
||||
expected_bits,
|
||||
&validator_public[..],
|
||||
bitfields.clone(),
|
||||
<scheduler::Pallet<T>>::core_para,
|
||||
);
|
||||
|
||||
// Inform the disputes module of all included candidates.
|
||||
for (_, candidate_hash) in &freed_concluded {
|
||||
T::DisputesHandler::note_included(current_session, *candidate_hash, now);
|
||||
}
|
||||
|
||||
METRICS.on_candidates_included(freed_concluded.len() as u64);
|
||||
|
||||
let freed = collect_all_freed_cores::<T, _>(freed_concluded.iter().cloned());
|
||||
|
||||
<scheduler::Pallet<T>>::clear();
|
||||
<scheduler::Pallet<T>>::schedule(freed, now);
|
||||
|
||||
let scheduled = <scheduler::Pallet<T>>::scheduled();
|
||||
|
||||
let relay_parent_number = now - One::one();
|
||||
let parent_storage_root = *parent_header.state_root();
|
||||
|
||||
let check_ctx = CandidateCheckContext::<T>::new(now, relay_parent_number);
|
||||
|
||||
METRICS.on_candidates_processed_total(backed_candidates.len() as u64);
|
||||
|
||||
let backed_candidates = sanitize_backed_candidates::<T, _>(
|
||||
parent_hash,
|
||||
backed_candidates,
|
||||
move |candidate_idx: usize,
|
||||
backed_candidate: &BackedCandidate<<T as frame_system::Config>::Hash>|
|
||||
-> bool {
|
||||
// never include a concluded-invalid candidate
|
||||
current_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, parent_storage_root, candidate_idx, backed_candidate)
|
||||
.is_err()
|
||||
},
|
||||
&scheduled[..],
|
||||
);
|
||||
|
||||
METRICS.on_candidates_sanitized(backed_candidates.len() as u64);
|
||||
|
||||
// Process backed candidates according to scheduled cores.
|
||||
let parent_storage_root = *parent_header.state_root();
|
||||
let inclusion::ProcessedCandidates::<<T::Header as HeaderT>::Hash> {
|
||||
core_indices: occupied,
|
||||
candidate_receipt_with_backing_validator_indices,
|
||||
} = <inclusion::Pallet<T>>::process_candidates(
|
||||
parent_storage_root,
|
||||
backed_candidates.clone(),
|
||||
scheduled,
|
||||
<scheduler::Pallet<T>>::group_validators,
|
||||
)?;
|
||||
// Note which of the scheduled cores were actually occupied by a backed candidate.
|
||||
<scheduler::Pallet<T>>::occupied(&occupied);
|
||||
|
||||
set_scrapable_on_chain_backings::<T>(
|
||||
current_session,
|
||||
candidate_receipt_with_backing_validator_indices,
|
||||
);
|
||||
|
||||
let disputes = checked_disputes_sets
|
||||
.into_iter()
|
||||
.map(|checked| checked.into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(ParachainsInherentData::<T::Header> {
|
||||
bitfields,
|
||||
backed_candidates,
|
||||
disputes,
|
||||
parent_header,
|
||||
})
|
||||
let bitfields = bitfields.into_iter().map(|v| v.into_unchecked()).collect();
|
||||
|
||||
let processed =
|
||||
ParachainsInherentData { bitfields, backed_candidates, disputes, parent_header };
|
||||
Ok((processed, Some(full_weight).into()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -929,13 +729,6 @@ fn apply_weight_limit<T: Config + inclusion::Config>(
|
||||
/// 5. remove any disputed core indices
|
||||
///
|
||||
/// If any of those is not passed, the bitfield is dropped.
|
||||
///
|
||||
/// While this function technically returns a set of unchecked bitfields,
|
||||
/// they were actually checked and filtered to allow using it in both
|
||||
/// cases, as `filtering` and `checking` stage.
|
||||
///
|
||||
/// `full_check` determines if validator signatures are checked. If `::Yes`,
|
||||
/// bitfields that have an invalid signature will be filtered out.
|
||||
pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config>(
|
||||
unchecked_bitfields: UncheckedSignedAvailabilityBitfields,
|
||||
disputed_bitfield: DisputedBitfield,
|
||||
@@ -943,8 +736,7 @@ pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config>(
|
||||
parent_hash: T::Hash,
|
||||
session_index: SessionIndex,
|
||||
validators: &[ValidatorId],
|
||||
full_check: FullCheck,
|
||||
) -> UncheckedSignedAvailabilityBitfields {
|
||||
) -> SignedAvailabilityBitfields {
|
||||
let mut bitfields = Vec::with_capacity(unchecked_bitfields.len());
|
||||
|
||||
let mut last_index: Option<ValidatorIndex> = None;
|
||||
@@ -963,8 +755,7 @@ pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config>(
|
||||
if unchecked_bitfield.unchecked_payload().0.len() != expected_bits {
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"[{:?}] bad bitfield length: {} != {:?}",
|
||||
full_check,
|
||||
"bad bitfield length: {} != {:?}",
|
||||
unchecked_bitfield.unchecked_payload().0.len(),
|
||||
expected_bits,
|
||||
);
|
||||
@@ -976,8 +767,7 @@ pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config>(
|
||||
{
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"[{:?}] bitfield contains disputed cores: {:?}",
|
||||
full_check,
|
||||
"bitfield contains disputed cores: {:?}",
|
||||
unchecked_bitfield.unchecked_payload().0.clone() & disputed_bitfield.0.clone()
|
||||
);
|
||||
continue
|
||||
@@ -988,8 +778,7 @@ pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config>(
|
||||
if !last_index.map_or(true, |last_index: ValidatorIndex| last_index < validator_index) {
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"[{:?}] bitfield validator index is not greater than last: !({:?} < {})",
|
||||
full_check,
|
||||
"bitfield validator index is not greater than last: !({:?} < {})",
|
||||
last_index.as_ref().map(|x| x.0),
|
||||
validator_index.0
|
||||
);
|
||||
@@ -999,8 +788,7 @@ pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config>(
|
||||
if unchecked_bitfield.unchecked_validator_index().0 as usize >= validators.len() {
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"[{:?}] bitfield validator index is out of bounds: {} >= {}",
|
||||
full_check,
|
||||
"bitfield validator index is out of bounds: {} >= {}",
|
||||
validator_index.0,
|
||||
validators.len(),
|
||||
);
|
||||
@@ -1009,81 +797,22 @@ pub(crate) fn sanitize_bitfields<T: crate::inclusion::Config>(
|
||||
|
||||
let validator_public = &validators[validator_index.0 as usize];
|
||||
|
||||
if let FullCheck::Yes = full_check {
|
||||
// Validate bitfield signature.
|
||||
if let Ok(signed_bitfield) =
|
||||
unchecked_bitfield.try_into_checked(&signing_context, validator_public)
|
||||
{
|
||||
bitfields.push(signed_bitfield.into_unchecked());
|
||||
METRICS.on_valid_bitfield_signature();
|
||||
} else {
|
||||
log::warn!(target: LOG_TARGET, "Invalid bitfield signature");
|
||||
METRICS.on_invalid_bitfield_signature();
|
||||
};
|
||||
// Validate bitfield signature.
|
||||
if let Ok(signed_bitfield) =
|
||||
unchecked_bitfield.try_into_checked(&signing_context, validator_public)
|
||||
{
|
||||
bitfields.push(signed_bitfield);
|
||||
METRICS.on_valid_bitfield_signature();
|
||||
} else {
|
||||
bitfields.push(unchecked_bitfield);
|
||||
}
|
||||
log::warn!(target: LOG_TARGET, "Invalid bitfield signature");
|
||||
METRICS.on_invalid_bitfield_signature();
|
||||
};
|
||||
|
||||
last_index = Some(validator_index);
|
||||
}
|
||||
bitfields
|
||||
}
|
||||
|
||||
pub(crate) fn assure_sanity_bitfields<T: crate::inclusion::Config>(
|
||||
unchecked_bitfields: UncheckedSignedAvailabilityBitfields,
|
||||
disputed_bitfield: DisputedBitfield,
|
||||
expected_bits: usize,
|
||||
parent_hash: T::Hash,
|
||||
session_index: SessionIndex,
|
||||
validators: &[ValidatorId],
|
||||
full_check: FullCheck,
|
||||
) -> Result<UncheckedSignedAvailabilityBitfields, crate::inclusion::Error<T>> {
|
||||
let mut last_index: Option<ValidatorIndex> = None;
|
||||
|
||||
use crate::inclusion::Error;
|
||||
|
||||
ensure!(disputed_bitfield.0.len() == expected_bits, Error::<T>::WrongBitfieldSize);
|
||||
|
||||
let mut bitfields = Vec::with_capacity(unchecked_bitfields.len());
|
||||
|
||||
let signing_context = SigningContext { parent_hash, session_index };
|
||||
for unchecked_bitfield in unchecked_bitfields {
|
||||
// Find and skip invalid bitfields.
|
||||
ensure!(
|
||||
unchecked_bitfield.unchecked_payload().0.len() == expected_bits,
|
||||
Error::<T>::WrongBitfieldSize
|
||||
);
|
||||
|
||||
let validator_index = unchecked_bitfield.unchecked_validator_index();
|
||||
|
||||
if !last_index.map_or(true, |last_index: ValidatorIndex| last_index < validator_index) {
|
||||
return Err(Error::<T>::UnsortedOrDuplicateValidatorIndices)
|
||||
}
|
||||
|
||||
if unchecked_bitfield.unchecked_validator_index().0 as usize >= validators.len() {
|
||||
return Err(Error::<T>::ValidatorIndexOutOfBounds)
|
||||
}
|
||||
|
||||
let validator_public = &validators[validator_index.0 as usize];
|
||||
|
||||
if let FullCheck::Yes = full_check {
|
||||
// Validate bitfield signature.
|
||||
if let Ok(signed_bitfield) =
|
||||
unchecked_bitfield.try_into_checked(&signing_context, validator_public)
|
||||
{
|
||||
bitfields.push(signed_bitfield.into_unchecked());
|
||||
} else {
|
||||
return Err(Error::<T>::InvalidBitfieldSignature)
|
||||
}
|
||||
} else {
|
||||
bitfields.push(unchecked_bitfield);
|
||||
}
|
||||
|
||||
last_index = Some(validator_index);
|
||||
}
|
||||
Ok(bitfields)
|
||||
}
|
||||
|
||||
/// Filter out any candidates that have a concluded invalid dispute.
|
||||
///
|
||||
/// `scheduled` follows the same naming scheme as provided in the
|
||||
@@ -1139,46 +868,6 @@ fn sanitize_backed_candidates<
|
||||
backed_candidates
|
||||
}
|
||||
|
||||
/// Assumes sorted candidates.
|
||||
pub(crate) fn assure_sanity_backed_candidates<
|
||||
T: crate::inclusion::Config,
|
||||
F: FnMut(usize, &BackedCandidate<T::Hash>) -> bool,
|
||||
>(
|
||||
relay_parent: T::Hash,
|
||||
backed_candidates: &[BackedCandidate<T::Hash>],
|
||||
mut candidate_has_concluded_invalid_dispute_or_is_invalid: F,
|
||||
scheduled: &[CoreAssignment],
|
||||
) -> Result<(), crate::inclusion::Error<T>> {
|
||||
use crate::inclusion::Error;
|
||||
|
||||
for (idx, backed_candidate) in backed_candidates.iter().enumerate() {
|
||||
if candidate_has_concluded_invalid_dispute_or_is_invalid(idx, backed_candidate) {
|
||||
return Err(Error::<T>::UnsortedOrDuplicateBackedCandidates)
|
||||
}
|
||||
// Assure the backed candidate's `ParaId`'s core is free.
|
||||
// This holds under the assumption that `Scheduler::schedule` is called _before_.
|
||||
// Also checks the candidate references the correct relay parent.
|
||||
let desc = backed_candidate.descriptor();
|
||||
if desc.relay_parent != relay_parent {
|
||||
return Err(Error::<T>::UnexpectedRelayParent)
|
||||
}
|
||||
}
|
||||
|
||||
let scheduled_paras_to_core_idx = scheduled
|
||||
.into_iter()
|
||||
.map(|core_assignment| (core_assignment.para_id, core_assignment.core))
|
||||
.collect::<BTreeMap<ParaId, CoreIndex>>();
|
||||
|
||||
if !IsSortedBy::is_sorted_by(backed_candidates, |x, y| {
|
||||
// Never panics, since we would have early returned on those in the above loop.
|
||||
scheduled_paras_to_core_idx[&x.descriptor().para_id]
|
||||
.cmp(&scheduled_paras_to_core_idx[&y.descriptor().para_id])
|
||||
}) {
|
||||
return Err(Error::<T>::UnsortedOrDuplicateBackedCandidates)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Derive entropy from babe provided per block randomness.
|
||||
///
|
||||
/// In the odd case none is available, uses the `parent_hash` and
|
||||
|
||||
@@ -111,16 +111,6 @@ mod enter {
|
||||
expected_para_inherent_data
|
||||
);
|
||||
|
||||
// The schedule is still empty prior to calling `enter`. (`create_inherent_inner` should not
|
||||
// alter storage, but just double checking for sanity).
|
||||
assert_eq!(<scheduler::Pallet<Test>>::scheduled(), vec![]);
|
||||
|
||||
assert_eq!(Pallet::<Test>::on_chain_votes(), None);
|
||||
// Call enter with our 2 backed candidates
|
||||
assert_ok!(Pallet::<Test>::enter(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
expected_para_inherent_data
|
||||
));
|
||||
assert_eq!(
|
||||
// The length of this vec is equal to the number of candidates, so we know our 2
|
||||
// backed candidates did not get filtered out
|
||||
@@ -286,12 +276,6 @@ mod enter {
|
||||
&expected_para_inherent_data.disputes[..2],
|
||||
);
|
||||
|
||||
// The schedule is still empty prior to calling `enter`. (`create_inherent_inner` should not
|
||||
// alter storage, but just double checking for sanity).
|
||||
assert_eq!(<scheduler::Pallet<Test>>::scheduled(), vec![]);
|
||||
|
||||
assert_eq!(Pallet::<Test>::on_chain_votes(), None);
|
||||
// Call enter with our 2 disputes
|
||||
assert_ok!(Pallet::<Test>::enter(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
multi_dispute_inherent_data,
|
||||
@@ -357,12 +341,6 @@ mod enter {
|
||||
assert_eq!(limit_inherent_data.disputes[0].session, 1);
|
||||
assert_eq!(limit_inherent_data.disputes[1].session, 2);
|
||||
|
||||
// The schedule is still empty prior to calling `enter`. (`create_inherent_inner` should not
|
||||
// alter storage, but just double checking for sanity).
|
||||
assert_eq!(<scheduler::Pallet<Test>>::scheduled(), vec![]);
|
||||
|
||||
assert_eq!(Pallet::<Test>::on_chain_votes(), None);
|
||||
// Call enter with our 2 disputes
|
||||
assert_ok!(Pallet::<Test>::enter(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
limit_inherent_data,
|
||||
@@ -382,51 +360,6 @@ mod enter {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Ensure that when dispute data establishes an over weight block that we abort
|
||||
// due to an over weight block
|
||||
fn limit_dispute_data_overweight() {
|
||||
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
|
||||
// Create the inherent data for this block
|
||||
let dispute_statements = BTreeMap::new();
|
||||
// No backed and concluding cores, so all cores will be filled with disputes.
|
||||
let backed_and_concluding = BTreeMap::new();
|
||||
|
||||
let scenario = make_inherent_data(TestConfig {
|
||||
dispute_statements,
|
||||
dispute_sessions: vec![2, 2, 1], // 3 cores with disputes
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 6,
|
||||
code_upgrade: None,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
|
||||
// Check the para inherent data is as expected:
|
||||
// * 1 bitfield per validator (6 validators per core, 3 disputes => 18 validators)
|
||||
assert_eq!(expected_para_inherent_data.bitfields.len(), 18);
|
||||
// * 0 backed candidate per core
|
||||
assert_eq!(expected_para_inherent_data.backed_candidates.len(), 0);
|
||||
// * 3 disputes.
|
||||
assert_eq!(expected_para_inherent_data.disputes.len(), 3);
|
||||
let mut inherent_data = InherentData::new();
|
||||
inherent_data
|
||||
.put_data(PARACHAINS_INHERENT_IDENTIFIER, &expected_para_inherent_data)
|
||||
.unwrap();
|
||||
|
||||
// The current schedule is empty prior to calling `create_inherent_enter`.
|
||||
assert_eq!(<scheduler::Pallet<Test>>::scheduled(), vec![]);
|
||||
|
||||
assert_matches!(Pallet::<Test>::enter(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
expected_para_inherent_data,
|
||||
), Err(e) => { dbg!(e) });
|
||||
|
||||
// The block was not included, as such, `on_chain_votes` _must_ return `None`.
|
||||
assert_eq!(Pallet::<Test>::on_chain_votes(), None,);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Ensure that when a block is over weight due to disputes, but there is still sufficient
|
||||
// block weight to include a number of signed bitfields, the inherent data is filtered
|
||||
@@ -484,12 +417,6 @@ mod enter {
|
||||
// Ensure that all backed candidates are filtered out as either would make the block over weight
|
||||
assert_eq!(limit_inherent_data.backed_candidates.len(), 0);
|
||||
|
||||
// The schedule is still empty prior to calling `enter`. (`create_inherent_inner` should not
|
||||
// alter storage, but just double checking for sanity).
|
||||
assert_eq!(<scheduler::Pallet<Test>>::scheduled(), vec![]);
|
||||
|
||||
assert_eq!(Pallet::<Test>::on_chain_votes(), None);
|
||||
// Call enter with our 2 disputes
|
||||
assert_ok!(Pallet::<Test>::enter(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
limit_inherent_data,
|
||||
@@ -510,56 +437,6 @@ mod enter {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Ensure that we abort if we encounter an over weight block for disputes + bitfields
|
||||
fn limit_dispute_data_ignore_backed_candidates_overweight() {
|
||||
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
|
||||
// Create the inherent data for this block
|
||||
let dispute_statements = BTreeMap::new();
|
||||
|
||||
let mut backed_and_concluding = BTreeMap::new();
|
||||
// 2 backed candidates shall be scheduled
|
||||
backed_and_concluding.insert(0, 2);
|
||||
backed_and_concluding.insert(1, 2);
|
||||
|
||||
let scenario = make_inherent_data(TestConfig {
|
||||
dispute_statements,
|
||||
dispute_sessions: vec![2, 2, 1], // 3 cores with disputes
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 4,
|
||||
code_upgrade: None,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
|
||||
// Check the para inherent data is as expected:
|
||||
// * 1 bitfield per validator (4 validators per core, 2 backed candidates, 3 disputes => 4*5 = 20)
|
||||
assert_eq!(expected_para_inherent_data.bitfields.len(), 20);
|
||||
// * 2 backed candidates
|
||||
assert_eq!(expected_para_inherent_data.backed_candidates.len(), 2);
|
||||
// * 3 disputes.
|
||||
assert_eq!(expected_para_inherent_data.disputes.len(), 3);
|
||||
let mut inherent_data = InherentData::new();
|
||||
inherent_data
|
||||
.put_data(PARACHAINS_INHERENT_IDENTIFIER, &expected_para_inherent_data)
|
||||
.unwrap();
|
||||
|
||||
// The current schedule is empty prior to calling `create_inherent_enter`.
|
||||
assert_eq!(<scheduler::Pallet<Test>>::scheduled(), vec![]);
|
||||
|
||||
// Ensure that calling enter with 3 disputes and 2 candidates is over weight
|
||||
assert_matches!(Pallet::<Test>::enter(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
expected_para_inherent_data,
|
||||
), Err(e) => {
|
||||
dbg!(e)
|
||||
});
|
||||
|
||||
// The block was not included, as such, `on_chain_votes` _must_ return `None`.
|
||||
assert_eq!(Pallet::<Test>::on_chain_votes(), None,);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Ensure an overweight block with an excess amount of disputes and bitfields, the bitfields are
|
||||
// filtered to accommodate the block size and no backed candidates are included.
|
||||
@@ -621,12 +498,6 @@ mod enter {
|
||||
// Ensure that all backed candidates are filtered out as either would make the block over weight
|
||||
assert_eq!(limit_inherent_data.backed_candidates.len(), 0);
|
||||
|
||||
// The schedule is still empty prior to calling `enter`. (`create_inherent_inner` should not
|
||||
// alter storage, but just double checking for sanity).
|
||||
assert_eq!(<scheduler::Pallet<Test>>::scheduled(), vec![]);
|
||||
|
||||
assert_eq!(Pallet::<Test>::on_chain_votes(), None);
|
||||
// Call enter with our 2 disputes
|
||||
assert_ok!(Pallet::<Test>::enter(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
limit_inherent_data,
|
||||
@@ -648,7 +519,7 @@ mod enter {
|
||||
}
|
||||
|
||||
#[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 filter.
|
||||
fn limit_bitfields_overweight() {
|
||||
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
|
||||
// Create the inherent data for this block
|
||||
@@ -686,18 +557,11 @@ mod enter {
|
||||
.put_data(PARACHAINS_INHERENT_IDENTIFIER, &expected_para_inherent_data)
|
||||
.unwrap();
|
||||
|
||||
// The current schedule is empty prior to calling `create_inherent_enter`.
|
||||
assert_eq!(<scheduler::Pallet<Test>>::scheduled(), vec![]);
|
||||
|
||||
assert_matches!(Pallet::<Test>::enter(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
expected_para_inherent_data,
|
||||
), Err(_e) => {
|
||||
/* TODO */
|
||||
});
|
||||
|
||||
// The block was not included, as such, `on_chain_votes` _must_ return `None`.
|
||||
assert_matches!(Pallet::<Test>::on_chain_votes(), None);
|
||||
let limit_inherent_data =
|
||||
Pallet::<Test>::create_inherent_inner(&inherent_data.clone()).unwrap();
|
||||
assert_eq!(limit_inherent_data.bitfields.len(), 20);
|
||||
assert_eq!(limit_inherent_data.disputes.len(), 2);
|
||||
assert_eq!(limit_inherent_data.backed_candidates.len(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -733,7 +597,7 @@ mod enter {
|
||||
}
|
||||
|
||||
#[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 filter.
|
||||
fn limit_candidates_over_weight_1() {
|
||||
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
|
||||
// Create the inherent data for this block
|
||||
@@ -790,9 +654,6 @@ mod enter {
|
||||
// * 3 disputes.
|
||||
assert_eq!(limit_inherent_data.disputes.len(), 2);
|
||||
|
||||
// The current schedule is empty prior to calling `create_inherent_enter`.
|
||||
assert_eq!(<scheduler::Pallet<Test>>::scheduled(), vec![]);
|
||||
|
||||
assert_ok!(Pallet::<Test>::enter(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
limit_inherent_data,
|
||||
@@ -812,51 +673,6 @@ mod enter {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Ensure that when a block is over weight due to disputes and bitfields, we abort
|
||||
fn limit_candidates_over_weight_0() {
|
||||
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
|
||||
// Create the inherent data for this block
|
||||
let mut dispute_statements = BTreeMap::new();
|
||||
// Control the number of statements per dispute to ensure we have enough space
|
||||
// in the block for some (but not all) bitfields
|
||||
dispute_statements.insert(2, 17);
|
||||
dispute_statements.insert(3, 17);
|
||||
dispute_statements.insert(4, 17);
|
||||
|
||||
let mut backed_and_concluding = BTreeMap::new();
|
||||
// 2 backed candidates shall be scheduled
|
||||
backed_and_concluding.insert(0, 16);
|
||||
backed_and_concluding.insert(1, 25);
|
||||
|
||||
let scenario = make_inherent_data(TestConfig {
|
||||
dispute_statements,
|
||||
dispute_sessions: vec![2, 2, 1], // 3 cores with disputes
|
||||
backed_and_concluding,
|
||||
num_validators_per_core: 5,
|
||||
code_upgrade: None,
|
||||
});
|
||||
|
||||
let expected_para_inherent_data = scenario.data.clone();
|
||||
|
||||
// Check the para inherent data is as expected:
|
||||
// * 1 bitfield per validator (5 validators per core, 2 backed candidates, 3 disputes => 5*5 = 25)
|
||||
assert_eq!(expected_para_inherent_data.bitfields.len(), 25);
|
||||
// * 2 backed candidates
|
||||
assert_eq!(expected_para_inherent_data.backed_candidates.len(), 2);
|
||||
// * 3 disputes.
|
||||
assert_eq!(expected_para_inherent_data.disputes.len(), 3);
|
||||
|
||||
assert_matches!(Pallet::<Test>::enter(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
expected_para_inherent_data,
|
||||
), Err(e) => { dbg!(e) });
|
||||
|
||||
// The block was not included, as such, `on_chain_votes` _must_ return `None`.
|
||||
assert_matches!(Pallet::<Test>::on_chain_votes(), None);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn default_header() -> primitives::Header {
|
||||
@@ -921,7 +737,7 @@ mod sanitizers {
|
||||
}
|
||||
let validator_public = validator_pubkeys(&validators);
|
||||
|
||||
let unchecked_bitfields = [
|
||||
let checked_bitfields = [
|
||||
BitVec::<u8, Lsb0>::repeat(true, expected_bits),
|
||||
BitVec::<u8, Lsb0>::repeat(true, expected_bits),
|
||||
{
|
||||
@@ -943,9 +759,14 @@ mod sanitizers {
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_unchecked()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<Vec<SignedAvailabilityBitfield>>();
|
||||
|
||||
let unchecked_bitfields = checked_bitfields
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|v| v.into_unchecked())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let disputed_bitfield = DisputedBitfield::zeros(expected_bits);
|
||||
|
||||
@@ -958,9 +779,8 @@ mod sanitizers {
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..],
|
||||
FullCheck::Skip,
|
||||
),
|
||||
unchecked_bitfields.clone()
|
||||
checked_bitfields.clone()
|
||||
);
|
||||
assert_eq!(
|
||||
sanitize_bitfields::<Test>(
|
||||
@@ -970,9 +790,8 @@ mod sanitizers {
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..],
|
||||
FullCheck::Yes
|
||||
),
|
||||
unchecked_bitfields.clone()
|
||||
checked_bitfields.clone()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -991,7 +810,6 @@ mod sanitizers {
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..],
|
||||
FullCheck::Yes
|
||||
)
|
||||
.len(),
|
||||
1
|
||||
@@ -1004,7 +822,6 @@ mod sanitizers {
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..],
|
||||
FullCheck::Skip
|
||||
)
|
||||
.len(),
|
||||
1
|
||||
@@ -1020,7 +837,6 @@ mod sanitizers {
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..],
|
||||
FullCheck::Yes
|
||||
)
|
||||
.is_empty());
|
||||
assert!(sanitize_bitfields::<Test>(
|
||||
@@ -1030,7 +846,6 @@ mod sanitizers {
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..],
|
||||
FullCheck::Skip
|
||||
)
|
||||
.is_empty());
|
||||
}
|
||||
@@ -1046,9 +861,8 @@ mod sanitizers {
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..shortened],
|
||||
FullCheck::Yes,
|
||||
)[..],
|
||||
&unchecked_bitfields[..shortened]
|
||||
&checked_bitfields[..shortened]
|
||||
);
|
||||
assert_eq!(
|
||||
&sanitize_bitfields::<Test>(
|
||||
@@ -1058,9 +872,8 @@ mod sanitizers {
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..shortened],
|
||||
FullCheck::Skip,
|
||||
)[..],
|
||||
&unchecked_bitfields[..shortened]
|
||||
&checked_bitfields[..shortened]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1069,30 +882,18 @@ mod sanitizers {
|
||||
let mut unchecked_bitfields = unchecked_bitfields.clone();
|
||||
let x = unchecked_bitfields.swap_remove(0);
|
||||
unchecked_bitfields.push(x);
|
||||
assert_eq!(
|
||||
&sanitize_bitfields::<Test>(
|
||||
unchecked_bitfields.clone(),
|
||||
disputed_bitfield.clone(),
|
||||
expected_bits,
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..],
|
||||
FullCheck::Yes
|
||||
)[..],
|
||||
&unchecked_bitfields[..(unchecked_bitfields.len() - 2)]
|
||||
);
|
||||
assert_eq!(
|
||||
&sanitize_bitfields::<Test>(
|
||||
unchecked_bitfields.clone(),
|
||||
disputed_bitfield.clone(),
|
||||
expected_bits,
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..],
|
||||
FullCheck::Skip
|
||||
)[..],
|
||||
&unchecked_bitfields[..(unchecked_bitfields.len() - 2)]
|
||||
);
|
||||
let result: UncheckedSignedAvailabilityBitfields = sanitize_bitfields::<Test>(
|
||||
unchecked_bitfields.clone(),
|
||||
disputed_bitfield.clone(),
|
||||
expected_bits,
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..],
|
||||
)
|
||||
.into_iter()
|
||||
.map(|v| v.into_unchecked())
|
||||
.collect();
|
||||
assert_eq!(&result, &unchecked_bitfields[..(unchecked_bitfields.len() - 2)]);
|
||||
}
|
||||
|
||||
// check the validators signature
|
||||
@@ -1113,21 +914,30 @@ mod sanitizers {
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..],
|
||||
FullCheck::Yes
|
||||
)[..],
|
||||
&unchecked_bitfields[..last_bit_idx]
|
||||
&checked_bitfields[..last_bit_idx]
|
||||
);
|
||||
}
|
||||
// duplicate bitfields
|
||||
{
|
||||
let mut unchecked_bitfields = unchecked_bitfields.clone();
|
||||
|
||||
// insert a bad signature for the last bitfield
|
||||
let last_bit_idx = unchecked_bitfields.len() - 1;
|
||||
unchecked_bitfields
|
||||
.get_mut(last_bit_idx)
|
||||
.and_then(|u| Some(u.set_signature(UncheckedFrom::unchecked_from([1u8; 64]))))
|
||||
.expect("we are accessing a valid index");
|
||||
assert_eq!(
|
||||
&sanitize_bitfields::<Test>(
|
||||
unchecked_bitfields.clone(),
|
||||
unchecked_bitfields.clone().into_iter().chain(unchecked_bitfields).collect(),
|
||||
disputed_bitfield.clone(),
|
||||
expected_bits,
|
||||
parent_hash,
|
||||
session_index,
|
||||
&validator_public[..],
|
||||
FullCheck::Skip
|
||||
)[..],
|
||||
&unchecked_bitfields[..]
|
||||
&checked_bitfields[..last_bit_idx]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user