-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #99 from InvArch/gabriel-maintenance-pallet
Added pallet-maintenance-mode
- Loading branch information
Showing
9 changed files
with
994 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
[package] | ||
name = "pallet-maintenance-mode" | ||
authors = [ "PureStake" ] | ||
description = "Puts a FRAME-based runtime into maintenance mode where restricted interactions are allowed." | ||
edition = "2021" | ||
version = "0.1.0" | ||
|
||
[dependencies] | ||
log = "0.4" | ||
|
||
# Substrate | ||
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.26" } | ||
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.26" } | ||
parity-scale-codec = { version = "3.0.0", default-features = false } | ||
scale-info = { version = "2.0", default-features = false, features = [ "derive" ] } | ||
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.26" } | ||
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.26" } | ||
|
||
|
||
[dev-dependencies] | ||
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } | ||
sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } | ||
|
||
[features] | ||
default = [ "std" ] | ||
std = [ | ||
"frame-support/std", | ||
"frame-system/std", | ||
"scale-info/std", | ||
"sp-runtime/std", | ||
"sp-std/std", | ||
] | ||
try-runtime = [ "frame-support/try-runtime" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
// Copyright 2019-2022 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/>. | ||
|
||
//! A pallet to put your runtime into a restricted maintenance or safe mode. This is useful when | ||
//! performing site maintenance, running data migrations, or protecting the chain during an attack. | ||
//! | ||
//! This introduces one storage read to fetch the base filter for each extrinsic. However, it should | ||
//! be that the state cache eliminates this cost almost entirely. I wonder if that can or should be | ||
//! reflected in the weight calculation. | ||
//! | ||
//! Possible future improvements | ||
//! 1. This could be more configurable by letting the runtime developer specify a type (probably an | ||
//! enum) that can be converted into a filter. Similar end result (but different implementation) as | ||
//! Acala has it | ||
//! github.com/AcalaNetwork/Acala/blob/pause-transaction/modules/transaction-pause/src/lib.rs#L71 | ||
//! | ||
//! 2. Automatically enable maintenance mode after a long timeout is detected between blocks. | ||
//! To implement this we would couple to the timestamp pallet and store the timestamp of the | ||
//! previous block. | ||
//! | ||
//! 3. Different origins for entering and leaving maintenance mode. | ||
//! | ||
//! 4. Maintenance mode timeout. To avoid getting stuck in maintenance mode. It could automatically | ||
//! switch back to normal mode after a pre-decided number of blocks. Maybe there could be an | ||
//! extrinsic to extend the maintenance time. | ||
#![allow(non_camel_case_types)] | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
#[cfg(test)] | ||
mod mock; | ||
#[cfg(test)] | ||
mod tests; | ||
|
||
mod types; | ||
pub use types::*; | ||
|
||
use frame_support::pallet; | ||
|
||
pub use pallet::*; | ||
|
||
#[pallet] | ||
pub mod pallet { | ||
use frame_support::pallet_prelude::*; | ||
use frame_support::traits::{ | ||
Contains, EnsureOrigin, OffchainWorker, OnFinalize, OnIdle, OnInitialize, OnRuntimeUpgrade, | ||
}; | ||
use frame_system::pallet_prelude::*; | ||
use sp_runtime::DispatchResult; | ||
/// Pallet for migrations | ||
#[pallet::pallet] | ||
#[pallet::without_storage_info] | ||
pub struct Pallet<T>(PhantomData<T>); | ||
|
||
/// Pause and resume execution of XCM | ||
pub trait PauseXcmExecution { | ||
fn suspend_xcm_execution() -> DispatchResult; | ||
fn resume_xcm_execution() -> DispatchResult; | ||
} | ||
|
||
impl PauseXcmExecution for () { | ||
fn suspend_xcm_execution() -> DispatchResult { | ||
Ok(()) | ||
} | ||
fn resume_xcm_execution() -> DispatchResult { | ||
Ok(()) | ||
} | ||
} | ||
|
||
/// Configuration trait of this pallet. | ||
#[pallet::config] | ||
pub trait Config: frame_system::Config { | ||
/// Overarching event type | ||
type Event: From<Event> + IsType<<Self as frame_system::Config>::Event>; | ||
/// The base call filter to be used in normal operating mode | ||
/// (When we aren't in the middle of a migration) | ||
type NormalCallFilter: Contains<Self::Call>; | ||
/// The base call filter to be used when we are in the middle of migrations | ||
/// This should be very restrictive. Probably not allowing anything except possibly | ||
/// something like sudo or other emergency processes | ||
type MaintenanceCallFilter: Contains<Self::Call>; | ||
/// The origin from which the call to enter or exit maintenance mode must come | ||
/// Take care when choosing your maintenance call filter to ensure that you'll still be | ||
/// able to return to normal mode. For example, if your MaintenanceOrigin is a council, make | ||
/// sure that your councilors can still cast votes. | ||
type MaintenanceOrigin: EnsureOrigin<Self::Origin>; | ||
/// The executive hooks that will be used in normal operating mode | ||
/// Important: Use AllPalletsReversedWithSystemFirst here if you dont want to modify the | ||
/// hooks behaviour | ||
type NormalExecutiveHooks: OnRuntimeUpgrade | ||
+ OnInitialize<Self::BlockNumber> | ||
+ OnIdle<Self::BlockNumber> | ||
+ OnFinalize<Self::BlockNumber> | ||
+ OffchainWorker<Self::BlockNumber>; | ||
/// The executive hooks that will be used in maintenance mode | ||
/// Important: Use AllPalletsReversedWithSystemFirst here if you dont want to modify the | ||
/// hooks behaviour | ||
type MaintenanceExecutiveHooks: OnRuntimeUpgrade | ||
+ OnInitialize<Self::BlockNumber> | ||
+ OnIdle<Self::BlockNumber> | ||
+ OnFinalize<Self::BlockNumber> | ||
+ OffchainWorker<Self::BlockNumber>; | ||
} | ||
|
||
#[pallet::event] | ||
#[pallet::generate_deposit(pub(crate) fn deposit_event)] | ||
pub enum Event { | ||
/// The chain was put into Maintenance Mode | ||
EnteredMaintenanceMode, | ||
/// The chain returned to its normal operating state | ||
NormalOperationResumed, | ||
/// The call to suspend on_idle XCM execution failed with inner error | ||
FailedToSuspendIdleXcmExecution { error: DispatchError }, | ||
/// The call to resume on_idle XCM execution failed with inner error | ||
FailedToResumeIdleXcmExecution { error: DispatchError }, | ||
} | ||
|
||
/// An error that can occur while executing this pallet's extrinsics. | ||
#[pallet::error] | ||
pub enum Error<T> { | ||
/// The chain cannot enter maintenance mode because it is already in maintenance mode | ||
AlreadyInMaintenanceMode, | ||
/// The chain cannot resume normal operation because it is not in maintenance mode | ||
NotInMaintenanceMode, | ||
} | ||
|
||
#[pallet::storage] | ||
#[pallet::getter(fn maintenance_mode)] | ||
/// Whether the site is in maintenance mode | ||
type MaintenanceMode<T: Config> = StorageValue<_, bool, ValueQuery>; | ||
|
||
#[pallet::call] | ||
impl<T: Config> Pallet<T> { | ||
/// Place the chain in maintenance mode | ||
/// | ||
/// Weight cost is: | ||
/// * One DB read to ensure we're not already in maintenance mode | ||
/// * Three DB writes - 1 for the mode, 1 for suspending xcm execution, 1 for the event | ||
#[pallet::weight(T::DbWeight::get().read + 3 * T::DbWeight::get().write)] | ||
pub fn enter_maintenance_mode(origin: OriginFor<T>) -> DispatchResultWithPostInfo { | ||
// Ensure Origin | ||
T::MaintenanceOrigin::ensure_origin(origin)?; | ||
|
||
// Ensure we're not aleady in maintenance mode. | ||
// This test is not strictly necessary, but seeing the error may help a confused chain | ||
// operator during an emergency | ||
ensure!( | ||
!MaintenanceMode::<T>::get(), | ||
Error::<T>::AlreadyInMaintenanceMode | ||
); | ||
|
||
// Write to storage | ||
MaintenanceMode::<T>::put(true); | ||
|
||
// Event | ||
<Pallet<T>>::deposit_event(Event::EnteredMaintenanceMode); | ||
|
||
Ok(().into()) | ||
} | ||
|
||
/// Return the chain to normal operating mode | ||
/// | ||
/// Weight cost is: | ||
/// * One DB read to ensure we're in maintenance mode | ||
/// * Three DB writes - 1 for the mode, 1 for resuming xcm execution, 1 for the event | ||
#[pallet::weight(T::DbWeight::get().read + 3 * T::DbWeight::get().write)] | ||
pub fn resume_normal_operation(origin: OriginFor<T>) -> DispatchResultWithPostInfo { | ||
// Ensure Origin | ||
T::MaintenanceOrigin::ensure_origin(origin)?; | ||
|
||
// Ensure we're actually in maintenance mode. | ||
// This test is not strictly necessary, but seeing the error may help a confused chain | ||
// operator during an emergency | ||
ensure!( | ||
MaintenanceMode::<T>::get(), | ||
Error::<T>::NotInMaintenanceMode | ||
); | ||
|
||
// Write to storage | ||
MaintenanceMode::<T>::put(false); | ||
|
||
// Event | ||
<Pallet<T>>::deposit_event(Event::NormalOperationResumed); | ||
|
||
Ok(().into()) | ||
} | ||
} | ||
|
||
#[derive(Default)] | ||
#[pallet::genesis_config] | ||
/// Genesis config for maintenance mode pallet | ||
pub struct GenesisConfig { | ||
/// Whether to launch in maintenance mode | ||
pub start_in_maintenance_mode: bool, | ||
} | ||
|
||
#[pallet::genesis_build] | ||
impl<T: Config> GenesisBuild<T> for GenesisConfig { | ||
fn build(&self) { | ||
if self.start_in_maintenance_mode { | ||
MaintenanceMode::<T>::put(true); | ||
} | ||
} | ||
} | ||
|
||
impl<T: Config> Contains<T::Call> for Pallet<T> { | ||
fn contains(call: &T::Call) -> bool { | ||
if MaintenanceMode::<T>::get() { | ||
T::MaintenanceCallFilter::contains(call) | ||
} else { | ||
T::NormalCallFilter::contains(call) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.