Streamline frame_system weight parametrization (#6629)

* Basic weights builder.

* Fixing WiP

* Make the tests work.

* Fix weights in node/runtime.

* WiP.

* Update pallets with new weights parameters.

* Validate returns a Result now.

* Count mandatory weight separately.

* DRY

* BREAKING: Updating state root, because of the left-over weight-tracking stuff

* Update tests affected by Mandatory tracking.

* Fixing tests.

* Fix defaults for simple_max

* Update frame/system/src/weights.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Rework the API a bit.

* Fix compilation & tests.

* Apply suggestions from code review

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Add extra docs & rename few things.

* Fix whitespace in ASCII art.

* Update frame/system/src/limits.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Fix max_extrinsic calculations.

* Fix conflicts.

* Fix compilation.

* Fix new code.

* re-remove generic asset

* Fix usage.

* Update state root.

* Update proxy.

* Fix tests.

* Move weights validity to integrity_test

* Remove redundant BlockWeights.

* Add all/non_mandatory comment

* Add test.

* Remove fn block_weights

* Make the macro prettier.

* Fix some docs.

* Make max_total behave more predictabily.

* Add BlockWeights to metadata.

* fix balances test

* Fix utility test.

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Benjamin Kampmann <ben@gnunicorn.org>
Co-authored-by: thiolliere <gui.thiolliere@gmail.com>
This commit is contained in:
Tomasz Drwięga
2020-12-08 13:18:34 +01:00
committed by GitHub
parent f6198b4c1b
commit 39a776cd00
66 changed files with 1275 additions and 929 deletions
@@ -15,7 +15,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{Config, Module};
use crate::{limits::BlockWeights, Config, Module};
use codec::{Encode, Decode};
use sp_runtime::{
traits::{SignedExtension, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Printable},
@@ -23,7 +23,7 @@ use sp_runtime::{
ValidTransaction, TransactionValidityError, InvalidTransaction, TransactionValidity,
TransactionPriority,
},
Perbill, DispatchResult,
DispatchResult,
};
use frame_support::{
traits::{Get},
@@ -36,52 +36,19 @@ use frame_support::{
pub struct CheckWeight<T: Config + Send + Sync>(sp_std::marker::PhantomData<T>);
impl<T: Config + Send + Sync> CheckWeight<T> where
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>,
{
/// Get the quota ratio of each dispatch class type. This indicates that all operational and mandatory
/// dispatches can use the full capacity of any resource, while user-triggered ones can consume
/// a portion.
fn get_dispatch_limit_ratio(class: DispatchClass) -> Perbill {
match class {
DispatchClass::Operational | DispatchClass::Mandatory
=> <Perbill as sp_runtime::PerThing>::one(),
DispatchClass::Normal => T::AvailableBlockRatio::get(),
}
}
/// Checks if the current extrinsic does not exceed `MaximumExtrinsicWeight` limit.
/// Checks if the current extrinsic does not exceed the maximum weight a single extrinsic
/// with given `DispatchClass` can have.
fn check_extrinsic_weight(
info: &DispatchInfoOf<T::Call>,
) -> Result<(), TransactionValidityError> {
match info.class {
// Mandatory transactions are included in a block unconditionally, so
// we don't verify weight.
DispatchClass::Mandatory => Ok(()),
// Normal transactions must not exceed `MaximumExtrinsicWeight`.
DispatchClass::Normal => {
let maximum_weight = T::MaximumExtrinsicWeight::get();
let extrinsic_weight = info.weight.saturating_add(T::ExtrinsicBaseWeight::get());
if extrinsic_weight > maximum_weight {
Err(InvalidTransaction::ExhaustsResources.into())
} else {
Ok(())
}
},
// For operational transactions we make sure it doesn't exceed
// the space alloted for `Operational` class.
DispatchClass::Operational => {
let maximum_weight = T::MaximumBlockWeight::get();
let operational_limit =
Self::get_dispatch_limit_ratio(DispatchClass::Operational) * maximum_weight;
let operational_limit =
operational_limit.saturating_sub(T::BlockExecutionWeight::get());
let extrinsic_weight = info.weight.saturating_add(T::ExtrinsicBaseWeight::get());
if extrinsic_weight > operational_limit {
Err(InvalidTransaction::ExhaustsResources.into())
} else {
Ok(())
}
let max = T::BlockWeights::get().get(info.class).max_extrinsic;
match max {
Some(max) if info.weight > max => {
Err(InvalidTransaction::ExhaustsResources.into())
},
_ => Ok(()),
}
}
@@ -90,51 +57,10 @@ impl<T: Config + Send + Sync> CheckWeight<T> where
/// Upon successes, it returns the new block weight as a `Result`.
fn check_block_weight(
info: &DispatchInfoOf<T::Call>,
) -> Result<crate::weight::ExtrinsicsWeight, TransactionValidityError> {
let maximum_weight = T::MaximumBlockWeight::get();
let mut all_weight = Module::<T>::block_weight();
match info.class {
// If we have a dispatch that must be included in the block, it ignores all the limits.
DispatchClass::Mandatory => {
let extrinsic_weight = info.weight.saturating_add(T::ExtrinsicBaseWeight::get());
all_weight.add(extrinsic_weight, DispatchClass::Mandatory);
Ok(all_weight)
},
// If we have a normal dispatch, we follow all the normal rules and limits.
DispatchClass::Normal => {
let normal_limit = Self::get_dispatch_limit_ratio(DispatchClass::Normal) * maximum_weight;
let extrinsic_weight = info.weight.checked_add(T::ExtrinsicBaseWeight::get())
.ok_or(InvalidTransaction::ExhaustsResources)?;
all_weight.checked_add(extrinsic_weight, DispatchClass::Normal)
.map_err(|_| InvalidTransaction::ExhaustsResources)?;
if all_weight.get(DispatchClass::Normal) > normal_limit {
Err(InvalidTransaction::ExhaustsResources.into())
} else {
Ok(all_weight)
}
},
// If we have an operational dispatch, allow it if we have not used our full
// "operational space" (independent of existing fullness).
DispatchClass::Operational => {
let operational_limit = Self::get_dispatch_limit_ratio(DispatchClass::Operational) * maximum_weight;
let normal_limit = Self::get_dispatch_limit_ratio(DispatchClass::Normal) * maximum_weight;
let operational_space = operational_limit.saturating_sub(normal_limit);
let extrinsic_weight = info.weight.checked_add(T::ExtrinsicBaseWeight::get())
.ok_or(InvalidTransaction::ExhaustsResources)?;
all_weight.checked_add(extrinsic_weight, DispatchClass::Operational)
.map_err(|_| InvalidTransaction::ExhaustsResources)?;
// If it would fit in normally, its okay
if all_weight.total() <= maximum_weight ||
// If we have not used our operational space
all_weight.get(DispatchClass::Operational) <= operational_space {
Ok(all_weight)
} else {
Err(InvalidTransaction::ExhaustsResources.into())
}
}
}
) -> Result<crate::ConsumedWeight, TransactionValidityError> {
let maximum_weight = T::BlockWeights::get();
let all_weight = Module::<T>::block_weight();
calculate_consumed_weight::<T::Call>(maximum_weight, all_weight, info)
}
/// Checks if the current extrinsic can fit into the block with respect to block length limits.
@@ -144,19 +70,18 @@ impl<T: Config + Send + Sync> CheckWeight<T> where
info: &DispatchInfoOf<T::Call>,
len: usize,
) -> Result<u32, TransactionValidityError> {
let length_limit = T::BlockLength::get();
let current_len = Module::<T>::all_extrinsics_len();
let maximum_len = T::MaximumBlockLength::get();
let limit = Self::get_dispatch_limit_ratio(info.class) * maximum_len;
let added_len = len as u32;
let next_len = current_len.saturating_add(added_len);
if next_len > limit {
if next_len > *length_limit.max.get(info.class) {
Err(InvalidTransaction::ExhaustsResources.into())
} else {
Ok(next_len)
}
}
/// get the priority of an extrinsic denoted by `info`.
/// Get the priority of an extrinsic denoted by `info`.
///
/// Operational transaction will be given a fixed initial amount to be fairly distinguished from
/// the normal ones.
@@ -213,6 +138,53 @@ impl<T: Config + Send + Sync> CheckWeight<T> where
}
}
pub fn calculate_consumed_weight<Call>(
maximum_weight: BlockWeights,
mut all_weight: crate::ConsumedWeight,
info: &DispatchInfoOf<Call>,
) -> Result<crate::ConsumedWeight, TransactionValidityError> where
Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>,
{
let extrinsic_weight = info.weight.saturating_add(maximum_weight.get(info.class).base_extrinsic);
let limit_per_class = maximum_weight.get(info.class);
// 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)
} else {
all_weight.checked_add(extrinsic_weight, info.class)
.map_err(|_| InvalidTransaction::ExhaustsResources)?;
}
let per_class = *all_weight.get(info.class);
// Check if we don't exceed per-class allowance
match limit_per_class.max_total {
Some(max) if per_class > max => {
return Err(InvalidTransaction::ExhaustsResources.into());
},
// There is no `max_total` limit (`None`),
// or we are below the limit.
_ => {},
}
// In cases total block weight is exceeded, we need to fall back
// to `reserved` pool if there is any.
if all_weight.total() > maximum_weight.max_block {
match limit_per_class.reserved {
// We are over the limit in reserved pool.
Some(reserved) if per_class > reserved => {
return Err(InvalidTransaction::ExhaustsResources.into());
}
// There is either no limit in reserved pool (`None`),
// or we are below the limit.
_ => {},
}
}
Ok(all_weight)
}
impl<T: Config + Send + Sync> SignedExtension for CheckWeight<T> where
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>
{
@@ -277,7 +249,7 @@ impl<T: Config + Send + Sync> SignedExtension for CheckWeight<T> where
// to them actually being useful. Block producers are thus not allowed to include mandatory
// extrinsics that result in error.
if let (DispatchClass::Mandatory, Err(e)) = (info.class, result) {
"Bad mandantory".print();
"Bad mandatory".print();
e.print();
Err(InvalidTransaction::BadMandatory)?
@@ -315,12 +287,21 @@ mod tests {
use frame_support::{assert_ok, assert_noop};
use frame_support::weights::{Weight, Pays};
fn block_weights() -> crate::limits::BlockWeights {
<Test as crate::Config>::BlockWeights::get()
}
fn normal_weight_limit() -> Weight {
<Test as Config>::AvailableBlockRatio::get() * <Test as Config>::MaximumBlockWeight::get()
block_weights().get(DispatchClass::Normal).max_total
.unwrap_or_else(|| block_weights().max_block)
}
fn block_weight_limit() -> Weight {
block_weights().max_block
}
fn normal_length_limit() -> u32 {
<Test as Config>::AvailableBlockRatio::get() * <Test as Config>::MaximumBlockLength::get()
*<Test as Config>::BlockLength::get().max.get(DispatchClass::Normal)
}
#[test]
@@ -341,7 +322,7 @@ mod tests {
check(|max, len| {
assert_ok!(CheckWeight::<Test>::do_pre_dispatch(max, len));
assert_eq!(System::block_weight().total(), Weight::max_value());
assert!(System::block_weight().total() > <Test as Config>::MaximumBlockWeight::get());
assert!(System::block_weight().total() > block_weight_limit());
});
check(|max, len| {
assert_ok!(CheckWeight::<Test>::do_validate(max, len));
@@ -352,7 +333,7 @@ mod tests {
fn normal_extrinsic_limited_by_maximum_extrinsic_weight() {
new_test_ext().execute_with(|| {
let max = DispatchInfo {
weight: <Test as Config>::MaximumExtrinsicWeight::get() + 1,
weight: block_weights().get(DispatchClass::Normal).max_extrinsic.unwrap() + 1,
class: DispatchClass::Normal,
..Default::default()
};
@@ -368,13 +349,12 @@ mod tests {
#[test]
fn operational_extrinsic_limited_by_operational_space_limit() {
new_test_ext().execute_with(|| {
let operational_limit = CheckWeight::<Test>::get_dispatch_limit_ratio(
DispatchClass::Operational
) * <Test as Config>::MaximumBlockWeight::get();
let base_weight = <Test as Config>::ExtrinsicBaseWeight::get();
let block_base = <Test as Config>::BlockExecutionWeight::get();
let weights = block_weights();
let operational_limit = weights.get(DispatchClass::Operational).max_total
.unwrap_or_else(|| weights.max_block);
let base_weight = weights.get(DispatchClass::Normal).base_extrinsic;
let weight = operational_limit - base_weight - block_base;
let weight = operational_limit - base_weight;
let okay = DispatchInfo {
weight,
class: DispatchClass::Operational,
@@ -406,7 +386,7 @@ mod tests {
new_test_ext().execute_with(|| {
System::register_extra_weight_unchecked(Weight::max_value(), DispatchClass::Normal);
assert_eq!(System::block_weight().total(), Weight::max_value());
assert!(System::block_weight().total() > <Test as Config>::MaximumBlockWeight::get());
assert!(System::block_weight().total() > block_weight_limit());
});
}
@@ -426,8 +406,8 @@ mod tests {
assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&max_normal, len));
assert_eq!(System::block_weight().total(), 768);
assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&rest_operational, len));
assert_eq!(<Test as Config>::MaximumBlockWeight::get(), 1024);
assert_eq!(System::block_weight().total(), <Test as Config>::MaximumBlockWeight::get());
assert_eq!(block_weight_limit(), 1024);
assert_eq!(System::block_weight().total(), block_weight_limit());
// Checking single extrinsic should not take current block weight into account.
assert_eq!(CheckWeight::<Test>::check_extrinsic_weight(&rest_operational), Ok(()));
});
@@ -446,8 +426,8 @@ mod tests {
// Extra 15 here from block execution + base extrinsic weight
assert_eq!(System::block_weight().total(), 266);
assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&max_normal, len));
assert_eq!(<Test as Config>::MaximumBlockWeight::get(), 1024);
assert_eq!(System::block_weight().total(), <Test as Config>::MaximumBlockWeight::get());
assert_eq!(block_weight_limit(), 1024);
assert_eq!(System::block_weight().total(), block_weight_limit());
});
}
@@ -486,7 +466,7 @@ mod tests {
// given almost full block
BlockWeight::mutate(|current_weight| {
current_weight.put(normal_limit, DispatchClass::Normal)
current_weight.set(normal_limit, DispatchClass::Normal)
});
// will not fit.
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &normal, len).is_err());
@@ -552,19 +532,20 @@ mod tests {
new_test_ext().execute_with(|| {
let normal_limit = normal_weight_limit();
let small = DispatchInfo { weight: 100, ..Default::default() };
let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
let medium = DispatchInfo {
weight: normal_limit - <Test as Config>::ExtrinsicBaseWeight::get(),
weight: normal_limit - base_extrinsic,
..Default::default()
};
let big = DispatchInfo {
weight: normal_limit - <Test as Config>::ExtrinsicBaseWeight::get() + 1,
weight: normal_limit - base_extrinsic + 1,
..Default::default()
};
let len = 0_usize;
let reset_check_weight = |i, f, s| {
BlockWeight::mutate(|current_weight| {
current_weight.put(s, DispatchClass::Normal)
current_weight.set(s, DispatchClass::Normal)
});
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, i, len);
if f { assert!(r.is_err()) } else { assert!(r.is_ok()) }
@@ -586,10 +567,12 @@ mod tests {
pays_fee: Default::default(),
};
let len = 0_usize;
let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
// We allow 75% for normal transaction, so we put 25% - extrinsic base weight
BlockWeight::mutate(|current_weight| {
current_weight.put(256 - <Test as Config>::ExtrinsicBaseWeight::get(), DispatchClass::Normal)
current_weight.set(0, DispatchClass::Mandatory);
current_weight.set(256 - base_extrinsic, DispatchClass::Normal);
});
let pre = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap();
@@ -617,13 +600,14 @@ mod tests {
let len = 0_usize;
BlockWeight::mutate(|current_weight| {
current_weight.put(128, DispatchClass::Normal)
current_weight.set(0, DispatchClass::Mandatory);
current_weight.set(128, DispatchClass::Normal);
});
let pre = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap();
assert_eq!(
BlockWeight::get().total(),
info.weight + 128 + <Test as Config>::ExtrinsicBaseWeight::get(),
info.weight + 128 + block_weights().get(DispatchClass::Normal).base_extrinsic,
);
assert!(
@@ -632,7 +616,7 @@ mod tests {
);
assert_eq!(
BlockWeight::get().total(),
info.weight + 128 + <Test as Config>::ExtrinsicBaseWeight::get(),
info.weight + 128 + block_weights().get(DispatchClass::Normal).base_extrinsic,
);
})
}
@@ -640,17 +624,81 @@ mod tests {
#[test]
fn zero_weight_extrinsic_still_has_base_weight() {
new_test_ext().execute_with(|| {
let weights = block_weights();
let free = DispatchInfo { weight: 0, ..Default::default() };
let len = 0_usize;
// Initial weight from `BlockExecutionWeight`
assert_eq!(System::block_weight().total(), <Test as Config>::BlockExecutionWeight::get());
// Initial weight from `weights.base_block`
assert_eq!(
System::block_weight().total(),
weights.base_block
);
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &free, len);
assert!(r.is_ok());
assert_eq!(
System::block_weight().total(),
<Test as Config>::ExtrinsicBaseWeight::get() + <Test as Config>::BlockExecutionWeight::get()
weights.get(DispatchClass::Normal).base_extrinsic + weights.base_block
);
})
}
#[test]
fn normal_and_mandatory_tracked_separately() {
new_test_ext().execute_with(|| {
// Max block is 1024
// Max normal is 768 (75%)
// Max mandatory is unlimited
let max_normal = DispatchInfo { weight: 753, ..Default::default() };
let mandatory = DispatchInfo { weight: 1019, class: DispatchClass::Mandatory, ..Default::default() };
let len = 0_usize;
assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&max_normal, len));
assert_eq!(System::block_weight().total(), 768);
assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&mandatory, len));
assert_eq!(block_weight_limit(), 1024);
assert_eq!(System::block_weight().total(), 1024 + 768);
assert_eq!(CheckWeight::<Test>::check_extrinsic_weight(&mandatory), Ok(()));
});
}
#[test]
fn no_max_total_should_still_be_limited_by_max_block() {
// given
let maximum_weight = BlockWeights::builder()
.base_block(0)
.for_class(DispatchClass::non_mandatory(), |w| {
w.base_extrinsic = 0;
w.max_total = Some(20);
})
.for_class(DispatchClass::Mandatory, |w| {
w.base_extrinsic = 0;
w.reserved = Some(5);
w.max_total = None;
})
.build_or_panic();
let all_weight = crate::ConsumedWeight::new(|class| match class {
DispatchClass::Normal => 10,
DispatchClass::Operational => 10,
DispatchClass::Mandatory => 0,
});
assert_eq!(maximum_weight.max_block, all_weight.total());
// fits into reserved
let mandatory1 = DispatchInfo { weight: 5, class: DispatchClass::Mandatory, ..Default::default() };
// does not fit into reserved and the block is full.
let mandatory2 = DispatchInfo { weight: 6, class: DispatchClass::Mandatory, ..Default::default() };
// when
let result1 = calculate_consumed_weight::<<Test as Config>::Call>(
maximum_weight.clone(), all_weight.clone(), &mandatory1
);
let result2 = calculate_consumed_weight::<<Test as Config>::Call>(
maximum_weight, all_weight, &mandatory2
);
// then
assert!(result2.is_err());
assert!(result1.is_ok());
}
}
+27 -43
View File
@@ -122,7 +122,7 @@ use frame_support::{
},
weights::{
Weight, RuntimeDbWeight, DispatchInfo, DispatchClass,
extract_actual_weight,
extract_actual_weight, PerDispatchClass,
},
dispatch::DispatchResultWithPostInfo,
};
@@ -132,15 +132,16 @@ use codec::{Encode, Decode, FullCodec, EncodeLike};
use sp_io::TestExternalities;
pub mod offchain;
pub mod limits;
#[cfg(test)]
pub(crate) mod mock;
mod extensions;
mod weight;
pub mod weights;
#[cfg(test)]
mod tests;
pub use extensions::{
check_mortality::CheckMortality, check_genesis::CheckGenesis, check_nonce::CheckNonce,
check_spec_version::CheckSpecVersion, check_tx_version::CheckTxVersion,
@@ -160,11 +161,20 @@ pub fn extrinsics_data_root<H: Hash>(xts: Vec<Vec<u8>>) -> H::Output {
H::ordered_trie_root(xts)
}
/// An object to track the currently used extrinsic weight in a block.
pub type ConsumedWeight = PerDispatchClass<Weight>;
pub trait Config: 'static + Eq + Clone {
/// The basic call filter to use in Origin. All origins are built with this filter as base,
/// except Root.
type BaseCallFilter: Filter<Self::Call>;
/// Block & extrinsics weights: base values and limits.
type BlockWeights: Get<limits::BlockWeights>;
/// The maximum length of a block (in bytes).
type BlockLength: Get<limits::BlockLength>;
/// The `Origin` type used by dispatchable calls.
type Origin:
Into<Result<RawOrigin<Self::AccountId>, Self::Origin>>
@@ -219,31 +229,9 @@ pub trait Config: 'static + Eq + Clone {
/// Maximum number of block number to block hash mappings to keep (oldest pruned first).
type BlockHashCount: Get<Self::BlockNumber>;
/// The maximum weight of a block.
type MaximumBlockWeight: Get<Weight>;
/// The weight of runtime database operations the runtime can invoke.
type DbWeight: Get<RuntimeDbWeight>;
/// The base weight of executing a block, independent of the transactions in the block.
type BlockExecutionWeight: Get<Weight>;
/// The base weight of an Extrinsic in the block, independent of the of extrinsic being executed.
type ExtrinsicBaseWeight: Get<Weight>;
/// The maximal weight of a single Extrinsic. This should be set to at most
/// `MaximumBlockWeight - AverageOnInitializeWeight`. The limit only applies to extrinsics
/// containing `Normal` dispatch class calls.
type MaximumExtrinsicWeight: Get<Weight>;
/// The maximum length of a block (in bytes).
type MaximumBlockLength: Get<u32>;
/// The portion of the block that is available to normal transaction. The rest can only be used
/// by operational transactions. This can be applied to any resource limit managed by the system
/// module, including weight and length.
type AvailableBlockRatio: Get<Perbill>;
/// Get the chain's current version.
type Version: Get<RuntimeVersion>;
@@ -399,7 +387,7 @@ decl_storage! {
ExtrinsicCount: Option<u32>;
/// The current weight for the block.
BlockWeight get(fn block_weight): weight::ExtrinsicsWeight;
BlockWeight get(fn block_weight): ConsumedWeight;
/// Total length (in bytes) for all extrinsics put together, for the current block.
AllExtrinsicsLen: Option<u32>;
@@ -519,20 +507,11 @@ decl_module! {
/// The maximum number of blocks to allow in mortal eras.
const BlockHashCount: T::BlockNumber = T::BlockHashCount::get();
/// The maximum weight of a block.
const MaximumBlockWeight: Weight = T::MaximumBlockWeight::get();
/// The weight of runtime database operations the runtime can invoke.
const DbWeight: RuntimeDbWeight = T::DbWeight::get();
/// The base weight of executing a block, independent of the transactions in the block.
const BlockExecutionWeight: Weight = T::BlockExecutionWeight::get();
/// The base weight of an Extrinsic in the block, independent of the of extrinsic being executed.
const ExtrinsicBaseWeight: Weight = T::ExtrinsicBaseWeight::get();
/// The maximum length of a block (in bytes).
const MaximumBlockLength: u32 = T::MaximumBlockLength::get();
/// The weight configuration (limits & base values) for each class of extrinsics and block.
const BlockWeights: limits::BlockWeights = T::BlockWeights::get();
fn on_runtime_upgrade() -> frame_support::weights::Weight {
if !UpgradedToU32RefCount::get() {
@@ -540,16 +519,22 @@ decl_module! {
Some(AccountInfo { nonce, refcount: rc as RefCount, data })
);
UpgradedToU32RefCount::put(true);
T::MaximumBlockWeight::get()
T::BlockWeights::get().max_block
} else {
0
}
}
fn integrity_test() {
T::BlockWeights::get()
.validate()
.expect("The weights are invalid.");
}
/// A dispatch that will fill the block weight up to the given ratio.
// TODO: This should only be available for testing, rather than in general usage, but
// that's not possible at present (since it's within the decl_module macro).
#[weight = *_ratio * T::MaximumBlockWeight::get()]
#[weight = *_ratio * T::BlockWeights::get().max_block]
fn fill_block(origin, _ratio: Perbill) {
ensure_root(origin)?;
}
@@ -590,7 +575,7 @@ decl_module! {
/// The weight of this function is dependent on the runtime, but generally this is very expensive.
/// We will treat this as a full block.
/// # </weight>
#[weight = (T::MaximumBlockWeight::get(), DispatchClass::Operational)]
#[weight = (T::BlockWeights::get().max_block, DispatchClass::Operational)]
pub fn set_code(origin, code: Vec<u8>) {
ensure_root(origin)?;
Self::can_set_code(&code)?;
@@ -607,7 +592,7 @@ decl_module! {
/// - 1 event.
/// The weight of this function is dependent on the runtime. We will treat this as a full block.
/// # </weight>
#[weight = (T::MaximumBlockWeight::get(), DispatchClass::Operational)]
#[weight = (T::BlockWeights::get().max_block, DispatchClass::Operational)]
pub fn set_code_without_checks(origin, code: Vec<u8>) {
ensure_root(origin)?;
storage::unhashed::put_raw(well_known_keys::CODE, &code);
@@ -1120,9 +1105,9 @@ impl<T: Config> Module<T> {
/// Set the current block weight. This should only be used in some integration tests.
#[cfg(any(feature = "std", test))]
pub fn set_block_limits(weight: Weight, len: usize) {
pub fn set_block_consumed_resources(weight: Weight, len: usize) {
BlockWeight::mutate(|current_weight| {
current_weight.put(weight, DispatchClass::Normal)
current_weight.set(weight, DispatchClass::Normal)
});
AllExtrinsicsLen::put(len as u32);
}
@@ -1348,7 +1333,6 @@ pub fn split_inner<T, R, S>(option: Option<T>, splitter: impl FnOnce(T) -> (R, S
}
}
impl<T: Config> IsDeadAccount<T::AccountId> for Module<T> {
fn is_dead_account(who: &T::AccountId) -> bool {
!Account::<T>::contains_key(who)
+434
View File
@@ -0,0 +1,434 @@
// This file is part of Substrate.
// Copyright (C) 2017-2020 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Block resource limits configuration structures.
//!
//! FRAME defines two resources that are limited within a block:
//! - Weight (execution cost/time)
//! - Length (block size)
//!
//! `frame_system` tracks consumption of each of these resources separately for each
//! `DispatchClass`. This module contains configuration object for both resources,
//! which should be passed to `frame_system` configuration when runtime is being set up.
use frame_support::weights::{Weight, DispatchClass, constants, PerDispatchClass, OneOrMany};
use sp_runtime::{RuntimeDebug, Perbill};
/// Block length limit configuration.
#[derive(RuntimeDebug, Clone)]
pub struct BlockLength {
/// Maximal total length in bytes for each extrinsic class.
///
/// In the worst case, the total block length is going to be:
/// `MAX(max)`
pub max: PerDispatchClass<u32>,
}
impl Default for BlockLength {
fn default() -> Self {
BlockLength::max_with_normal_ratio(
5 * 1024 * 1024,
DEFAULT_NORMAL_RATIO,
)
}
}
impl BlockLength {
/// Create new `BlockLength` with `max` for every class.
pub fn max(max: u32) -> Self {
Self {
max: PerDispatchClass::new(|_| max),
}
}
/// Create new `BlockLength` with `max` for `Operational` & `Mandatory`
/// and `normal * max` for `Normal`.
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 )*)); }
}
}
}
/// A result of validating `BlockWeights` correctness.
pub type ValidationResult = Result<BlockWeights, ValidationErrors>;
/// A ratio of `Normal` dispatch class within block, used as default value for
/// `BlockWeight` and `BlockLength`. The `Default` impls are provided mostly for convenience
/// to use in tests.
const DEFAULT_NORMAL_RATIO: Perbill = Perbill::from_percent(75);
/// `DispatchClass`-specific weight configuration.
#[derive(RuntimeDebug, Clone, codec::Encode, codec::Decode)]
pub struct WeightsPerClass {
/// Base weight of single extrinsic of given class.
pub base_extrinsic: Weight,
/// Maximal weight of single extrinsic. Should NOT include `base_extrinsic` cost.
///
/// `None` indicates that this class of extrinsics doesn't have a limit.
pub max_extrinsic: Option<Weight>,
/// Block maximal total weight for all extrinsics of given class.
///
/// `None` indicates that weight sum of this class of extrinsics is not
/// restricted. Use this value carefully, since it might produce heavily oversized
/// blocks.
///
/// In the worst case, the total weight consumed by the class is going to be:
/// `MAX(max_total) + MAX(reserved)`.
pub max_total: Option<Weight>,
/// Block reserved allowance for all extrinsics of a particular class.
///
/// Setting to `None` indicates that extrinsics of that class are allowed
/// to go over total block weight (but at most `max_total` for that class).
/// Setting to `Some(x)` guarantees that at least `x` weight of particular class
/// is processed in every block.
pub reserved: Option<Weight>,
}
/// Block weight limits & base values configuration.
///
/// This object is responsible for defining weight limits and base weight values tracked
/// during extrinsic execution.
///
/// Each block starts with `base_block` weight being consumed right away. Next up the
/// `on_initialize` pallet callbacks are invoked and their cost is added before any extrinsic
/// is executed. This cost is tracked as `Mandatory` dispatch class.
///
/// | | `max_block` | |
/// | | | |
/// | | | |
/// | | | |
/// | | | #| `on_initialize`
/// | #| `base_block` | #|
/// |NOM| |NOM|
/// ||\_ Mandatory
/// |\__ Operational
/// \___ Normal
///
/// The remaining capacity can be used to dispatch extrinsics. Note that each dispatch class
/// is being tracked separately, but the sum can't exceed `max_block` (except for `reserved`).
/// Below you can see a picture representing full block with 3 extrinsics (two `Operational` and
/// one `Normal`). Each class has it's own limit `max_total`, but also the sum cannot exceed
/// `max_block` value.
/// -- `Mandatory` limit (unlimited)
/// | # | | |
/// | # | `Ext3` | - - `Operational` limit
/// |# | `Ext2` |- - `Normal` limit
/// | # | `Ext1` | # |
/// | #| `on_initialize` | ##|
/// | #| `base_block` |###|
/// |NOM| |NOM|
///
/// It should be obvious now that it's possible for one class to reach it's limit (say `Normal`),
/// while the block has still capacity to process more transactions (`max_block` not reached,
/// `Operational` transactions can still go in). Setting `max_total` to `None` disables the
/// per-class limit. This is generally highly recommended for `Mandatory` dispatch class, while it
/// can be dangerous for `Normal` class and should only be done with extra care and consideration.
///
/// Often it's desirable for some class of transactions to be added to the block despite it being
/// full. For instance one might want to prevent high-priority `Normal` transactions from pushing
/// out lower-priority `Operational` transactions. In such cases you might add a `reserved` capacity
/// for given class.
/// _
/// # \
/// # `Ext8` - `reserved`
/// # _/
/// | # | `Ext7 | - - `Operational` limit
/// |# | `Ext6` | |
/// |# | `Ext5` |-# - `Normal` limit
/// |# | `Ext4` |## |
/// | #| `on_initialize` |###|
/// | #| `base_block` |###|
/// |NOM| |NOM|
///
/// In the above example, `Ext4-6` fill up the block almost up to `max_block`. `Ext7` would not fit
/// if there wasn't the extra `reserved` space for `Operational` transactions. Note that `max_total`
/// limit applies to `reserved` space as well (i.e. the sum of weights of `Ext7` & `Ext8` mustn't
/// exceed it). Setting `reserved` to `None` allows the extrinsics to always get into the block up
/// to their `max_total` limit. If `max_total` is set to `None` as well, all extrinsics witch
/// dispatchables of given class will always end up in the block (recommended for `Mandatory`
/// dispatch class).
///
/// As a consequence of `reserved` space, total consumed block weight might exceed `max_block`
/// value, so this parameter should rather be thought of as "target block weight" than a hard limit.
#[derive(RuntimeDebug, Clone, codec::Encode, codec::Decode)]
pub struct BlockWeights {
/// Base weight of block execution.
pub base_block: Weight,
/// Maximal total weight consumed by all kinds of extrinsics (without `reserved` space).
pub max_block: Weight,
/// Weight limits for extrinsics of given dispatch class.
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 {
/// Get per-class weight settings.
pub fn get(&self, class: DispatchClass) -> &WeightsPerClass {
self.per_class.get(class)
}
/// Verifies correctness of this `BlockWeights` object.
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);
// Make sure that if total is set it's greater than base_block &&
// base_for_class
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,
);
// Max extrinsic can't be greater than max_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),
);
// Max extrinsic should not be 0
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,
);
// Make sure that if reserved is set it's greater than base_for_class.
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,
);
// Make sure max block is greater than max_total if it's set.
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,
);
// Make sure we can fit at least one extrinsic.
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)
}
}
/// Create new weights definition, with both `Normal` and `Operational`
/// classes limited to given weight.
///
/// Note there is no reservation for `Operational` class, so this constructor
/// is not suitable for production deployments.
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")
}
/// Create a sensible default weights system given only expected maximal block weight and the
/// ratio that `Normal` extrinsics should occupy.
///
/// Assumptions:
/// - Average block initialization is assumed to be `10%`.
/// - `Operational` transactions have reserved allowance (`1.0 - normal_ratio`)
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")
}
/// Start constructing new `BlockWeights` object.
///
/// By default all kinds except of `Mandatory` extrinsics are disallowed.
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,
}
}
}
/// An opinionated builder for `Weights` object.
pub struct BlockWeightsBuilder {
weights: BlockWeights,
init_cost: Option<Perbill>,
}
impl BlockWeightsBuilder {
/// Set base block weight.
pub fn base_block(mut self, base_block: Weight) -> Self {
self.weights.base_block = base_block;
self
}
/// Average block initialization weight cost.
///
/// This value is used to derive maximal allowed extrinsic weight for each
/// class, based on the allowance.
///
/// This is to make sure that extrinsics don't stay forever in the pool,
/// because they could seamingly fit the block (since they are below `max_block`),
/// but the cost of calling `on_initialize` alway prevents them from being included.
pub fn avg_block_initialization(mut self, init_cost: Perbill) -> Self {
self.init_cost = Some(init_cost);
self
}
/// Set parameters for particular class.
///
/// Note: `None` values of `max_extrinsic` will be overwritten in `build` in case
/// `avg_block_initialization` rate is set to a non-zero value.
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
}
/// Construct the `BlockWeights` object.
pub fn build(self) -> ValidationResult {
// compute max extrinsic size
let Self { mut weights, init_cost } = self;
// compute max block size.
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,
};
}
// compute max size of single extrinsic
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));
}
}
}
// Validate the result
weights.validate()
}
/// Construct the `BlockWeights` object or panic if it's invalid.
///
/// This is a convenience method to be called whenever you construct a runtime.
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();
}
}
+24 -13
View File
@@ -34,12 +34,11 @@ impl_outer_origin! {
#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct Test;
const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
const MAX_BLOCK_WEIGHT: Weight = 1024;
parameter_types! {
pub const BlockHashCount: u64 = 10;
pub const MaximumBlockWeight: Weight = 1024;
pub const MaximumExtrinsicWeight: Weight = 768;
pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
pub const MaximumBlockLength: u32 = 1024;
pub Version: RuntimeVersion = RuntimeVersion {
spec_name: sp_version::create_runtime_str!("test"),
impl_name: sp_version::create_runtime_str!("system-test"),
@@ -49,12 +48,28 @@ parameter_types! {
apis: sp_version::create_apis_vec!([]),
transaction_version: 1,
};
pub const BlockExecutionWeight: Weight = 10;
pub const ExtrinsicBaseWeight: Weight = 5;
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight {
read: 10,
write: 100,
};
pub RuntimeBlockWeights: limits::BlockWeights = limits::BlockWeights::builder()
.base_block(10)
.for_class(DispatchClass::all(), |weights| {
weights.base_extrinsic = 5;
})
.for_class(DispatchClass::Normal, |weights| {
weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAX_BLOCK_WEIGHT);
})
.for_class(DispatchClass::Operational, |weights| {
weights.max_total = Some(MAX_BLOCK_WEIGHT);
weights.reserved = Some(
MAX_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAX_BLOCK_WEIGHT
);
})
.avg_block_initialization(Perbill::from_percent(0))
.build_or_panic();
pub RuntimeBlockLength: limits::BlockLength =
limits::BlockLength::max_with_normal_ratio(1024, NORMAL_DISPATCH_RATIO);
}
thread_local!{
@@ -82,6 +97,8 @@ impl Dispatchable for Call {
impl Config for Test {
type BaseCallFilter = ();
type BlockWeights = RuntimeBlockWeights;
type BlockLength = RuntimeBlockLength;
type Origin = Origin;
type Call = Call;
type Index = u64;
@@ -93,13 +110,7 @@ impl Config for Test {
type Header = Header;
type Event = Event<Self>;
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type DbWeight = DbWeight;
type BlockExecutionWeight = BlockExecutionWeight;
type ExtrinsicBaseWeight = ExtrinsicBaseWeight;
type MaximumExtrinsicWeight = MaximumExtrinsicWeight;
type AvailableBlockRatio = AvailableBlockRatio;
type MaximumBlockLength = MaximumBlockLength;
type Version = Version;
type PalletInfo = ();
type AccountData = u32;
@@ -118,7 +129,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
let mut ext: sp_io::TestExternalities = GenesisConfig::default().build_storage::<Test>().unwrap().into();
// Add to each test the initial weight of a block
ext.execute_with(|| System::register_extra_weight_unchecked(
<Test as Config>::BlockExecutionWeight::get(),
<Test as crate::Config>::BlockWeights::get().base_block,
DispatchClass::Mandatory
));
ext
-76
View File
@@ -1,76 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2017-2020 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use codec::{Encode, Decode};
use frame_support::weights::{Weight, DispatchClass};
use sp_runtime::RuntimeDebug;
/// An object to track the currently used extrinsic weight in a block.
#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode)]
pub struct ExtrinsicsWeight {
normal: Weight,
operational: Weight,
}
impl ExtrinsicsWeight {
/// Returns the total weight consumed by all extrinsics in the block.
pub fn total(&self) -> Weight {
self.normal.saturating_add(self.operational)
}
/// 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);
}
/// 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(())
}
/// 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);
}
/// Get the current weight of a specific dispatch class.
pub fn get(&self, class: DispatchClass) -> Weight {
match class {
DispatchClass::Operational => self.operational,
DispatchClass::Normal | DispatchClass::Mandatory => self.normal,
}
}
/// Get a mutable reference to the current weight of a specific dispatch class.
fn get_mut(&mut self, class: DispatchClass) -> &mut Weight {
match class {
DispatchClass::Operational => &mut self.operational,
DispatchClass::Normal | DispatchClass::Mandatory => &mut self.normal,
}
}
/// Set the weight of a specific dispatch class.
pub fn put(&mut self, new: Weight, class: DispatchClass) {
*self.get_mut(class) = new;
}
}