Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Correct arithmetical semantic of PerDispatchClass #13194

Merged
merged 10 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 193 additions & 16 deletions frame/support/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,33 +423,35 @@ impl<T: Clone> PerDispatchClass<T> {

impl PerDispatchClass<Weight> {
/// Returns the total weight consumed by all extrinsics in the block.
///
/// Saturates on overflow.
pub fn total(&self) -> Weight {
let mut sum = Weight::zero();
for class in DispatchClass::all() {
sum = sum.saturating_add(*self.get(*class));
sum.saturating_accrue(*self.get(*class));
}
sum
}

/// Add some weight of a specific dispatch class, saturating at the numeric bounds of `Weight`.
pub fn add(&mut self, weight: Weight, class: DispatchClass) {
let value = self.get_mut(class);
*value = value.saturating_add(weight);
/// Add some weight to the given class. Saturates at the numeric bounds.
pub fn saturating_add(mut self, weight: Weight, class: DispatchClass) -> Self {
self.saturating_accrue(weight, class);
self
}

/// Increase the weight of the given class. Saturates at the numeric bounds.
pub fn saturating_accrue(&mut self, weight: Weight, class: DispatchClass) {
self.get_mut(class).saturating_accrue(weight);
}

/// Try to add some weight of a specific dispatch class, returning Err(()) if overflow would
/// occur.
pub fn checked_add(&mut self, weight: Weight, class: DispatchClass) -> Result<(), ()> {
let value = self.get_mut(class);
*value = value.checked_add(&weight).ok_or(())?;
Ok(())
/// Try to increase the weight of the given class. Saturates at the numeric bounds.
pub fn checked_accrue(&mut self, weight: Weight, class: DispatchClass) -> Result<(), ()> {
self.get_mut(class).checked_accrue(weight).ok_or(())
}

/// Subtract some weight of a specific dispatch class, saturating at the numeric bounds of
/// `Weight`.
pub fn sub(&mut self, weight: Weight, class: DispatchClass) {
let value = self.get_mut(class);
*value = value.saturating_sub(weight);
/// Reduce the weight of the given class. Saturates at the numeric bounds.
pub fn saturating_reduce(&mut self, weight: Weight, class: DispatchClass) {
self.get_mut(class).saturating_reduce(weight);
}
}

Expand Down Expand Up @@ -3693,3 +3695,178 @@ mod weight_tests {
assert_eq!(extract_actual_pays_fee(&Ok((Some(1000), Pays::Yes).into()), &pre), Pays::No);
}
}

#[cfg(test)]
mod per_dispatch_class_tests {
use super::*;
use sp_runtime::traits::Zero;
use DispatchClass::*;

#[test]
fn saturating_add_works() {
let a = PerDispatchClass {
normal: (5, 10).into(),
operational: (20, 30).into(),
mandatory: Weight::MAX,
};
assert_eq!(
a.clone()
.saturating_add((20, 5).into(), Normal)
.saturating_add((10, 10).into(), Operational)
.saturating_add((u64::MAX, 3).into(), Mandatory),
PerDispatchClass {
normal: (25, 15).into(),
operational: (30, 40).into(),
mandatory: Weight::MAX
}
);
let b = a
.saturating_add(Weight::MAX, Normal)
.saturating_add(Weight::MAX, Operational)
.saturating_add(Weight::MAX, Mandatory);
assert_eq!(
b,
PerDispatchClass {
normal: Weight::MAX,
operational: Weight::MAX,
mandatory: Weight::MAX
}
);
assert_eq!(b.total(), Weight::MAX);
}

#[test]
fn saturating_accrue_works() {
let mut a = PerDispatchClass::default();

a.saturating_accrue((10, 15).into(), Normal);
assert_eq!(a.normal, (10, 15).into());
assert_eq!(a.total(), (10, 15).into());

a.saturating_accrue((20, 25).into(), Operational);
assert_eq!(a.operational, (20, 25).into());
assert_eq!(a.total(), (30, 40).into());

a.saturating_accrue((30, 35).into(), Mandatory);
assert_eq!(a.mandatory, (30, 35).into());
assert_eq!(a.total(), (60, 75).into());

a.saturating_accrue((u64::MAX, 10).into(), Operational);
assert_eq!(a.operational, (u64::MAX, 35).into());
assert_eq!(a.total(), (u64::MAX, 85).into());

a.saturating_accrue((10, u64::MAX).into(), Normal);
assert_eq!(a.normal, (20, u64::MAX).into());
assert_eq!(a.total(), Weight::MAX);
}

#[test]
fn saturating_reduce_works() {
let mut a = PerDispatchClass {
normal: (10, u64::MAX).into(),
mandatory: (u64::MAX, 10).into(),
operational: (20, 20).into(),
};

a.saturating_reduce((5, 100).into(), Normal);
assert_eq!(a.normal, (5, u64::MAX - 100).into());
assert_eq!(a.total(), (u64::MAX, u64::MAX - 70).into());

a.saturating_reduce((15, 5).into(), Operational);
assert_eq!(a.operational, (5, 15).into());
assert_eq!(a.total(), (u64::MAX, u64::MAX - 75).into());

a.saturating_reduce((50, 0).into(), Mandatory);
assert_eq!(a.mandatory, (u64::MAX - 50, 10).into());
assert_eq!(a.total(), (u64::MAX - 40, u64::MAX - 75).into());

a.saturating_reduce((u64::MAX, 100).into(), Operational);
assert!(a.operational.is_zero());
assert_eq!(a.total(), (u64::MAX - 45, u64::MAX - 90).into());

a.saturating_reduce((5, u64::MAX).into(), Normal);
assert!(a.normal.is_zero());
assert_eq!(a.total(), (u64::MAX - 50, 10).into());
}

#[test]
fn checked_accrue_works() {
let mut a = PerDispatchClass::default();

a.checked_accrue((1, 2).into(), Normal).unwrap();
a.checked_accrue((3, 4).into(), Operational).unwrap();
a.checked_accrue((5, 6).into(), Mandatory).unwrap();
a.checked_accrue((7, 8).into(), Operational).unwrap();
a.checked_accrue((9, 0).into(), Normal).unwrap();

assert_eq!(
a,
PerDispatchClass {
normal: (10, 2).into(),
operational: (10, 12).into(),
mandatory: (5, 6).into(),
}
);

a.checked_accrue((u64::MAX - 10, u64::MAX - 2).into(), Normal).unwrap();
a.checked_accrue((0, 0).into(), Normal).unwrap();
a.checked_accrue((1, 0).into(), Normal).unwrap_err();
a.checked_accrue((0, 1).into(), Normal).unwrap_err();

assert_eq!(
a,
PerDispatchClass {
normal: Weight::MAX,
operational: (10, 12).into(),
mandatory: (5, 6).into(),
}
);
}

#[test]
fn checked_accrue_does_not_modify_on_error() {
let mut a = PerDispatchClass {
normal: 0.into(),
operational: Weight::MAX / 2 + 2.into(),
mandatory: 10.into(),
};

a.checked_accrue(Weight::MAX / 2, Operational).unwrap_err();
a.checked_accrue(Weight::MAX - 9.into(), Mandatory).unwrap_err();
a.checked_accrue(Weight::MAX, Normal).unwrap(); // This one works

assert_eq!(
a,
PerDispatchClass {
normal: Weight::MAX,
operational: Weight::MAX / 2 + 2.into(),
mandatory: 10.into(),
}
);
}

#[test]
fn total_works() {
assert!(PerDispatchClass::default().total().is_zero());

assert_eq!(
PerDispatchClass {
normal: 0.into(),
operational: (10, 20).into(),
mandatory: (20, u64::MAX).into(),
}
.total(),
(30, u64::MAX).into()
);

assert_eq!(
PerDispatchClass {
normal: (u64::MAX - 10, 10).into(),
operational: (3, u64::MAX).into(),
mandatory: (4, u64::MAX).into(),
}
.total(),
(u64::MAX - 3, u64::MAX).into()
);
}
}
10 changes: 1 addition & 9 deletions frame/support/src/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//! Re-exports `sp-weights` public API, and contains benchmarked weight constants specific to
//! FRAME.
//!
//! Latest machine specification used to benchmark are:
//! - Digital Ocean: ubuntu-s-2vcpu-4gb-ams3-01
//! - 2x Intel(R) Xeon(R) CPU E5-2650 v4 @ 2.20GHz
//! - 4GB RAM
//! - Ubuntu 19.10 (GNU/Linux 5.3.0-18-generic x86_64)
//! - rustc 1.42.0 (b8cedc004 2020-03-09)
//! Re-exports `sp-weights` public API, and contains benchmarked weight constants specific to FRAME.

mod block_weights;
mod extrinsic_weights;
Expand Down
6 changes: 3 additions & 3 deletions frame/system/src/extensions/check_weight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@ where

// add the weight. If class is unlimited, use saturating add instead of checked one.
if limit_per_class.max_total.is_none() && limit_per_class.reserved.is_none() {
all_weight.add(extrinsic_weight, info.class)
all_weight.saturating_accrue(extrinsic_weight, info.class)
} else {
all_weight
.checked_add(extrinsic_weight, info.class)
.checked_accrue(extrinsic_weight, info.class)
.map_err(|_| InvalidTransaction::ExhaustsResources)?;
}

Expand Down Expand Up @@ -229,7 +229,7 @@ where
let unspent = post_info.calc_unspent(info);
if unspent.any_gt(Weight::zero()) {
crate::BlockWeight::<T>::mutate(|current_weight| {
current_weight.sub(unspent, info.class);
current_weight.saturating_reduce(unspent, info.class);
})
}

Expand Down
2 changes: 1 addition & 1 deletion frame/system/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1317,7 +1317,7 @@ impl<T: Config> Pallet<T> {
/// Another potential use-case could be for the `on_initialize` and `on_finalize` hooks.
pub fn register_extra_weight_unchecked(weight: Weight, class: DispatchClass) {
BlockWeight::<T>::mutate(|current_weight| {
current_weight.add(weight, class);
current_weight.saturating_accrue(weight, class);
});
}

Expand Down
7 changes: 0 additions & 7 deletions primitives/weights/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@
// limitations under the License.

//! # Primitives for transaction weighting.
//!
//! Latest machine specification used to benchmark are:
//! - Digital Ocean: ubuntu-s-2vcpu-4gb-ams3-01
//! - 2x Intel(R) Xeon(R) CPU E5-2650 v4 @ 2.20GHz
//! - 4GB RAM
//! - Ubuntu 19.10 (GNU/Linux 5.3.0-18-generic x86_64)
//! - rustc 1.42.0 (b8cedc004 2020-03-09)

#![cfg_attr(not(feature = "std"), no_std)]

Expand Down
Loading