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

Storage Layer for All FRAME Extrinsics #11431

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6e0ca26
add new trait
shawntabrizi May 17, 2022
9f0d290
implement DispatchableWithStorageLayer
shawntabrizi May 17, 2022
57fe674
at least one transactional
shawntabrizi May 17, 2022
28d35bd
all dispatch is at least transactional
shawntabrizi May 17, 2022
eaa330d
storage_layer api
shawntabrizi May 17, 2022
213e537
add test
shawntabrizi May 17, 2022
b8d32b9
storage layer tests
shawntabrizi May 17, 2022
a03c13f
deprecate transactional tag
shawntabrizi May 17, 2022
5dc88cd
i guess no reason to deprecate
shawntabrizi May 17, 2022
2856ecd
remove transactional from batch_all
shawntabrizi May 17, 2022
b0f7241
update tests
shawntabrizi May 17, 2022
0f26a7d
Merge remote-tracking branch 'origin/master' into shawntabrizi-dispat…
shawntabrizi May 17, 2022
f9f65db
extend trait
shawntabrizi May 19, 2022
738c021
Merge branch 'master' of https://github.com/paritytech/substrate into…
May 19, 2022
d1fd82f
cargo run --quiet --profile=production --features runtime-benchmarks …
May 19, 2022
5afa7e7
cargo run --quiet --profile=production --features runtime-benchmarks …
May 19, 2022
1a447b1
cargo run --quiet --profile=production --features runtime-benchmarks …
May 20, 2022
1e657bf
fix copy paste name
shawntabrizi May 20, 2022
5793a59
Merge branch 'master' of https://github.com/paritytech/substrate into…
May 20, 2022
09b4274
cargo run --quiet --profile=production --features runtime-benchmarks …
May 20, 2022
03c19ec
Create run_all_benchmarks.sh
shawntabrizi May 21, 2022
0e7e939
uncomment build
shawntabrizi May 21, 2022
fe1a750
update number of steps and repeats
shawntabrizi May 21, 2022
2068319
add skip build
shawntabrizi May 21, 2022
71b45db
Update run_all_benchmarks.sh
shawntabrizi May 21, 2022
c357618
Update run_all_benchmarks.sh
shawntabrizi May 21, 2022
85e7a28
new benchmarks
shawntabrizi May 21, 2022
d2aa0ed
Merge remote-tracking branch 'origin/master' into shawntabrizi-dispat…
shawntabrizi May 23, 2022
e0abb8f
Update frame/support/src/traits/dispatch.rs
shawntabrizi May 23, 2022
a87f4bc
Update frame/support/src/traits/dispatch.rs
shawntabrizi May 23, 2022
3633d3c
Update frame/support/test/tests/storage_layers.rs
shawntabrizi May 23, 2022
82b3576
Update frame/support/test/tests/storage_layers.rs
shawntabrizi May 23, 2022
db8dbe3
weights
shawntabrizi May 24, 2022
630f149
Update dispatch.rs
shawntabrizi May 24, 2022
3035fc8
Merge branch 'master' into shawntabrizi-dispatchable-with-storage-layer
shawntabrizi May 25, 2022
993383a
doc link
shawntabrizi May 26, 2022
d7530da
decl_macro support
shawntabrizi May 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions frame/support/procedural/src/construct_runtime/expand/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ pub fn expand_outer_dispatch(
}
}
}
impl #scrate::traits::DispatchableWithStorageLayer for Call {
type Origin = Origin;
fn dispatch_with_storage_layer(self, origin: Origin) -> #scrate::dispatch::DispatchResultWithPostInfo {
#scrate::storage::with_storage_layer(|| {
#scrate::dispatch::Dispatchable::dispatch(self, origin)
})
}
}

#(
impl #scrate::traits::IsSubType<#scrate::dispatch::CallableCallFor<#pallet_names, #runtime>> for Call {
Expand Down
12 changes: 6 additions & 6 deletions frame/support/procedural/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,12 @@ pub fn transactional(attr: TokenStream, input: TokenStream) -> TokenStream {
transactional::transactional(attr, input).unwrap_or_else(|e| e.to_compile_error().into())
}

#[proc_macro_attribute]
pub fn require_transactional(attr: TokenStream, input: TokenStream) -> TokenStream {
transactional::require_transactional(attr, input)
.unwrap_or_else(|e| e.to_compile_error().into())
}

/// Derive [`Clone`] but do not bound any generic. Docs are at `frame_support::CloneNoBound`.
#[proc_macro_derive(CloneNoBound)]
pub fn derive_clone_no_bound(input: TokenStream) -> TokenStream {
Expand Down Expand Up @@ -509,12 +515,6 @@ pub fn derive_default_no_bound(input: TokenStream) -> TokenStream {
default_no_bound::derive_default_no_bound(input)
}

#[proc_macro_attribute]
pub fn require_transactional(attr: TokenStream, input: TokenStream) -> TokenStream {
transactional::require_transactional(attr, input)
.unwrap_or_else(|e| e.to_compile_error().into())
}

#[proc_macro]
pub fn crate_to_crate_version(input: TokenStream) -> TokenStream {
crate_version::crate_to_crate_version(input)
Expand Down
8 changes: 6 additions & 2 deletions frame/support/procedural/src/pallet/expand/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,12 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
#frame_support::sp_tracing::enter_span!(
#frame_support::sp_tracing::trace_span!(stringify!(#fn_name))
);
<#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* )
.map(Into::into).map_err(Into::into)
// We execute all dispatchable in at least one storage layer, allowing them
// to return an error at any point, and undoing any storage changes.
#frame_support::storage::in_storage_layer(|| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change would also need to be done for the decl_module macro.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done and test

<#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* )
.map(Into::into).map_err(Into::into)
})
},
)*
Self::__Ignore(_, _) => {
Expand Down
3 changes: 2 additions & 1 deletion frame/support/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ pub use crate::{
result,
},
traits::{
CallMetadata, GetCallMetadata, GetCallName, GetStorageVersion, UnfilteredDispatchable,
CallMetadata, DispatchableWithStorageLayer, GetCallMetadata, GetCallName,
GetStorageVersion, UnfilteredDispatchable,
},
weights::{
ClassifyDispatch, DispatchInfo, GetDispatchInfo, PaysFee, PostDispatchInfo,
Expand Down
4 changes: 3 additions & 1 deletion frame/support/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ use sp_runtime::generic::{Digest, DigestItem};
use sp_std::prelude::*;

pub use self::{
transactional::{with_transaction, with_transaction_unchecked},
transactional::{
in_storage_layer, with_storage_layer, with_transaction, with_transaction_unchecked,
},
types::StorageEntryMetadataBuilder,
};
pub use sp_runtime::TransactionOutcome;
Expand Down
63 changes: 63 additions & 0 deletions frame/support/src/storage/transactional.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,40 @@ pub fn with_transaction_unchecked<R>(f: impl FnOnce() -> TransactionOutcome<R>)
}
}

/// Execute the supplied function, adding a new storage layer.
///
/// This is the same as `with_transaction`, but assuming that any function returning
/// an `Err` should rollback, and any function returning `Ok` should commit. This
/// provides a cleaner API to the developer who wants this behavior.
pub fn with_storage_layer<T, E>(f: impl FnOnce() -> Result<T, E>) -> Result<T, E>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where
E: From<DispatchError>,
KiChjang marked this conversation as resolved.
Show resolved Hide resolved
{
with_transaction(|| {
let r = f();
if r.is_ok() {
TransactionOutcome::Commit(r)
} else {
TransactionOutcome::Rollback(r)
}
})
}

/// Execute the supplied function, ensuring we are at least in one storage layer.
///
/// If we are already in a storage layer, we just execute the provided closure.
/// If we are not, we execute the closure within a [`with_storage_layer`].
pub fn in_storage_layer<T, E>(f: impl FnOnce() -> Result<T, E>) -> Result<T, E>
where
E: From<DispatchError>,
KiChjang marked this conversation as resolved.
Show resolved Hide resolved
{
if is_transactional() {
f()
} else {
with_storage_layer(f)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -229,4 +263,33 @@ mod tests {
assert_eq!(get_transaction_level(), 0);
});
}

#[test]
fn in_storage_layer_works() {
TestExternalities::default().execute_with(|| {
assert_eq!(get_transaction_level(), 0);

let res = in_storage_layer(|| -> DispatchResult {
assert_eq!(get_transaction_level(), 1);
in_storage_layer(|| -> DispatchResult {
// We are still in the same layer :)
assert_eq!(get_transaction_level(), 1);
Ok(())
})
});

assert_ok!(res);

let res = in_storage_layer(|| -> DispatchResult {
assert_eq!(get_transaction_level(), 1);
in_storage_layer(|| -> DispatchResult {
// We are still in the same layer :)
assert_eq!(get_transaction_level(), 1);
Err("epic fail".into())
})
});

assert_noop!(res, "epic fail");
});
}
}
4 changes: 2 additions & 2 deletions frame/support/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ pub use storage::{

mod dispatch;
pub use dispatch::{
AsEnsureOriginWithArg, EnsureOneOf, EnsureOrigin, EnsureOriginWithArg, OriginTrait,
UnfilteredDispatchable,
AsEnsureOriginWithArg, DispatchableWithStorageLayer, EnsureOneOf, EnsureOrigin,
EnsureOriginWithArg, OriginTrait, UnfilteredDispatchable,
};

mod voting;
Expand Down
10 changes: 10 additions & 0 deletions frame/support/src/traits/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ pub trait UnfilteredDispatchable {
fn dispatch_bypass_filter(self, origin: Self::Origin) -> DispatchResultWithPostInfo;
}

/// Type that can be dispatched with an additional storage layer which is used to execute the call.
pub trait DispatchableWithStorageLayer {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would split this in two and do the following:

trait DispatchableWithStorage {
    type Origin;

    fn dispatch_with_storage_layer(self, origin: Self::Origin) -> DispatchResultWithPostInfo;
}

impl<T: Dispatchable> DispatchableWithStorage for T {
    type Origin = T::Origin;
    
    fn dispatch_with_storage_layer(self, origin: Self::Origin) -> DispatchResultWithPostInfo {
        with_storage_layer(|| self.dispatch(origin))
    }
}

And then the same for UnfilteredDispatchable. Then you don't need to implement this in the macros.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how useful this is. The caller can just explicitly create a storage layer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't see the downside of this though either. Seems like just another friendly impl

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the impl here isnt very clean, and the restrictions you end up needing to put makes it pretty much useless. So I agree at this point, probably not useful to include.

/// The origin type of the runtime, (i.e. `frame_system::Config::Origin`).
type Origin;

/// Same as `dispatch` from the `Dispatchable` trait, but specifically spawns a new storage
shawntabrizi marked this conversation as resolved.
Show resolved Hide resolved
/// layer to execute the call inside of.
fn dispatch_with_storage_layer(self, origin: Self::Origin) -> DispatchResultWithPostInfo;
}

/// Methods available on `frame_system::Config::Origin`.
pub trait OriginTrait: Sized {
/// Runtime call type, as in `frame_system::Config::Call`
Expand Down
19 changes: 9 additions & 10 deletions frame/support/test/tests/pallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,7 @@ pub mod pallet {

/// Doc comment put in metadata
#[pallet::weight(1)]
#[frame_support::transactional]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will you already remove this macro or keep it as NO-OP for compatibility?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was originally thinking about deprecating it, but i didnt see a reason to remove it. It can still be used on top of other functions which are not extrinsics.

5dc88cd

But yeah, in these test contexts, it is not needed anymore

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I see.
So then as second step we can remove #[transactional] from all extrinsics where we already use it.

Copy link
Member Author

@shawntabrizi shawntabrizi May 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there are only some left in the new nomination-pools pallet

pub fn foo_transactional(
pub fn foo_storage_layer(
_origin: OriginFor<T>,
#[pallet::compact] foo: u32,
) -> DispatchResultWithPostInfo {
Expand Down Expand Up @@ -376,7 +375,7 @@ pub mod pallet {
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
let _ = T::AccountId::from(SomeType1); // Test for where clause
let _ = T::AccountId::from(SomeType5); // Test for where clause
if matches!(call, Call::foo_transactional { .. }) {
if matches!(call, Call::foo_storage_layer { .. }) {
return Ok(ValidTransaction::default())
}
Err(TransactionValidityError::Invalid(InvalidTransaction::Call))
Expand Down Expand Up @@ -614,13 +613,13 @@ fn transactional_works() {
TestExternalities::default().execute_with(|| {
frame_system::Pallet::<Runtime>::set_block_number(1);

pallet::Call::<Runtime>::foo_transactional { foo: 0 }
pallet::Call::<Runtime>::foo_storage_layer { foo: 0 }
.dispatch_bypass_filter(None.into())
.err()
.unwrap();
assert!(frame_system::Pallet::<Runtime>::events().is_empty());

pallet::Call::<Runtime>::foo_transactional { foo: 1 }
pallet::Call::<Runtime>::foo_storage_layer { foo: 1 }
.dispatch_bypass_filter(None.into())
.unwrap();
assert_eq!(
Expand All @@ -643,7 +642,7 @@ fn call_expand() {
assert_eq!(call_foo.get_call_name(), "foo");
assert_eq!(
pallet::Call::<Runtime>::get_call_names(),
&["foo", "foo_transactional", "foo_no_post_info"],
&["foo", "foo_storage_layer", "foo_no_post_info"],
);
}

Expand Down Expand Up @@ -747,7 +746,7 @@ fn inherent_expand() {
Digest::default(),
),
vec![UncheckedExtrinsic {
function: Call::Example(pallet::Call::foo_transactional { foo: 0 }),
function: Call::Example(pallet::Call::foo_storage_layer { foo: 0 }),
signature: None,
}],
);
Expand Down Expand Up @@ -788,7 +787,7 @@ fn inherent_expand() {
signature: None,
},
UncheckedExtrinsic {
function: Call::Example(pallet::Call::foo_transactional { foo: 0 }),
function: Call::Example(pallet::Call::foo_storage_layer { foo: 0 }),
signature: None,
},
],
Expand All @@ -810,7 +809,7 @@ fn inherent_expand() {
signature: None,
},
UncheckedExtrinsic {
function: Call::Example(pallet::Call::foo_transactional { foo: 0 }),
function: Call::Example(pallet::Call::foo_storage_layer { foo: 0 }),
signature: None,
},
UncheckedExtrinsic {
Expand Down Expand Up @@ -860,7 +859,7 @@ fn validate_unsigned_expand() {
let validity = pallet::Pallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err();
assert_eq!(validity, TransactionValidityError::Invalid(InvalidTransaction::Call));

let call = pallet::Call::<Runtime>::foo_transactional { foo: 0 };
let call = pallet::Call::<Runtime>::foo_storage_layer { foo: 0 };

let validity = pallet::Pallet::validate_unsigned(TransactionSource::External, &call).unwrap();
assert_eq!(validity, ValidTransaction::default());
Expand Down
7 changes: 3 additions & 4 deletions frame/support/test/tests/pallet_instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ pub mod pallet {

/// Doc comment put in metadata
#[pallet::weight(1)]
#[frame_support::transactional]
pub fn foo_transactional(
pub fn foo_storage_layer(
origin: OriginFor<T>,
#[pallet::compact] _foo: u32,
) -> DispatchResultWithPostInfo {
Expand Down Expand Up @@ -315,7 +314,7 @@ fn call_expand() {
DispatchInfo { weight: 3, class: DispatchClass::Normal, pays_fee: Pays::Yes }
);
assert_eq!(call_foo.get_call_name(), "foo");
assert_eq!(pallet::Call::<Runtime>::get_call_names(), &["foo", "foo_transactional"]);
assert_eq!(pallet::Call::<Runtime>::get_call_names(), &["foo", "foo_storage_layer"]);

let call_foo = pallet::Call::<Runtime, pallet::Instance1>::foo { foo: 3 };
assert_eq!(
Expand All @@ -325,7 +324,7 @@ fn call_expand() {
assert_eq!(call_foo.get_call_name(), "foo");
assert_eq!(
pallet::Call::<Runtime, pallet::Instance1>::get_call_names(),
&["foo", "foo_transactional"],
&["foo", "foo_storage_layer"],
);
}

Expand Down
Loading