-
Notifications
You must be signed in to change notification settings - Fork 336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pallet-migrations: try-runtime support, and manage author mapping blake2 migration #796
Changes from 91 commits
9ecdc74
e4f15ce
ca3620e
cdc042a
c4204b4
0cc885d
95308a1
93b96ce
275531c
d5abf1f
d25ffc1
0c9ce74
23837f8
85ecaa1
f22ce8d
d8d2c3c
bfd24f6
9404fbe
edee830
d13a471
986433e
92047ee
b885fc5
aefb302
f9f8a2a
bc2e538
19a9912
1f70d01
6e581f3
2ea1e3b
4afc532
85ed484
e377caf
95d6b95
d5da28a
2c0bfe5
67bede2
f924112
5022dcc
8a925ac
a2177dd
01727f8
8ed861f
0785e48
8c444e7
2e12fd2
9a9bfd4
2744571
e1f49c6
0684874
c19a4a1
7d57a4e
3f87f79
595e865
8f6e9e5
a4e2acc
41e9d0b
0a3ae7e
26ef7db
e6543e6
01bc09f
d2e5759
38ee3a4
30033ee
dfb6f42
f691977
dc42819
f31807a
607e0ee
3e9c26b
91f92f7
c0e6167
d9e561b
3722eaf
a585e3c
bdafb76
9d27292
e346883
3cb8bb4
1d86ea1
d48140c
06ee01f
5bf5b12
31d52fd
64605c8
e58599f
85b7095
3e33836
9370840
b90b726
ac5f99f
003a52c
9a41ed3
9b476e2
4a53747
6676265
935d0fd
b9e6a61
85d126b
4e459c4
7f546b7
e651798
a9da31d
95d69c5
fe80835
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// Copyright 2019-2021 PureStake Inc. | ||
// This file is part of Moonbeam. | ||
|
||
// Moonbeam is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
|
||
// Moonbeam is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
|
||
// You should have received a copy of the GNU General Public License | ||
// along with Moonbeam. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
use crate::{BalanceOf, Config, MappingWithDeposit, RegistrationInfo}; | ||
use frame_support::{ | ||
pallet_prelude::PhantomData, | ||
storage::migration::{remove_storage_prefix, storage_key_iter}, | ||
traits::{Get, OnRuntimeUpgrade}, | ||
weights::Weight, | ||
Twox64Concat, | ||
}; | ||
use sp_std::convert::TryInto; | ||
//TODO sometimes this is unused, sometimes its necessary | ||
use sp_std::vec::Vec; | ||
|
||
/// Migrates the AuthorMapping's storage map fro mthe insecure Twox64 hasher to the secure | ||
/// BlakeTwo hasher. | ||
pub struct TwoXToBlake<T>(PhantomData<T>); | ||
impl<T: Config> OnRuntimeUpgrade for TwoXToBlake<T> { | ||
fn on_runtime_upgrade() -> Weight { | ||
let pallet_prefix: &[u8] = b"AuthorMapping"; | ||
let storage_item_prefix: &[u8] = b"MappingWithDeposit"; | ||
|
||
// Read all the data into memory. | ||
// https://crates.parity.io/frame_support/storage/migration/fn.storage_key_iter.html | ||
let stored_data: Vec<_> = storage_key_iter::< | ||
T::AuthorId, | ||
RegistrationInfo<T::AccountId, BalanceOf<T>>, | ||
Twox64Concat, | ||
>(pallet_prefix, storage_item_prefix) | ||
.collect(); | ||
|
||
let migrated_count: Weight = stored_data | ||
.len() | ||
.try_into() | ||
.expect("There are between 0 and 2**64 mappings stored."); | ||
|
||
// Now remove the old storage | ||
// https://crates.parity.io/frame_support/storage/migration/fn.remove_storage_prefix.html | ||
remove_storage_prefix(pallet_prefix, storage_item_prefix, &[]); | ||
|
||
// Assert that old storage is empty | ||
assert!(storage_key_iter::< | ||
T::AuthorId, | ||
RegistrationInfo<T::AccountId, BalanceOf<T>>, | ||
Twox64Concat, | ||
>(pallet_prefix, storage_item_prefix) | ||
.next() | ||
.is_none()); | ||
|
||
// Write the mappings back to storage with the new secure hasher | ||
for (author_id, account_id) in stored_data { | ||
MappingWithDeposit::<T>::insert(author_id, account_id); | ||
} | ||
|
||
// Return the weight used. For each migrated mapping there is a red to get it into | ||
// memory, a write to clear the old stored value, and a write to re-store it. | ||
let db_weights = T::DbWeight::get(); | ||
migrated_count.saturating_mul(2 * db_weights.write + db_weights.read) | ||
} | ||
|
||
#[cfg(feature = "try-runtime")] | ||
fn pre_upgrade() -> Result<(), &'static str> { | ||
let pallet_prefix: &[u8] = b"AuthorMapping"; | ||
let storage_item_prefix: &[u8] = b"MappingWithDeposit"; | ||
|
||
// We want to test that: | ||
// There are no entries in the new storage beforehand | ||
// The same number of mappings exist before and after | ||
// As long as there are some mappings stored, one representative key maps to the | ||
// same value after the migration. | ||
// There are no entries in the old storage afterward | ||
|
||
// Assert new storage is empty | ||
// Because the pallet and item prefixes are the same, the old storage is still at this | ||
// key. However, the values can't be decoded so the assertion passes. | ||
assert!(MappingWithDeposit::<T>::iter().next().is_none()); | ||
|
||
// Check number of entries, and set it aside in temp storage | ||
let mapping_count = storage_iter::<RegistrationInfo<T::AccountId, BalanceOf<T>>>( | ||
pallet_prefix, | ||
storage_item_prefix, | ||
) | ||
.count() as u64; | ||
Self::set_temp_storage(mapping_count, "mapping_count"); | ||
|
||
// Read an example pair from old storage and set it aside in temp storage | ||
if mapping_count > 0 { | ||
let example_pair = storage_key_iter::< | ||
T::AuthorId, | ||
RegistrationInfo<T::AccountId, BalanceOf<T>>, | ||
Twox64Concat, | ||
>(pallet_prefix, storage_item_prefix) | ||
.next() | ||
.expect("We already confirmed that there was at least one item stored"); | ||
|
||
Self::set_temp_storage(example_pair, "example_pair"); | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
#[cfg(feature = "try-runtime")] | ||
fn post_upgrade() -> Result<(), &'static str> { | ||
// Check number of entries matches what was set aside in pre_upgrade | ||
let old_mapping_count: u64 = Self::get_temp_storage("mapping_count") | ||
.expect("We stored a mapping count; it should be there; qed"); | ||
let new_mapping_count = MappingWithDeposit::<T>::iter().count() as u64; | ||
assert_eq!(old_mapping_count, new_mapping_count); | ||
|
||
// Check that our example pair is still well-mapped after the migration | ||
if new_mapping_count > 0 { | ||
let (account, original_info): ( | ||
T::AuthorId, | ||
RegistrationInfo<T::AccountId, BalanceOf<T>>, | ||
) = Self::get_temp_storage("example_pair").expect("qed"); | ||
let migrated_info = MappingWithDeposit::<T>::get(account).expect("qed"); | ||
assert_eq!(original_info, migrated_info); | ||
} | ||
|
||
Ok(()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,3 +26,6 @@ std = [ | |
"sp-std/std", | ||
"sp-runtime/std", | ||
] | ||
try-runtime = [ | ||
"frame-support/try-runtime", | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,18 @@ pub trait Migration { | |
/// constraints will lead to a bricked chain upon a runtime upgrade because the parachain will | ||
/// not be able to produce a block that the relay chain will accept. | ||
fn migrate(&self, available_weight: Weight) -> Weight; | ||
|
||
/// Run a standard pre-runtime test. This works the same way as in a normal runtime upgrade. | ||
#[cfg(feature = "try-runtime")] | ||
fn pre_upgrade(&self) -> Result<(), &'static str> { | ||
Ok(()) | ||
} | ||
|
||
/// Run a standard post-runtime test. This works the same way as in a normal runtime upgrade. | ||
#[cfg(feature = "try-runtime")] | ||
fn post_upgrade(&self) -> Result<(), &'static str> { | ||
Ok(()) | ||
} | ||
Comment on lines
+48
to
+58
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. At this point you're basically re-inventing https://crates.parity.io/frame_support/traits/trait.OnRuntimeUpgrade.html What do you think about this instead: pub trait Migration: OnRuntimeUpgrade {
fn friendly_name(&self) -> &str;
} This means you can only implement 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. I think there's a fundamental problem we would need to solve to do something like this and it would require some larger redesign. The definition I also have a minor concern about reusing the name 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. Oh, good point about the I think if we do want to explore this route, the solution this problem would be to switch from using a 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. Sounds good to me. I'd like to explore that, if only to learn about it. I've leveled up a bit in rust since writing this pallet, so it would be a useful exercise to review my designs at some point. 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. I made a followup ticket for this https://purestake.atlassian.net/browse/MOON-932 |
||
} | ||
|
||
#[pallet] | ||
|
@@ -103,6 +115,88 @@ pub mod pallet { | |
|
||
weight | ||
} | ||
|
||
#[cfg(feature = "try-runtime")] | ||
fn pre_upgrade() -> Result<(), &'static str> { | ||
use frame_support::traits::OnRuntimeUpgradeHelpersExt; | ||
|
||
let mut failed = false; | ||
for migration in &T::MigrationsList::get() { | ||
let migration_name = migration.friendly_name(); | ||
let migration_name_as_bytes = migration_name.as_bytes(); | ||
|
||
let migration_done = <MigrationState<T>>::get(migration_name_as_bytes); | ||
if migration_done { | ||
continue; | ||
} | ||
log::trace!("invoking pre_upgrade() on migration {}", migration_name); | ||
|
||
// dump the migration name to temp storage so post_upgrade will know which | ||
// migrations were performed (as opposed to skipped) | ||
JoshOrndorff marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Self::set_temp_storage(true, migration_name); | ||
|
||
let result = migration.pre_upgrade(); | ||
match result { | ||
notlesh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Ok(()) => { | ||
log::info!("migration {} pre_upgrade() => Ok()", migration_name); | ||
} | ||
Err(msg) => { | ||
log::error!("migration {} pre_upgrade() => Err({})", migration_name, msg); | ||
failed = true; | ||
} | ||
} | ||
} | ||
|
||
if failed { | ||
Err("One or more pre_upgrade tests failed; see output above.") | ||
} else { | ||
Ok(()) | ||
} | ||
} | ||
|
||
/// Run a standard post-runtime test. This works the same way as in a normal runtime upgrade. | ||
#[cfg(feature = "try-runtime")] | ||
fn post_upgrade() -> Result<(), &'static str> { | ||
use frame_support::traits::OnRuntimeUpgradeHelpersExt; | ||
|
||
// TODO: my desire to DRY all the things feels like this code is very repetitive... | ||
|
||
let mut failed = false; | ||
for migration in &T::MigrationsList::get() { | ||
let migration_name = migration.friendly_name(); | ||
|
||
// we can't query MigrationState because on_runtime_upgrade() would have | ||
// unconditionally set it to true, so we read a hint from temp storage which was | ||
// left for us by pre_upgrade() | ||
match Self::get_temp_storage::<bool>(migration_name) { | ||
Some(value) => assert!(true == value, "our dummy value might as well be true"), | ||
JoshOrndorff marked this conversation as resolved.
Show resolved
Hide resolved
|
||
None => continue, | ||
} | ||
|
||
log::trace!("invoking post_upgrade() on migration {}", migration_name); | ||
|
||
let result = migration.post_upgrade(); | ||
match result { | ||
Ok(()) => { | ||
log::info!("migration {} post_upgrade() => Ok()", migration_name); | ||
} | ||
Err(msg) => { | ||
log::error!( | ||
"migration {} post_upgrade() => Err({})", | ||
migration_name, | ||
msg | ||
); | ||
failed = true; | ||
} | ||
} | ||
} | ||
|
||
if failed { | ||
Err("One or more post_upgrade tests failed; see output above.") | ||
} else { | ||
Ok(()) | ||
} | ||
} | ||
} | ||
|
||
#[pallet::storage] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are only using this in the crate context, we can make it pub crate only
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a valid point, but our other pallets also have them just
pub
(eg https://github.com/PureStake/moonbeam/blob/master/pallets/asset-manager/src/lib.rs#L119). And I worry that making stuff too restricted will make it hard to write precompiles in the short term.Here's a followup issue to treat this throughout the repo. https://purestake.atlassian.net/browse/MOON-933