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:
eskimor
2023-06-14 10:57:06 +02:00
committed by GitHub
parent e71c541ede
commit 596a9ccd02
13 changed files with 676 additions and 1550 deletions
+22 -93
View File
@@ -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()
+31 -149
View File
@@ -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.
+8 -20
View File
@@ -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(&current_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(&current_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]
);
}
}