Extract frame_system SignedExtensions into separate files. (#6474)

* Split the code.

* Restructure.

* Split tests.

* Self-review.

* Break lines.

* Move tests out.

* Rename CheckEra -> CheckMortality but keep backwards compatibility

* Update frame/system/src/extensions/check_mortality.rs

* Don't rename the IDENTIFIER for now.

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Tomasz Drwięga
2020-06-24 17:01:42 +02:00
committed by GitHub
parent e016a49322
commit fb56eacd8d
12 changed files with 1757 additions and 1464 deletions
@@ -0,0 +1,58 @@
// 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 crate::{Trait, Module};
use sp_runtime::{
traits::{SignedExtension, Zero},
transaction_validity::TransactionValidityError,
};
/// Genesis hash check to provide replay protection between different networks.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct CheckGenesis<T: Trait + Send + Sync>(sp_std::marker::PhantomData<T>);
impl<T: Trait + Send + Sync> sp_std::fmt::Debug for CheckGenesis<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "CheckGenesis")
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}
impl<T: Trait + Send + Sync> CheckGenesis<T> {
/// Creates new `SignedExtension` to check genesis hash.
pub fn new() -> Self {
Self(sp_std::marker::PhantomData)
}
}
impl<T: Trait + Send + Sync> SignedExtension for CheckGenesis<T> {
type AccountId = T::AccountId;
type Call = <T as Trait>::Call;
type AdditionalSigned = T::Hash;
type Pre = ();
const IDENTIFIER: &'static str = "CheckGenesis";
fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(<Module<T>>::block_hash(T::BlockNumber::zero()))
}
}
@@ -0,0 +1,124 @@
// 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 crate::{Trait, Module, BlockHash};
use frame_support::StorageMap;
use sp_runtime::{
generic::Era,
traits::{SignedExtension, DispatchInfoOf, SaturatedConversion},
transaction_validity::{
ValidTransaction, TransactionValidityError, InvalidTransaction, TransactionValidity,
},
};
/// Check for transaction mortality.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct CheckMortality<T: Trait + Send + Sync>(Era, sp_std::marker::PhantomData<T>);
impl<T: Trait + Send + Sync> CheckMortality<T> {
/// utility constructor. Used only in client/factory code.
pub fn from(era: Era) -> Self {
Self(era, sp_std::marker::PhantomData)
}
}
impl<T: Trait + Send + Sync> sp_std::fmt::Debug for CheckMortality<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "CheckMortality({:?})", self.0)
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}
impl<T: Trait + Send + Sync> SignedExtension for CheckMortality<T> {
type AccountId = T::AccountId;
type Call = T::Call;
type AdditionalSigned = T::Hash;
type Pre = ();
// TODO [#6483] rename to CheckMortality
const IDENTIFIER: &'static str = "CheckEra";
fn validate(
&self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> TransactionValidity {
let current_u64 = <Module<T>>::block_number().saturated_into::<u64>();
let valid_till = self.0.death(current_u64);
Ok(ValidTransaction {
longevity: valid_till.saturating_sub(current_u64),
..Default::default()
})
}
fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
let current_u64 = <Module<T>>::block_number().saturated_into::<u64>();
let n = self.0.birth(current_u64).saturated_into::<T::BlockNumber>();
if !<BlockHash<T>>::contains_key(n) {
Err(InvalidTransaction::AncientBirthBlock.into())
} else {
Ok(<Module<T>>::block_hash(n))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{Test, new_test_ext, System, CALL};
use frame_support::weights::{DispatchClass, DispatchInfo, Pays};
use sp_core::H256;
#[test]
fn signed_ext_check_era_should_work() {
new_test_ext().execute_with(|| {
// future
assert_eq!(
CheckMortality::<Test>::from(Era::mortal(4, 2)).additional_signed().err().unwrap(),
InvalidTransaction::AncientBirthBlock.into(),
);
// correct
System::set_block_number(13);
<BlockHash<Test>>::insert(12, H256::repeat_byte(1));
assert!(CheckMortality::<Test>::from(Era::mortal(4, 12)).additional_signed().is_ok());
})
}
#[test]
fn signed_ext_check_era_should_change_longevity() {
new_test_ext().execute_with(|| {
let normal = DispatchInfo { weight: 100, class: DispatchClass::Normal, pays_fee: Pays::Yes };
let len = 0_usize;
let ext = (
crate::CheckWeight::<Test>::default(),
CheckMortality::<Test>::from(Era::mortal(16, 256)),
);
System::set_block_number(17);
<BlockHash<Test>>::insert(16, H256::repeat_byte(1));
assert_eq!(ext.validate(&1, CALL, &normal, len).unwrap().longevity, 15);
})
}
}
@@ -0,0 +1,145 @@
// 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 crate::Trait;
use frame_support::{
weights::DispatchInfo,
StorageMap,
};
use sp_runtime::{
traits::{SignedExtension, DispatchInfoOf, Dispatchable, One},
transaction_validity::{
ValidTransaction, TransactionValidityError, InvalidTransaction, TransactionValidity,
TransactionLongevity, TransactionPriority,
},
};
use sp_std::vec;
/// Nonce check and increment to give replay protection for transactions.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct CheckNonce<T: Trait>(#[codec(compact)] T::Index);
impl<T: Trait> CheckNonce<T> {
/// utility constructor. Used only in client/factory code.
pub fn from(nonce: T::Index) -> Self {
Self(nonce)
}
}
impl<T: Trait> sp_std::fmt::Debug for CheckNonce<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "CheckNonce({})", self.0)
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}
impl<T: Trait> SignedExtension for CheckNonce<T> where
T::Call: Dispatchable<Info=DispatchInfo>
{
type AccountId = T::AccountId;
type Call = T::Call;
type AdditionalSigned = ();
type Pre = ();
const IDENTIFIER: &'static str = "CheckNonce";
fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) }
fn pre_dispatch(
self,
who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<(), TransactionValidityError> {
let mut account = crate::Account::<T>::get(who);
if self.0 != account.nonce {
return Err(
if self.0 < account.nonce {
InvalidTransaction::Stale
} else {
InvalidTransaction::Future
}.into()
)
}
account.nonce += T::Index::one();
crate::Account::<T>::insert(who, account);
Ok(())
}
fn validate(
&self,
who: &Self::AccountId,
_call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> TransactionValidity {
// check index
let account = crate::Account::<T>::get(who);
if self.0 < account.nonce {
return InvalidTransaction::Stale.into()
}
let provides = vec![Encode::encode(&(who, self.0))];
let requires = if account.nonce < self.0 {
vec![Encode::encode(&(who, self.0 - One::one()))]
} else {
vec![]
};
Ok(ValidTransaction {
priority: info.weight as TransactionPriority,
requires,
provides,
longevity: TransactionLongevity::max_value(),
propagate: true,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{Test, new_test_ext, CALL};
#[test]
fn signed_ext_check_nonce_works() {
new_test_ext().execute_with(|| {
crate::Account::<Test>::insert(1, crate::AccountInfo {
nonce: 1,
refcount: 0,
data: 0,
});
let info = DispatchInfo::default();
let len = 0_usize;
// stale
assert!(CheckNonce::<Test>(0).validate(&1, CALL, &info, len).is_err());
assert!(CheckNonce::<Test>(0).pre_dispatch(&1, CALL, &info, len).is_err());
// correct
assert!(CheckNonce::<Test>(1).validate(&1, CALL, &info, len).is_ok());
assert!(CheckNonce::<Test>(1).pre_dispatch(&1, CALL, &info, len).is_ok());
// future
assert!(CheckNonce::<Test>(5).validate(&1, CALL, &info, len).is_ok());
assert!(CheckNonce::<Test>(5).pre_dispatch(&1, CALL, &info, len).is_err());
})
}
}
@@ -0,0 +1,58 @@
// 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 crate::{Trait, Module};
use codec::{Encode, Decode};
use sp_runtime::{
traits::SignedExtension,
transaction_validity::TransactionValidityError,
};
/// Ensure the runtime version registered in the transaction is the same as at present.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct CheckSpecVersion<T: Trait + Send + Sync>(sp_std::marker::PhantomData<T>);
impl<T: Trait + Send + Sync> sp_std::fmt::Debug for CheckSpecVersion<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "CheckSpecVersion")
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}
impl<T: Trait + Send + Sync> CheckSpecVersion<T> {
/// Create new `SignedExtension` to check runtime version.
pub fn new() -> Self {
Self(sp_std::marker::PhantomData)
}
}
impl<T: Trait + Send + Sync> SignedExtension for CheckSpecVersion<T> {
type AccountId = T::AccountId;
type Call = <T as Trait>::Call;
type AdditionalSigned = u32;
type Pre = ();
const IDENTIFIER: &'static str = "CheckSpecVersion";
fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(<Module<T>>::runtime_version().spec_version)
}
}
@@ -0,0 +1,58 @@
// 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 crate::{Trait, Module};
use codec::{Encode, Decode};
use sp_runtime::{
traits::SignedExtension,
transaction_validity::TransactionValidityError,
};
/// Ensure the transaction version registered in the transaction is the same as at present.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct CheckTxVersion<T: Trait + Send + Sync>(sp_std::marker::PhantomData<T>);
impl<T: Trait + Send + Sync> sp_std::fmt::Debug for CheckTxVersion<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "CheckTxVersion")
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}
impl<T: Trait + Send + Sync> CheckTxVersion<T> {
/// Create new `SignedExtension` to check transaction version.
pub fn new() -> Self {
Self(sp_std::marker::PhantomData)
}
}
impl<T: Trait + Send + Sync> SignedExtension for CheckTxVersion<T> {
type AccountId = T::AccountId;
type Call = <T as Trait>::Call;
type AdditionalSigned = u32;
type Pre = ();
const IDENTIFIER: &'static str = "CheckTxVersion";
fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(<Module<T>>::runtime_version().transaction_version)
}
}
@@ -0,0 +1,644 @@
// 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 crate::{Trait, Module};
use codec::{Encode, Decode};
use sp_runtime::{
traits::{SignedExtension, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Printable},
transaction_validity::{
ValidTransaction, TransactionValidityError, InvalidTransaction, TransactionValidity,
TransactionPriority,
},
Perbill, DispatchResult,
};
use frame_support::{
traits::{Get},
weights::{PostDispatchInfo, DispatchInfo, DispatchClass},
StorageValue,
};
/// Block resource (weight) limit check.
#[derive(Encode, Decode, Clone, Eq, PartialEq, Default)]
pub struct CheckWeight<T: Trait + Send + Sync>(sp_std::marker::PhantomData<T>);
impl<T: Trait + Send + Sync> CheckWeight<T> where
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.
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(())
}
},
}
}
/// Checks if the current extrinsic can fit into the block with respect to block weight limits.
///
/// Upon successes, it returns the new block weight as a `Result`.
fn check_block_weight(
info: &DispatchInfoOf<T::Call>,
) -> Result<crate::weights::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())
}
}
}
}
/// Checks if the current extrinsic can fit into the block with respect to block length limits.
///
/// Upon successes, it returns the new block length as a `Result`.
fn check_block_length(
info: &DispatchInfoOf<T::Call>,
len: usize,
) -> Result<u32, TransactionValidityError> {
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 {
Err(InvalidTransaction::ExhaustsResources.into())
} else {
Ok(next_len)
}
}
/// get the priority of an extrinsic denoted by `info`.
fn get_priority(info: &DispatchInfoOf<T::Call>) -> TransactionPriority {
match info.class {
DispatchClass::Normal => info.weight.into(),
// Don't use up the whole priority space, to allow things like `tip`
// to be taken into account as well.
DispatchClass::Operational => TransactionPriority::max_value() / 2,
// Mandatory extrinsics are only for inherents; never transactions.
DispatchClass::Mandatory => TransactionPriority::min_value(),
}
}
/// Creates new `SignedExtension` to check weight of the extrinsic.
pub fn new() -> Self {
Self(Default::default())
}
/// Do the pre-dispatch checks. This can be applied to both signed and unsigned.
///
/// It checks and notes the new weight and length.
fn do_pre_dispatch(
info: &DispatchInfoOf<T::Call>,
len: usize,
) -> Result<(), TransactionValidityError> {
let next_len = Self::check_block_length(info, len)?;
let next_weight = Self::check_block_weight(info)?;
Self::check_extrinsic_weight(info)?;
crate::AllExtrinsicsLen::put(next_len);
crate::BlockWeight::put(next_weight);
Ok(())
}
/// Do the validate checks. This can be applied to both signed and unsigned.
///
/// It only checks that the block weight and length limit will not exceed.
fn do_validate(
info: &DispatchInfoOf<T::Call>,
len: usize,
) -> TransactionValidity {
// ignore the next length. If they return `Ok`, then it is below the limit.
let _ = Self::check_block_length(info, len)?;
// during validation we skip block limit check. Since the `validate_transaction`
// call runs on an empty block anyway, by this we prevent `on_initialize` weight
// consumption from causing false negatives.
Self::check_extrinsic_weight(info)?;
Ok(ValidTransaction { priority: Self::get_priority(info), ..Default::default() })
}
}
impl<T: Trait + Send + Sync> SignedExtension for CheckWeight<T> where
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>
{
type AccountId = T::AccountId;
type Call = T::Call;
type AdditionalSigned = ();
type Pre = ();
const IDENTIFIER: &'static str = "CheckWeight";
fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) }
fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> Result<(), TransactionValidityError> {
if info.class == DispatchClass::Mandatory {
Err(InvalidTransaction::MandatoryDispatch)?
}
Self::do_pre_dispatch(info, len)
}
fn validate(
&self,
_who: &Self::AccountId,
_call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> TransactionValidity {
if info.class == DispatchClass::Mandatory {
Err(InvalidTransaction::MandatoryDispatch)?
}
Self::do_validate(info, len)
}
fn pre_dispatch_unsigned(
_call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> Result<(), TransactionValidityError> {
Self::do_pre_dispatch(info, len)
}
fn validate_unsigned(
_call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> TransactionValidity {
Self::do_validate(info, len)
}
fn post_dispatch(
_pre: Self::Pre,
info: &DispatchInfoOf<Self::Call>,
post_info: &PostDispatchInfoOf<Self::Call>,
_len: usize,
result: &DispatchResult,
) -> Result<(), TransactionValidityError> {
// Since mandatory dispatched do not get validated for being overweight, we are sensitive
// 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();
e.print();
Err(InvalidTransaction::BadMandatory)?
}
let unspent = post_info.calc_unspent(info);
if unspent > 0 {
crate::BlockWeight::mutate(|current_weight| {
current_weight.sub(unspent, info.class);
})
}
Ok(())
}
}
impl<T: Trait + Send + Sync> sp_std::fmt::Debug for CheckWeight<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "CheckWeight")
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{BlockWeight, AllExtrinsicsLen};
use crate::mock::{Test, CALL, new_test_ext, System};
use sp_std::marker::PhantomData;
use frame_support::{assert_ok, assert_noop};
use frame_support::weights::{Weight, Pays};
fn normal_weight_limit() -> Weight {
<Test as Trait>::AvailableBlockRatio::get() * <Test as Trait>::MaximumBlockWeight::get()
}
fn normal_length_limit() -> u32 {
<Test as Trait>::AvailableBlockRatio::get() * <Test as Trait>::MaximumBlockLength::get()
}
#[test]
fn mandatory_extrinsic_doesnt_care_about_limits() {
fn check(call: impl FnOnce(&DispatchInfo, usize)) {
new_test_ext().execute_with(|| {
let max = DispatchInfo {
weight: Weight::max_value(),
class: DispatchClass::Mandatory,
..Default::default()
};
let len = 0_usize;
call(&max, len);
});
}
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 Trait>::MaximumBlockWeight::get());
});
check(|max, len| {
assert_ok!(CheckWeight::<Test>::do_validate(max, len));
});
}
#[test]
fn normal_extrinsic_limited_by_maximum_extrinsic_weight() {
new_test_ext().execute_with(|| {
let max = DispatchInfo {
weight: <Test as Trait>::MaximumExtrinsicWeight::get() + 1,
class: DispatchClass::Normal,
..Default::default()
};
let len = 0_usize;
assert_noop!(
CheckWeight::<Test>::do_validate(&max, len),
InvalidTransaction::ExhaustsResources
);
});
}
#[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 Trait>::MaximumBlockWeight::get();
let base_weight = <Test as Trait>::ExtrinsicBaseWeight::get();
let block_base = <Test as Trait>::BlockExecutionWeight::get();
let weight = operational_limit - base_weight - block_base;
let okay = DispatchInfo {
weight,
class: DispatchClass::Operational,
..Default::default()
};
let max = DispatchInfo {
weight: weight + 1,
class: DispatchClass::Operational,
..Default::default()
};
let len = 0_usize;
assert_eq!(
CheckWeight::<Test>::do_validate(&okay, len),
Ok(ValidTransaction {
priority: CheckWeight::<Test>::get_priority(&okay),
..Default::default()
})
);
assert_noop!(
CheckWeight::<Test>::do_validate(&max, len),
InvalidTransaction::ExhaustsResources
);
});
}
#[test]
fn register_extra_weight_unchecked_doesnt_care_about_limits() {
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 Trait>::MaximumBlockWeight::get());
});
}
#[test]
fn full_block_with_normal_and_operational() {
new_test_ext().execute_with(|| {
// Max block is 1024
// Max normal is 768 (75%)
// 10 is taken for block execution weight
// So normal extrinsic can be 758 weight (-5 for base extrinsic weight)
// And Operational can be 256 to produce a full block (-5 for base)
let max_normal = DispatchInfo { weight: 753, ..Default::default() };
let rest_operational = DispatchInfo { weight: 251, class: DispatchClass::Operational, ..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(&rest_operational, len));
assert_eq!(<Test as Trait>::MaximumBlockWeight::get(), 1024);
assert_eq!(System::block_weight().total(), <Test as Trait>::MaximumBlockWeight::get());
// Checking single extrinsic should not take current block weight into account.
assert_eq!(CheckWeight::<Test>::check_extrinsic_weight(&rest_operational), Ok(()));
});
}
#[test]
fn dispatch_order_does_not_effect_weight_logic() {
new_test_ext().execute_with(|| {
// We switch the order of `full_block_with_normal_and_operational`
let max_normal = DispatchInfo { weight: 753, ..Default::default() };
let rest_operational = DispatchInfo { weight: 251, class: DispatchClass::Operational, ..Default::default() };
let len = 0_usize;
assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&rest_operational, len));
// 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 Trait>::MaximumBlockWeight::get(), 1024);
assert_eq!(System::block_weight().total(), <Test as Trait>::MaximumBlockWeight::get());
});
}
#[test]
fn operational_works_on_full_block() {
new_test_ext().execute_with(|| {
// An on_initialize takes up the whole block! (Every time!)
System::register_extra_weight_unchecked(Weight::max_value(), DispatchClass::Mandatory);
let dispatch_normal = DispatchInfo { weight: 251, class: DispatchClass::Normal, ..Default::default() };
let dispatch_operational = DispatchInfo { weight: 251, class: DispatchClass::Operational, ..Default::default() };
let len = 0_usize;
assert_noop!(
CheckWeight::<Test>::do_pre_dispatch(&dispatch_normal, len),
InvalidTransaction::ExhaustsResources
);
// Thank goodness we can still do an operational transaction to possibly save the blockchain.
assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&dispatch_operational, len));
// Not too much though
assert_noop!(
CheckWeight::<Test>::do_pre_dispatch(&dispatch_operational, len),
InvalidTransaction::ExhaustsResources
);
// Even with full block, validity of single transaction should be correct.
assert_eq!(CheckWeight::<Test>::check_extrinsic_weight(&dispatch_operational), Ok(()));
});
}
#[test]
fn signed_ext_check_weight_works_operational_tx() {
new_test_ext().execute_with(|| {
let normal = DispatchInfo { weight: 100, ..Default::default() };
let op = DispatchInfo { weight: 100, class: DispatchClass::Operational, pays_fee: Pays::Yes };
let len = 0_usize;
let normal_limit = normal_weight_limit();
// given almost full block
BlockWeight::mutate(|current_weight| {
current_weight.put(normal_limit, DispatchClass::Normal)
});
// will not fit.
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &normal, len).is_err());
// will fit.
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &op, len).is_ok());
// likewise for length limit.
let len = 100_usize;
AllExtrinsicsLen::put(normal_length_limit());
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &normal, len).is_err());
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &op, len).is_ok());
})
}
#[test]
fn signed_ext() {
new_test_ext().execute_with(|| {
let normal = DispatchInfo { weight: 100, class: DispatchClass::Normal, pays_fee: Pays::Yes };
let op = DispatchInfo { weight: 100, class: DispatchClass::Operational, pays_fee: Pays::Yes };
let len = 0_usize;
let priority = CheckWeight::<Test>(PhantomData)
.validate(&1, CALL, &normal, len)
.unwrap()
.priority;
assert_eq!(priority, 100);
let priority = CheckWeight::<Test>(PhantomData)
.validate(&1, CALL, &op, len)
.unwrap()
.priority;
assert_eq!(priority, u64::max_value() / 2);
})
}
#[test]
fn signed_ext_check_weight_block_size_works() {
new_test_ext().execute_with(|| {
let normal = DispatchInfo::default();
let normal_limit = normal_weight_limit() as usize;
let reset_check_weight = |tx, s, f| {
AllExtrinsicsLen::put(0);
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, tx, s);
if f { assert!(r.is_err()) } else { assert!(r.is_ok()) }
};
reset_check_weight(&normal, normal_limit - 1, false);
reset_check_weight(&normal, normal_limit, false);
reset_check_weight(&normal, normal_limit + 1, true);
// Operational ones don't have this limit.
let op = DispatchInfo { weight: 0, class: DispatchClass::Operational, pays_fee: Pays::Yes };
reset_check_weight(&op, normal_limit, false);
reset_check_weight(&op, normal_limit + 100, false);
reset_check_weight(&op, 1024, false);
reset_check_weight(&op, 1025, true);
})
}
#[test]
fn signed_ext_check_weight_works_normal_tx() {
new_test_ext().execute_with(|| {
let normal_limit = normal_weight_limit();
let small = DispatchInfo { weight: 100, ..Default::default() };
let medium = DispatchInfo {
weight: normal_limit - <Test as Trait>::ExtrinsicBaseWeight::get(),
..Default::default()
};
let big = DispatchInfo {
weight: normal_limit - <Test as Trait>::ExtrinsicBaseWeight::get() + 1,
..Default::default()
};
let len = 0_usize;
let reset_check_weight = |i, f, s| {
BlockWeight::mutate(|current_weight| {
current_weight.put(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()) }
};
reset_check_weight(&small, false, 0);
reset_check_weight(&medium, false, 0);
reset_check_weight(&big, true, 1);
})
}
#[test]
fn signed_ext_check_weight_refund_works() {
new_test_ext().execute_with(|| {
// This is half of the max block weight
let info = DispatchInfo { weight: 512, ..Default::default() };
let post_info = PostDispatchInfo { actual_weight: Some(128), };
let len = 0_usize;
// We allow 75% for normal transaction, so we put 25% - extrinsic base weight
BlockWeight::mutate(|current_weight| {
current_weight.put(256 - <Test as Trait>::ExtrinsicBaseWeight::get(), DispatchClass::Normal)
});
let pre = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap();
assert_eq!(BlockWeight::get().total(), info.weight + 256);
assert!(
CheckWeight::<Test>::post_dispatch(pre, &info, &post_info, len, &Ok(()))
.is_ok()
);
assert_eq!(
BlockWeight::get().total(),
post_info.actual_weight.unwrap() + 256,
);
})
}
#[test]
fn signed_ext_check_weight_actual_weight_higher_than_max_is_capped() {
new_test_ext().execute_with(|| {
let info = DispatchInfo { weight: 512, ..Default::default() };
let post_info = PostDispatchInfo { actual_weight: Some(700), };
let len = 0_usize;
BlockWeight::mutate(|current_weight| {
current_weight.put(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 Trait>::ExtrinsicBaseWeight::get(),
);
assert!(
CheckWeight::<Test>::post_dispatch(pre, &info, &post_info, len, &Ok(()))
.is_ok()
);
assert_eq!(
BlockWeight::get().total(),
info.weight + 128 + <Test as Trait>::ExtrinsicBaseWeight::get(),
);
})
}
#[test]
fn zero_weight_extrinsic_still_has_base_weight() {
new_test_ext().execute_with(|| {
let free = DispatchInfo { weight: 0, ..Default::default() };
let len = 0_usize;
// Initial weight from `BlockExecutionWeight`
assert_eq!(System::block_weight().total(), <Test as Trait>::BlockExecutionWeight::get());
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &free, len);
assert!(r.is_ok());
assert_eq!(
System::block_weight().total(),
<Test as Trait>::ExtrinsicBaseWeight::get() + <Test as Trait>::BlockExecutionWeight::get()
);
})
}
}
@@ -0,0 +1,24 @@
// This file is part of Substrate.
// Copyright (C) 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.
pub mod check_genesis;
pub mod check_mortality;
pub mod check_nonce;
pub mod check_spec_version;
pub mod check_tx_version;
pub mod check_weight;
File diff suppressed because it is too large Load Diff
+124
View File
@@ -0,0 +1,124 @@
// 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 crate::*;
use sp_std::cell::RefCell;
use sp_core::H256;
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
testing::Header,
};
use frame_support::{
impl_outer_origin, parameter_types,
weights::PostDispatchInfo,
};
impl_outer_origin! {
pub enum Origin for Test where system = super {}
}
#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct Test;
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"),
authoring_version: 1,
spec_version: 1,
impl_version: 1,
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,
};
}
thread_local!{
pub static KILLED: RefCell<Vec<u64>> = RefCell::new(vec![]);
}
pub struct RecordKilled;
impl OnKilledAccount<u64> for RecordKilled {
fn on_killed_account(who: &u64) { KILLED.with(|r| r.borrow_mut().push(*who)) }
}
#[derive(Debug, codec::Encode, codec::Decode)]
pub struct Call;
impl Dispatchable for Call {
type Origin = Origin;
type Trait = ();
type Info = DispatchInfo;
type PostInfo = PostDispatchInfo;
fn dispatch(self, _origin: Self::Origin)
-> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
panic!("Do not use dummy implementation for dispatch.");
}
}
impl Trait for Test {
type BaseCallFilter = ();
type Origin = Origin;
type Call = Call;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
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 ModuleToIndex = ();
type AccountData = u32;
type OnNewAccount = ();
type OnKilledAccount = RecordKilled;
}
pub type System = Module<Test>;
pub type SysEvent = <Test as Trait>::Event;
pub const CALL: &<Test as Trait>::Call = &Call;
/// Create new externalities for `System` module tests.
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 Trait>::BlockExecutionWeight::get(),
DispatchClass::Mandatory
));
ext
}
+1 -1
View File
@@ -638,7 +638,7 @@ pub trait SignedPayload<T: SigningTypes>: Encode {
mod tests {
use super::*;
use codec::Decode;
use crate::tests::{Test as TestRuntime, Call};
use crate::mock::{Test as TestRuntime, Call};
use sp_core::offchain::{testing, TransactionPoolExt};
use sp_runtime::testing::{UintAuthorityId, TestSignature, TestXt};
+424
View File
@@ -0,0 +1,424 @@
// 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 crate::*;
use mock::{*, Origin};
use sp_core::H256;
use sp_runtime::DispatchError;
use frame_support::weights::WithPostDispatchInfo;
#[test]
fn origin_works() {
let o = Origin::from(RawOrigin::<u64>::Signed(1u64));
let x: Result<RawOrigin<u64>, Origin> = o.into();
assert_eq!(x.unwrap(), RawOrigin::<u64>::Signed(1u64));
}
#[test]
fn stored_map_works() {
new_test_ext().execute_with(|| {
System::insert(&0, 42);
assert!(System::allow_death(&0));
System::inc_ref(&0);
assert!(!System::allow_death(&0));
System::insert(&0, 69);
assert!(!System::allow_death(&0));
System::dec_ref(&0);
assert!(System::allow_death(&0));
assert!(KILLED.with(|r| r.borrow().is_empty()));
System::kill_account(&0);
assert_eq!(KILLED.with(|r| r.borrow().clone()), vec![0u64]);
});
}
#[test]
fn deposit_event_should_work() {
new_test_ext().execute_with(|| {
System::initialize(
&1,
&[0u8; 32].into(),
&[0u8; 32].into(),
&Default::default(),
InitKind::Full,
);
System::note_finished_extrinsics();
System::deposit_event(SysEvent::CodeUpdated);
System::finalize();
assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Finalization,
event: SysEvent::CodeUpdated,
topics: vec![],
}
]
);
System::initialize(
&2,
&[0u8; 32].into(),
&[0u8; 32].into(),
&Default::default(),
InitKind::Full,
);
System::deposit_event(SysEvent::NewAccount(32));
System::note_finished_initialize();
System::deposit_event(SysEvent::KilledAccount(42));
System::note_applied_extrinsic(&Ok(().into()), Default::default());
System::note_applied_extrinsic(
&Err(DispatchError::BadOrigin.into()),
Default::default()
);
System::note_finished_extrinsics();
System::deposit_event(SysEvent::NewAccount(3));
System::finalize();
assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Initialization,
event: SysEvent::NewAccount(32),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: SysEvent::KilledAccount(42),
topics: vec![]
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: SysEvent::ExtrinsicSuccess(Default::default()),
topics: vec![]
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: SysEvent::ExtrinsicFailed(
DispatchError::BadOrigin.into(),
Default::default()
),
topics: vec![]
},
EventRecord {
phase: Phase::Finalization,
event: SysEvent::NewAccount(3),
topics: vec![]
},
]
);
});
}
#[test]
fn deposit_event_uses_actual_weight() {
new_test_ext().execute_with(|| {
System::initialize(
&1,
&[0u8; 32].into(),
&[0u8; 32].into(),
&Default::default(),
InitKind::Full,
);
System::note_finished_initialize();
let pre_info = DispatchInfo {
weight: 1000,
.. Default::default()
};
System::note_applied_extrinsic(
&Ok(Some(300).into()),
pre_info,
);
System::note_applied_extrinsic(
&Ok(Some(1000).into()),
pre_info,
);
System::note_applied_extrinsic(
// values over the pre info should be capped at pre dispatch value
&Ok(Some(1200).into()),
pre_info,
);
System::note_applied_extrinsic(
&Err(DispatchError::BadOrigin.with_weight(999)),
pre_info,
);
assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: SysEvent::ExtrinsicSuccess(
DispatchInfo {
weight: 300,
.. Default::default()
},
),
topics: vec![]
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: SysEvent::ExtrinsicSuccess(
DispatchInfo {
weight: 1000,
.. Default::default()
},
),
topics: vec![]
},
EventRecord {
phase: Phase::ApplyExtrinsic(2),
event: SysEvent::ExtrinsicSuccess(
DispatchInfo {
weight: 1000,
.. Default::default()
},
),
topics: vec![]
},
EventRecord {
phase: Phase::ApplyExtrinsic(3),
event: SysEvent::ExtrinsicFailed(
DispatchError::BadOrigin.into(),
DispatchInfo {
weight: 999,
.. Default::default()
},
),
topics: vec![]
},
]
);
});
}
#[test]
fn deposit_event_topics() {
new_test_ext().execute_with(|| {
const BLOCK_NUMBER: u64 = 1;
System::initialize(
&BLOCK_NUMBER,
&[0u8; 32].into(),
&[0u8; 32].into(),
&Default::default(),
InitKind::Full,
);
System::note_finished_extrinsics();
let topics = vec![
H256::repeat_byte(1),
H256::repeat_byte(2),
H256::repeat_byte(3),
];
// We deposit a few events with different sets of topics.
System::deposit_event_indexed(&topics[0..3], SysEvent::NewAccount(1));
System::deposit_event_indexed(&topics[0..1], SysEvent::NewAccount(2));
System::deposit_event_indexed(&topics[1..2], SysEvent::NewAccount(3));
System::finalize();
// Check that topics are reflected in the event record.
assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Finalization,
event: SysEvent::NewAccount(1),
topics: topics[0..3].to_vec(),
},
EventRecord {
phase: Phase::Finalization,
event: SysEvent::NewAccount(2),
topics: topics[0..1].to_vec(),
},
EventRecord {
phase: Phase::Finalization,
event: SysEvent::NewAccount(3),
topics: topics[1..2].to_vec(),
}
]
);
// Check that the topic-events mapping reflects the deposited topics.
// Note that these are indexes of the events.
assert_eq!(
System::event_topics(&topics[0]),
vec![(BLOCK_NUMBER, 0), (BLOCK_NUMBER, 1)],
);
assert_eq!(
System::event_topics(&topics[1]),
vec![(BLOCK_NUMBER, 0), (BLOCK_NUMBER, 2)],
);
assert_eq!(
System::event_topics(&topics[2]),
vec![(BLOCK_NUMBER, 0)],
);
});
}
#[test]
fn prunes_block_hash_mappings() {
new_test_ext().execute_with(|| {
// simulate import of 15 blocks
for n in 1..=15 {
System::initialize(
&n,
&[n as u8 - 1; 32].into(),
&[0u8; 32].into(),
&Default::default(),
InitKind::Full,
);
System::finalize();
}
// first 5 block hashes are pruned
for n in 0..5 {
assert_eq!(
System::block_hash(n),
H256::zero(),
);
}
// the remaining 10 are kept
for n in 5..15 {
assert_eq!(
System::block_hash(n),
[n as u8; 32].into(),
);
}
})
}
#[test]
fn set_code_checks_works() {
struct CallInWasm(Vec<u8>);
impl sp_core::traits::CallInWasm for CallInWasm {
fn call_in_wasm(
&self,
_: &[u8],
_: Option<Vec<u8>>,
_: &str,
_: &[u8],
_: &mut dyn sp_externalities::Externalities,
_: sp_core::traits::MissingHostFunctions,
) -> Result<Vec<u8>, String> {
Ok(self.0.clone())
}
}
let test_data = vec![
("test", 1, 2, Err(Error::<Test>::SpecVersionNeedsToIncrease)),
("test", 1, 1, Err(Error::<Test>::SpecVersionNeedsToIncrease)),
("test2", 1, 1, Err(Error::<Test>::InvalidSpecName)),
("test", 2, 1, Ok(())),
("test", 0, 1, Err(Error::<Test>::SpecVersionNeedsToIncrease)),
("test", 1, 0, Err(Error::<Test>::SpecVersionNeedsToIncrease)),
];
for (spec_name, spec_version, impl_version, expected) in test_data.into_iter() {
let version = RuntimeVersion {
spec_name: spec_name.into(),
spec_version,
impl_version,
..Default::default()
};
let call_in_wasm = CallInWasm(version.encode());
let mut ext = new_test_ext();
ext.register_extension(sp_core::traits::CallInWasmExt::new(call_in_wasm));
ext.execute_with(|| {
let res = System::set_code(
RawOrigin::Root.into(),
vec![1, 2, 3, 4],
);
assert_eq!(expected.map_err(DispatchError::from), res);
});
}
}
#[test]
fn set_code_with_real_wasm_blob() {
let executor = substrate_test_runtime_client::new_native_executor();
let mut ext = new_test_ext();
ext.register_extension(sp_core::traits::CallInWasmExt::new(executor));
ext.execute_with(|| {
System::set_block_number(1);
System::set_code(
RawOrigin::Root.into(),
substrate_test_runtime_client::runtime::WASM_BINARY.to_vec(),
).unwrap();
assert_eq!(
System::events(),
vec![EventRecord {
phase: Phase::Initialization,
event: SysEvent::CodeUpdated,
topics: vec![],
}],
);
});
}
#[test]
fn runtime_upgraded_with_set_storage() {
let executor = substrate_test_runtime_client::new_native_executor();
let mut ext = new_test_ext();
ext.register_extension(sp_core::traits::CallInWasmExt::new(executor));
ext.execute_with(|| {
System::set_storage(
RawOrigin::Root.into(),
vec![(
well_known_keys::CODE.to_vec(),
substrate_test_runtime_client::runtime::WASM_BINARY.to_vec()
)],
).unwrap();
});
}
#[test]
fn events_not_emitted_during_genesis() {
new_test_ext().execute_with(|| {
// Block Number is zero at genesis
assert!(System::block_number().is_zero());
System::on_created_account(Default::default());
assert!(System::events().is_empty());
// Events will be emitted starting on block 1
System::set_block_number(1);
System::on_created_account(Default::default());
assert!(System::events().len() == 1);
});
}
#[test]
fn ensure_one_of_works() {
fn ensure_root_or_signed(o: RawOrigin<u64>) -> Result<Either<(), u64>, Origin> {
EnsureOneOf::<u64, EnsureRoot<u64>, EnsureSigned<u64>>::try_origin(o.into())
}
assert_eq!(ensure_root_or_signed(RawOrigin::Root).unwrap(), Either::Left(()));
assert_eq!(ensure_root_or_signed(RawOrigin::Signed(0)).unwrap(), Either::Right(0));
assert!(ensure_root_or_signed(RawOrigin::None).is_err())
}
+76
View File
@@ -0,0 +1,76 @@
// 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;
}
}