-
Notifications
You must be signed in to change notification settings - Fork 335
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 Initial Implementation #527
Changes from 83 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
99ca061
62eb821
cf735f9
fd0a165
3bcfce5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
[package] | ||
name = "pallet-migrations" | ||
version = "0.1.0" | ||
authors = ["PureStake"] | ||
edition = "2018" | ||
description = "migrations management pallet" | ||
|
||
[dependencies] | ||
frame-support = { git = "https://github.com/purestake/substrate", branch = "crystalin-v0.9.9-block-response-length", default-features = false } | ||
frame-system = { git = "https://github.com/purestake/substrate", branch = "crystalin-v0.9.9-block-response-length", default-features = false } | ||
log = "0.4" | ||
sp-std = { git = "https://github.com/purestake/substrate", branch = "crystalin-v0.9.9-block-response-length", default-features = false } | ||
sp-runtime = { git = "https://github.com/purestake/substrate", branch = "crystalin-v0.9.9-block-response-length", default-features = false } | ||
parity-scale-codec = { version = "2.0.0", default-features = false } | ||
|
||
[dev-dependencies] | ||
environmental = "1.1.2" | ||
sp-io = { git = "https://github.com/purestake/substrate", branch = "crystalin-v0.9.9-block-response-length" } | ||
sp-core = { git = "https://github.com/purestake/substrate", branch = "crystalin-v0.9.9-block-response-length" } | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"frame-support/std", | ||
"frame-system/std", | ||
"sp-std/std", | ||
"sp-runtime/std", | ||
"sp-io/std", | ||
"sp-core/std", | ||
"environmental/std", | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
// Copyright 2019-2020 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/>. | ||
|
||
//! # Migration Pallet | ||
|
||
#![allow(non_camel_case_types)] | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
#[cfg(test)] | ||
mod mock; | ||
#[cfg(test)] | ||
mod tests; | ||
|
||
use frame_support::{pallet, weights::Weight}; | ||
|
||
pub use pallet::*; | ||
|
||
#[cfg(test)] | ||
#[macro_use] | ||
extern crate environmental; | ||
|
||
/// A Migration that must happen on-chain upon a runtime-upgrade | ||
pub trait Migration { | ||
/// A human-readable name for this migration. Also used as storage key. | ||
fn friendly_name(&self) -> &str; | ||
|
||
/// Perform the required migration and return the weight consumed. | ||
/// | ||
/// Currently there is no way to migrate across blocks, so this method must (1) perform its full | ||
/// migration and (2) not produce a block that has gone over-weight. Not meeting these strict | ||
/// 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; | ||
} | ||
|
||
#[pallet] | ||
pub mod pallet { | ||
use super::*; | ||
use frame_support::pallet_prelude::*; | ||
use frame_system::pallet_prelude::*; | ||
use sp_std::prelude::*; | ||
|
||
/// Pallet for migrations | ||
#[pallet::pallet] | ||
pub struct Pallet<T>(PhantomData<T>); | ||
|
||
/// Configuration trait of this pallet. | ||
#[pallet::config] | ||
pub trait Config: frame_system::Config { | ||
/// Overarching event type | ||
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>; | ||
/// The list of migrations that will be performed | ||
type MigrationsList: Get<Vec<Box<dyn Migration>>>; | ||
} | ||
|
||
#[pallet::event] | ||
#[pallet::generate_deposit(pub(crate) fn deposit_event)] | ||
pub enum Event<T: Config> { | ||
RuntimeUpgradeStarted(), | ||
RuntimeUpgradeCompleted(Weight), | ||
MigrationStarted(Vec<u8>), | ||
MigrationCompleted(Vec<u8>, Weight), | ||
} | ||
|
||
#[pallet::hooks] | ||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { | ||
/// on_runtime_upgrade is expected to be called exactly once after a runtime upgrade. | ||
/// We use this as a chance to flag that we are now in upgrade-mode and begin our | ||
/// migrations. | ||
fn on_runtime_upgrade() -> Weight { | ||
log::warn!("Performing on_runtime_upgrade"); | ||
|
||
let mut weight: Weight = 0u64.into(); | ||
// TODO: derive a suitable value here, which is probably something < max_block | ||
let available_weight: Weight = T::BlockWeights::get().max_block; | ||
Comment on lines
+87
to
+88
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. yeah, |
||
|
||
// start by flagging that we are not fully upgraded | ||
<FullyUpgraded<T>>::put(false); | ||
JoshOrndorff marked this conversation as resolved.
Show resolved
Hide resolved
|
||
weight += T::DbWeight::get().writes(1); | ||
Self::deposit_event(Event::RuntimeUpgradeStarted()); | ||
|
||
weight += perform_runtime_upgrades::<T>(available_weight.saturating_sub(weight)); | ||
|
||
if !<FullyUpgraded<T>>::get() { | ||
log::error!( | ||
"migrations weren't completed in on_runtime_upgrade(), but we're not | ||
configured for multi-block migrations; state is potentially inconsistent!" | ||
); | ||
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. Should we panic in this case? If you panic the block is immediately invalid. But that may be better than getting one that we suspect is corrupted finalized in the relay chain. 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've generally made the assumption that if we fail during a runtime upgrade and can't produce a block that we'll just end up doing the same thing when we try to produce another block. One reason to allow the block is that we are better off if we at least continue producing blocks (e.g. we could vote to modify storage, do another runtime upgrade, etc.) I've definitely gone back and forth on this, though. It's a situation where we can't do anything good... |
||
} | ||
|
||
weight | ||
} | ||
} | ||
|
||
#[pallet::storage] | ||
#[pallet::getter(fn is_fully_upgraded)] | ||
/// True if all required migrations have completed | ||
type FullyUpgraded<T: Config> = StorageValue<_, bool, ValueQuery>; | ||
|
||
#[pallet::storage] | ||
#[pallet::getter(fn migration_state)] | ||
/// MigrationState tracks the progress of a migration. | ||
/// Maps name (Vec<u8>) -> whether or not migration has been completed (bool) | ||
type MigrationState<T: Config> = StorageMap<_, Twox64Concat, Vec<u8>, bool, ValueQuery>; | ||
|
||
#[pallet::genesis_config] | ||
#[derive(Default)] | ||
pub struct GenesisConfig { | ||
pub completed_migrations: Vec<Vec<u8>>, | ||
} | ||
|
||
#[pallet::genesis_build] | ||
impl<T: Config> GenesisBuild<T> for GenesisConfig { | ||
fn build(&self) { | ||
for migration_name in &self.completed_migrations { | ||
<MigrationState<T>>::insert(migration_name, true); | ||
} | ||
} | ||
} | ||
|
||
fn perform_runtime_upgrades<T: Config>(available_weight: Weight) -> Weight { | ||
let mut weight: Weight = 0u64.into(); | ||
|
||
for migration in &T::MigrationsList::get() { | ||
let migration_name = migration.friendly_name(); | ||
let migration_name_as_bytes = migration_name.as_bytes(); | ||
log::trace!("evaluating migration {}", migration_name); | ||
|
||
let migration_done = <MigrationState<T>>::get(migration_name_as_bytes); | ||
|
||
if !migration_done { | ||
<Pallet<T>>::deposit_event(Event::MigrationStarted(migration_name_as_bytes.into())); | ||
|
||
// when we go overweight, leave a warning... there's nothing we can really do about | ||
// this scenario other than hope that the block is actually accepted. | ||
let available_for_step = if available_weight > weight { | ||
available_weight - weight | ||
} else { | ||
log::error!( | ||
"previous migration went overweight; | ||
ignoring and providing migration {} 0 weight.", | ||
migration_name, | ||
); | ||
|
||
0u64.into() | ||
}; | ||
|
||
log::trace!( | ||
"performing migration {}, available weight: {}", | ||
migration_name, | ||
available_for_step | ||
); | ||
|
||
let consumed_weight = migration.migrate(available_for_step); | ||
4meta5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<Pallet<T>>::deposit_event(Event::MigrationCompleted( | ||
migration_name_as_bytes.into(), | ||
consumed_weight, | ||
)); | ||
<MigrationState<T>>::insert(migration_name_as_bytes, true); | ||
4meta5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
weight += consumed_weight; | ||
if weight > available_weight { | ||
log::error!( | ||
"Migration {} consumed more weight than it was given! ({} > {})", | ||
migration_name, | ||
consumed_weight, | ||
available_for_step | ||
); | ||
} | ||
} | ||
} | ||
|
||
<FullyUpgraded<T>>::put(true); | ||
weight += T::DbWeight::get().writes(1); | ||
<Pallet<T>>::deposit_event(Event::RuntimeUpgradeCompleted(weight)); | ||
|
||
weight | ||
} | ||
} |
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.
I agree with an earlier suggestion to change this trait bound to
Migration
and allow it to accept multiple migrations by implementingMigration
for tuple instead of passing them in as a vec.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.
Agree. We can do it in a followup though.