Skip to content

Commit

Permalink
pallet-migrations Initial Implementation (#527)
Browse files Browse the repository at this point in the history
Co-authored-by: Alexander Popiak <alexander.popiak@gmail.com>
Co-authored-by: Amar Singh <asinghchrony@protonmail.com>
Co-authored-by: Joshy Orndorff <admin@joshyorndorff.com>
  • Loading branch information
4 people authored Sep 20, 2021
1 parent d0f120c commit c34608b
Show file tree
Hide file tree
Showing 18 changed files with 1,535 additions and 562 deletions.
1,215 changes: 654 additions & 561 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
'node',
'node/cli',
'node/service',
'pallets/migrations',
'bin/utils/moonkey',
'pallets/maintenance-mode',
'precompiles/utils/macro',
Expand Down
1 change: 1 addition & 0 deletions node/service/src/chain_spec/moonbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ pub fn testnet_genesis(
.collect(),
},
treasury: Default::default(),
migrations: Default::default(),
maintenance_mode: MaintenanceModeConfig {
start_in_maintenance_mode: false,
},
Expand Down
1 change: 1 addition & 0 deletions node/service/src/chain_spec/moonbeam.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ pub fn testnet_genesis(
.collect(),
},
treasury: Default::default(),
migrations: Default::default(),
maintenance_mode: MaintenanceModeConfig {
start_in_maintenance_mode: false,
},
Expand Down
1 change: 1 addition & 0 deletions node/service/src/chain_spec/moonriver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ pub fn testnet_genesis(
.collect(),
},
treasury: Default::default(),
migrations: Default::default(),
maintenance_mode: MaintenanceModeConfig {
start_in_maintenance_mode: false,
},
Expand Down
28 changes: 28 additions & 0 deletions pallets/migrations/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[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",
]
192 changes: 192 additions & 0 deletions pallets/migrations/src/lib.rs
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;

// start by flagging that we are not fully upgraded
<FullyUpgraded<T>>::put(false);
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!"
);
}

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);
<Pallet<T>>::deposit_event(Event::MigrationCompleted(
migration_name_as_bytes.into(),
consumed_weight,
));
<MigrationState<T>>::insert(migration_name_as_bytes, true);

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
}
}
Loading

0 comments on commit c34608b

Please sign in to comment.