1#![cfg_attr(not(feature = "std"), no_std)]
57
58extern crate alloc;
59
60pub use pallet::*;
61pub mod weights;
62
63const LOG_TARGET: &str = "runtime::state-trie-migration";
64
65#[macro_export]
66macro_rules! log {
67 ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
68 log::$level!(
69 target: crate::LOG_TARGET,
70 concat!("[{:?}] 🤖 ", $patter), frame_system::Pallet::<T>::block_number() $(, $values)*
71 )
72 };
73}
74
75#[frame_support::pallet]
76pub mod pallet {
77
78 pub use crate::weights::WeightInfo;
79
80 use alloc::{vec, vec::Vec};
81 use core::ops::Deref;
82 use frame_support::{
83 dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo},
84 ensure,
85 pallet_prelude::*,
86 traits::{
87 fungible::{hold::Balanced, Inspect, InspectHold, Mutate, MutateHold},
88 tokens::{Fortitude, Precision},
89 Get,
90 },
91 };
92 use frame_system::{self, pallet_prelude::*};
93 use sp_core::{
94 hexdisplay::HexDisplay, storage::well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX,
95 };
96 use sp_runtime::{
97 self,
98 traits::{Saturating, Zero},
99 };
100
101 pub(crate) type BalanceOf<T> =
102 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
103
104 #[derive(
106 CloneNoBound,
107 Encode,
108 Decode,
109 DecodeWithMemTracking,
110 scale_info::TypeInfo,
111 PartialEqNoBound,
112 EqNoBound,
113 MaxEncodedLen,
114 )]
115 #[scale_info(skip_type_params(MaxKeyLen))]
116 pub enum Progress<MaxKeyLen: Get<u32>> {
117 ToStart,
119 LastKey(BoundedVec<u8, MaxKeyLen>),
121 Complete,
123 }
124
125 pub type ProgressOf<T> = Progress<<T as Config>::MaxKeyLen>;
127
128 #[derive(
132 Clone,
133 Encode,
134 Decode,
135 DecodeWithMemTracking,
136 scale_info::TypeInfo,
137 PartialEq,
138 Eq,
139 MaxEncodedLen,
140 )]
141 #[scale_info(skip_type_params(T))]
142 pub struct MigrationTask<T: Config> {
143 pub(crate) progress_top: ProgressOf<T>,
145 pub(crate) progress_child: ProgressOf<T>,
150
151 #[codec(skip)]
156 pub(crate) dyn_top_items: u32,
157 #[codec(skip)]
162 pub(crate) dyn_child_items: u32,
163
164 #[codec(skip)]
169 pub(crate) dyn_size: u32,
170
171 pub(crate) size: u32,
175 pub(crate) top_items: u32,
179 pub(crate) child_items: u32,
183
184 #[codec(skip)]
185 pub(crate) _ph: core::marker::PhantomData<T>,
186 }
187
188 impl<Size: Get<u32>> core::fmt::Debug for Progress<Size> {
189 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
190 match self {
191 Progress::ToStart => f.write_str("To start"),
192 Progress::LastKey(key) => write!(f, "Last: {:?}", HexDisplay::from(key.deref())),
193 Progress::Complete => f.write_str("Complete"),
194 }
195 }
196 }
197
198 impl<T: Config> core::fmt::Debug for MigrationTask<T> {
199 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
200 f.debug_struct("MigrationTask")
201 .field("top", &self.progress_top)
202 .field("child", &self.progress_child)
203 .field("dyn_top_items", &self.dyn_top_items)
204 .field("dyn_child_items", &self.dyn_child_items)
205 .field("dyn_size", &self.dyn_size)
206 .field("size", &self.size)
207 .field("top_items", &self.top_items)
208 .field("child_items", &self.child_items)
209 .finish()
210 }
211 }
212
213 impl<T: Config> Default for MigrationTask<T> {
214 fn default() -> Self {
215 Self {
216 progress_top: Progress::ToStart,
217 progress_child: Progress::ToStart,
218 dyn_child_items: Default::default(),
219 dyn_top_items: Default::default(),
220 dyn_size: Default::default(),
221 _ph: Default::default(),
222 size: Default::default(),
223 top_items: Default::default(),
224 child_items: Default::default(),
225 }
226 }
227 }
228
229 impl<T: Config> MigrationTask<T> {
230 pub(crate) fn finished(&self) -> bool {
232 matches!(self.progress_top, Progress::Complete)
233 }
234
235 fn exhausted(&self, limits: MigrationLimits) -> bool {
237 self.dyn_total_items() >= limits.item || self.dyn_size >= limits.size
238 }
239
240 pub(crate) fn dyn_total_items(&self) -> u32 {
242 self.dyn_child_items.saturating_add(self.dyn_top_items)
243 }
244
245 pub fn migrate_until_exhaustion(
254 &mut self,
255 limits: MigrationLimits,
256 ) -> Result<(), Error<T>> {
257 log!(debug, "running migrations on top of {:?} until {:?}", self, limits);
258
259 if limits.item.is_zero() || limits.size.is_zero() {
260 log!(warn, "limits are zero. stopping");
262 return Ok(());
263 }
264
265 while !self.exhausted(limits) && !self.finished() {
266 if let Err(e) = self.migrate_tick() {
267 log!(error, "migrate_until_exhaustion failed: {:?}", e);
268 return Err(e);
269 }
270 }
271
272 self.size = self.size.saturating_add(self.dyn_size);
274 self.child_items = self.child_items.saturating_add(self.dyn_child_items);
275 self.top_items = self.top_items.saturating_add(self.dyn_top_items);
276 log!(debug, "finished with {:?}", self);
277 Ok(())
278 }
279
280 fn migrate_tick(&mut self) -> Result<(), Error<T>> {
284 match (&self.progress_top, &self.progress_child) {
285 (Progress::ToStart, _) => self.migrate_top(),
286 (Progress::LastKey(_), Progress::LastKey(_)) => {
287 self.migrate_child()
289 },
290 (Progress::LastKey(top_key), Progress::ToStart) => {
291 if !top_key.starts_with(DEFAULT_CHILD_STORAGE_KEY_PREFIX) {
297 self.migrate_top()
300 } else {
301 self.migrate_child()
304 }
305 },
306 (Progress::LastKey(_), Progress::Complete) => {
307 self.migrate_top()?;
309 self.progress_child = Progress::ToStart;
310 Ok(())
311 },
312 (Progress::Complete, _) => {
313 Ok(())
315 },
316 }
317 }
318
319 fn migrate_child(&mut self) -> Result<(), Error<T>> {
323 use sp_io::default_child_storage as child_io;
324 let (maybe_current_child, child_root) = match (&self.progress_child, &self.progress_top)
325 {
326 (Progress::LastKey(last_child), Progress::LastKey(last_top)) => {
327 let child_root = Pallet::<T>::transform_child_key_or_halt(last_top);
328 let maybe_current_child: Option<BoundedVec<u8, T::MaxKeyLen>> =
329 if let Some(next) = child_io::next_key(child_root, last_child) {
330 Some(next.try_into().map_err(|_| Error::<T>::KeyTooLong)?)
331 } else {
332 None
333 };
334
335 (maybe_current_child, child_root)
336 },
337 (Progress::ToStart, Progress::LastKey(last_top)) => {
338 let child_root = Pallet::<T>::transform_child_key_or_halt(last_top);
339 (Some(Default::default()), child_root)
341 },
342 _ => {
343 frame_support::defensive!("cannot migrate child key.");
345 return Ok(());
346 },
347 };
348
349 if let Some(current_child) = maybe_current_child.as_ref() {
350 let added_size = if let Some(data) = child_io::get(child_root, current_child) {
351 child_io::set(child_root, current_child, &data);
352 data.len() as u32
353 } else {
354 Zero::zero()
355 };
356 self.dyn_size = self.dyn_size.saturating_add(added_size);
357 self.dyn_child_items.saturating_inc();
358 }
359
360 log!(trace, "migrated a child key, next_child_key: {:?}", maybe_current_child);
361 self.progress_child = match maybe_current_child {
362 Some(last_child) => Progress::LastKey(last_child),
363 None => Progress::Complete,
364 };
365 Ok(())
366 }
367
368 fn migrate_top(&mut self) -> Result<(), Error<T>> {
372 let maybe_current_top = match &self.progress_top {
373 Progress::LastKey(last_top) => {
374 let maybe_top: Option<BoundedVec<u8, T::MaxKeyLen>> =
375 if let Some(next) = sp_io::storage::next_key(last_top) {
376 Some(next.try_into().map_err(|_| Error::<T>::KeyTooLong)?)
377 } else {
378 None
379 };
380 maybe_top
381 },
382 Progress::ToStart => Some(Default::default()),
384 Progress::Complete => {
385 frame_support::defensive!("cannot migrate top key.");
387 return Ok(());
388 },
389 };
390
391 if let Some(current_top) = maybe_current_top.as_ref() {
392 let added_size = if let Some(data) = sp_io::storage::get(current_top) {
393 sp_io::storage::set(current_top, &data);
394 data.len() as u32
395 } else {
396 Zero::zero()
397 };
398 self.dyn_size = self.dyn_size.saturating_add(added_size);
399 self.dyn_top_items.saturating_inc();
400 }
401
402 log!(trace, "migrated a top key, next_top_key = {:?}", maybe_current_top);
403 self.progress_top = match maybe_current_top {
404 Some(last_top) => Progress::LastKey(last_top),
405 None => Progress::Complete,
406 };
407 Ok(())
408 }
409 }
410
411 #[derive(
413 Clone,
414 Copy,
415 Encode,
416 Decode,
417 DecodeWithMemTracking,
418 scale_info::TypeInfo,
419 Default,
420 Debug,
421 PartialEq,
422 Eq,
423 MaxEncodedLen,
424 )]
425 pub struct MigrationLimits {
426 pub size: u32,
428 pub item: u32,
430 }
431
432 #[derive(
434 Clone,
435 Copy,
436 Encode,
437 Decode,
438 DecodeWithMemTracking,
439 scale_info::TypeInfo,
440 Debug,
441 PartialEq,
442 Eq,
443 )]
444 pub enum MigrationCompute {
445 Signed,
447 Auto,
449 }
450
451 #[pallet::event]
453 #[pallet::generate_deposit(pub(super) fn deposit_event)]
454 pub enum Event<T: Config> {
455 Migrated { top: u32, child: u32, compute: MigrationCompute },
458 Slashed { who: T::AccountId, amount: BalanceOf<T> },
460 AutoMigrationFinished,
462 Halted { error: Error<T> },
464 }
465
466 #[pallet::pallet]
468 pub struct Pallet<T>(_);
469
470 pub mod config_preludes {
472 use super::*;
473 use frame_support::derive_impl;
474
475 pub struct TestDefaultConfig;
476
477 #[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
478 impl frame_system::DefaultConfig for TestDefaultConfig {}
479
480 #[frame_support::register_default_impl(TestDefaultConfig)]
481 impl DefaultConfig for TestDefaultConfig {
482 #[inject_runtime_type]
483 type RuntimeEvent = ();
484 #[inject_runtime_type]
485 type RuntimeHoldReason = ();
486 }
487 }
488
489 #[pallet::composite_enum]
491 pub enum HoldReason {
492 #[codec(index = 0)]
494 SlashForMigrate,
495 }
496
497 #[pallet::config(with_default)]
499 pub trait Config: frame_system::Config {
500 #[pallet::no_default]
502 type ControlOrigin: EnsureOrigin<Self::RuntimeOrigin>;
503
504 #[pallet::no_default]
506 type SignedFilter: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
507
508 #[pallet::no_default_bounds]
510 #[allow(deprecated)]
511 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
512
513 #[pallet::no_default]
515 type Currency: InspectHold<Self::AccountId>
516 + Mutate<Self::AccountId>
517 + MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>
518 + Balanced<Self::AccountId>;
519
520 #[pallet::no_default_bounds]
522 type RuntimeHoldReason: From<HoldReason>;
523
524 #[pallet::constant]
547 #[pallet::no_default]
548 type MaxKeyLen: Get<u32>;
549
550 #[pallet::no_default]
554 type SignedDepositPerItem: Get<BalanceOf<Self>>;
555
556 #[pallet::no_default]
560 type SignedDepositBase: Get<BalanceOf<Self>>;
561
562 #[pallet::no_default]
564 type WeightInfo: WeightInfo;
565 }
566
567 #[pallet::storage]
572 #[pallet::getter(fn migration_process)]
573 pub type MigrationProcess<T> = StorageValue<_, MigrationTask<T>, ValueQuery>;
574
575 #[pallet::storage]
579 #[pallet::getter(fn auto_limits)]
580 pub type AutoLimits<T> = StorageValue<_, Option<MigrationLimits>, ValueQuery>;
581
582 #[pallet::storage]
586 #[pallet::getter(fn signed_migration_max_limits)]
587 pub type SignedMigrationMaxLimits<T> = StorageValue<_, MigrationLimits, OptionQuery>;
588
589 #[pallet::error]
590 #[derive(Clone, PartialEq)]
591 pub enum Error<T> {
592 MaxSignedLimits,
594 KeyTooLong,
602 NotEnoughFunds,
604 BadWitness,
606 SignedMigrationNotAllowed,
608 BadChildRoot,
610 }
611
612 #[pallet::call]
613 impl<T: Config> Pallet<T> {
614 #[pallet::call_index(0)]
618 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
619 pub fn control_auto_migration(
620 origin: OriginFor<T>,
621 maybe_config: Option<MigrationLimits>,
622 ) -> DispatchResult {
623 T::ControlOrigin::ensure_origin(origin)?;
624 AutoLimits::<T>::put(maybe_config);
625 Ok(())
626 }
627
628 #[pallet::call_index(1)]
650 #[pallet::weight(
651 Pallet::<T>::dynamic_weight(limits.item, * real_size_upper)
653 + T::WeightInfo::continue_migrate()
655 )]
656 pub fn continue_migrate(
657 origin: OriginFor<T>,
658 limits: MigrationLimits,
659 real_size_upper: u32,
660 witness_task: MigrationTask<T>,
661 ) -> DispatchResultWithPostInfo {
662 let who = T::SignedFilter::ensure_origin(origin)?;
663
664 let max_limits =
665 Self::signed_migration_max_limits().ok_or(Error::<T>::SignedMigrationNotAllowed)?;
666 ensure!(
667 limits.size <= max_limits.size && limits.item <= max_limits.item,
668 Error::<T>::MaxSignedLimits,
669 );
670
671 let deposit = Self::calculate_deposit_for(limits.item);
673 ensure!(
674 T::Currency::can_hold(&HoldReason::SlashForMigrate.into(), &who, deposit),
675 Error::<T>::NotEnoughFunds
676 );
677
678 let mut task = Self::migration_process();
679 ensure!(
680 task == witness_task,
681 DispatchErrorWithPostInfo {
682 error: Error::<T>::BadWitness.into(),
683 post_info: PostDispatchInfo {
684 actual_weight: Some(T::WeightInfo::continue_migrate_wrong_witness()),
685 pays_fee: Pays::Yes
686 }
687 }
688 );
689 let migration = task.migrate_until_exhaustion(limits);
690
691 if real_size_upper < task.dyn_size {
693 Self::slash(who, deposit)?;
694 return Ok(().into());
695 }
696
697 Self::deposit_event(Event::<T>::Migrated {
698 top: task.dyn_top_items,
699 child: task.dyn_child_items,
700 compute: MigrationCompute::Signed,
701 });
702
703 let actual_weight = Some(
705 Pallet::<T>::dynamic_weight(limits.item, task.dyn_size)
706 .saturating_add(T::WeightInfo::continue_migrate()),
707 );
708
709 MigrationProcess::<T>::put(task);
710 let post_info = PostDispatchInfo { actual_weight, pays_fee: Pays::No };
711 if let Err(error) = migration {
712 Self::halt(error);
713 }
714 Ok(post_info)
715 }
716
717 #[pallet::call_index(2)]
722 #[pallet::weight(
723 T::WeightInfo::migrate_custom_top_success()
724 .max(T::WeightInfo::migrate_custom_top_fail())
725 .saturating_add(
726 Pallet::<T>::dynamic_weight(keys.len() as u32, *witness_size)
727 )
728 )]
729 pub fn migrate_custom_top(
730 origin: OriginFor<T>,
731 keys: Vec<Vec<u8>>,
732 witness_size: u32,
733 ) -> DispatchResultWithPostInfo {
734 let who = T::SignedFilter::ensure_origin(origin)?;
735
736 let deposit = Self::calculate_deposit_for(keys.len() as u32);
738 ensure!(
739 T::Currency::can_hold(&HoldReason::SlashForMigrate.into(), &who, deposit),
740 Error::<T>::NotEnoughFunds
741 );
742
743 let mut dyn_size = 0u32;
744 for key in &keys {
745 if let Some(data) = sp_io::storage::get(key) {
746 dyn_size = dyn_size.saturating_add(data.len() as u32);
747 sp_io::storage::set(key, &data);
748 }
749 }
750
751 if dyn_size > witness_size {
752 Self::slash(who, deposit)?;
753 Ok(().into())
754 } else {
755 Self::deposit_event(Event::<T>::Migrated {
756 top: keys.len() as u32,
757 child: 0,
758 compute: MigrationCompute::Signed,
759 });
760 Ok(PostDispatchInfo {
761 actual_weight: Some(
762 T::WeightInfo::migrate_custom_top_success().saturating_add(
763 Pallet::<T>::dynamic_weight(keys.len() as u32, dyn_size),
764 ),
765 ),
766 pays_fee: Pays::Yes,
767 })
768 }
769 }
770
771 #[pallet::call_index(3)]
778 #[pallet::weight(
779 T::WeightInfo::migrate_custom_child_success()
780 .max(T::WeightInfo::migrate_custom_child_fail())
781 .saturating_add(
782 Pallet::<T>::dynamic_weight(child_keys.len() as u32, *total_size)
783 )
784 )]
785 pub fn migrate_custom_child(
786 origin: OriginFor<T>,
787 root: Vec<u8>,
788 child_keys: Vec<Vec<u8>>,
789 total_size: u32,
790 ) -> DispatchResultWithPostInfo {
791 use sp_io::default_child_storage as child_io;
792 let who = T::SignedFilter::ensure_origin(origin)?;
793
794 let deposit = Self::calculate_deposit_for(child_keys.len() as u32);
796 ensure!(
797 T::Currency::can_hold(&HoldReason::SlashForMigrate.into(), &who, deposit),
798 Error::<T>::NotEnoughFunds
799 );
800
801 let mut dyn_size = 0u32;
802 let transformed_child_key = Self::transform_child_key(&root).ok_or("bad child key")?;
803 for child_key in &child_keys {
804 if let Some(data) = child_io::get(transformed_child_key, child_key) {
805 dyn_size = dyn_size.saturating_add(data.len() as u32);
806 child_io::set(transformed_child_key, child_key, &data);
807 }
808 }
809
810 if dyn_size != total_size {
811 Self::slash(who, deposit)?;
812 Ok(PostDispatchInfo {
813 actual_weight: Some(T::WeightInfo::migrate_custom_child_fail()),
814 pays_fee: Pays::Yes,
815 })
816 } else {
817 Self::deposit_event(Event::<T>::Migrated {
818 top: 0,
819 child: child_keys.len() as u32,
820 compute: MigrationCompute::Signed,
821 });
822 Ok(PostDispatchInfo {
823 actual_weight: Some(
824 T::WeightInfo::migrate_custom_child_success().saturating_add(
825 Pallet::<T>::dynamic_weight(child_keys.len() as u32, total_size),
826 ),
827 ),
828 pays_fee: Pays::Yes,
829 })
830 }
831 }
832
833 #[pallet::call_index(4)]
835 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
836 pub fn set_signed_max_limits(
837 origin: OriginFor<T>,
838 limits: MigrationLimits,
839 ) -> DispatchResult {
840 T::ControlOrigin::ensure_origin(origin)?;
841 SignedMigrationMaxLimits::<T>::put(limits);
842 Ok(())
843 }
844
845 #[pallet::call_index(5)]
855 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
856 pub fn force_set_progress(
857 origin: OriginFor<T>,
858 progress_top: ProgressOf<T>,
859 progress_child: ProgressOf<T>,
860 ) -> DispatchResult {
861 T::ControlOrigin::ensure_origin(origin)?;
862 MigrationProcess::<T>::mutate(|task| {
863 task.progress_top = progress_top;
864 task.progress_child = progress_child;
865 });
866 Ok(())
867 }
868 }
869
870 #[pallet::hooks]
871 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
872 fn on_initialize(_: BlockNumberFor<T>) -> Weight {
873 if let Some(limits) = Self::auto_limits() {
874 let mut task = Self::migration_process();
875 if let Err(e) = task.migrate_until_exhaustion(limits) {
876 Self::halt(e);
877 }
878 let weight = Self::dynamic_weight(task.dyn_total_items(), task.dyn_size);
879
880 log!(
881 info,
882 "migrated {} top keys, {} child keys, and a total of {} bytes.",
883 task.dyn_top_items,
884 task.dyn_child_items,
885 task.dyn_size,
886 );
887
888 if task.finished() {
889 Self::deposit_event(Event::<T>::AutoMigrationFinished);
890 AutoLimits::<T>::kill();
891 } else {
892 Self::deposit_event(Event::<T>::Migrated {
893 top: task.dyn_top_items,
894 child: task.dyn_child_items,
895 compute: MigrationCompute::Auto,
896 });
897 }
898
899 MigrationProcess::<T>::put(task);
900
901 weight
902 } else {
903 T::DbWeight::get().reads(1)
904 }
905 }
906 }
907
908 impl<T: Config> Pallet<T> {
909 fn dynamic_weight(items: u32, size: u32) -> frame_support::pallet_prelude::Weight {
911 let items = items as u64;
912 <T as frame_system::Config>::DbWeight::get()
913 .reads_writes(1, 1)
914 .saturating_mul(items)
915 .saturating_add(T::WeightInfo::process_top_key(size))
917 }
918
919 fn halt(error: Error<T>) {
921 log!(error, "migration halted due to: {:?}", error);
922 AutoLimits::<T>::kill();
923 Self::deposit_event(Event::<T>::Halted { error });
924 }
925
926 fn transform_child_key(root: &Vec<u8>) -> Option<&[u8]> {
928 use sp_core::storage::{ChildType, PrefixedStorageKey};
929 match ChildType::from_prefixed_key(PrefixedStorageKey::new_ref(root)) {
930 Some((ChildType::ParentKeyId, root)) => Some(root),
931 _ => None,
932 }
933 }
934
935 fn transform_child_key_or_halt(root: &Vec<u8>) -> &[u8] {
940 let key = Self::transform_child_key(root);
941 if key.is_none() {
942 Self::halt(Error::<T>::BadChildRoot);
943 }
944 key.unwrap_or_default()
945 }
946
947 #[cfg(any(test, feature = "runtime-benchmarks"))]
949 pub(crate) fn childify(root: &'static str) -> Vec<u8> {
950 let mut string = DEFAULT_CHILD_STORAGE_KEY_PREFIX.to_vec();
951 string.extend_from_slice(root.as_ref());
952 string
953 }
954
955 pub(crate) fn calculate_deposit_for(keys_count: u32) -> BalanceOf<T> {
957 T::SignedDepositBase::get()
958 .saturating_add(T::SignedDepositPerItem::get().saturating_mul(keys_count.into()))
959 }
960
961 fn slash(who: T::AccountId, amount: BalanceOf<T>) -> Result<(), DispatchError> {
963 T::Currency::hold(&HoldReason::SlashForMigrate.into(), &who, amount)?;
964 let _burned = T::Currency::burn_all_held(
966 &HoldReason::SlashForMigrate.into(),
967 &who,
968 Precision::BestEffort,
969 Fortitude::Force,
970 )?;
971 debug_assert!(amount.saturating_sub(_burned).is_zero());
972 Self::deposit_event(Event::<T>::Slashed { who, amount });
973 Ok(())
974 }
975 }
976}
977
978#[cfg(feature = "runtime-benchmarks")]
979mod benchmarks {
980 use super::{pallet::Pallet as StateTrieMigration, *};
981 use alloc::vec;
982 use frame_benchmarking::v2::*;
983 use frame_support::traits::fungible::{Inspect, Mutate};
984
985 const KEY: &[u8] = b"key";
988
989 fn set_balance_for_deposit<T: Config>(caller: &T::AccountId, item: u32) -> BalanceOf<T> {
990 let deposit = StateTrieMigration::<T>::calculate_deposit_for(item);
991 let stash = T::Currency::minimum_balance() * BalanceOf::<T>::from(1000u32) + deposit;
992 T::Currency::set_balance(caller, stash);
993 stash
994 }
995
996 #[benchmarks]
997 mod inner_benchmarks {
998 use super::*;
999
1000 #[benchmark]
1001 fn continue_migrate() -> Result<(), BenchmarkError> {
1002 let null = MigrationLimits::default();
1006 let caller = frame_benchmarking::whitelisted_caller();
1007 let stash = set_balance_for_deposit::<T>(&caller, null.item);
1008 SignedMigrationMaxLimits::<T>::put(MigrationLimits { size: 1024, item: 5 });
1010
1011 #[extrinsic_call]
1012 _(
1013 frame_system::RawOrigin::Signed(caller.clone()),
1014 null,
1015 0,
1016 StateTrieMigration::<T>::migration_process(),
1017 );
1018
1019 assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default());
1020 assert_eq!(T::Currency::balance(&caller), stash);
1021
1022 Ok(())
1023 }
1024
1025 #[benchmark]
1026 fn continue_migrate_wrong_witness() -> Result<(), BenchmarkError> {
1027 let null = MigrationLimits::default();
1028 let caller = frame_benchmarking::whitelisted_caller();
1029 let bad_witness = MigrationTask {
1030 progress_top: Progress::LastKey(vec![1u8].try_into().unwrap()),
1031 ..Default::default()
1032 };
1033 #[block]
1034 {
1035 assert!(StateTrieMigration::<T>::continue_migrate(
1036 frame_system::RawOrigin::Signed(caller).into(),
1037 null,
1038 0,
1039 bad_witness,
1040 )
1041 .is_err());
1042 }
1043
1044 assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default());
1045
1046 Ok(())
1047 }
1048
1049 #[benchmark]
1050 fn migrate_custom_top_success() -> Result<(), BenchmarkError> {
1051 let null = MigrationLimits::default();
1052 let caller: T::AccountId = frame_benchmarking::whitelisted_caller();
1053 let stash = set_balance_for_deposit::<T>(&caller, null.item);
1054 #[extrinsic_call]
1055 migrate_custom_top(
1056 frame_system::RawOrigin::Signed(caller.clone()),
1057 Default::default(),
1058 0,
1059 );
1060
1061 assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default());
1062 assert_eq!(T::Currency::balance(&caller), stash);
1063 Ok(())
1064 }
1065
1066 #[benchmark]
1067 fn migrate_custom_top_fail() -> Result<(), BenchmarkError> {
1068 let null = MigrationLimits::default();
1069 let caller: T::AccountId = frame_benchmarking::whitelisted_caller();
1070 let stash = set_balance_for_deposit::<T>(&caller, null.item);
1071 sp_io::storage::set(b"foo", vec![1u8; 33].as_ref());
1074 #[block]
1075 {
1076 assert!(StateTrieMigration::<T>::migrate_custom_top(
1077 frame_system::RawOrigin::Signed(caller.clone()).into(),
1078 vec![b"foo".to_vec()],
1079 1,
1080 )
1081 .is_ok());
1082
1083 frame_system::Pallet::<T>::assert_last_event(
1084 <T as Config>::RuntimeEvent::from(crate::Event::Slashed {
1085 who: caller.clone(),
1086 amount: StateTrieMigration::<T>::calculate_deposit_for(1u32),
1087 })
1088 .into(),
1089 );
1090 }
1091
1092 assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default());
1093 assert!(T::Currency::balance(&caller) < stash);
1095
1096 Ok(())
1097 }
1098
1099 #[benchmark]
1100 fn migrate_custom_child_success() -> Result<(), BenchmarkError> {
1101 let caller: T::AccountId = frame_benchmarking::whitelisted_caller();
1102 let stash = set_balance_for_deposit::<T>(&caller, 0);
1103
1104 #[extrinsic_call]
1105 migrate_custom_child(
1106 frame_system::RawOrigin::Signed(caller.clone()),
1107 StateTrieMigration::<T>::childify(Default::default()),
1108 Default::default(),
1109 0,
1110 );
1111
1112 assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default());
1113 assert_eq!(T::Currency::balance(&caller), stash);
1114
1115 Ok(())
1116 }
1117
1118 #[benchmark]
1119 fn migrate_custom_child_fail() -> Result<(), BenchmarkError> {
1120 let caller: T::AccountId = frame_benchmarking::whitelisted_caller();
1121 let stash = set_balance_for_deposit::<T>(&caller, 1);
1122 sp_io::default_child_storage::set(b"top", b"foo", vec![1u8; 33].as_ref());
1125
1126 #[block]
1127 {
1128 assert!(StateTrieMigration::<T>::migrate_custom_child(
1129 frame_system::RawOrigin::Signed(caller.clone()).into(),
1130 StateTrieMigration::<T>::childify("top"),
1131 vec![b"foo".to_vec()],
1132 1,
1133 )
1134 .is_ok());
1135 }
1136 assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default());
1137 assert!(T::Currency::balance(&caller) < stash);
1139 Ok(())
1140 }
1141
1142 #[benchmark]
1143 fn process_top_key(v: Linear<1, { 4 * 1024 * 1024 }>) -> Result<(), BenchmarkError> {
1144 let value = alloc::vec![1u8; v as usize];
1145 sp_io::storage::set(KEY, &value);
1146 #[block]
1147 {
1148 let data = sp_io::storage::get(KEY).unwrap();
1149 sp_io::storage::set(KEY, &data);
1150 let _next = sp_io::storage::next_key(KEY);
1151 assert_eq!(data, value);
1152 }
1153
1154 Ok(())
1155 }
1156
1157 impl_benchmark_test_suite!(
1158 StateTrieMigration,
1159 crate::mock::new_test_ext(sp_runtime::StateVersion::V0, true, None, None),
1160 crate::mock::Test
1161 );
1162 }
1163}
1164
1165#[cfg(test)]
1166mod mock {
1167 use super::*;
1168 use crate as pallet_state_trie_migration;
1169 use alloc::{vec, vec::Vec};
1170 use frame_support::{derive_impl, parameter_types, traits::Hooks, weights::Weight};
1171 use frame_system::{EnsureRoot, EnsureSigned};
1172 use sp_core::{
1173 storage::{ChildInfo, StateVersion},
1174 H256,
1175 };
1176 use sp_runtime::{traits::Header as _, BuildStorage, StorageChild};
1177
1178 type Block = frame_system::mocking::MockBlockU32<Test>;
1179
1180 frame_support::construct_runtime!(
1182 pub enum Test
1183 {
1184 System: frame_system,
1185 Balances: pallet_balances,
1186 StateTrieMigration: pallet_state_trie_migration,
1187 }
1188 );
1189
1190 parameter_types! {
1191 pub const SS58Prefix: u8 = 42;
1192 }
1193
1194 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
1195 impl frame_system::Config for Test {
1196 type Block = Block;
1197 type AccountData = pallet_balances::AccountData<u64>;
1198 }
1199
1200 parameter_types! {
1201 pub const SignedDepositPerItem: u64 = 1;
1202 pub const SignedDepositBase: u64 = 5;
1203 pub const MigrationMaxKeyLen: u32 = 512;
1204 }
1205
1206 #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
1207 impl pallet_balances::Config for Test {
1208 type ReserveIdentifier = [u8; 8];
1209 type AccountStore = System;
1210 }
1211
1212 pub struct StateMigrationTestWeight;
1214
1215 impl WeightInfo for StateMigrationTestWeight {
1216 fn process_top_key(_: u32) -> Weight {
1217 Weight::from_parts(1000000, 0)
1218 }
1219 fn continue_migrate() -> Weight {
1220 Weight::from_parts(1000000, 0)
1221 }
1222 fn continue_migrate_wrong_witness() -> Weight {
1223 Weight::from_parts(1000000, 0)
1224 }
1225 fn migrate_custom_top_fail() -> Weight {
1226 Weight::from_parts(1000000, 0)
1227 }
1228 fn migrate_custom_top_success() -> Weight {
1229 Weight::from_parts(1000000, 0)
1230 }
1231 fn migrate_custom_child_fail() -> Weight {
1232 Weight::from_parts(1000000, 0)
1233 }
1234 fn migrate_custom_child_success() -> Weight {
1235 Weight::from_parts(1000000, 0)
1236 }
1237 }
1238
1239 #[derive_impl(super::config_preludes::TestDefaultConfig)]
1240 impl pallet_state_trie_migration::Config for Test {
1241 type ControlOrigin = EnsureRoot<u64>;
1242 type Currency = Balances;
1243 type MaxKeyLen = MigrationMaxKeyLen;
1244 type SignedDepositPerItem = SignedDepositPerItem;
1245 type SignedDepositBase = SignedDepositBase;
1246 type SignedFilter = EnsureSigned<Self::AccountId>;
1247 type WeightInfo = StateMigrationTestWeight;
1248 }
1249
1250 pub fn new_test_ext(
1251 version: StateVersion,
1252 with_pallets: bool,
1253 custom_keys: Option<Vec<(Vec<u8>, Vec<u8>)>>,
1254 custom_child: Option<Vec<(Vec<u8>, Vec<u8>, Vec<u8>)>>,
1255 ) -> sp_io::TestExternalities {
1256 let minimum_size = sp_core::storage::TRIE_VALUE_NODE_THRESHOLD as usize + 1;
1257 let mut custom_storage = sp_core::storage::Storage {
1258 top: vec![
1259 (b"key1".to_vec(), vec![1u8; minimum_size + 1]), (b"key2".to_vec(), vec![1u8; minimum_size + 2]), (b"key3".to_vec(), vec![1u8; minimum_size + 3]), (b"key4".to_vec(), vec![1u8; minimum_size + 4]), (b"key5".to_vec(), vec![1u8; minimum_size + 5]), (b"key6".to_vec(), vec![1u8; minimum_size + 6]), (b"key7".to_vec(), vec![1u8; minimum_size + 7]), (b"key8".to_vec(), vec![1u8; minimum_size + 8]), (b"key9".to_vec(), vec![1u8; minimum_size + 9]), (b"CODE".to_vec(), vec![1u8; minimum_size + 100]), ]
1270 .into_iter()
1271 .chain(custom_keys.unwrap_or_default())
1272 .collect(),
1273 children_default: vec![
1274 (
1275 b"chk1".to_vec(), StorageChild {
1277 data: vec![
1278 (b"key1".to_vec(), vec![1u8; 55]),
1279 (b"key2".to_vec(), vec![2u8; 66]),
1280 ]
1281 .into_iter()
1282 .collect(),
1283 child_info: ChildInfo::new_default(b"chk1"),
1284 },
1285 ),
1286 (
1287 b"chk2".to_vec(),
1288 StorageChild {
1289 data: vec![
1290 (b"key1".to_vec(), vec![1u8; 54]),
1291 (b"key2".to_vec(), vec![2u8; 64]),
1292 ]
1293 .into_iter()
1294 .collect(),
1295 child_info: ChildInfo::new_default(b"chk2"),
1296 },
1297 ),
1298 ]
1299 .into_iter()
1300 .chain(
1301 custom_child
1302 .unwrap_or_default()
1303 .into_iter()
1304 .map(|(r, k, v)| {
1305 (
1306 r.clone(),
1307 StorageChild {
1308 data: vec![(k, v)].into_iter().collect(),
1309 child_info: ChildInfo::new_default(&r),
1310 },
1311 )
1312 })
1313 .collect::<Vec<_>>(),
1314 )
1315 .collect(),
1316 };
1317
1318 if with_pallets {
1319 frame_system::GenesisConfig::<Test>::default()
1320 .assimilate_storage(&mut custom_storage)
1321 .unwrap();
1322 pallet_balances::GenesisConfig::<Test> {
1323 balances: vec![(1, 1000)],
1324 ..Default::default()
1325 }
1326 .assimilate_storage(&mut custom_storage)
1327 .unwrap();
1328 }
1329
1330 sp_tracing::try_init_simple();
1331 (custom_storage, version).into()
1332 }
1333
1334 pub(crate) fn run_to_block(n: u32) -> (H256, Weight) {
1335 let mut root = Default::default();
1336 let mut weight_sum = Weight::zero();
1337
1338 log::trace!(target: LOG_TARGET, "running from {:?} to {:?}", System::block_number(), n);
1339
1340 System::run_to_block_with::<AllPalletsWithSystem>(
1341 n,
1342 frame_system::RunToBlockHooks::default().after_initialize(|bn| {
1343 weight_sum += StateTrieMigration::on_initialize(bn);
1344 root = *System::finalize().state_root();
1345 }),
1346 );
1347
1348 (root, weight_sum)
1349 }
1350}
1351
1352#[cfg(test)]
1353mod test {
1354 use super::{mock::*, *};
1355 use frame_support::assert_ok;
1356 use sp_runtime::{bounded_vec, traits::Bounded, StateVersion};
1357
1358 #[test]
1359 fn fails_if_no_migration() {
1360 let mut ext = new_test_ext(StateVersion::V0, false, None, None);
1361 let root1 = ext.execute_with(|| run_to_block(30).0);
1362
1363 let mut ext2 = new_test_ext(StateVersion::V1, false, None, None);
1364 let root2 = ext2.execute_with(|| run_to_block(30).0);
1365
1366 assert_ne!(root1, root2);
1368 }
1369
1370 #[test]
1371 fn halts_if_top_key_too_long() {
1372 let bad_key = vec![1u8; MigrationMaxKeyLen::get() as usize + 1];
1373 let bad_top_keys = vec![(bad_key.clone(), vec![])];
1374
1375 new_test_ext(StateVersion::V0, true, Some(bad_top_keys), None).execute_with(|| {
1376 System::set_block_number(1);
1377 assert_eq!(MigrationProcess::<Test>::get(), Default::default());
1378
1379 SignedMigrationMaxLimits::<Test>::put(MigrationLimits { size: 1 << 20, item: 50 });
1381
1382 frame_support::assert_ok!(StateTrieMigration::continue_migrate(
1384 RuntimeOrigin::signed(1),
1385 MigrationLimits { item: 50, size: 1 << 20 },
1386 Bounded::max_value(),
1387 MigrationProcess::<Test>::get()
1388 ),);
1389 System::assert_last_event(
1391 crate::Event::Halted { error: Error::<Test>::KeyTooLong }.into(),
1392 );
1393 assert!(AutoLimits::<Test>::get().is_none());
1395
1396 let mut task = StateTrieMigration::migration_process();
1398 let result = task.migrate_until_exhaustion(
1399 StateTrieMigration::signed_migration_max_limits().unwrap(),
1400 );
1401 assert!(result.is_err());
1402 });
1403 }
1404
1405 #[test]
1406 fn halts_if_child_key_too_long() {
1407 let bad_key = vec![1u8; MigrationMaxKeyLen::get() as usize + 1];
1408 let bad_child_keys = vec![(bad_key.clone(), vec![], vec![])];
1409
1410 new_test_ext(StateVersion::V0, true, None, Some(bad_child_keys)).execute_with(|| {
1411 System::set_block_number(1);
1412 assert_eq!(MigrationProcess::<Test>::get(), Default::default());
1413
1414 SignedMigrationMaxLimits::<Test>::put(MigrationLimits { size: 1 << 20, item: 50 });
1416
1417 frame_support::assert_ok!(StateTrieMigration::continue_migrate(
1419 RuntimeOrigin::signed(1),
1420 MigrationLimits { item: 50, size: 1 << 20 },
1421 Bounded::max_value(),
1422 MigrationProcess::<Test>::get()
1423 ));
1424 System::assert_last_event(
1426 crate::Event::Halted { error: Error::<Test>::KeyTooLong }.into(),
1427 );
1428 assert!(AutoLimits::<Test>::get().is_none());
1430
1431 let mut task = StateTrieMigration::migration_process();
1433 let result = task.migrate_until_exhaustion(
1434 StateTrieMigration::signed_migration_max_limits().unwrap(),
1435 );
1436 assert!(result.is_err());
1437 });
1438 }
1439
1440 #[test]
1441 fn detects_value_in_empty_top_key() {
1442 let limit = MigrationLimits { item: 1, size: 1000 };
1443 let initial_keys = Some(vec![(vec![], vec![66u8; 77])]);
1444 let mut ext = new_test_ext(StateVersion::V0, false, initial_keys.clone(), None);
1445
1446 let root_upgraded = ext.execute_with(|| {
1447 AutoLimits::<Test>::put(Some(limit));
1448 let root = run_to_block(30).0;
1449
1450 assert!(StateTrieMigration::migration_process().finished());
1452 root
1453 });
1454
1455 let mut ext2 = new_test_ext(StateVersion::V1, false, initial_keys, None);
1456 let root = ext2.execute_with(|| {
1457 AutoLimits::<Test>::put(Some(limit));
1458 run_to_block(30).0
1459 });
1460
1461 assert_eq!(root, root_upgraded);
1462 }
1463
1464 #[test]
1465 fn detects_value_in_first_child_key() {
1466 let limit = MigrationLimits { item: 1, size: 1000 };
1467 let initial_child = Some(vec![(b"chk1".to_vec(), vec![], vec![66u8; 77])]);
1468 let mut ext = new_test_ext(StateVersion::V0, false, None, initial_child.clone());
1469
1470 let root_upgraded = ext.execute_with(|| {
1471 AutoLimits::<Test>::put(Some(limit));
1472 let root = run_to_block(30).0;
1473
1474 assert!(StateTrieMigration::migration_process().finished());
1476 root
1477 });
1478
1479 let mut ext2 = new_test_ext(StateVersion::V1, false, None, initial_child);
1480 let root = ext2.execute_with(|| {
1481 AutoLimits::<Test>::put(Some(limit));
1482 run_to_block(30).0
1483 });
1484
1485 assert_eq!(root, root_upgraded);
1486 }
1487
1488 #[test]
1489 fn auto_migrate_works() {
1490 let run_with_limits = |limit, from, until| {
1491 let mut ext = new_test_ext(StateVersion::V0, false, None, None);
1492 let root_upgraded = ext.execute_with(|| {
1493 assert_eq!(AutoLimits::<Test>::get(), None);
1494 assert_eq!(MigrationProcess::<Test>::get(), Default::default());
1495
1496 let _ = run_to_block(from);
1498 assert_eq!(MigrationProcess::<Test>::get(), Default::default());
1499
1500 AutoLimits::<Test>::put(Some(limit));
1502
1503 let root = run_to_block(until).0;
1504
1505 assert!(matches!(
1507 StateTrieMigration::migration_process(),
1508 MigrationTask { progress_top: Progress::Complete, .. }
1509 ));
1510 root
1511 });
1512
1513 let mut ext2 = new_test_ext(StateVersion::V1, false, None, None);
1514 let root = ext2.execute_with(|| {
1515 let _ = run_to_block(from);
1517 AutoLimits::<Test>::put(Some(limit));
1518 run_to_block(until).0
1519 });
1520 assert_eq!(root, root_upgraded);
1521 };
1522
1523 run_with_limits(MigrationLimits { item: 1, size: 1000 }, 10, 100);
1525 run_with_limits(MigrationLimits { item: 5, size: 1000 }, 10, 100);
1527 run_with_limits(MigrationLimits { item: 1000, size: 128 }, 10, 100);
1529 run_with_limits(
1531 MigrationLimits { item: Bounded::max_value(), size: Bounded::max_value() },
1532 10,
1533 100,
1534 );
1535 }
1536
1537 #[test]
1538 fn signed_migrate_works() {
1539 new_test_ext(StateVersion::V0, true, None, None).execute_with(|| {
1540 assert_eq!(MigrationProcess::<Test>::get(), Default::default());
1541
1542 SignedMigrationMaxLimits::<Test>::put(MigrationLimits { size: 1024, item: 5 });
1544
1545 frame_support::assert_err!(
1547 StateTrieMigration::continue_migrate(
1548 RuntimeOrigin::signed(1),
1549 MigrationLimits { item: 5, size: sp_runtime::traits::Bounded::max_value() },
1550 Bounded::max_value(),
1551 MigrationProcess::<Test>::get()
1552 ),
1553 Error::<Test>::MaxSignedLimits,
1554 );
1555
1556 frame_support::assert_err!(
1558 StateTrieMigration::continue_migrate(
1559 RuntimeOrigin::signed(2),
1560 MigrationLimits { item: 5, size: 100 },
1561 100,
1562 MigrationProcess::<Test>::get()
1563 ),
1564 Error::<Test>::NotEnoughFunds,
1565 );
1566
1567 frame_support::assert_err_ignore_postinfo!(
1569 StateTrieMigration::continue_migrate(
1570 RuntimeOrigin::signed(1),
1571 MigrationLimits { item: 5, size: 100 },
1572 100,
1573 MigrationTask {
1574 progress_top: Progress::LastKey(bounded_vec![1u8]),
1575 ..Default::default()
1576 }
1577 ),
1578 Error::<Test>::BadWitness,
1579 );
1580
1581 while !MigrationProcess::<Test>::get().finished() {
1583 let mut task = StateTrieMigration::migration_process();
1585 assert_ok!(task.migrate_until_exhaustion(
1586 StateTrieMigration::signed_migration_max_limits().unwrap(),
1587 ));
1588
1589 frame_support::assert_ok!(StateTrieMigration::continue_migrate(
1590 RuntimeOrigin::signed(1),
1591 StateTrieMigration::signed_migration_max_limits().unwrap(),
1592 task.dyn_size,
1593 MigrationProcess::<Test>::get()
1594 ));
1595
1596 assert_eq!(Balances::reserved_balance(&1), 0);
1598 assert_eq!(Balances::free_balance(&1), 1000);
1599
1600 assert!(matches!(
1602 StateTrieMigration::migration_process(),
1603 MigrationTask { size: x, .. } if x > 0,
1604 ));
1605 }
1606 });
1607 }
1608
1609 #[test]
1610 fn continue_migrate_slashing_works() {
1611 new_test_ext(StateVersion::V0, true, None, None).execute_with(|| {
1612 assert_eq!(MigrationProcess::<Test>::get(), Default::default());
1613
1614 SignedMigrationMaxLimits::<Test>::put(MigrationLimits { size: 1024, item: 5 });
1616
1617 let mut task = StateTrieMigration::migration_process();
1619 assert_ok!(task.migrate_until_exhaustion(
1620 StateTrieMigration::signed_migration_max_limits().unwrap(),
1621 ));
1622
1623 frame_support::assert_ok!(StateTrieMigration::continue_migrate(
1625 RuntimeOrigin::signed(1),
1626 StateTrieMigration::signed_migration_max_limits().unwrap(),
1627 task.dyn_size - 1,
1628 MigrationProcess::<Test>::get()
1629 ));
1630 assert_eq!(Balances::reserved_balance(&1), 0);
1632 assert_eq!(
1634 Balances::free_balance(&1),
1635 1000 - StateTrieMigration::calculate_deposit_for(5)
1636 );
1637 });
1638 }
1639
1640 #[test]
1641 fn custom_migrate_top_works() {
1642 let correct_witness = 3 + sp_core::storage::TRIE_VALUE_NODE_THRESHOLD * 3 + 1 + 2 + 3;
1643 new_test_ext(StateVersion::V0, true, None, None).execute_with(|| {
1644 frame_support::assert_ok!(StateTrieMigration::migrate_custom_top(
1645 RuntimeOrigin::signed(1),
1646 vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()],
1647 correct_witness,
1648 ));
1649
1650 assert_eq!(Balances::reserved_balance(&1), 0);
1652 assert_eq!(Balances::free_balance(&1), 1000);
1653 });
1654
1655 new_test_ext(StateVersion::V0, true, None, None).execute_with(|| {
1656 frame_support::assert_ok!(StateTrieMigration::migrate_custom_top(
1658 RuntimeOrigin::signed(1),
1659 vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()],
1660 correct_witness + 99,
1661 ));
1662
1663 assert_eq!(Balances::reserved_balance(&1), 0);
1665 assert_eq!(Balances::free_balance(&1), 1000);
1666 });
1667
1668 new_test_ext(StateVersion::V0, true, None, None).execute_with(|| {
1669 assert_eq!(Balances::free_balance(&1), 1000);
1670
1671 frame_support::assert_ok!(StateTrieMigration::migrate_custom_top(
1673 RuntimeOrigin::signed(1),
1674 vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()],
1675 correct_witness - 1,
1676 ),);
1677
1678 assert_eq!(Balances::reserved_balance(&1), 0);
1680 assert_eq!(
1681 Balances::free_balance(&1),
1682 1000 - StateTrieMigration::calculate_deposit_for(3)
1683 );
1684 });
1685 }
1686
1687 #[test]
1688 fn custom_migrate_child_works() {
1689 new_test_ext(StateVersion::V0, true, None, None).execute_with(|| {
1690 frame_support::assert_ok!(StateTrieMigration::migrate_custom_child(
1691 RuntimeOrigin::signed(1),
1692 StateTrieMigration::childify("chk1"),
1693 vec![b"key1".to_vec(), b"key2".to_vec()],
1694 55 + 66,
1695 ));
1696
1697 assert_eq!(Balances::reserved_balance(&1), 0);
1699 assert_eq!(Balances::free_balance(&1), 1000);
1700 });
1701
1702 new_test_ext(StateVersion::V0, true, None, None).execute_with(|| {
1703 assert_eq!(Balances::free_balance(&1), 1000);
1704
1705 frame_support::assert_ok!(StateTrieMigration::migrate_custom_child(
1707 RuntimeOrigin::signed(1),
1708 StateTrieMigration::childify("chk1"),
1709 vec![b"key1".to_vec(), b"key2".to_vec()],
1710 999999, ));
1712
1713 assert_eq!(Balances::reserved_balance(&1), 0);
1715 assert_eq!(
1716 Balances::free_balance(&1),
1717 1000 - StateTrieMigration::calculate_deposit_for(2)
1718 );
1719 });
1720 }
1721}
1722
1723#[cfg(feature = "remote-test")]
1725pub(crate) mod remote_tests {
1726 use crate::{AutoLimits, MigrationLimits, Pallet as StateTrieMigration, LOG_TARGET};
1727 use codec::Encode;
1728 use frame_support::{
1729 traits::{Get, Hooks},
1730 weights::Weight,
1731 };
1732 use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System};
1733 use remote_externalities::Mode;
1734 use sp_core::H256;
1735 use sp_runtime::{
1736 traits::{Block as BlockT, HashingFor, Header as _, One, Zero},
1737 DeserializeOwned,
1738 };
1739 use thousands::Separable;
1740
1741 #[allow(dead_code)]
1742 fn run_to_block<Runtime: crate::Config<Hash = H256>>(
1743 n: BlockNumberFor<Runtime>,
1744 ) -> (H256, Weight) {
1745 let mut root = Default::default();
1746 let mut weight_sum = Weight::zero();
1747 while System::<Runtime>::block_number() < n {
1748 System::<Runtime>::set_block_number(System::<Runtime>::block_number() + One::one());
1749 System::<Runtime>::on_initialize(System::<Runtime>::block_number());
1750
1751 weight_sum +=
1752 StateTrieMigration::<Runtime>::on_initialize(System::<Runtime>::block_number());
1753
1754 root = *System::<Runtime>::finalize().state_root();
1755 System::<Runtime>::on_finalize(System::<Runtime>::block_number());
1756 }
1757 (root, weight_sum)
1758 }
1759
1760 #[allow(dead_code)]
1764 pub(crate) async fn run_with_limits<Runtime, Block>(
1765 limits: MigrationLimits,
1766 mode: Mode<Block::Hash>,
1767 ) where
1768 Runtime: crate::Config<Hash = H256>,
1769 Block: BlockT<Hash = H256> + DeserializeOwned,
1770 Block::Header: serde::de::DeserializeOwned,
1771 {
1772 let mut ext = remote_externalities::Builder::<Block>::new()
1773 .mode(mode)
1774 .overwrite_state_version(sp_core::storage::StateVersion::V0)
1775 .build()
1776 .await
1777 .unwrap();
1778
1779 let mut now = ext.execute_with(|| {
1780 AutoLimits::<Runtime>::put(Some(limits));
1781 frame_system::Pallet::<Runtime>::block_number()
1783 });
1784
1785 let mut duration: BlockNumberFor<Runtime> = Zero::zero();
1786 ext.state_version = sp_core::storage::StateVersion::V1;
1788
1789 let status =
1790 substrate_state_trie_migration_rpc::migration_status(&ext.as_backend()).unwrap();
1791 assert!(
1792 status.top_remaining_to_migrate > 0,
1793 "no node needs migrating, this probably means that state was initialized with `StateVersion::V1`",
1794 );
1795
1796 log::info!(
1797 target: LOG_TARGET,
1798 "initial check: top_left: {}, child_left: {}, total_top {}, total_child {}",
1799 status.top_remaining_to_migrate.separate_with_commas(),
1800 status.child_remaining_to_migrate.separate_with_commas(),
1801 status.total_top.separate_with_commas(),
1802 status.total_child.separate_with_commas(),
1803 );
1804
1805 loop {
1806 let last_state_root = *ext.backend.root();
1807 let ((finished, weight), proof) = ext.execute_and_prove(|| {
1808 let weight = run_to_block::<Runtime>(now + One::one()).1;
1809 if StateTrieMigration::<Runtime>::migration_process().finished() {
1810 return (true, weight);
1811 }
1812 duration += One::one();
1813 now += One::one();
1814 (false, weight)
1815 });
1816
1817 let compact_proof =
1818 proof.clone().into_compact_proof::<HashingFor<Block>>(last_state_root).unwrap();
1819 log::info!(
1820 target: LOG_TARGET,
1821 "proceeded to #{}, weight: [{} / {}], proof: [{} / {} / {}]",
1822 now,
1823 weight.separate_with_commas(),
1824 <Runtime as frame_system::Config>::BlockWeights::get()
1825 .max_block
1826 .separate_with_commas(),
1827 proof.encoded_size().separate_with_commas(),
1828 compact_proof.encoded_size().separate_with_commas(),
1829 zstd::stream::encode_all(&compact_proof.encode()[..], 0)
1830 .unwrap()
1831 .len()
1832 .separate_with_commas(),
1833 );
1834 ext.commit_all().unwrap();
1835
1836 if finished {
1837 break;
1838 }
1839 }
1840
1841 ext.execute_with(|| {
1842 log::info!(
1843 target: LOG_TARGET,
1844 "finished on_initialize migration in {} block, final state of the task: {:?}",
1845 duration,
1846 StateTrieMigration::<Runtime>::migration_process(),
1847 )
1848 });
1849
1850 let status =
1851 substrate_state_trie_migration_rpc::migration_status(&ext.as_backend()).unwrap();
1852 assert_eq!(status.top_remaining_to_migrate, 0);
1853 assert_eq!(status.child_remaining_to_migrate, 0);
1854 }
1855}
1856
1857#[cfg(all(test, feature = "remote-test"))]
1858mod remote_tests_local {
1859 use super::{
1860 mock::{RuntimeCall as MockCall, *},
1861 remote_tests::run_with_limits,
1862 *,
1863 };
1864 use remote_externalities::{Mode, OfflineConfig, OnlineConfig, SnapshotConfig};
1865 use sp_runtime::traits::Bounded;
1866 use std::env::var as env_var;
1867
1868 type Extrinsic = sp_runtime::testing::TestXt<MockCall, ()>;
1870 type Block = sp_runtime::testing::Block<Extrinsic>;
1871
1872 #[tokio::test]
1873 async fn on_initialize_migration() {
1874 let snap: SnapshotConfig = env_var("SNAP").expect("Need SNAP env var").into();
1875 let ws_api = env_var("WS_API").expect("Need WS_API env var").into();
1876
1877 sp_tracing::try_init_simple();
1878 let mode = Mode::OfflineOrElseOnline(
1879 OfflineConfig { state_snapshot: snap.clone() },
1880 OnlineConfig { transport: ws_api, state_snapshot: Some(snap), ..Default::default() },
1881 );
1882
1883 run_with_limits::<Test, Block>(
1885 MigrationLimits { item: 8 * 1024, size: 128 * 1024 * 1024 },
1886 mode.clone(),
1887 )
1888 .await;
1889 run_with_limits::<Test, Block>(
1891 MigrationLimits { item: Bounded::max_value(), size: 64 * 1024 },
1892 mode,
1893 )
1894 .await;
1895 }
1896}