-
Notifications
You must be signed in to change notification settings - Fork 2.6k
transactional: Wrap pallet::calls directly in storage layers
#11927
Changes from 8 commits
0fd5b85
028cf63
d80af32
ab7fa66
f24d062
fe15f9d
9a82ad5
b6095fd
ad18fa6
cec575f
7f400d1
1807e1d
22e5fab
e3bd7c1
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 |
|---|---|---|
|
|
@@ -235,7 +235,10 @@ pub mod pallet { | |
| /// reading a key, we simply cannot know how many bytes it is. In other words, this should | ||
| /// not be used in any environment where resources are strictly bounded (e.g. a parachain), | ||
| /// but it is acceptable otherwise (relay chain, offchain workers). | ||
| pub fn migrate_until_exhaustion(&mut self, limits: MigrationLimits) -> DispatchResult { | ||
| pub fn migrate_until_exhaustion( | ||
| &mut self, | ||
| limits: MigrationLimits, | ||
| ) -> Result<(), Error<T>> { | ||
| log!(debug, "running migrations on top of {:?} until {:?}", self, limits); | ||
|
|
||
| if limits.item.is_zero() || limits.size.is_zero() { | ||
|
|
@@ -262,7 +265,7 @@ pub mod pallet { | |
| /// Migrate AT MOST ONE KEY. This can be either a top or a child key. | ||
| /// | ||
| /// This function is *the* core of this entire pallet. | ||
| fn migrate_tick(&mut self) -> DispatchResult { | ||
| fn migrate_tick(&mut self) -> Result<(), Error<T>> { | ||
| match (&self.progress_top, &self.progress_child) { | ||
| (Progress::ToStart, _) => self.migrate_top(), | ||
| (Progress::LastKey(_), Progress::LastKey(_)) => { | ||
|
|
@@ -301,7 +304,7 @@ pub mod pallet { | |
| /// Migrate the current child key, setting it to its new value, if one exists. | ||
| /// | ||
| /// It updates the dynamic counters. | ||
| fn migrate_child(&mut self) -> DispatchResult { | ||
| fn migrate_child(&mut self) -> Result<(), Error<T>> { | ||
| use sp_io::default_child_storage as child_io; | ||
| let (maybe_current_child, child_root) = match (&self.progress_child, &self.progress_top) | ||
| { | ||
|
|
@@ -350,7 +353,7 @@ pub mod pallet { | |
| /// Migrate the current top key, setting it to its new value, if one exists. | ||
| /// | ||
| /// It updates the dynamic counters. | ||
| fn migrate_top(&mut self) -> DispatchResult { | ||
| fn migrate_top(&mut self) -> Result<(), Error<T>> { | ||
| let maybe_current_top = match &self.progress_top { | ||
| Progress::LastKey(last_top) => { | ||
| let maybe_top: Option<BoundedVec<u8, T::MaxKeyLen>> = | ||
|
|
@@ -431,7 +434,7 @@ pub mod pallet { | |
| /// The auto migration task finished. | ||
| AutoMigrationFinished, | ||
| /// Migration got halted due to an error or miss-configuration. | ||
| Halted, | ||
| Halted { error: Error<T> }, | ||
| } | ||
|
|
||
| /// The outer Pallet struct. | ||
|
|
@@ -516,8 +519,9 @@ pub mod pallet { | |
| pub type SignedMigrationMaxLimits<T> = StorageValue<_, MigrationLimits, OptionQuery>; | ||
|
|
||
| #[pallet::error] | ||
| #[derive(Clone, PartialEq)] | ||
| pub enum Error<T> { | ||
| /// max signed limits not respected. | ||
| /// Max signed limits not respected. | ||
| MaxSignedLimits, | ||
| /// A key was longer than the configured maximum. | ||
| /// | ||
|
|
@@ -529,12 +533,12 @@ pub mod pallet { | |
| KeyTooLong, | ||
| /// submitter does not have enough funds. | ||
| NotEnoughFunds, | ||
| /// bad witness data provided. | ||
| /// Bad witness data provided. | ||
| BadWitness, | ||
| /// upper bound of size is exceeded, | ||
| SizeUpperBoundExceeded, | ||
| /// Signed migration is not allowed because the maximum limit is not set yet. | ||
| SignedMigrationNotAllowed, | ||
| /// Bad child root provided. | ||
| BadChildRoot, | ||
| } | ||
|
|
||
| #[pallet::call] | ||
|
|
@@ -617,7 +621,7 @@ pub mod pallet { | |
| let (_imbalance, _remainder) = T::Currency::slash(&who, deposit); | ||
| Self::deposit_event(Event::<T>::Slashed { who, amount: deposit }); | ||
| debug_assert!(_remainder.is_zero()); | ||
| return Err(Error::<T>::SizeUpperBoundExceeded.into()) | ||
| return Ok(().into()) | ||
| } | ||
|
|
||
| Self::deposit_event(Event::<T>::Migrated { | ||
|
|
@@ -637,8 +641,8 @@ pub mod pallet { | |
| match migration { | ||
| Ok(_) => Ok(post_info), | ||
| Err(error) => { | ||
|
bkchr marked this conversation as resolved.
Outdated
|
||
| Self::halt(&error); | ||
| Err(DispatchErrorWithPostInfo { post_info, error }) | ||
|
shawntabrizi marked this conversation as resolved.
|
||
| Self::halt(error); | ||
| Ok(().into()) | ||
|
Member
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. Why is there no post dispatch weight correction anymore?
bkchr marked this conversation as resolved.
Outdated
|
||
| }, | ||
| } | ||
|
bkchr marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
@@ -679,7 +683,7 @@ pub mod pallet { | |
| let (_imbalance, _remainder) = T::Currency::slash(&who, deposit); | ||
| Self::deposit_event(Event::<T>::Slashed { who, amount: deposit }); | ||
| debug_assert!(_remainder.is_zero()); | ||
| Err("wrong witness data".into()) | ||
| Ok(().into()) | ||
| } else { | ||
| Self::deposit_event(Event::<T>::Migrated { | ||
| top: keys.len() as u32, | ||
|
|
@@ -741,13 +745,7 @@ pub mod pallet { | |
| let (_imbalance, _remainder) = T::Currency::slash(&who, deposit); | ||
| debug_assert!(_remainder.is_zero()); | ||
| Self::deposit_event(Event::<T>::Slashed { who, amount: deposit }); | ||
| Err(DispatchErrorWithPostInfo { | ||
| error: "bad witness".into(), | ||
| post_info: PostDispatchInfo { | ||
| actual_weight: Some(T::WeightInfo::migrate_custom_child_fail()), | ||
| pays_fee: Pays::Yes, | ||
| }, | ||
| }) | ||
| Ok(().into()) | ||
|
bkchr marked this conversation as resolved.
Outdated
|
||
| } else { | ||
| Self::deposit_event(Event::<T>::Migrated { | ||
| top: 0, | ||
|
|
@@ -806,7 +804,7 @@ pub mod pallet { | |
| if let Some(limits) = Self::auto_limits() { | ||
| let mut task = Self::migration_process(); | ||
| if let Err(e) = task.migrate_until_exhaustion(limits) { | ||
| Self::halt(&e); | ||
| Self::halt(e); | ||
| } | ||
| let weight = Self::dynamic_weight(task.dyn_total_items(), task.dyn_size); | ||
|
|
||
|
|
@@ -849,10 +847,10 @@ pub mod pallet { | |
| } | ||
|
|
||
| /// Put a stop to all ongoing migrations and logs an error. | ||
| fn halt<E: sp_std::fmt::Debug + ?Sized>(msg: &E) { | ||
| log!(error, "migration halted due to: {:?}", msg); | ||
| fn halt(error: Error<T>) { | ||
| log!(error, "migration halted due to: {:?}", error); | ||
| AutoLimits::<T>::kill(); | ||
| Self::deposit_event(Event::<T>::Halted); | ||
| Self::deposit_event(Event::<T>::Halted { error }); | ||
| } | ||
|
|
||
| /// Convert a child root key, aka. "Child-bearing top key" into the proper format. | ||
|
|
@@ -871,7 +869,7 @@ pub mod pallet { | |
| fn transform_child_key_or_halt(root: &Vec<u8>) -> &[u8] { | ||
| let key = Self::transform_child_key(root); | ||
| if key.is_none() { | ||
| Self::halt("bad child root key"); | ||
| Self::halt(Error::<T>::BadChildRoot); | ||
| } | ||
| key.unwrap_or_default() | ||
| } | ||
|
|
@@ -961,7 +959,7 @@ mod benchmarks { | |
| frame_system::RawOrigin::Signed(caller.clone()).into(), | ||
| vec![b"foo".to_vec()], | ||
| 1, | ||
| ).is_err() | ||
| ).is_ok() | ||
|
bkchr marked this conversation as resolved.
|
||
| ) | ||
| } | ||
| verify { | ||
|
|
@@ -1005,7 +1003,7 @@ mod benchmarks { | |
| StateTrieMigration::<T>::childify("top"), | ||
| vec![b"foo".to_vec()], | ||
| 1, | ||
| ).is_err() | ||
| ).is_ok() | ||
| ) | ||
| } | ||
| verify { | ||
|
|
@@ -1285,18 +1283,16 @@ mod test { | |
| SignedMigrationMaxLimits::<Test>::put(MigrationLimits { size: 1 << 20, item: 50 }); | ||
|
|
||
| // fails if the top key is too long. | ||
| frame_support::assert_err_with_weight!( | ||
| StateTrieMigration::continue_migrate( | ||
| Origin::signed(1), | ||
| MigrationLimits { item: 50, size: 1 << 20 }, | ||
| Bounded::max_value(), | ||
| MigrationProcess::<Test>::get() | ||
| ), | ||
| Error::<Test>::KeyTooLong, | ||
| Some(2000000), | ||
| ); | ||
| frame_support::assert_ok!(StateTrieMigration::continue_migrate( | ||
| Origin::signed(1), | ||
| MigrationLimits { item: 50, size: 1 << 20 }, | ||
| Bounded::max_value(), | ||
| MigrationProcess::<Test>::get() | ||
| ),); | ||
| // The auto migration halted. | ||
| System::assert_last_event(crate::Event::Halted {}.into()); | ||
| System::assert_last_event( | ||
| crate::Event::Halted { error: Error::<Test>::KeyTooLong }.into(), | ||
| ); | ||
| // Limits are killed. | ||
| assert!(AutoLimits::<Test>::get().is_none()); | ||
|
|
||
|
|
@@ -1322,18 +1318,16 @@ mod test { | |
| SignedMigrationMaxLimits::<Test>::put(MigrationLimits { size: 1 << 20, item: 50 }); | ||
|
|
||
| // fails if the top key is too long. | ||
| frame_support::assert_err_with_weight!( | ||
| StateTrieMigration::continue_migrate( | ||
| Origin::signed(1), | ||
| MigrationLimits { item: 50, size: 1 << 20 }, | ||
| Bounded::max_value(), | ||
| MigrationProcess::<Test>::get() | ||
| ), | ||
| Error::<Test>::KeyTooLong, | ||
| Some(2000000), | ||
| ); | ||
| frame_support::assert_ok!(StateTrieMigration::continue_migrate( | ||
| Origin::signed(1), | ||
| MigrationLimits { item: 50, size: 1 << 20 }, | ||
| Bounded::max_value(), | ||
| MigrationProcess::<Test>::get() | ||
| )); | ||
| // The auto migration halted. | ||
| System::assert_last_event(crate::Event::Halted {}.into()); | ||
| System::assert_last_event( | ||
| crate::Event::Halted { error: Error::<Test>::KeyTooLong }.into(), | ||
| ); | ||
| // Limits are killed. | ||
| assert!(AutoLimits::<Test>::get().is_none()); | ||
|
|
||
|
|
@@ -1484,7 +1478,7 @@ mod test { | |
| ..Default::default() | ||
| } | ||
| ), | ||
| Error::<Test>::BadWitness | ||
| Error::<Test>::BadWitness, | ||
| ); | ||
|
|
||
| // migrate all keys in a series of submissions | ||
|
|
@@ -1547,14 +1541,11 @@ mod test { | |
| assert_eq!(Balances::free_balance(&1), 1000); | ||
|
|
||
| // note that we don't expect this to be a noop -- we do slash. | ||
| frame_support::assert_err!( | ||
| StateTrieMigration::migrate_custom_top( | ||
| Origin::signed(1), | ||
| vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()], | ||
| correct_witness - 1, | ||
| ), | ||
| "wrong witness data" | ||
| ); | ||
| frame_support::assert_ok!(StateTrieMigration::migrate_custom_top( | ||
| Origin::signed(1), | ||
| vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()], | ||
| correct_witness - 1, | ||
| ),); | ||
|
|
||
| // no funds should remain reserved. | ||
| assert_eq!(Balances::reserved_balance(&1), 0); | ||
|
|
@@ -1584,13 +1575,12 @@ mod test { | |
| assert_eq!(Balances::free_balance(&1), 1000); | ||
|
|
||
| // note that we don't expect this to be a noop -- we do slash. | ||
| assert!(StateTrieMigration::migrate_custom_child( | ||
| frame_support::assert_ok!(StateTrieMigration::migrate_custom_child( | ||
| Origin::signed(1), | ||
| StateTrieMigration::childify("chk1"), | ||
| vec![b"key1".to_vec(), b"key2".to_vec()], | ||
| 999999, // wrong witness | ||
| ) | ||
| .is_err()); | ||
| )); | ||
|
|
||
| // no funds should remain reserved. | ||
| assert_eq!(Balances::reserved_balance(&1), 0); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -140,6 +140,24 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { | |
|
|
||
| let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" }; | ||
|
|
||
| // Wrap all calls inside of storage layers | ||
| if let Some(syn::Item::Impl(item_impl)) = def | ||
| .call | ||
| .as_ref() | ||
| .map(|c| &mut def.item.content.as_mut().expect("Checked by def parser").1[c.index]) | ||
| { | ||
| item_impl.items.iter_mut().for_each(|i| { | ||
| if let syn::ImplItem::Method(method) = i { | ||
| let block = &method.block; | ||
| method.block = syn::parse_quote! {{ | ||
|
Member
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.
You change the logic of an extrinsic to be wrapped inside a
Contributor
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. That's the idea, if you are thinking of making it configurable, there is this PR that attempts to solve it.
Member
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. Yeah, we should make it configurable eventually |
||
| // We execute all dispatchable in a new storage layer, allowing them | ||
| // to return an error at any point, and undoing any storage changes. | ||
| #frame_support::storage::with_storage_layer(|| #block) | ||
| }}; | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| quote::quote_spanned!(span => | ||
| #[doc(hidden)] | ||
| pub mod __substrate_call_check { | ||
|
|
@@ -267,12 +285,8 @@ 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)) | ||
| ); | ||
| // We execute all dispatchable in a new storage layer, allowing them | ||
| // to return an error at any point, and undoing any storage changes. | ||
| #frame_support::storage::with_storage_layer(|| { | ||
| <#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* ) | ||
| .map(Into::into).map_err(Into::into) | ||
| }) | ||
| <#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* ) | ||
| .map(Into::into).map_err(Into::into) | ||
| }, | ||
| )* | ||
| Self::__Ignore(_, _) => { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.