4

Here are the details, I need to do a storage migration for a pallet. I can read the storage item from the polkadot.js.app, as shown below: register info of KSM

however, I can not read it from migration script(returned None), code is:

let reg_info = RegistryInfoByIds::<T>::get(asset_id).ok_or(Error::<T>::AssetNotRegistered)?;

I assume the storage item must exist if we can query from the polkadot.js.app. Don't know what the problem is.

8
  • What is the value of asset_id in your script?
    – forgetso
    May 16, 2022 at 9:23
  • @forgetso It's a type of pallet_assets::AssetId, for example: let roc_id: <T as pallet_assets::Config>::AssetId = 0u32.into(); May 16, 2022 at 9:25
  • Can you please share the code where you are defining your storage? May 16, 2022 at 11:00
  • @PawanBisht Here is the storage definition, and here is the migration script. May 16, 2022 at 11:33
  • Are you importing your pallet for accessing the storage? May 16, 2022 at 14:32

1 Answer 1

1

When updating types in a runtime migration, you must read the "old" storage using the old storage type.

Imagine you have an item originally stored as:

struct Item {
    id: u32,
    field1: Field,
}

Then imagine you update the item to be:

struct Item {
    id: u32,
    field1: Field,
    field2: Field,
}

If you were to try to read this storage item after updating the struct Item, you will get the "corrupt state" error because on your blockchain, there is no field2, and thus, when trying to read from state to create the new Item, it will fail.

Instead, a common pattern for migrations is to write a special type which acts as a way to read the old storage, and then re-write the storage with the new updated struct.

That pattern looks like this:

mod old {
    pub struct OldItem {
        id: u32,
        field1: Field,
    }

    #[frame_support::storage_alias]
    pub type ItemMap<T> = StorageMap<
        crate::Pallet<T>,
        Twox64Concat,
        u32,
        OldItem<T>,
    >;
}

fn migration() {
    // Note here we are reading from `old`, which has our old type.
    for (_key, old_item) in old::ListNodes::<T, I>::iter() {
        // Note here we are using the `crate::Item`, which has the updated object.
        let new_item = crate::Item {
             id: old_item.id,
             field1: old_item.field1,
             // Here we just duplicate field1, but you may have your own logic...
             field2: old_item.field1,
        };

        // Note that we write back to `crate::ItemMap` which uses the new type.
        crate::ItemMap::<T>::insert(old_item.id, new_item);
    }
}

There are plenty of examples in Substrate, but here is one:

https://github.com/paritytech/substrate/blob/master/frame/bags-list/src/migrations.rs

3
  • Thanks, I finally used the translate() method to convert an old item to a new one. Sure an old item also needs to be declared to help read from the storage. Jun 6, 2022 at 3:18
  • If you found your own answer to the question, please do post the answer and mark the question as resolved :)
    – Shawn Tabrizi
    Jun 9, 2022 at 10:41
  • Apologize for the late reply. I solved the problem by using the translate() method provided by the substrate storage map. And definitely no matter what approach we still need to define an old type to make sure the storage decoding before migration is done can succeed. Here is the details. Jun 24, 2022 at 9:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.