diff --git a/runtime/parachains/src/dmp.rs b/runtime/parachains/src/dmp.rs index 08def77f9c35..0ac3620cbf06 100644 --- a/runtime/parachains/src/dmp.rs +++ b/runtime/parachains/src/dmp.rs @@ -203,6 +203,10 @@ impl Pallet { T::DbWeight::get().reads_writes(1, 1) } + pub(crate) fn prune_dmq_weight() -> Weight { + T::DbWeight::get().reads_writes(1, 1) + } + /// Returns the Head of Message Queue Chain for the given para or `None` if there is none /// associated with it. #[cfg(test)] diff --git a/runtime/parachains/src/hrmp.rs b/runtime/parachains/src/hrmp.rs index 82e8f90a26ba..84bd2177944b 100644 --- a/runtime/parachains/src/hrmp.rs +++ b/runtime/parachains/src/hrmp.rs @@ -908,6 +908,19 @@ impl Pallet { weight } + /// Worst case weight for `prune_hrmp`. + pub(crate) fn prune_hrmp_weight( + hrmp_max_parachain_inbound_channels: u32, + hrmp_max_parathread_inbound_channels: u32, + ) -> Weight { + let max_pruneable_channels: u64 = hrmp_max_parachain_inbound_channels + .max(hrmp_max_parathread_inbound_channels) + .into(); + + T::DbWeight::get() + .reads_writes(1 + 2 * max_pruneable_channels, 2 + 2 * max_pruneable_channels) + } + /// Process the outbound HRMP messages by putting them into the appropriate recipient queues. /// /// Returns the amount of weight consumed. @@ -973,12 +986,19 @@ impl Pallet { } ::HrmpChannelDigests::insert(&channel_id.recipient, recipient_digest); - weight += T::DbWeight::get().reads_writes(2, 2); + weight += T::DbWeight::get().reads_writes(3, 2); } weight } + /// Worst case weight for `queue_outbound_hrmp`. + pub(crate) fn queue_outbound_hrmp_weight(hrmp_max_message_num_per_candidate: u32) -> Weight { + let reads = (3 * hrmp_max_message_num_per_candidate).into(); + let writes = (2 * hrmp_max_message_num_per_candidate).into(); + T::DbWeight::get().reads_writes(reads, writes) + } + /// Initiate opening a channel from a parachain to a given recipient with given channel /// parameters. /// diff --git a/runtime/parachains/src/inclusion/mod.rs b/runtime/parachains/src/inclusion/mod.rs index 025f1ae700a2..f28ff71d3621 100644 --- a/runtime/parachains/src/inclusion/mod.rs +++ b/runtime/parachains/src/inclusion/mod.rs @@ -307,7 +307,7 @@ impl Pallet { validators: &[ValidatorId], signed_bitfields: UncheckedSignedAvailabilityBitfields, core_lookup: F, - ) -> Vec<(CoreIndex, CandidateHash)> + ) -> (Vec<(CoreIndex, CandidateHash)>, Weight) where F: Fn(CoreIndex) -> Option, { @@ -356,6 +356,7 @@ impl Pallet { >::insert(&validator_index, record); } + let mut enacted_candidate_weight: Weight = 0; let threshold = availability_threshold(validators.len()); let mut freed_cores = Vec::with_capacity(expected_bits); @@ -383,7 +384,8 @@ impl Pallet { descriptor: pending_availability.descriptor, commitments, }; - let _weight = Self::enact_candidate( + + let weight = Self::enact_candidate( pending_availability.relay_parent_number, receipt, pending_availability.backers, @@ -391,6 +393,8 @@ impl Pallet { pending_availability.core, pending_availability.backing_group, ); + + enacted_candidate_weight = enacted_candidate_weight.saturating_add(weight); } freed_cores.push((pending_availability.core, pending_availability.hash)); @@ -399,24 +403,24 @@ impl Pallet { } } - freed_cores + (freed_cores, enacted_candidate_weight) } /// Process a set of incoming bitfields. /// /// Returns a `Vec` of `CandidateHash`es and their respective `AvailabilityCore`s that became available, - /// and cores free. + /// and cores free. Additionally returns the weight consumed by enacted candidates. pub(crate) fn process_bitfields( expected_bits: usize, signed_bitfields: UncheckedSignedAvailabilityBitfields, disputed_bitfield: DisputedBitfield, core_lookup: impl Fn(CoreIndex) -> Option, - ) -> Vec<(CoreIndex, CandidateHash)> { + ) -> (Vec<(CoreIndex, CandidateHash)>, Weight) { let validators = shared::Pallet::::active_validator_keys(); let session_index = shared::Pallet::::session_index(); let parent_hash = frame_system::Pallet::::parent_hash(); - let checked_bitfields = sanitize_bitfields::( + let (checked_bitfields, _) = sanitize_bitfields::( signed_bitfields, disputed_bitfield, expected_bits, @@ -426,14 +430,15 @@ impl Pallet { FullCheck::Yes, ); - let freed_cores = Self::update_pending_availability_and_get_freed_cores::<_, true>( - expected_bits, - &validators[..], - checked_bitfields, - core_lookup, - ); + let (freed_cores, enacted_candidate_weight) = + Self::update_pending_availability_and_get_freed_cores::<_, true>( + expected_bits, + &validators[..], + checked_bitfields, + core_lookup, + ); - freed_cores + (freed_cores, enacted_candidate_weight) } /// Process candidates that have been backed. Provide the relay storage root, a set of candidates @@ -722,6 +727,8 @@ impl Pallet { let plain = receipt.to_plain(); let commitments = receipt.commitments; let config = >::config(); + // initial weight is config read. + let mut weight = T::DbWeight::get().reads_writes(1, 0); T::RewardValidators::reward_backing( backers @@ -739,8 +746,6 @@ impl Pallet { .map(|(i, _)| ValidatorIndex(i as _)), ); - // initial weight is config read. - let mut weight = T::DbWeight::get().reads_writes(1, 0); if let Some(new_code) = commitments.new_validation_code { weight += >::schedule_code_upgrade( receipt.descriptor.para_id, @@ -783,6 +788,30 @@ impl Pallet { ) } + /// Worst case weight for `enact_candidate`. + pub(crate) fn enact_candidate_weight( + hrmp_max_message_num_per_candidate: u32, + max_upward_message_num_per_candidate: u32, + hrmp_max_parachain_inbound_channels: u32, + hrmp_max_parathread_inbound_channels: u32, + ) -> Weight { + T::DbWeight::get() + .reads(1) + .saturating_add(>::schedule_code_upgrade_weight()) + .saturating_add(>::prune_dmq_weight()) + .saturating_add(>::receive_upward_messages_weight( + max_upward_message_num_per_candidate, + )) + .saturating_add(>::prune_hrmp_weight( + hrmp_max_parachain_inbound_channels, + hrmp_max_parathread_inbound_channels, + )) + .saturating_add(>::queue_outbound_hrmp_weight( + hrmp_max_message_num_per_candidate, + )) + .saturating_add(>::note_new_head_weight()) + } + /// Cleans up all paras pending availability that the predicate returns true for. /// /// The predicate accepts the index of the core and the block number the core has been occupied diff --git a/runtime/parachains/src/inclusion/tests.rs b/runtime/parachains/src/inclusion/tests.rs index a717c6b1bf3f..af7170933cd3 100644 --- a/runtime/parachains/src/inclusion/tests.rs +++ b/runtime/parachains/src/inclusion/tests.rs @@ -412,7 +412,8 @@ fn bitfield_checks() { vec![signed.into()], DisputedBitfield::zeros(expected_bits()), &core_lookup, - ), + ) + .0, vec![] ); } @@ -434,7 +435,8 @@ fn bitfield_checks() { vec![signed.into()], DisputedBitfield::zeros(expected_bits()), &core_lookup, - ), + ) + .0, vec![] ); } @@ -472,6 +474,7 @@ fn bitfield_checks() { DisputedBitfield::zeros(expected_bits()), &core_lookup, ) + .0 .is_empty()); assert_eq!( @@ -528,6 +531,7 @@ fn bitfield_checks() { DisputedBitfield::zeros(expected_bits()), &core_lookup, ) + .0 .is_empty()); assert_eq!( @@ -559,6 +563,7 @@ fn bitfield_checks() { DisputedBitfield::zeros(expected_bits()), &core_lookup, ) + .0 .is_empty()); } @@ -579,6 +584,7 @@ fn bitfield_checks() { DisputedBitfield::zeros(expected_bits()), &core_lookup, ) + .0 .is_empty()); } @@ -619,6 +625,7 @@ fn bitfield_checks() { DisputedBitfield::zeros(expected_bits()), &core_lookup, ) + .0 .is_empty()); >::remove(chain_a); @@ -662,6 +669,7 @@ fn bitfield_checks() { DisputedBitfield::zeros(expected_bits()), &core_lookup, ) + .0 .is_empty()); } }); @@ -805,7 +813,8 @@ fn supermajority_bitfields_trigger_availability() { signed_bitfields, DisputedBitfield::zeros(expected_bits()), &core_lookup, - ), + ) + .0, vec![(CoreIndex(0), candidate_a.hash())] ); diff --git a/runtime/parachains/src/paras.rs b/runtime/parachains/src/paras.rs index ad84b7bc051c..09541c58496b 100644 --- a/runtime/parachains/src/paras.rs +++ b/runtime/parachains/src/paras.rs @@ -905,7 +905,7 @@ impl Pallet { ) -> Weight { ::FutureCodeUpgrades::mutate(&id, |up| { if up.is_some() { - T::DbWeight::get().reads_writes(1, 0) + T::DbWeight::get().reads_writes(1, 1) } else { let expected_at = relay_parent_number + cfg.validation_upgrade_delay; let next_possible_upgrade_at = @@ -938,11 +938,16 @@ impl Pallet { let (reads, writes) = Self::increase_code_ref(&new_code_hash, &new_code); FutureCodeHash::::insert(&id, new_code_hash); - T::DbWeight::get().reads_writes(2 + reads, 3 + writes) + T::DbWeight::get().reads_writes(3 + reads, 5 + writes) } }) } + /// Worst case weight for `schedule_code_upgrade`. + pub(crate) fn schedule_code_upgrade_weight() -> Weight { + T::DbWeight::get().reads_writes(4, 8) + } + /// Note that a para has progressed to a new head, where the new head was executed in the context /// of a relay-chain block with given number. This will apply pending code upgrades based /// on the relay-parent block number provided. @@ -981,6 +986,11 @@ impl Pallet { } } + /// Worst case weight for `note_new_head`. + pub(crate) fn note_new_head_weight() -> Weight { + T::DbWeight::get().reads_writes(6, 6) + } + /// Returns the current lifecycle state of the para. pub fn lifecycle(id: ParaId) -> Option { ParaLifecycles::::get(&id) diff --git a/runtime/parachains/src/paras_inherent/mod.rs b/runtime/parachains/src/paras_inherent/mod.rs index 86fd19772efd..4fc39a477c1a 100644 --- a/runtime/parachains/src/paras_inherent/mod.rs +++ b/runtime/parachains/src/paras_inherent/mod.rs @@ -22,6 +22,8 @@ //! this module. use crate::{ + configuration, + configuration::HostConfiguration, disputes::DisputesHandler, inclusion, inclusion::{CandidateCheckContext, FullCheck}, @@ -61,8 +63,9 @@ mod weights; pub use self::{ misc::IndexedRetain, weights::{ - backed_candidate_weight, backed_candidates_weight, dispute_statements_weight, - paras_inherent_total_weight, signed_bitfields_weight, TestWeightInfo, WeightInfo, + backed_candidate_weight, backed_candidates_weight, bitfields_count_ones, + dispute_statements_weight, enact_candidates_weight, paras_inherent_total_weight, + signed_bitfields_weight, TestWeightInfo, WeightInfo, }, }; @@ -226,12 +229,15 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Enter the paras inherent. This will process bitfields and backed candidates. + /// Enter the paras inherent. This will process disputes, bitfields and backed candidates. #[pallet::weight(( paras_inherent_total_weight::( data.backed_candidates.as_slice(), data.bitfields.as_slice(), data.disputes.as_slice(), + scheduler::Pallet::::availability_cores().len(), + shared::Pallet::::active_validator_keys().len(), + &>::config(), ), DispatchClass::Mandatory, ))] @@ -280,10 +286,19 @@ impl Pallet { ); let now = >::block_number(); + let expected_bits = >::availability_cores().len(); let mut candidate_weight = backed_candidates_weight::(&backed_candidates); let mut bitfields_weight = signed_bitfields_weight::(signed_bitfields.len()); let disputes_weight = dispute_statements_weight::(&disputes); + let mut max_enact_candidates_weight = enact_candidates_weight::( + bitfields_count_ones( + &signed_bitfields, + expected_bits, + shared::Pallet::::active_validator_keys().len(), + ) as u32, + &>::config(), + ); let max_block_weight = ::BlockWeights::get().max_block; @@ -291,7 +306,8 @@ impl Pallet { let total_weight = { if candidate_weight .saturating_add(bitfields_weight) - .saturating_add(disputes_weight) > + .saturating_add(disputes_weight) + .saturating_add(max_enact_candidates_weight) > max_block_weight { // if the total weight is over the max block weight, first try clearing backed @@ -300,6 +316,7 @@ impl Pallet { candidate_weight = 0; signed_bitfields.clear(); bitfields_weight = 0; + max_enact_candidates_weight = 0; } if disputes_weight > max_block_weight { @@ -316,11 +333,10 @@ impl Pallet { candidate_weight .saturating_add(bitfields_weight) .saturating_add(disputes_weight) + .saturating_add(max_enact_candidates_weight) } }; - let expected_bits = >::availability_cores().len(); - // Handle disputes logic. let current_session = >::session_index(); let disputed_bitfield = { @@ -376,12 +392,13 @@ impl Pallet { // Process new availability bitfields, yielding any availability cores whose // work has now concluded. - let freed_concluded = >::process_bitfields( - expected_bits, - signed_bitfields, - disputed_bitfield, - >::core_para, - ); + let (freed_concluded, actual_enact_candidates_weight) = + >::process_bitfields( + expected_bits, + signed_bitfields, + disputed_bitfield, + >::core_para, + ); // Inform the disputes module of all included candidates. for (_, candidate_hash) in &freed_concluded { @@ -432,7 +449,13 @@ impl Pallet { // this is max config.ump_service_total_weight let _ump_weight = >::process_pending_upward_messages(); - Ok(Some(total_weight).into()) + let actual_total_weight = total_weight + // subtract the max enact candidate weight, + .saturating_sub(max_enact_candidates_weight) + // and add back the actual enact candidate weight + .saturating_add(actual_enact_candidates_weight); + + Ok(Some(actual_total_weight).into()) } } @@ -478,7 +501,7 @@ impl Pallet { T::DisputesHandler::filter_multi_dispute_data(&mut disputes); - let (mut backed_candidates, mut bitfields) = + let (mut backed_candidates, mut bitfields, cores_with_votes) = frame_support::storage::with_transaction(|| { // we don't care about fresh or not disputes // this writes them to storage, so let's query it via those means @@ -538,7 +561,7 @@ impl Pallet { // The following 3 calls are equiv to a call to `process_bitfields` // but we can retain access to `bitfields`. - let bitfields = sanitize_bitfields::( + let (bitfields, cores_with_votes) = sanitize_bitfields::( bitfields, disputed_bitfield, expected_bits, @@ -548,7 +571,7 @@ impl Pallet { FullCheck::Skip, ); - let freed_concluded = + let (freed_concluded, _enact_candidate_weight) = >::update_pending_availability_and_get_freed_cores::< _, false, @@ -594,6 +617,8 @@ impl Pallet { backed_candidates, // filtered bitfields bitfields, + // number of cores voted on by filtered bitfields + cores_with_votes.count_ones(), )) }); @@ -606,8 +631,10 @@ impl Pallet { &mut backed_candidates, &mut bitfields, &mut disputes, + cores_with_votes as u32, max_block_weight, &mut rng, + &>::config(), ); Some(ParachainsInherentData:: { @@ -711,17 +738,24 @@ fn apply_weight_limit( candidates: &mut Vec::Hash>>, bitfields: &mut UncheckedSignedAvailabilityBitfields, disputes: &mut MultiDisputeStatementSet, + cores_with_votes: u32, max_block_weight: Weight, rng: &mut rand_chacha::ChaChaRng, + config: &HostConfiguration, ) -> Weight { // include as many disputes as possible, always let remaining_weight = limit_disputes::(disputes, max_block_weight, rng); let total_candidates_weight = backed_candidates_weight::(candidates.as_slice()); + // we include the worst case weight of enacting the candidates voted for by the bitfields let total_bitfields_weight = signed_bitfields_weight::(bitfields.len()); - let total = total_bitfields_weight.saturating_add(total_candidates_weight); + let total_enact_candidates_weight = enact_candidates_weight::(cores_with_votes, config); + + let total = total_bitfields_weight + .saturating_add(total_candidates_weight) + .saturating_add(total_enact_candidates_weight); // candidates + bitfields fit into the block if remaining_weight >= total { @@ -740,7 +774,10 @@ fn apply_weight_limit( // There is weight remaining to be consumed by a subset of candidates // which are going to be picked now. - if let Some(remaining_weight) = remaining_weight.checked_sub(total_bitfields_weight) { + if let Some(remaining_weight) = remaining_weight + .checked_sub(total_bitfields_weight) + .and_then(|r| r.checked_sub(total_enact_candidates_weight)) + { let (acc_candidate_weight, indices) = random_sel::::Hash>, _>( rng, @@ -764,8 +801,11 @@ fn apply_weight_limit( rng, bitfields.clone(), vec![], + // bitfields can lead to enacting a candidate; however we don't have a good way of + // accounting for that when tracking individual bitfield weight, thus the weight here likely + // ends up being an underestimate. |_| <::WeightInfo as WeightInfo>::enter_bitfields(), - remaining_weight, + remaining_weight.saturating_sub(total_enact_candidates_weight), ); bitfields.indexed_retain(|idx, _bitfield| indices.binary_search(&idx).is_ok()); @@ -799,7 +839,7 @@ pub(crate) fn sanitize_bitfields( session_index: SessionIndex, validators: &[ValidatorId], full_check: FullCheck, -) -> UncheckedSignedAvailabilityBitfields { +) -> (UncheckedSignedAvailabilityBitfields, usize) { let mut bitfields = Vec::with_capacity(unchecked_bitfields.len()); let mut last_index: Option = None; @@ -808,21 +848,21 @@ pub(crate) fn sanitize_bitfields( // This is a system logic error that should never occur, but we want to handle it gracefully // so we just drop all bitfields log::error!(target: LOG_TARGET, "BUG: disputed_bitfield != expected_bits"); - return vec![] + return (vec![], 0) } let all_zeros = BitVec::::repeat(false, expected_bits); + let mut cores_with_votes = BitVec::::repeat(false, expected_bits); let signing_context = SigningContext { parent_hash, session_index }; for unchecked_bitfield in unchecked_bitfields { // Find and skip invalid bitfields. - if unchecked_bitfield.unchecked_payload().0.len() != expected_bits { - log::trace!( - target: LOG_TARGET, - "[{:?}] bad bitfield length: {} != {:?}", - full_check, - unchecked_bitfield.unchecked_payload().0.len(), - expected_bits, - ); + if !cheap_bitfield_checks( + &unchecked_bitfield, + expected_bits, + validators.len(), + last_index, + &full_check, + ) { continue } @@ -839,46 +879,74 @@ pub(crate) fn sanitize_bitfields( } let validator_index = unchecked_bitfield.unchecked_validator_index(); - - 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, - last_index.as_ref().map(|x| x.0), - validator_index.0 - ); - continue - } - - 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, - validator_index.0, - validators.len(), - ); - continue - } - let validator_public = &validators[validator_index.0 as usize]; if let FullCheck::Yes = full_check { if let Ok(signed_bitfield) = unchecked_bitfield.try_into_checked(&signing_context, validator_public) { + cores_with_votes |= signed_bitfield.payload().0.clone(); bitfields.push(signed_bitfield.into_unchecked()); } else { log::warn!(target: LOG_TARGET, "Invalid bitfield signature"); }; } else { + cores_with_votes |= unchecked_bitfield.unchecked_payload().0.clone(); bitfields.push(unchecked_bitfield); } last_index = Some(validator_index); } - bitfields + + (bitfields, cores_with_votes.count_ones()) +} + +// Returns `true` iff the checks pass. +fn cheap_bitfield_checks( + unchecked_bitfield: &UncheckedSignedAvailabilityBitfield, + expected_bits: usize, + validator_count: usize, + last_index: Option, + full_check: &FullCheck, +) -> bool { + if unchecked_bitfield.unchecked_payload().0.len() != expected_bits { + log::trace!( + target: LOG_TARGET, + "[{:?}] bad bitfield length: {} != {:?}", + full_check, + unchecked_bitfield.unchecked_payload().0.len(), + expected_bits, + ); + + return false + } + + let validator_index = unchecked_bitfield.unchecked_validator_index(); + 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, + last_index.as_ref().map(|x| x.0), + validator_index.0 + ); + + return false + } + + if unchecked_bitfield.unchecked_validator_index().0 as usize >= validator_count { + log::trace!( + target: LOG_TARGET, + "[{:?}] bitfield validator index is out of bounds: {} >= {}", + full_check, + validator_index.0, + validator_count, + ); + + return false + } + + true } /// Filter out any candidates that have a concluded invalid dispute. diff --git a/runtime/parachains/src/paras_inherent/tests.rs b/runtime/parachains/src/paras_inherent/tests.rs index 713a313dd9c6..b905089d1130 100644 --- a/runtime/parachains/src/paras_inherent/tests.rs +++ b/runtime/parachains/src/paras_inherent/tests.rs @@ -784,7 +784,7 @@ mod sanitizers { &validator_public[..], FullCheck::Skip, ), - unchecked_bitfields.clone() + (unchecked_bitfields.clone(), expected_bits) ); assert_eq!( sanitize_bitfields::( @@ -796,7 +796,7 @@ mod sanitizers { &validator_public[..], FullCheck::Yes ), - unchecked_bitfields.clone() + (unchecked_bitfields.clone(), expected_bits) ); } @@ -817,6 +817,7 @@ mod sanitizers { &validator_public[..], FullCheck::Yes ) + .0 .len(), 1 ); @@ -830,6 +831,7 @@ mod sanitizers { &validator_public[..], FullCheck::Skip ) + .0 .len(), 1 ); @@ -846,6 +848,7 @@ mod sanitizers { &validator_public[..], FullCheck::Yes ) + .0 .is_empty()); assert!(sanitize_bitfields::( unchecked_bitfields.clone(), @@ -856,6 +859,7 @@ mod sanitizers { &validator_public[..], FullCheck::Skip ) + .0 .is_empty()); } @@ -871,7 +875,8 @@ mod sanitizers { session_index, &validator_public[..shortened], FullCheck::Yes, - )[..], + ) + .0[..], &unchecked_bitfields[..shortened] ); assert_eq!( @@ -883,7 +888,8 @@ mod sanitizers { session_index, &validator_public[..shortened], FullCheck::Skip, - )[..], + ) + .0[..], &unchecked_bitfields[..shortened] ); } @@ -902,7 +908,8 @@ mod sanitizers { session_index, &validator_public[..], FullCheck::Yes - )[..], + ) + .0[..], &unchecked_bitfields[..(unchecked_bitfields.len() - 2)] ); assert_eq!( @@ -914,7 +921,8 @@ mod sanitizers { session_index, &validator_public[..], FullCheck::Skip - )[..], + ) + .0[..], &unchecked_bitfields[..(unchecked_bitfields.len() - 2)] ); } @@ -939,7 +947,8 @@ mod sanitizers { session_index, &validator_public[..], FullCheck::Yes - )[..], + ) + .0[..], &unchecked_bitfields[..last_bit_idx] ); assert_eq!( @@ -951,7 +960,8 @@ mod sanitizers { session_index, &validator_public[..], FullCheck::Skip - )[..], + ) + .0[..], &unchecked_bitfields[..] ); } diff --git a/runtime/parachains/src/paras_inherent/weights.rs b/runtime/parachains/src/paras_inherent/weights.rs index 06dcbe57d198..76aca9ff5dea 100644 --- a/runtime/parachains/src/paras_inherent/weights.rs +++ b/runtime/parachains/src/paras_inherent/weights.rs @@ -14,8 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . use super::{ - BackedCandidate, Config, DisputeStatementSet, UncheckedSignedAvailabilityBitfield, Weight, + cheap_bitfield_checks, BackedCandidate, Config, DisputeStatementSet, + UncheckedSignedAvailabilityBitfield, Weight, }; +use crate::{ + configuration::HostConfiguration, + inclusion::{self, FullCheck}, +}; +use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; +const MAX_UNCHECKED_BITFIELD_ITERATIONS: usize = 1_000; pub trait WeightInfo { /// Variant over `v`, the count of dispute statements in a dispute statement set. This gives the @@ -70,14 +77,54 @@ impl WeightInfo for TestWeightInfo { } } +// OR together all bitfields and count the ones from the result. +// Note that this will only OR together the first `MAX_UNCHECKED_BITFIELD_ITERATIONS` to avoid +// excessive iteration. +pub fn bitfields_count_ones( + bitfields: &[UncheckedSignedAvailabilityBitfield], + expected_bits: usize, + validator_count: usize, +) -> usize { + let mut last_index = None; + bitfields + .iter() + .take(MAX_UNCHECKED_BITFIELD_ITERATIONS) + .fold( + BitVec::::repeat(false, expected_bits), + |acc: BitVec, cur| { + if cheap_bitfield_checks( + cur, + expected_bits, + validator_count, + last_index, + &FullCheck::Skip, + ) { + last_index = Some(cur.unchecked_validator_index()); + acc | cur.unchecked_payload().0.clone() + } else { + acc + } + }, + ) + .count_ones() +} + +// Note that this does a storage read. pub fn paras_inherent_total_weight( backed_candidates: &[BackedCandidate<::Hash>], bitfields: &[UncheckedSignedAvailabilityBitfield], disputes: &[DisputeStatementSet], + expected_bits: usize, + validator_count: usize, + config: &HostConfiguration, ) -> Weight { backed_candidates_weight::(backed_candidates) .saturating_add(signed_bitfields_weight::(bitfields.len())) .saturating_add(dispute_statements_weight::(disputes)) + .saturating_add(enact_candidates_weight::( + bitfields_count_ones(bitfields, expected_bits, validator_count) as u32, + config, + )) } pub fn dispute_statements_weight(disputes: &[DisputeStatementSet]) -> Weight { @@ -96,6 +143,20 @@ pub fn signed_bitfields_weight(bitfields_len: usize) -> Weight { .saturating_mul(bitfields_len as Weight) } +// Calculate the worst case weight for enacting the given number of candidates. +pub fn enact_candidates_weight( + candidate_count: u32, + config: &HostConfiguration, +) -> Weight { + >::enact_candidate_weight( + config.hrmp_max_parathread_inbound_channels, + config.hrmp_max_parachain_inbound_channels, + config.max_upward_message_num_per_candidate, + config.hrmp_max_message_num_per_candidate, + ) + .saturating_mul(candidate_count as Weight) +} + pub fn backed_candidate_weight( candidate: &BackedCandidate, ) -> Weight { @@ -108,6 +169,7 @@ pub fn backed_candidate_weight( } } +// Calculate the max weight of the given candidates. pub fn backed_candidates_weight( candidates: &[BackedCandidate], ) -> Weight { diff --git a/runtime/parachains/src/ump.rs b/runtime/parachains/src/ump.rs index 9bcb393f9de8..0caaae0ec5fd 100644 --- a/runtime/parachains/src/ump.rs +++ b/runtime/parachains/src/ump.rs @@ -451,6 +451,18 @@ impl Pallet { weight } + /// Worst case weight for `receive_upward_messages`. + pub(crate) fn receive_upward_messages_weight( + max_upward_message_num_per_candidate: u32, + ) -> Weight { + use sp_runtime::traits::Zero; + if !max_upward_message_num_per_candidate.is_zero() { + T::DbWeight::get().reads_writes(3, 3) + } else { + 0 + } + } + /// Devote some time into dispatching pending upward messages. pub(crate) fn process_pending_upward_messages() -> Weight { let mut weight_used = 0;