use fabric_support::weights::{Weight, DispatchClass, constants, PerDispatchClass, OneOrMany};
use tp_runtime::{RuntimeDebug, Perbill};
#[derive(RuntimeDebug, Clone, codec::Encode, codec::Decode)]
pub struct BlockLength {
pub max: PerDispatchClass<u32>,
}
impl Default for BlockLength {
fn default() -> Self {
BlockLength::max_with_normal_ratio(
5 * 1024 * 1024,
DEFAULT_NORMAL_RATIO,
)
}
}
impl BlockLength {
pub fn max(max: u32) -> Self {
Self {
max: PerDispatchClass::new(|_| max),
}
}
pub fn max_with_normal_ratio(max: u32, normal: Perbill) -> Self {
Self {
max: PerDispatchClass::new(|class| if class == DispatchClass::Normal {
normal * max
} else {
max
}),
}
}
}
#[derive(Default, RuntimeDebug)]
pub struct ValidationErrors {
pub has_errors: bool,
#[cfg(feature = "std")]
pub errors: Vec<String>,
}
macro_rules! error_assert {
($cond : expr, $err : expr, $format : expr $(, $params: expr )*$(,)*) => {
if !$cond {
$err.has_errors = true;
#[cfg(feature = "std")]
{ $err.errors.push(format!($format $(, &$params )*)); }
}
}
}
pub type ValidationResult = Result<BlockWeights, ValidationErrors>;
const DEFAULT_NORMAL_RATIO: Perbill = Perbill::from_percent(75);
#[derive(RuntimeDebug, Clone, codec::Encode, codec::Decode)]
pub struct WeightsPerClass {
pub base_extrinsic: Weight,
pub max_extrinsic: Option<Weight>,
pub max_total: Option<Weight>,
pub reserved: Option<Weight>,
}
#[derive(RuntimeDebug, Clone, codec::Encode, codec::Decode)]
pub struct BlockWeights {
pub base_block: Weight,
pub max_block: Weight,
pub per_class: PerDispatchClass<WeightsPerClass>,
}
impl Default for BlockWeights {
fn default() -> Self {
Self::with_sensible_defaults(
1 * constants::WEIGHT_PER_SECOND,
DEFAULT_NORMAL_RATIO,
)
}
}
impl BlockWeights {
pub fn get(&self, class: DispatchClass) -> &WeightsPerClass {
self.per_class.get(class)
}
pub fn validate(self) -> ValidationResult {
fn or_max(w: Option<Weight>) -> Weight {
w.unwrap_or_else(|| Weight::max_value())
}
let mut error = ValidationErrors::default();
for class in DispatchClass::all() {
let weights = self.per_class.get(*class);
let max_for_class = or_max(weights.max_total);
let base_for_class = weights.base_extrinsic;
let reserved = or_max(weights.reserved);
error_assert!(
(max_for_class > self.base_block && max_for_class > base_for_class)
|| max_for_class == 0,
&mut error,
"[{:?}] {:?} (total) has to be greater than {:?} (base block) & {:?} (base extrinsic)",
class, max_for_class, self.base_block, base_for_class,
);
error_assert!(
weights.max_extrinsic.unwrap_or(0) <= max_for_class.saturating_sub(base_for_class),
&mut error,
"[{:?}] {:?} (max_extrinsic) can't be greater than {:?} (max for class)",
class, weights.max_extrinsic,
max_for_class.saturating_sub(base_for_class),
);
error_assert!(
weights.max_extrinsic.unwrap_or_else(|| Weight::max_value()) > 0,
&mut error,
"[{:?}] {:?} (max_extrinsic) must not be 0. Check base cost and average initialization cost.",
class, weights.max_extrinsic,
);
error_assert!(
reserved > base_for_class || reserved == 0,
&mut error,
"[{:?}] {:?} (reserved) has to be greater than {:?} (base extrinsic) if set",
class, reserved, base_for_class,
);
error_assert!(
self.max_block >= weights.max_total.unwrap_or(0),
&mut error,
"[{:?}] {:?} (max block) has to be greater than {:?} (max for class)",
class, self.max_block, weights.max_total,
);
error_assert!(
self.max_block > base_for_class + self.base_block,
&mut error,
"[{:?}] {:?} (max block) must fit at least one extrinsic {:?} (base weight)",
class, self.max_block, base_for_class + self.base_block,
);
}
if error.has_errors {
Err(error)
} else {
Ok(self)
}
}
pub fn simple_max(block_weight: Weight) -> Self {
Self::builder()
.base_block(0)
.for_class(DispatchClass::all(), |weights| {
weights.base_extrinsic = 0;
})
.for_class(DispatchClass::non_mandatory(), |weights| {
weights.max_total = block_weight.into();
})
.build()
.expect("We only specify max_total and leave base values as defaults; qed")
}
pub fn with_sensible_defaults(
expected_block_weight: Weight,
normal_ratio: Perbill,
) -> Self {
let normal_weight = normal_ratio * expected_block_weight;
Self::builder()
.for_class(DispatchClass::Normal, |weights| {
weights.max_total = normal_weight.into();
})
.for_class(DispatchClass::Operational, |weights| {
weights.max_total = expected_block_weight.into();
weights.reserved = (expected_block_weight - normal_weight).into();
})
.avg_block_initialization(Perbill::from_percent(10))
.build()
.expect("Sensible defaults are tested to be valid; qed")
}
pub fn builder() -> BlockWeightsBuilder {
BlockWeightsBuilder {
weights: BlockWeights {
base_block: constants::BlockExecutionWeight::get(),
max_block: 0,
per_class: PerDispatchClass::new(|class| {
let initial = if class == DispatchClass::Mandatory { None } else { Some(0) };
WeightsPerClass {
base_extrinsic: constants::ExtrinsicBaseWeight::get(),
max_extrinsic: None,
max_total: initial,
reserved: initial,
}
}),
},
init_cost: None,
}
}
}
pub struct BlockWeightsBuilder {
weights: BlockWeights,
init_cost: Option<Perbill>,
}
impl BlockWeightsBuilder {
pub fn base_block(mut self, base_block: Weight) -> Self {
self.weights.base_block = base_block;
self
}
pub fn avg_block_initialization(mut self, init_cost: Perbill) -> Self {
self.init_cost = Some(init_cost);
self
}
pub fn for_class(
mut self,
class: impl OneOrMany<DispatchClass>,
action: impl Fn(&mut WeightsPerClass),
) -> Self {
for class in class.into_iter() {
action(self.weights.per_class.get_mut(class));
}
self
}
pub fn build(self) -> ValidationResult {
let Self { mut weights, init_cost } = self;
for class in DispatchClass::all() {
weights.max_block = match weights.per_class.get(*class).max_total {
Some(max) if max > weights.max_block => max,
_ => weights.max_block,
};
}
if let Some(init_weight) = init_cost.map(|rate| rate * weights.max_block) {
for class in DispatchClass::all() {
let per_class = weights.per_class.get_mut(*class);
if per_class.max_extrinsic.is_none() && init_cost.is_some() {
per_class.max_extrinsic = per_class.max_total
.map(|x| x.saturating_sub(init_weight))
.map(|x| x.saturating_sub(per_class.base_extrinsic));
}
}
}
weights.validate()
}
pub fn build_or_panic(self) -> BlockWeights {
self.build().expect(
"Builder finished with `build_or_panic`; The panic is expected if runtime weights are not correct"
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_weights_are_valid() {
BlockWeights::default()
.validate()
.unwrap();
}
}