When adding a pallet to chain after genesis we currently don't set the StorageVersion. So, when calling on_chain_storage_version it returns 0 while the pallet is maybe already at storage version 9 when it was added to the chain. This could lead to issues when running migrations.
Solution
Add an exists method to StorageVersion, which can be used to check whether the on-chain storage version for a pallet has been initialized
Added a before_all method to the OnRuntimeUpgrade trait with a noop default implementation
Modify Executive to call before_all for all migrations before running any other hooks
Implement before_all in the pallet proc macro impl of OnRuntimeUpgrade to initialize the on-chain version to the current pallet version if it does not exist. If no current pallet version exists, the on-chain version is initialized to 0.
Other changes in this PR
Abstracted repeated boilerplate to access the pallet_name in the pallet expand proc macro.
Some discussion about the placement of before_all
It was decided in https://github.com/paritytech/polkadot-sdk/issues/109 to add before_all directly to OnRuntimeUpgrade. However, unless there are use cases outside of initializing the storage version of a pallet maybe it is confusing it make it part of that trait and should be put elsewhere?
@kianenigma suggested creating a superset of OnRuntimeUpgrade called something like OnPalletRuntimeUpgrade and putting before_all there. I like this suggestion because it removes it from view from the average Substrate developer using OnRuntimeUpgrade, but maybe there'll be complications having two different migration traits (like couldn't combine into tuples anymore)? Open to suggestions here, @bkchr curious to hear what you think.
FAQ
Why create a new hook instead of adding this logic to the pallet pre_upgrade?
Executive currently runs COnRuntimeUpgrade (custom migrations) before AllPalletsWithSystem migrations. We need versions to be initialized before the COnRuntimeUpgrade migrations are run, because COnRuntimeUpgrade migrations may use the on-chain version for critical logic. e.g. VersionedRuntimeUpgrade uses it to decide whether or not to execute.
We cannot reorder COnRuntimeUpgrade and AllPalletsWithSystem so AllPalletsWithSystem runs first, because AllPalletsWithSystem have some logic in their post_upgrade hooks to verify that the on-chain version and current pallet version match. A common use case of COnRuntimeUpgrade migrations is to perform a migration which will result in the versions matching, so if they were reordered these post_upgrade checks would fail.
Why init the on-chain version for pallets without a current storage version?
We must init the on-chain version for pallets even if they don't have a defined storage version so if there is a future version bump, the on-chain version is not automatically set to that new version without a proper migration.
e.g. bad scenario:
A pallet with no 'current version' is added to the runtime
Later, the pallet is upgraded with the 'current version' getting set to 1 and a migration is added to Executive Migrations to migrate the storage from 0 to 1
a. Runtime upgrade occurs
b. before_all hook initializes the on-chain version to 1
c. on_runtime_upgrade of the migration executes, and sees the on-chain version is already 1 therefore think storage is already migrated and does not execute the storage migration
Now, on-chain version is 1 but storage is still at version 0.
By always initializing the on-chain version when the pallet is added to the runtime we avoid that scenario.
TODO
[x] Tests
[x] Return correct weights from pallet implementation of before_all
Closes https://github.com/paritytech/polkadot-sdk/issues/109
Problem
Quoting from the above issue:
Solution
exists
method toStorageVersion
, which can be used to check whether the on-chain storage version for a pallet has been initializedbefore_all
method to theOnRuntimeUpgrade
trait with a noop default implementationExecutive
to callbefore_all
for all migrations before running any other hooksbefore_all
in the pallet proc macro impl ofOnRuntimeUpgrade
to initialize the on-chain version to the current pallet version if it does not exist. If no current pallet version exists, the on-chain version is initialized to 0.Other changes in this PR
pallet_name
in the pallet expand proc macro.Some discussion about the placement of
before_all
It was decided in https://github.com/paritytech/polkadot-sdk/issues/109 to add
before_all
directly toOnRuntimeUpgrade
. However, unless there are use cases outside of initializing the storage version of a pallet maybe it is confusing it make it part of that trait and should be put elsewhere?@kianenigma suggested creating a superset of
OnRuntimeUpgrade
called something likeOnPalletRuntimeUpgrade
and puttingbefore_all
there. I like this suggestion because it removes it from view from the average Substrate developer usingOnRuntimeUpgrade
, but maybe there'll be complications having two different migration traits (like couldn't combine into tuples anymore)? Open to suggestions here, @bkchr curious to hear what you think.FAQ
Why create a new hook instead of adding this logic to the pallet
pre_upgrade
?Executive
currently runsCOnRuntimeUpgrade
(custom migrations) beforeAllPalletsWithSystem
migrations. We need versions to be initialized before theCOnRuntimeUpgrade
migrations are run, becauseCOnRuntimeUpgrade
migrations may use the on-chain version for critical logic. e.g.VersionedRuntimeUpgrade
uses it to decide whether or not to execute.We cannot reorder
COnRuntimeUpgrade
andAllPalletsWithSystem
soAllPalletsWithSystem
runs first, becauseAllPalletsWithSystem
have some logic in theirpost_upgrade
hooks to verify that the on-chain version and current pallet version match. A common use case ofCOnRuntimeUpgrade
migrations is to perform a migration which will result in the versions matching, so if they were reordered thesepost_upgrade
checks would fail.Why init the on-chain version for pallets without a current storage version?
We must init the on-chain version for pallets even if they don't have a defined storage version so if there is a future version bump, the on-chain version is not automatically set to that new version without a proper migration.
e.g. bad scenario:
before_all
hook initializes the on-chain version to 1 c.on_runtime_upgrade
of the migration executes, and sees the on-chain version is already 1 therefore think storage is already migrated and does not execute the storage migrationNow, on-chain version is 1 but storage is still at version 0.
By always initializing the on-chain version when the pallet is added to the runtime we avoid that scenario.
TODO
before_all