-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Storage Layer for All FRAME Extrinsics #11431
Changes from 10 commits
6e0ca26
9f0d290
57fe674
28d35bd
eaa330d
213e537
b8d32b9
a03c13f
5dc88cd
2856ecd
b0f7241
0f26a7d
f9f65db
738c021
d1fd82f
5afa7e7
1a447b1
1e657bf
5793a59
09b4274
03c19ec
0e7e939
fe1a750
2068319
71b45db
c357618
85e7a28
d2aa0ed
e0abb8f
a87f4bc
3633d3c
82b3576
db8dbe3
630f149
3035fc8
993383a
d7530da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool. Another method that can graduate from ORML https://github.com/open-web3-stack/open-runtime-module-library/blob/954cc7d97f70fe5c3c5ba286e0160b48ab5c6758/utilities/src/lib.rs#L28 |
||
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::*; | ||
|
@@ -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"); | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would split this in two and do the following:
And then the same for There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -208,8 +208,7 @@ pub mod pallet { | |
|
||
/// Doc comment put in metadata | ||
#[pallet::weight(1)] | ||
#[frame_support::transactional] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. But yeah, in these test contexts, it is not needed anymore There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay I see. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
@@ -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)) | ||
|
@@ -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!( | ||
|
@@ -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"], | ||
); | ||
} | ||
|
||
|
@@ -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, | ||
}], | ||
); | ||
|
@@ -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, | ||
}, | ||
], | ||
|
@@ -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 { | ||
|
@@ -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()); | ||
|
There was a problem hiding this comment.
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.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done and test