Basics
Illustrates how to modify the storage for a specific pallet and prepare to migrate to the new storage layout.
This guide illustrates how to perform storage migration for a specific pallet by modifying the FRAME Nicks pallet. In this tutorial, you'll modify a storage map to provide an optional field that includes a last name and write a migration function that can then be triggered with a runtime upgrade. You can use this type of simple storage migration when changes are limited to specific pallets and individual storage items. You can follow similar steps for more complex data migration, but you'll need to write more complex migration functions and use additional tooling to test your migration than explained in this tutorial.
Add the Nicks pallet locally
We are going to make a change in the FRAME's Nick's pallet, in the tutorial Add a pallet to the runtime we show how to add the Nicks pallet to the runtime for the node template.
For this guide, because we are going to make changes in the code of the pallet we are going to take the code of the pallet and add it locally in our node template. You can check an example of how to add it locally here.
For testing we can now start our node and set a nickname using the Nicks pallet within the extrinsic setName
.
Create a storage struct and update storage item
By default, the Nicks pallet uses a storage map to provide a lookup table with a BoundedVec
to store the nickname. For example, the default storage definition looks like this:
We want to update the storage to add an optional field that includes a last name too. For that we create a new struct Nickname
to manage the previous and new storage items, first name and last name:
To change now the data stored in the storage we will update the StorageMap NameOf
to store the Nickname struct instead of only a BoundedVec
Update functions
Now that you have added the new data structure and modified storage to include both a first name and an optional last name, you must update the Nicks pallet functions to include the new last: Option<BoundedVec<u8>>
parameter declaration. In most cases, updating the functions when modifying storage items will require adding some logic to account for the changes.For example, you might need to modify parameter names or add new variables.
In this case, most of the changes required are in the set_name
and force_name
functions. For example, you might modify the set_name
function to change bounded_name
to bounded_first
and add the bounded_last
declaration with code similar to the following:
In addition, update all storage writes with the Nickname
struct:
Check an example of how to update the extrinsics here.
Add the storage version
The pallet::pallet
macro implements traits::GetStorageVersion
but the current storage version needs to be communicated to the macro. This can be done by using the pallet::storage_version
macro.
Declare a migration module
The migration module should contain two parts:
A module indicating the deprecated storage to migrate from.
The migration function which returns a weight.
Create a new file in src/pallets/nicks/migration.rs
The scaffolding of this module looks like this:
Write migrate_to_v2
migrate_to_v2
Here's an overview of what this function needs to do:
Check the storage version to make sure a migration is needed (good practice)
Transform the storage values into the new storage format
Update the storage version
Return the weight consumed by the migration
Check the storage version
Construct the migrate_to_v2
logic around the check. If the storage migration doesn't need to happen, return 0:
Transform storage values
Using the translate storage method
, transform the storage values to the new format. Since the existing nick
value in storage can be made of a string separated by a space, split it at the ' '
and place anything after that into the new last
storage item. If it isn't, last
takes the None
value:
Update the storage version
Return the consumed weight
To do this, count the number of storage reads and writes and return the corresponding weight:
7. Use migrate_to_v2
in on_runtime_upgrade
migrate_to_v2
in on_runtime_upgrade
In your pallet lib.rs, declare the mod migration.
And go back to the pallet's functions and specify the migrate_to_v2
function in on_runtime_upgrade
. This lets you express what should happen when the runtime upgrades:
Check an example of the full migration code [here] (https://github.com/substrate-developer-hub/substrate-node-template/commit/cfbe01dd4be358d0df45a81b87b6ba7393e20368).
Update unit tests
When writing a runtime migration module it is important to test it to avoid any critical issues caused by mangling storage items.
For the Nicks pallet we have following tests:
fn kill_name_should_work()
fn force_name_should_work()
fn normal_operation_should_work()
fn error_catching_should_work()
We have to update them to work with the new code we have added, for example:
Check an example of the full test fixes here.
Examples
Resources
Rust docs
frame_support::storage::migration
utility docs
Last updated