Skip to content

Commit

Permalink
Merge pull request #99 from InvArch/gabriel-maintenance-pallet
Browse files Browse the repository at this point in the history
Added pallet-maintenance-mode
  • Loading branch information
arrudagates authored Aug 20, 2022
2 parents 7c11707 + bcb8fb6 commit 6c5dbb8
Show file tree
Hide file tree
Showing 9 changed files with 994 additions and 2 deletions.
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions node/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,5 +338,6 @@ fn testnet_genesis(
},
treasury: Default::default(),
vesting: Default::default(),
maintenance_mode: Default::default(),
}
}
33 changes: 33 additions & 0 deletions pallets/maintenance-mode/Cargo.toml
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" ]
228 changes: 228 additions & 0 deletions pallets/maintenance-mode/src/lib.rs
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)
}
}
}
}
Loading

0 comments on commit 6c5dbb8

Please sign in to comment.