diff --git a/frame/system/src/extensions/check_genesis.rs b/frame/system/src/extensions/check_genesis.rs new file mode 100644 index 0000000000000..d0a346519ca23 --- /dev/null +++ b/frame/system/src/extensions/check_genesis.rs @@ -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(sp_std::marker::PhantomData); + +impl sp_std::fmt::Debug for CheckGenesis { + #[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 CheckGenesis { + /// Creates new `SignedExtension` to check genesis hash. + pub fn new() -> Self { + Self(sp_std::marker::PhantomData) + } +} + +impl SignedExtension for CheckGenesis { + type AccountId = T::AccountId; + type Call = ::Call; + type AdditionalSigned = T::Hash; + type Pre = (); + const IDENTIFIER: &'static str = "CheckGenesis"; + + fn additional_signed(&self) -> Result { + Ok(>::block_hash(T::BlockNumber::zero())) + } +} diff --git a/frame/system/src/extensions/check_mortality.rs b/frame/system/src/extensions/check_mortality.rs new file mode 100644 index 0000000000000..cc7496df9a20a --- /dev/null +++ b/frame/system/src/extensions/check_mortality.rs @@ -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(Era, sp_std::marker::PhantomData); + +impl CheckMortality { + /// utility constructor. Used only in client/factory code. + pub fn from(era: Era) -> Self { + Self(era, sp_std::marker::PhantomData) + } +} + +impl sp_std::fmt::Debug for CheckMortality { + #[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 SignedExtension for CheckMortality { + 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, + _len: usize, + ) -> TransactionValidity { + let current_u64 = >::block_number().saturated_into::(); + let valid_till = self.0.death(current_u64); + Ok(ValidTransaction { + longevity: valid_till.saturating_sub(current_u64), + ..Default::default() + }) + } + + fn additional_signed(&self) -> Result { + let current_u64 = >::block_number().saturated_into::(); + let n = self.0.birth(current_u64).saturated_into::(); + if !>::contains_key(n) { + Err(InvalidTransaction::AncientBirthBlock.into()) + } else { + Ok(>::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::::from(Era::mortal(4, 2)).additional_signed().err().unwrap(), + InvalidTransaction::AncientBirthBlock.into(), + ); + + // correct + System::set_block_number(13); + >::insert(12, H256::repeat_byte(1)); + assert!(CheckMortality::::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::::default(), + CheckMortality::::from(Era::mortal(16, 256)), + ); + System::set_block_number(17); + >::insert(16, H256::repeat_byte(1)); + + assert_eq!(ext.validate(&1, CALL, &normal, len).unwrap().longevity, 15); + }) + } +} diff --git a/frame/system/src/extensions/check_nonce.rs b/frame/system/src/extensions/check_nonce.rs new file mode 100644 index 0000000000000..1af3a1210aaf4 --- /dev/null +++ b/frame/system/src/extensions/check_nonce.rs @@ -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(#[codec(compact)] T::Index); + +impl CheckNonce { + /// utility constructor. Used only in client/factory code. + pub fn from(nonce: T::Index) -> Self { + Self(nonce) + } +} + +impl sp_std::fmt::Debug for CheckNonce { + #[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 SignedExtension for CheckNonce where + T::Call: Dispatchable +{ + 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, + _len: usize, + ) -> Result<(), TransactionValidityError> { + let mut account = crate::Account::::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::::insert(who, account); + Ok(()) + } + + fn validate( + &self, + who: &Self::AccountId, + _call: &Self::Call, + info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + // check index + let account = crate::Account::::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::::insert(1, crate::AccountInfo { + nonce: 1, + refcount: 0, + data: 0, + }); + let info = DispatchInfo::default(); + let len = 0_usize; + // stale + assert!(CheckNonce::(0).validate(&1, CALL, &info, len).is_err()); + assert!(CheckNonce::(0).pre_dispatch(&1, CALL, &info, len).is_err()); + // correct + assert!(CheckNonce::(1).validate(&1, CALL, &info, len).is_ok()); + assert!(CheckNonce::(1).pre_dispatch(&1, CALL, &info, len).is_ok()); + // future + assert!(CheckNonce::(5).validate(&1, CALL, &info, len).is_ok()); + assert!(CheckNonce::(5).pre_dispatch(&1, CALL, &info, len).is_err()); + }) + } +} diff --git a/frame/system/src/extensions/check_spec_version.rs b/frame/system/src/extensions/check_spec_version.rs new file mode 100644 index 0000000000000..8dc4d8d9ceddc --- /dev/null +++ b/frame/system/src/extensions/check_spec_version.rs @@ -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(sp_std::marker::PhantomData); + +impl sp_std::fmt::Debug for CheckSpecVersion { + #[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 CheckSpecVersion { + /// Create new `SignedExtension` to check runtime version. + pub fn new() -> Self { + Self(sp_std::marker::PhantomData) + } +} + +impl SignedExtension for CheckSpecVersion { + type AccountId = T::AccountId; + type Call = ::Call; + type AdditionalSigned = u32; + type Pre = (); + const IDENTIFIER: &'static str = "CheckSpecVersion"; + + fn additional_signed(&self) -> Result { + Ok(>::runtime_version().spec_version) + } +} diff --git a/frame/system/src/extensions/check_tx_version.rs b/frame/system/src/extensions/check_tx_version.rs new file mode 100644 index 0000000000000..ee6f3349365b9 --- /dev/null +++ b/frame/system/src/extensions/check_tx_version.rs @@ -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(sp_std::marker::PhantomData); + +impl sp_std::fmt::Debug for CheckTxVersion { + #[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 CheckTxVersion { + /// Create new `SignedExtension` to check transaction version. + pub fn new() -> Self { + Self(sp_std::marker::PhantomData) + } +} + +impl SignedExtension for CheckTxVersion { + type AccountId = T::AccountId; + type Call = ::Call; + type AdditionalSigned = u32; + type Pre = (); + const IDENTIFIER: &'static str = "CheckTxVersion"; + + fn additional_signed(&self) -> Result { + Ok(>::runtime_version().transaction_version) + } +} diff --git a/frame/system/src/extensions/check_weight.rs b/frame/system/src/extensions/check_weight.rs new file mode 100644 index 0000000000000..d52138b1e3bbe --- /dev/null +++ b/frame/system/src/extensions/check_weight.rs @@ -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(sp_std::marker::PhantomData); + +impl CheckWeight where + T::Call: Dispatchable +{ + /// 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 + => ::one(), + DispatchClass::Normal => T::AvailableBlockRatio::get(), + } + } + + /// Checks if the current extrinsic does not exceed `MaximumExtrinsicWeight` limit. + fn check_extrinsic_weight( + info: &DispatchInfoOf, + ) -> 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, + ) -> Result { + let maximum_weight = T::MaximumBlockWeight::get(); + let mut all_weight = Module::::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, + len: usize, + ) -> Result { + let current_len = Module::::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) -> 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, + 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, + 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 SignedExtension for CheckWeight where + T::Call: Dispatchable +{ + 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, + 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, + 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, + len: usize, + ) -> Result<(), TransactionValidityError> { + Self::do_pre_dispatch(info, len) + } + + fn validate_unsigned( + _call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> TransactionValidity { + Self::do_validate(info, len) + } + + fn post_dispatch( + _pre: Self::Pre, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + _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 sp_std::fmt::Debug for CheckWeight { + #[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 { + ::AvailableBlockRatio::get() * ::MaximumBlockWeight::get() + } + + fn normal_length_limit() -> u32 { + ::AvailableBlockRatio::get() * ::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::::do_pre_dispatch(max, len)); + assert_eq!(System::block_weight().total(), Weight::max_value()); + assert!(System::block_weight().total() > ::MaximumBlockWeight::get()); + }); + check(|max, len| { + assert_ok!(CheckWeight::::do_validate(max, len)); + }); + } + + #[test] + fn normal_extrinsic_limited_by_maximum_extrinsic_weight() { + new_test_ext().execute_with(|| { + let max = DispatchInfo { + weight: ::MaximumExtrinsicWeight::get() + 1, + class: DispatchClass::Normal, + ..Default::default() + }; + let len = 0_usize; + + assert_noop!( + CheckWeight::::do_validate(&max, len), + InvalidTransaction::ExhaustsResources + ); + }); + } + + #[test] + fn operational_extrinsic_limited_by_operational_space_limit() { + new_test_ext().execute_with(|| { + let operational_limit = CheckWeight::::get_dispatch_limit_ratio( + DispatchClass::Operational + ) * ::MaximumBlockWeight::get(); + let base_weight = ::ExtrinsicBaseWeight::get(); + let block_base = ::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::::do_validate(&okay, len), + Ok(ValidTransaction { + priority: CheckWeight::::get_priority(&okay), + ..Default::default() + }) + ); + assert_noop!( + CheckWeight::::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() > ::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::::do_pre_dispatch(&max_normal, len)); + assert_eq!(System::block_weight().total(), 768); + assert_ok!(CheckWeight::::do_pre_dispatch(&rest_operational, len)); + assert_eq!(::MaximumBlockWeight::get(), 1024); + assert_eq!(System::block_weight().total(), ::MaximumBlockWeight::get()); + // Checking single extrinsic should not take current block weight into account. + assert_eq!(CheckWeight::::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::::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::::do_pre_dispatch(&max_normal, len)); + assert_eq!(::MaximumBlockWeight::get(), 1024); + assert_eq!(System::block_weight().total(), ::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::::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::::do_pre_dispatch(&dispatch_operational, len)); + // Not too much though + assert_noop!( + CheckWeight::::do_pre_dispatch(&dispatch_operational, len), + InvalidTransaction::ExhaustsResources + ); + // Even with full block, validity of single transaction should be correct. + assert_eq!(CheckWeight::::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::(PhantomData).pre_dispatch(&1, CALL, &normal, len).is_err()); + // will fit. + assert!(CheckWeight::(PhantomData).pre_dispatch(&1, CALL, &op, len).is_ok()); + + // likewise for length limit. + let len = 100_usize; + AllExtrinsicsLen::put(normal_length_limit()); + assert!(CheckWeight::(PhantomData).pre_dispatch(&1, CALL, &normal, len).is_err()); + assert!(CheckWeight::(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::(PhantomData) + .validate(&1, CALL, &normal, len) + .unwrap() + .priority; + assert_eq!(priority, 100); + + let priority = CheckWeight::(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::(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 - ::ExtrinsicBaseWeight::get(), + ..Default::default() + }; + let big = DispatchInfo { + weight: normal_limit - ::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::(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 - ::ExtrinsicBaseWeight::get(), DispatchClass::Normal) + }); + + let pre = CheckWeight::(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap(); + assert_eq!(BlockWeight::get().total(), info.weight + 256); + + assert!( + CheckWeight::::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::(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap(); + assert_eq!( + BlockWeight::get().total(), + info.weight + 128 + ::ExtrinsicBaseWeight::get(), + ); + + assert!( + CheckWeight::::post_dispatch(pre, &info, &post_info, len, &Ok(())) + .is_ok() + ); + assert_eq!( + BlockWeight::get().total(), + info.weight + 128 + ::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(), ::BlockExecutionWeight::get()); + let r = CheckWeight::(PhantomData).pre_dispatch(&1, CALL, &free, len); + assert!(r.is_ok()); + assert_eq!( + System::block_weight().total(), + ::ExtrinsicBaseWeight::get() + ::BlockExecutionWeight::get() + ); + }) + } +} diff --git a/frame/system/src/extensions/mod.rs b/frame/system/src/extensions/mod.rs new file mode 100644 index 0000000000000..ff61353e2d176 --- /dev/null +++ b/frame/system/src/extensions/mod.rs @@ -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; + diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 8eec6a2c37585..18723fff299be 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -102,17 +102,12 @@ use sp_std::marker::PhantomData; use sp_std::fmt::Debug; use sp_version::RuntimeVersion; use sp_runtime::{ - RuntimeDebug, Perbill, DispatchError, DispatchResult, Either, - generic::{self, Era}, - transaction_validity::{ - ValidTransaction, TransactionPriority, TransactionLongevity, TransactionValidityError, - InvalidTransaction, TransactionValidity, - }, + RuntimeDebug, Perbill, DispatchError, Either, generic, traits::{ - self, CheckEqual, AtLeast32Bit, Zero, SignedExtension, Lookup, LookupError, - SimpleBitOps, Hash, Member, MaybeDisplay, BadOrigin, SaturatedConversion, + self, CheckEqual, AtLeast32Bit, Zero, Lookup, LookupError, + SimpleBitOps, Hash, Member, MaybeDisplay, BadOrigin, MaybeSerialize, MaybeSerializeDeserialize, MaybeMallocSizeOf, StaticLookup, One, Bounded, - Dispatchable, DispatchInfoOf, PostDispatchInfoOf, Printable, + Dispatchable, }, offchain::storage_lock::BlockNumberProvider, }; @@ -126,7 +121,7 @@ use frame_support::{ StoredMap, EnsureOrigin, OriginTrait, Filter, }, weights::{ - Weight, RuntimeDbWeight, DispatchInfo, PostDispatchInfo, DispatchClass, + Weight, RuntimeDbWeight, DispatchInfo, DispatchClass, extract_actual_weight, }, dispatch::DispatchResultWithPostInfo, @@ -137,6 +132,21 @@ use codec::{Encode, Decode, FullCodec, EncodeLike}; use sp_io::TestExternalities; pub mod offchain; +#[cfg(test)] +pub(crate) mod mock; + +mod extensions; +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, + check_weight::CheckWeight, +}; +// Backward compatible re-export. +pub use extensions::check_mortality::CheckMortality as CheckEra; /// Compute the trie root of a list of extrinsics. pub fn extrinsics_root(extrinsics: &[E]) -> H::Output { @@ -372,60 +382,6 @@ impl From for LastRuntimeUpgradeInfo { } } -/// 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; - } -} - decl_storage! { trait Store for Module as System { /// The full account information for a particular account ID. @@ -436,7 +392,7 @@ decl_storage! { ExtrinsicCount: Option; /// The current weight for the block. - BlockWeight get(fn block_weight): ExtrinsicsWeight; + BlockWeight get(fn block_weight): weights::ExtrinsicsWeight; /// Total length (in bytes) for all extrinsics put together, for the current block. AllExtrinsicsLen: Option; @@ -1372,360 +1328,6 @@ pub fn split_inner(option: Option, splitter: impl FnOnce(T) -> (R, S } } -/// resource limit check. -#[derive(Encode, Decode, Clone, Eq, PartialEq)] -pub struct CheckWeight(PhantomData); - -impl CheckWeight where - T::Call: Dispatchable -{ - /// 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 - => ::one(), - DispatchClass::Normal => T::AvailableBlockRatio::get(), - } - } - - /// Checks if the current extrinsic does not exceed `MaximumExtrinsicWeight` limit. - fn check_extrinsic_weight( - info: &DispatchInfoOf, - ) -> 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, - ) -> Result { - let maximum_weight = T::MaximumBlockWeight::get(); - let mut all_weight = Module::::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, - len: usize, - ) -> Result { - let current_len = Module::::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) -> 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(PhantomData) - } - - /// 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, - 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)?; - - AllExtrinsicsLen::put(next_len); - 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, - 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 SignedExtension for CheckWeight where - T::Call: Dispatchable -{ - 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, - 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, - 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, - len: usize, - ) -> Result<(), TransactionValidityError> { - Self::do_pre_dispatch(info, len) - } - - fn validate_unsigned( - _call: &Self::Call, - info: &DispatchInfoOf, - len: usize, - ) -> TransactionValidity { - Self::do_validate(info, len) - } - - fn post_dispatch( - _pre: Self::Pre, - info: &DispatchInfoOf, - post_info: &PostDispatchInfoOf, - _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 { - BlockWeight::mutate(|current_weight| { - current_weight.sub(unspent, info.class); - }) - } - - Ok(()) - } -} - -impl Debug for CheckWeight { - #[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(()) - } -} - -/// Nonce check and increment to give replay protection for transactions. -#[derive(Encode, Decode, Clone, Eq, PartialEq)] -pub struct CheckNonce(#[codec(compact)] T::Index); - -impl CheckNonce { - /// utility constructor. Used only in client/factory code. - pub fn from(nonce: T::Index) -> Self { - Self(nonce) - } -} - -impl Debug for CheckNonce { - #[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 SignedExtension for CheckNonce where - T::Call: Dispatchable -{ - 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, - _len: usize, - ) -> Result<(), TransactionValidityError> { - let mut account = Account::::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(); - Account::::insert(who, account); - Ok(()) - } - - fn validate( - &self, - who: &Self::AccountId, - _call: &Self::Call, - info: &DispatchInfoOf, - _len: usize, - ) -> TransactionValidity { - // check index - let account = Account::::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, - }) - } -} impl IsDeadAccount for Module { fn is_dead_account(who: &T::AccountId) -> bool { @@ -1733,167 +1335,6 @@ impl IsDeadAccount for Module { } } -/// Check for transaction mortality. -#[derive(Encode, Decode, Clone, Eq, PartialEq)] -pub struct CheckEra(Era, sp_std::marker::PhantomData); - -impl CheckEra { - /// utility constructor. Used only in client/factory code. - pub fn from(era: Era) -> Self { - Self(era, sp_std::marker::PhantomData) - } -} - -impl Debug for CheckEra { - #[cfg(feature = "std")] - fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - write!(f, "CheckEra({:?})", self.0) - } - - #[cfg(not(feature = "std"))] - fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - Ok(()) - } -} - -impl SignedExtension for CheckEra { - type AccountId = T::AccountId; - type Call = T::Call; - type AdditionalSigned = T::Hash; - type Pre = (); - const IDENTIFIER: &'static str = "CheckEra"; - - fn validate( - &self, - _who: &Self::AccountId, - _call: &Self::Call, - _info: &DispatchInfoOf, - _len: usize, - ) -> TransactionValidity { - let current_u64 = >::block_number().saturated_into::(); - let valid_till = self.0.death(current_u64); - Ok(ValidTransaction { - longevity: valid_till.saturating_sub(current_u64), - ..Default::default() - }) - } - - fn additional_signed(&self) -> Result { - let current_u64 = >::block_number().saturated_into::(); - let n = self.0.birth(current_u64).saturated_into::(); - if !>::contains_key(n) { - Err(InvalidTransaction::AncientBirthBlock.into()) - } else { - Ok(>::block_hash(n)) - } - } -} - -/// Nonce check and increment to give replay protection for transactions. -#[derive(Encode, Decode, Clone, Eq, PartialEq)] -pub struct CheckGenesis(sp_std::marker::PhantomData); - -impl Debug for CheckGenesis { - #[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 CheckGenesis { - /// Creates new `SignedExtension` to check genesis hash. - pub fn new() -> Self { - Self(sp_std::marker::PhantomData) - } -} - -impl SignedExtension for CheckGenesis { - type AccountId = T::AccountId; - type Call = ::Call; - type AdditionalSigned = T::Hash; - type Pre = (); - const IDENTIFIER: &'static str = "CheckGenesis"; - - fn additional_signed(&self) -> Result { - Ok(>::block_hash(T::BlockNumber::zero())) - } -} - -/// Ensure the transaction version registered in the transaction is the same as at present. -#[derive(Encode, Decode, Clone, Eq, PartialEq)] -pub struct CheckTxVersion(sp_std::marker::PhantomData); - -impl Debug for CheckTxVersion { - #[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 CheckTxVersion { - /// Create new `SignedExtension` to check transaction version. - pub fn new() -> Self { - Self(sp_std::marker::PhantomData) - } -} - -impl SignedExtension for CheckTxVersion { - type AccountId = T::AccountId; - type Call = ::Call; - type AdditionalSigned = u32; - type Pre = (); - const IDENTIFIER: &'static str = "CheckTxVersion"; - - fn additional_signed(&self) -> Result { - Ok(>::runtime_version().transaction_version) - } -} - -/// Ensure the runtime version registered in the transaction is the same as at present. -#[derive(Encode, Decode, Clone, Eq, PartialEq)] -pub struct CheckSpecVersion(sp_std::marker::PhantomData); - -impl Debug for CheckSpecVersion { - #[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 CheckSpecVersion { - /// Create new `SignedExtension` to check runtime version. - pub fn new() -> Self { - Self(sp_std::marker::PhantomData) - } -} - -impl SignedExtension for CheckSpecVersion { - type AccountId = T::AccountId; - type Call = ::Call; - type AdditionalSigned = u32; - type Pre = (); - const IDENTIFIER: &'static str = "CheckSpecVersion"; - - fn additional_signed(&self) -> Result { - Ok(>::runtime_version().spec_version) - } -} - pub struct ChainContext(sp_std::marker::PhantomData); impl Default for ChainContext { fn default() -> Self { @@ -1909,886 +1350,3 @@ impl Lookup for ChainContext { ::lookup(s) } } - -#[cfg(test)] -pub(crate) mod tests { - use super::*; - use sp_std::cell::RefCell; - use sp_core::H256; - use sp_runtime::{traits::{BlakeTwo256, IdentityLookup, SignedExtension}, testing::Header, DispatchError}; - use frame_support::{ - impl_outer_origin, parameter_types, assert_ok, assert_noop, - weights::{WithPostDispatchInfo, Pays}, - }; - - impl_outer_origin! { - pub enum Origin for Test where system = super {} - } - - #[derive(Clone, Eq, PartialEq, Debug)] - 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> = RefCell::new(vec![]); - } - - pub struct RecordKilled; - impl OnKilledAccount 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 { - 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; - type Header = Header; - type Event = Event; - 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; - } - - type System = Module; - type SysEvent = ::Event; - - const CALL: &::Call = &Call; - - fn new_test_ext() -> sp_io::TestExternalities { - let mut ext: sp_io::TestExternalities = GenesisConfig::default().build_storage::().unwrap().into(); - // Add to each test the initial weight of a block - ext.execute_with(|| System::register_extra_weight_unchecked(::BlockExecutionWeight::get(), DispatchClass::Mandatory)); - ext - } - - fn normal_weight_limit() -> Weight { - ::AvailableBlockRatio::get() * ::MaximumBlockWeight::get() - } - - fn normal_length_limit() -> u32 { - ::AvailableBlockRatio::get() * ::MaximumBlockLength::get() - } - - #[test] - fn origin_works() { - let o = Origin::from(RawOrigin::::Signed(1u64)); - let x: Result, Origin> = o.into(); - assert_eq!(x.unwrap(), RawOrigin::::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 signed_ext_check_nonce_works() { - new_test_ext().execute_with(|| { - Account::::insert(1, AccountInfo { nonce: 1, refcount: 0, data: 0 }); - let info = DispatchInfo::default(); - let len = 0_usize; - // stale - assert!(CheckNonce::(0).validate(&1, CALL, &info, len).is_err()); - assert!(CheckNonce::(0).pre_dispatch(&1, CALL, &info, len).is_err()); - // correct - assert!(CheckNonce::(1).validate(&1, CALL, &info, len).is_ok()); - assert!(CheckNonce::(1).pre_dispatch(&1, CALL, &info, len).is_ok()); - // future - assert!(CheckNonce::(5).validate(&1, CALL, &info, len).is_ok()); - assert!(CheckNonce::(5).pre_dispatch(&1, CALL, &info, len).is_err()); - }) - } - - #[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 - ::ExtrinsicBaseWeight::get(), - ..Default::default() - }; - let big = DispatchInfo { - weight: normal_limit - ::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::(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 - ::ExtrinsicBaseWeight::get(), DispatchClass::Normal) - }); - - let pre = CheckWeight::(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap(); - assert_eq!(BlockWeight::get().total(), info.weight + 256); - - assert!( - CheckWeight::::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::(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap(); - assert_eq!( - BlockWeight::get().total(), - info.weight + 128 + ::ExtrinsicBaseWeight::get(), - ); - - assert!( - CheckWeight::::post_dispatch(pre, &info, &post_info, len, &Ok(())) - .is_ok() - ); - assert_eq!( - BlockWeight::get().total(), - info.weight + 128 + ::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(), ::BlockExecutionWeight::get()); - let r = CheckWeight::(PhantomData).pre_dispatch(&1, CALL, &free, len); - assert!(r.is_ok()); - assert_eq!( - System::block_weight().total(), - ::ExtrinsicBaseWeight::get() + ::BlockExecutionWeight::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::::do_pre_dispatch(max, len)); - assert_eq!(System::block_weight().total(), Weight::max_value()); - assert!(System::block_weight().total() > ::MaximumBlockWeight::get()); - }); - check(|max, len| { - assert_ok!(CheckWeight::::do_validate(max, len)); - }); - } - - #[test] - fn normal_extrinsic_limited_by_maximum_extrinsic_weight() { - new_test_ext().execute_with(|| { - let max = DispatchInfo { - weight: MaximumExtrinsicWeight::get() + 1, - class: DispatchClass::Normal, - ..Default::default() - }; - let len = 0_usize; - - assert_noop!( - CheckWeight::::do_validate(&max, len), - InvalidTransaction::ExhaustsResources - ); - }); - } - - #[test] - fn operational_extrinsic_limited_by_operational_space_limit() { - new_test_ext().execute_with(|| { - let operational_limit = CheckWeight::::get_dispatch_limit_ratio( - DispatchClass::Operational - ) * ::MaximumBlockWeight::get(); - let base_weight = ::ExtrinsicBaseWeight::get(); - let block_base = ::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::::do_validate(&okay, len), - Ok(ValidTransaction { - priority: CheckWeight::::get_priority(&okay), - ..Default::default() - }) - ); - assert_noop!( - CheckWeight::::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() > ::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::::do_pre_dispatch(&max_normal, len)); - assert_eq!(System::block_weight().total(), 768); - assert_ok!(CheckWeight::::do_pre_dispatch(&rest_operational, len)); - assert_eq!(::MaximumBlockWeight::get(), 1024); - assert_eq!(System::block_weight().total(), ::MaximumBlockWeight::get()); - // Checking single extrinsic should not take current block weight into account. - assert_eq!(CheckWeight::::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::::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::::do_pre_dispatch(&max_normal, len)); - assert_eq!(::MaximumBlockWeight::get(), 1024); - assert_eq!(System::block_weight().total(), ::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::::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::::do_pre_dispatch(&dispatch_operational, len)); - // Not too much though - assert_noop!(CheckWeight::::do_pre_dispatch(&dispatch_operational, len), InvalidTransaction::ExhaustsResources); - // Even with full block, validity of single transaction should be correct. - assert_eq!(CheckWeight::::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::(PhantomData).pre_dispatch(&1, CALL, &normal, len).is_err()); - // will fit. - assert!(CheckWeight::(PhantomData).pre_dispatch(&1, CALL, &op, len).is_ok()); - - // likewise for length limit. - let len = 100_usize; - AllExtrinsicsLen::put(normal_length_limit()); - assert!(CheckWeight::(PhantomData).pre_dispatch(&1, CALL, &normal, len).is_err()); - assert!(CheckWeight::(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::(PhantomData) - .validate(&1, CALL, &normal, len) - .unwrap() - .priority; - assert_eq!(priority, 100); - - let priority = CheckWeight::(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::(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_era_should_work() { - new_test_ext().execute_with(|| { - // future - assert_eq!( - CheckEra::::from(Era::mortal(4, 2)).additional_signed().err().unwrap(), - InvalidTransaction::AncientBirthBlock.into(), - ); - - // correct - System::set_block_number(13); - >::insert(12, H256::repeat_byte(1)); - assert!(CheckEra::::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 = ( - CheckWeight::(PhantomData), - CheckEra::::from(Era::mortal(16, 256)), - ); - System::set_block_number(17); - >::insert(16, H256::repeat_byte(1)); - - assert_eq!(ext.validate(&1, CALL, &normal, len).unwrap().longevity, 15); - }) - } - - - #[test] - fn set_code_checks_works() { - struct CallInWasm(Vec); - - impl sp_core::traits::CallInWasm for CallInWasm { - fn call_in_wasm( - &self, - _: &[u8], - _: Option>, - _: &str, - _: &[u8], - _: &mut dyn sp_externalities::Externalities, - _: sp_core::traits::MissingHostFunctions, - ) -> Result, String> { - Ok(self.0.clone()) - } - } - - let test_data = vec![ - ("test", 1, 2, Err(Error::::SpecVersionNeedsToIncrease)), - ("test", 1, 1, Err(Error::::SpecVersionNeedsToIncrease)), - ("test2", 1, 1, Err(Error::::InvalidSpecName)), - ("test", 2, 1, Ok(())), - ("test", 0, 1, Err(Error::::SpecVersionNeedsToIncrease)), - ("test", 1, 0, Err(Error::::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) -> Result, Origin> { - EnsureOneOf::, EnsureSigned>::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()) - } -} diff --git a/frame/system/src/mock.rs b/frame/system/src/mock.rs new file mode 100644 index 0000000000000..0484b34ba3e3c --- /dev/null +++ b/frame/system/src/mock.rs @@ -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> = RefCell::new(vec![]); +} + +pub struct RecordKilled; +impl OnKilledAccount 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 { + 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; + type Header = Header; + type Event = Event; + 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; +pub type SysEvent = ::Event; + +pub const CALL: &::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::().unwrap().into(); + // Add to each test the initial weight of a block + ext.execute_with(|| System::register_extra_weight_unchecked( + ::BlockExecutionWeight::get(), + DispatchClass::Mandatory + )); + ext +} diff --git a/frame/system/src/offchain.rs b/frame/system/src/offchain.rs index 42699362a36a3..1290ca6378eb8 100644 --- a/frame/system/src/offchain.rs +++ b/frame/system/src/offchain.rs @@ -638,7 +638,7 @@ pub trait SignedPayload: 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}; diff --git a/frame/system/src/tests.rs b/frame/system/src/tests.rs new file mode 100644 index 0000000000000..2f93dc858f10b --- /dev/null +++ b/frame/system/src/tests.rs @@ -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::::Signed(1u64)); + let x: Result, Origin> = o.into(); + assert_eq!(x.unwrap(), RawOrigin::::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); + + impl sp_core::traits::CallInWasm for CallInWasm { + fn call_in_wasm( + &self, + _: &[u8], + _: Option>, + _: &str, + _: &[u8], + _: &mut dyn sp_externalities::Externalities, + _: sp_core::traits::MissingHostFunctions, + ) -> Result, String> { + Ok(self.0.clone()) + } + } + + let test_data = vec![ + ("test", 1, 2, Err(Error::::SpecVersionNeedsToIncrease)), + ("test", 1, 1, Err(Error::::SpecVersionNeedsToIncrease)), + ("test2", 1, 1, Err(Error::::InvalidSpecName)), + ("test", 2, 1, Ok(())), + ("test", 0, 1, Err(Error::::SpecVersionNeedsToIncrease)), + ("test", 1, 0, Err(Error::::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) -> Result, Origin> { + EnsureOneOf::, EnsureSigned>::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()) +} diff --git a/frame/system/src/weights.rs b/frame/system/src/weights.rs new file mode 100644 index 0000000000000..93295093c4fb8 --- /dev/null +++ b/frame/system/src/weights.rs @@ -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; + } +}