From df5e0d9aba4537c2c88145c005485d21411f27c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 26 Mar 2020 14:17:08 +0100 Subject: [PATCH 1/2] Make Dispatchable return the actual weight consumed Add PostInfo associated type to Dispatchable and have frame implement Dispatchable { type PostInfo = PostDispatchInfo } where PostDispatchInfo contains the actual weight consumed. --- frame/recovery/src/lib.rs | 4 +- frame/scheduler/src/lib.rs | 6 +- frame/sudo/src/lib.rs | 4 +- frame/support/src/dispatch.rs | 73 +++++++++++++++++-- frame/support/src/lib.rs | 6 +- frame/support/src/weights.rs | 59 +++++++++++++++ frame/utility/src/lib.rs | 11 ++- .../runtime/src/generic/checked_extrinsic.rs | 4 +- primitives/runtime/src/lib.rs | 51 ++++++++++++- primitives/runtime/src/testing.rs | 2 +- primitives/runtime/src/traits.rs | 5 +- 11 files changed, 202 insertions(+), 23 deletions(-) diff --git a/frame/recovery/src/lib.rs b/frame/recovery/src/lib.rs index c59ba85bdc501..c055f2bd97c67 100644 --- a/frame/recovery/src/lib.rs +++ b/frame/recovery/src/lib.rs @@ -161,6 +161,7 @@ use frame_support::{ decl_module, decl_event, decl_storage, decl_error, ensure, Parameter, RuntimeDebug, weights::{GetDispatchInfo, SimpleDispatchInfo, FunctionOf}, traits::{Currency, ReservableCurrency, Get, BalanceStatus}, + dispatch::PostDispatchInfo, }; use frame_system::{self as system, ensure_signed, ensure_root}; @@ -178,7 +179,7 @@ pub trait Trait: frame_system::Trait { type Event: From> + Into<::Event>; /// The overarching call type. - type Call: Parameter + Dispatchable + GetDispatchInfo; + type Call: Parameter + Dispatchable + GetDispatchInfo; /// The currency mechanism. type Currency: ReservableCurrency; @@ -348,6 +349,7 @@ decl_module! { let target = Self::proxy(&who).ok_or(Error::::NotAllowed)?; ensure!(&target == &account, Error::::NotAllowed); call.dispatch(frame_system::RawOrigin::Signed(account).into()) + .map(|_| ()).map_err(|e| e.error) } /// Allow ROOT to bypass the recovery process and set an a rescuer account diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 8c60df352768a..f70204cb1a6b7 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -150,7 +150,11 @@ decl_module! { Lookup::::remove(id); } } - Self::deposit_event(RawEvent::Dispatched((now, index), maybe_id, r)); + Self::deposit_event(RawEvent::Dispatched( + (now, index), + maybe_id, + r.map(|_| ()).map_err(|e| e.error) + )); result = cumulative_weight; None } else { diff --git a/frame/sudo/src/lib.rs b/frame/sudo/src/lib.rs index 462a84c0a17b2..3f2eacdbf8139 100644 --- a/frame/sudo/src/lib.rs +++ b/frame/sudo/src/lib.rs @@ -87,7 +87,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use sp_std::prelude::*; -use sp_runtime::{traits::{StaticLookup, Dispatchable}, DispatchError}; +use sp_runtime::traits::{StaticLookup, Dispatchable}; use frame_support::{ Parameter, decl_module, decl_event, decl_storage, decl_error, ensure, @@ -133,7 +133,6 @@ decl_module! { let res = match call.dispatch(frame_system::RawOrigin::Root.into()) { Ok(_) => true, Err(e) => { - let e: DispatchError = e.into(); sp_runtime::print(e); false } @@ -192,7 +191,6 @@ decl_module! { let res = match call.dispatch(frame_system::RawOrigin::Signed(who).into()) { Ok(_) => true, Err(e) => { - let e: DispatchError = e.into(); sp_runtime::print(e); false } diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index dadeb5cf82ad8..3d60e96af7475 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -25,18 +25,35 @@ pub use frame_metadata::{ }; pub use crate::weights::{ SimpleDispatchInfo, GetDispatchInfo, DispatchInfo, WeighData, ClassifyDispatch, - TransactionPriority, Weight, PaysFee, + TransactionPriority, Weight, PaysFee, PostDispatchInfo, WithPostDispatchInfo, }; -pub use sp_runtime::{traits::Dispatchable, DispatchError, DispatchResult}; +pub use sp_runtime::{traits::Dispatchable, DispatchError}; pub use crate::traits::{CallMetadata, GetCallMetadata, GetCallName}; +/// The return typ of a `Dispatchable` in frame. When returned explicitly from +/// a dispatchable function it allows overriding the default `PostDispatchInfo` +/// returned from a dispatch. +pub type DispatchResultWithPostInfo = + sp_runtime::DispatchResultWithInfo; + +/// Unaugmented version of `DispatchResultWithPostInfo` that can be returned from +/// dispatchable functions and is automatically converted to the augmented type. Should be +/// used whenever the `PostDispatchInfo` does not need to be overwritten. As this should +/// be the common case it is the implicit return type when none is specified. +pub type DispatchResult = Result<(), sp_runtime::DispatchError>; + +/// The error type contained in a `DispatchResultWithPostInfo`. +pub type DispatchErrorWithPostInfo = + sp_runtime::DispatchErrorWithPostInfo; + + /// A type that cannot be instantiated. pub enum Never {} /// Serializable version of Dispatchable. /// This value can be used as a "function" in an extrinsic. pub trait Callable { - type Call: Dispatchable + Codec + Clone + PartialEq + Eq; + type Call: Dispatchable + Codec + Clone + PartialEq + Eq; } // dirty hack to work around serde_derive issue @@ -119,6 +136,44 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug {} /// # fn main() {} /// ``` /// +/// ### Consuming only portions of the annotated static weight +/// +/// Per default a callable function consumes all of its static weight as declared via +/// the #[weight] attribute. However, there are use cases where only a portion of this +/// weight should be consumed. In that case the static weight is charged pre dispatch and +/// the difference is refunded post dispatch. +/// +/// In order to make use of this feature the function must return `DispatchResultWithPostInfo` +/// in place of the default `DispatchResult`. Then the actually consumed weight can be returned. +/// To consume a non default weight while returning an error +/// [`WithPostDispatchInfo::with_weight`](./weight/trait.WithPostDispatchInfo.html) can be used +/// to augment any error with custom weight information. +/// +/// ``` +/// # #[macro_use] +/// # extern crate frame_support; +/// # use frame_support::dispatch::{DispatchResultWithPostInfo, WithPostDispatchInfo}; +/// # use frame_support::weights::SimpleDispatchInfo; +/// # use frame_system::{self as system, Trait, ensure_signed}; +/// decl_module! { +/// pub struct Module for enum Call where origin: T::Origin { +/// #[weight = SimpleDispatchInfo::FixedNormal(1_000_000)] +/// fn my_long_function(origin, do_expensive_calc: bool) -> DispatchResultWithPostInfo { +/// ensure_signed(origin).map_err(|e| e.with_weight(100_000))?; +/// if do_expensive_calc { +/// // do the expensive calculation +/// // ... +/// // return None to indicate that we are using all weight (the default) +/// return Ok(None.into()); +/// } +/// // expensive calculation not executed: use only a portion of the weight +/// Ok(Some(100_000).into()) +/// } +/// } +/// } +/// # fn main() {} +/// ``` +/// /// ### Privileged Function Example /// /// A privileged function checks that the origin of the call is `ROOT`. @@ -938,7 +993,7 @@ macro_rules! decl_module { $ignore:ident $mod_type:ident<$trait_instance:ident $(, $instance:ident)?> $fn_name:ident $origin:ident $system:ident [ $( $param_name:ident),* ] ) => { - <$mod_type<$trait_instance $(, $instance)?>>::$fn_name( $origin $(, $param_name )* ) + <$mod_type<$trait_instance $(, $instance)?>>::$fn_name( $origin $(, $param_name )* ).map(Into::into).map_err(Into::into) }; // no `deposit_event` function wanted @@ -1538,7 +1593,8 @@ macro_rules! decl_module { { type Trait = $trait_instance; type Origin = $origin_type; - fn dispatch(self, _origin: Self::Origin) -> $crate::sp_runtime::DispatchResult { + type PostInfo = $crate::weights::PostDispatchInfo; + fn dispatch(self, _origin: Self::Origin) -> $crate::dispatch::DispatchResultWithPostInfo { match self { $( $call_type::$fn_name( $( $param_name ),* ) => { @@ -1563,10 +1619,10 @@ macro_rules! decl_module { where $( $other_where_bounds )* { #[doc(hidden)] - pub fn dispatch>( + pub fn dispatch>( d: D, origin: D::Origin - ) -> $crate::sp_runtime::DispatchResult { + ) -> $crate::dispatch::DispatchResultWithPostInfo { d.dispatch(origin) } } @@ -1664,10 +1720,11 @@ macro_rules! impl_outer_dispatch { impl $crate::dispatch::Dispatchable for $call_type { type Origin = $origin; type Trait = $call_type; + type PostInfo = $crate::weights::PostDispatchInfo; fn dispatch( self, origin: $origin, - ) -> $crate::sp_runtime::DispatchResult { + ) -> $crate::dispatch::DispatchResultWithPostInfo { $crate::impl_outer_dispatch! { @DISPATCH_MATCH self diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index f242efecc401e..81438ea1bdeb5 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -209,7 +209,11 @@ macro_rules! assert_err { #[cfg(feature = "std")] macro_rules! assert_ok { ( $x:expr $(,)? ) => { - assert_eq!($x, Ok(())); + let is = $x; + match is { + Ok(_) => (), + _ => assert!(false, "Expected Ok(_). Got {:#?}", is), + } }; ( $x:expr, $y:expr $(,)? ) => { assert_eq!($x, Ok($y)); diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index 7e8174ca7b145..4d501305f0ddb 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -44,6 +44,7 @@ use sp_runtime::{ traits::SignedExtension, generic::{CheckedExtrinsic, UncheckedExtrinsic}, }; +use crate::dispatch::{DispatchErrorWithPostInfo, DispatchError}; /// Re-export priority as type pub use sp_runtime::transaction_validity::TransactionPriority; @@ -116,6 +117,64 @@ pub struct DispatchInfo { pub pays_fee: bool, } +/// Weight information that is only available post dispatch. +#[derive(Clone, Copy, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode)] +pub struct PostDispatchInfo { + /// Actual weight consumed by a call or `None` which stands for the worst case static weight. + pub actual_weight: Option, +} + +impl From> for PostDispatchInfo { + fn from(actual_weight: Option) -> Self { + Self { + actual_weight, + } + } +} + +impl From<()> for PostDispatchInfo { + fn from(_: ()) -> Self { + Self { + actual_weight: None, + } + } +} + +impl sp_runtime::traits::Printable for PostDispatchInfo { + fn print(&self) { + "actual_weight=".print(); + match self.actual_weight { + Some(weight) => weight.print(), + None => "max-weight".print(), + } + } +} + +/// Allows easy conversion from `DispatchError` to `DispatchErrorWithPostInfo` for dispatchables +/// that want to return a custom a posteriori weight on error. +pub trait WithPostDispatchInfo { + /// Call this on your modules custom errors type in order to return a custom weight on error. + /// + /// # Example + /// + /// ```ignore + /// let who = ensure_signed(origin).map_err(|e| e.with_weight(100))?; + /// ensure!(who == me, Error::::NotMe.with_weight(200_000)); + /// ``` + fn with_weight(self, actual_weight: Weight) -> DispatchErrorWithPostInfo; +} + +impl WithPostDispatchInfo for T where + T: Into +{ + fn with_weight(self, actual_weight: Weight) -> DispatchErrorWithPostInfo { + DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { actual_weight: Some(actual_weight) }, + error: self.into(), + } + } +} + /// A `Dispatchable` function (aka transaction) that can carry some static information along with /// it, using the `#[weight]` attribute. pub trait GetDispatchInfo { diff --git a/frame/utility/src/lib.rs b/frame/utility/src/lib.rs index 927d8b878631d..49aea15a0f595 100644 --- a/frame/utility/src/lib.rs +++ b/frame/utility/src/lib.rs @@ -68,6 +68,7 @@ use sp_io::hashing::blake2_256; use frame_support::{decl_module, decl_event, decl_error, decl_storage, Parameter, ensure, RuntimeDebug}; use frame_support::{traits::{Get, ReservableCurrency, Currency}, weights::{GetDispatchInfo, DispatchClass,FunctionOf}, + dispatch::PostDispatchInfo, }; use frame_system::{self as system, ensure_signed}; use sp_runtime::{DispatchError, DispatchResult, traits::Dispatchable}; @@ -83,7 +84,7 @@ pub trait Trait: frame_system::Trait { type Event: From> + Into<::Event>; /// The overarching call type. - type Call: Parameter + Dispatchable + GetDispatchInfo + From>; + type Call: Parameter + Dispatchable + GetDispatchInfo + From>; /// The currency mechanism. type Currency: ReservableCurrency; @@ -246,7 +247,7 @@ decl_module! { for (index, call) in calls.into_iter().enumerate() { let result = call.dispatch(origin.clone()); if let Err(e) = result { - Self::deposit_event(Event::::BatchInterrupted(index as u32, e)); + Self::deposit_event(Event::::BatchInterrupted(index as u32, e.error)); return Ok(()); } } @@ -269,6 +270,7 @@ decl_module! { let who = ensure_signed(origin)?; let pseudonym = Self::sub_account_id(who, index); call.dispatch(frame_system::RawOrigin::Signed(pseudonym).into()) + .map(|_| ()).map_err(|e| e.error) } /// Register approval for a dispatch to be made from a deterministic composite account if @@ -357,7 +359,9 @@ decl_module! { let result = call.dispatch(frame_system::RawOrigin::Signed(id.clone()).into()); let _ = T::Currency::unreserve(&m.depositor, m.deposit); >::remove(&id, call_hash); - Self::deposit_event(RawEvent::MultisigExecuted(who, timepoint, id, result)); + Self::deposit_event(RawEvent::MultisigExecuted( + who, timepoint, id, result.map(|_| ()).map_err(|e| e.error) + )); } else { ensure!(maybe_timepoint.is_none(), Error::::UnexpectedTimepoint); if threshold > 1 { @@ -373,6 +377,7 @@ decl_module! { Self::deposit_event(RawEvent::NewMultisig(who, id)); } else { return call.dispatch(frame_system::RawOrigin::Signed(id).into()) + .map(|_| ()).map_err(|e| e.error) } } Ok(()) diff --git a/primitives/runtime/src/generic/checked_extrinsic.rs b/primitives/runtime/src/generic/checked_extrinsic.rs index 911a1131bb5fc..7a71ef5218ffb 100644 --- a/primitives/runtime/src/generic/checked_extrinsic.rs +++ b/primitives/runtime/src/generic/checked_extrinsic.rs @@ -79,7 +79,7 @@ where (None, pre) }; let res = self.function.dispatch(Origin::from(maybe_who)); - Extra::post_dispatch(pre, info.clone(), len); - Ok(res.map_err(Into::into)) + Extra::post_dispatch(pre, info, len); + Ok(res.map(|_| ()).map_err(|e| e.error)) } } diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 1a9aad6bdf132..c80971b576df2 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -353,10 +353,15 @@ impl From for DispatchOutcome { } } -/// Result of a module function call; either nothing (functions are only called for "side effects") -/// or an error message. +/// This is the legacy return type of `Dispatchable`. It is still exposed for compatibilty +/// reasons. The new return type is `DispatchResultWithInfo`. +/// FRAME runtimes should use frame_support::dispatch::DispatchResult pub type DispatchResult = sp_std::result::Result<(), DispatchError>; +/// Return type of a `Dispatchable` which contains the `DispatchResult` and additional information +/// about the `Dispatchable` that is only known post dispatch. +pub type DispatchResultWithInfo = sp_std::result::Result>; + /// Reason why a dispatch call failed #[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, RuntimeDebug)] #[cfg_attr(feature = "std", derive(Serialize))] @@ -379,6 +384,18 @@ pub enum DispatchError { }, } +/// Result of a `Dispatchable` which contains the `DispatchResult` and additional information +/// about the `Dispatchable` that is only known post dispatch. +#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, RuntimeDebug)] +pub struct DispatchErrorWithPostInfo where + Info: Eq + PartialEq + Clone + Copy + Encode + Decode + traits::Printable +{ + /// Addditional information about the `Dispatchable` which is only known post dispatch. + pub post_info: Info, + /// The actual `DispatchResult` indicating whether the dispatch was succesfull. + pub error: DispatchError, +} + impl DispatchError { /// Return the same error but without the attached message. pub fn stripped(self) -> Self { @@ -390,6 +407,18 @@ impl DispatchError { } } +impl From for DispatchErrorWithPostInfo where + T: Eq + PartialEq + Clone + Copy + Encode + Decode + traits::Printable + Default, + E: Into +{ + fn from(error: E) -> Self { + Self { + post_info: Default::default(), + error: error.into(), + } + } +} + impl From for DispatchError { fn from(_: crate::traits::LookupError) -> Self { Self::CannotLookup @@ -419,6 +448,14 @@ impl From for &'static str { } } +impl From> for &'static str where + T: Eq + PartialEq + Clone + Copy + Encode + Decode + traits::Printable +{ + fn from(err: DispatchErrorWithPostInfo) -> &'static str { + err.error.into() + } +} + impl traits::Printable for DispatchError { fn print(&self) { "DispatchError".print(); @@ -437,6 +474,16 @@ impl traits::Printable for DispatchError { } } +impl traits::Printable for DispatchErrorWithPostInfo where + T: Eq + PartialEq + Clone + Copy + Encode + Decode + traits::Printable +{ + fn print(&self) { + self.error.print(); + "PostInfo: ".print(); + self.post_info.print(); + } +} + /// This type specifies the outcome of dispatching a call to a module. /// /// In case of failure an error specific to the module is returned. diff --git a/primitives/runtime/src/testing.rs b/primitives/runtime/src/testing.rs index b3139828c1d11..f5fc35fe8ecef 100644 --- a/primitives/runtime/src/testing.rs +++ b/primitives/runtime/src/testing.rs @@ -379,6 +379,6 @@ impl Applyable for TestXt where None }; - Ok(self.call.dispatch(maybe_who.into()).map_err(Into::into)) + Ok(self.call.dispatch(maybe_who.into()).map(|_| ()).map_err(|e| e.error)) } } diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 6bd4b263c39e3..b5e37944c992c 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -642,8 +642,11 @@ pub trait Dispatchable { type Origin; /// ... type Trait; + /// Additional information that is returned by `dispatch`. Can be used to supply the caller + /// with information about a `Dispatchable` that is ownly known post dispatch. + type PostInfo: Eq + PartialEq + Clone + Copy + Encode + Decode + Printable; /// Actually dispatch this call and result the result of it. - fn dispatch(self, origin: Self::Origin) -> crate::DispatchResult; + fn dispatch(self, origin: Self::Origin) -> crate::DispatchResultWithInfo; } /// Means by which a transaction may be extended. This type embodies both the data and the logic From 77a41dc0da2587343d21137cd64fcb5fb2917250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 2 Apr 2020 16:51:29 +0200 Subject: [PATCH 2/2] Fix whitespace issues in docs --- frame/support/src/dispatch.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index 3d60e96af7475..026b30d0f43d0 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -157,19 +157,19 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug {} /// # use frame_system::{self as system, Trait, ensure_signed}; /// decl_module! { /// pub struct Module for enum Call where origin: T::Origin { -/// #[weight = SimpleDispatchInfo::FixedNormal(1_000_000)] +/// #[weight = SimpleDispatchInfo::FixedNormal(1_000_000)] /// fn my_long_function(origin, do_expensive_calc: bool) -> DispatchResultWithPostInfo { -/// ensure_signed(origin).map_err(|e| e.with_weight(100_000))?; -/// if do_expensive_calc { -/// // do the expensive calculation -/// // ... -/// // return None to indicate that we are using all weight (the default) -/// return Ok(None.into()); -/// } -/// // expensive calculation not executed: use only a portion of the weight +/// ensure_signed(origin).map_err(|e| e.with_weight(100_000))?; +/// if do_expensive_calc { +/// // do the expensive calculation +/// // ... +/// // return None to indicate that we are using all weight (the default) +/// return Ok(None.into()); +/// } +/// // expensive calculation not executed: use only a portion of the weight /// Ok(Some(100_000).into()) /// } -/// } +/// } /// } /// # fn main() {} /// ```