diff --git a/Cargo.lock b/Cargo.lock
index 06004704..421654d5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5524,6 +5524,21 @@ dependencies = [
"sp-std",
]
+[[package]]
+name = "pallet-maintenance-mode"
+version = "0.1.0"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "log",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
[[package]]
name = "pallet-membership"
version = "4.0.0-dev"
@@ -10921,6 +10936,7 @@ dependencies = [
"pallet-collator-selection",
"pallet-inv4",
"pallet-ipf",
+ "pallet-maintenance-mode",
"pallet-preimage",
"pallet-randomness-collective-flip",
"pallet-rmrk-core",
diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs
index 4d2aeb75..4d9e0887 100644
--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -338,5 +338,6 @@ fn testnet_genesis(
},
treasury: Default::default(),
vesting: Default::default(),
+ maintenance_mode: Default::default(),
}
}
diff --git a/pallets/maintenance-mode/Cargo.toml b/pallets/maintenance-mode/Cargo.toml
new file mode 100644
index 00000000..6577db58
--- /dev/null
+++ b/pallets/maintenance-mode/Cargo.toml
@@ -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" ]
diff --git a/pallets/maintenance-mode/src/lib.rs b/pallets/maintenance-mode/src/lib.rs
new file mode 100644
index 00000000..15202499
--- /dev/null
+++ b/pallets/maintenance-mode/src/lib.rs
@@ -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 .
+
+//! 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(PhantomData);
+
+ /// 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 + IsType<::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;
+ /// 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;
+ /// 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;
+ /// 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
+ + OnIdle
+ + OnFinalize
+ + OffchainWorker;
+ /// 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
+ + OnIdle
+ + OnFinalize
+ + OffchainWorker;
+ }
+
+ #[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 {
+ /// 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 = StorageValue<_, bool, ValueQuery>;
+
+ #[pallet::call]
+ impl Pallet {
+ /// 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) -> 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::::get(),
+ Error::::AlreadyInMaintenanceMode
+ );
+
+ // Write to storage
+ MaintenanceMode::::put(true);
+
+ // Event
+ >::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) -> 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::::get(),
+ Error::::NotInMaintenanceMode
+ );
+
+ // Write to storage
+ MaintenanceMode::::put(false);
+
+ // Event
+ >::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 GenesisBuild for GenesisConfig {
+ fn build(&self) {
+ if self.start_in_maintenance_mode {
+ MaintenanceMode::::put(true);
+ }
+ }
+ }
+
+ impl Contains for Pallet {
+ fn contains(call: &T::Call) -> bool {
+ if MaintenanceMode::::get() {
+ T::MaintenanceCallFilter::contains(call)
+ } else {
+ T::NormalCallFilter::contains(call)
+ }
+ }
+ }
+}
diff --git a/pallets/maintenance-mode/src/mock.rs b/pallets/maintenance-mode/src/mock.rs
new file mode 100644
index 00000000..29a8d366
--- /dev/null
+++ b/pallets/maintenance-mode/src/mock.rs
@@ -0,0 +1,332 @@
+// 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 .
+
+//! A minimal runtime including the maintenance-mode pallet
+use super::*;
+use crate as pallet_maintenance_mode;
+use cumulus_primitives_core::{relay_chain::BlockNumber as RelayBlockNumber, DmpMessageHandler};
+use frame_support::{
+ construct_runtime, parameter_types,
+ traits::{
+ Contains, Everything, GenesisBuild, OffchainWorker, OnFinalize, OnIdle, OnInitialize,
+ OnRuntimeUpgrade,
+ },
+ weights::Weight,
+};
+use frame_system::EnsureRoot;
+use sp_core::H256;
+use sp_runtime::{
+ testing::Header,
+ traits::{BlakeTwo256, IdentityLookup},
+ Perbill,
+};
+
+//TODO use TestAccount once it is in a common place (currently it lives with democracy precompiles)
+pub type AccountId = u64;
+pub type BlockNumber = u64;
+
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic;
+type Block = frame_system::mocking::MockBlock;
+
+// Configure a mock runtime to test the pallet.
+construct_runtime!(
+ pub enum Test where
+ Block = Block,
+ NodeBlock = Block,
+ UncheckedExtrinsic = UncheckedExtrinsic,
+ {
+ System: frame_system::{Pallet, Call, Config, Storage, Event},
+ MaintenanceMode: pallet_maintenance_mode::{Pallet, Call, Storage, Event, Config},
+ MockPalletMaintenanceHooks: mock_pallet_maintenance_hooks::{Pallet, Call, Event},
+ }
+);
+
+parameter_types! {
+ pub const BlockHashCount: u64 = 250;
+ pub const MaximumBlockWeight: Weight = 1024;
+ pub const MaximumBlockLength: u32 = 2 * 1024;
+ pub const AvailableBlockRatio: Perbill = Perbill::one();
+ pub const SS58Prefix: u8 = 42;
+}
+impl frame_system::Config for Test {
+ type BaseCallFilter = MaintenanceMode;
+ type DbWeight = ();
+ type Origin = Origin;
+ type Index = u64;
+ type BlockNumber = BlockNumber;
+ type Call = Call;
+ type Hash = H256;
+ type Hashing = BlakeTwo256;
+ type AccountId = AccountId;
+ type Lookup = IdentityLookup;
+ type Header = Header;
+ type Event = Event;
+ type BlockHashCount = BlockHashCount;
+ type Version = ();
+ type PalletInfo = PalletInfo;
+ type AccountData = ();
+ type OnNewAccount = ();
+ type OnKilledAccount = ();
+ type SystemWeightInfo = ();
+ type BlockWeights = ();
+ type BlockLength = ();
+ type SS58Prefix = SS58Prefix;
+ type OnSetCode = ();
+ type MaxConsumers = frame_support::traits::ConstU32<16>;
+}
+
+/// During maintenance mode we will not allow any calls.
+pub struct MaintenanceCallFilter;
+impl Contains for MaintenanceCallFilter {
+ fn contains(_: &Call) -> bool {
+ false
+ }
+}
+
+pub struct MaintenanceDmpHandler;
+#[cfg(feature = "xcm-support")]
+impl DmpMessageHandler for MaintenanceDmpHandler {
+ // This implementation makes messages be queued
+ // Since the limit is 0, messages are queued for next iteration
+ fn handle_dmp_messages(
+ _iter: impl Iterator- )>,
+ _limit: Weight,
+ ) -> Weight {
+ return 1;
+ }
+}
+
+pub struct NormalDmpHandler;
+#[cfg(feature = "xcm-support")]
+impl DmpMessageHandler for NormalDmpHandler {
+ // This implementation makes messages be queued
+ // Since the limit is 0, messages are queued for next iteration
+ fn handle_dmp_messages(
+ _iter: impl Iterator
- )>,
+ _limit: Weight,
+ ) -> Weight {
+ return 0;
+ }
+}
+
+impl mock_pallet_maintenance_hooks::Config for Test {
+ type Event = Event;
+}
+
+// Pallet to throw events, used to test maintenance mode hooks
+#[frame_support::pallet]
+pub mod mock_pallet_maintenance_hooks {
+ use frame_support::pallet_prelude::*;
+
+ #[pallet::config]
+ pub trait Config: frame_system::Config {
+ type Event: From + IsType<::Event>;
+ }
+
+ #[pallet::pallet]
+ #[pallet::generate_store(pub(super) trait Store)]
+ pub struct Pallet(_);
+
+ #[pallet::call]
+ impl Pallet {}
+
+ #[pallet::event]
+ #[pallet::generate_deposit(pub(super) fn deposit_event)]
+ pub enum Event {
+ MaintenanceOnIdle,
+ MaintenanceOnInitialize,
+ MaintenanceOffchainWorker,
+ MaintenanceOnFinalize,
+ MaintenanceOnRuntimeUpgrade,
+ NormalOnIdle,
+ NormalOnInitialize,
+ NormalOffchainWorker,
+ NormalOnFinalize,
+ NormalOnRuntimeUpgrade,
+ }
+}
+
+pub struct MaintenanceHooks;
+
+impl OnInitialize for MaintenanceHooks {
+ fn on_initialize(_n: BlockNumber) -> Weight {
+ MockPalletMaintenanceHooks::deposit_event(
+ mock_pallet_maintenance_hooks::Event::MaintenanceOnInitialize,
+ );
+ return 1;
+ }
+}
+
+impl OnIdle for MaintenanceHooks {
+ fn on_idle(_n: BlockNumber, _max_weight: Weight) -> Weight {
+ MockPalletMaintenanceHooks::deposit_event(
+ mock_pallet_maintenance_hooks::Event::MaintenanceOnIdle,
+ );
+ return 1;
+ }
+}
+
+impl OnRuntimeUpgrade for MaintenanceHooks {
+ fn on_runtime_upgrade() -> Weight {
+ MockPalletMaintenanceHooks::deposit_event(
+ mock_pallet_maintenance_hooks::Event::MaintenanceOnRuntimeUpgrade,
+ );
+ return 1;
+ }
+}
+
+impl OnFinalize for MaintenanceHooks {
+ fn on_finalize(_n: BlockNumber) {
+ MockPalletMaintenanceHooks::deposit_event(
+ mock_pallet_maintenance_hooks::Event::MaintenanceOnFinalize,
+ );
+ }
+}
+
+impl OffchainWorker for MaintenanceHooks {
+ fn offchain_worker(_n: BlockNumber) {
+ MockPalletMaintenanceHooks::deposit_event(
+ mock_pallet_maintenance_hooks::Event::MaintenanceOffchainWorker,
+ );
+ }
+}
+
+pub struct NormalHooks;
+
+impl OnInitialize for NormalHooks {
+ fn on_initialize(_n: BlockNumber) -> Weight {
+ MockPalletMaintenanceHooks::deposit_event(
+ mock_pallet_maintenance_hooks::Event::NormalOnInitialize,
+ );
+ return 0;
+ }
+}
+
+impl OnIdle for NormalHooks {
+ fn on_idle(_n: BlockNumber, _max_weight: Weight) -> Weight {
+ MockPalletMaintenanceHooks::deposit_event(
+ mock_pallet_maintenance_hooks::Event::NormalOnIdle,
+ );
+ return 0;
+ }
+}
+
+impl OnRuntimeUpgrade for NormalHooks {
+ fn on_runtime_upgrade() -> Weight {
+ MockPalletMaintenanceHooks::deposit_event(
+ mock_pallet_maintenance_hooks::Event::NormalOnRuntimeUpgrade,
+ );
+
+ return 0;
+ }
+}
+
+impl OnFinalize for NormalHooks {
+ fn on_finalize(_n: BlockNumber) {
+ MockPalletMaintenanceHooks::deposit_event(
+ mock_pallet_maintenance_hooks::Event::NormalOnFinalize,
+ );
+ }
+}
+
+impl OffchainWorker for NormalHooks {
+ fn offchain_worker(_n: BlockNumber) {
+ MockPalletMaintenanceHooks::deposit_event(
+ mock_pallet_maintenance_hooks::Event::NormalOffchainWorker,
+ );
+ }
+}
+
+impl Config for Test {
+ type Event = Event;
+ type NormalCallFilter = Everything;
+ type MaintenanceCallFilter = MaintenanceCallFilter;
+ type MaintenanceOrigin = EnsureRoot;
+ #[cfg(feature = "xcm-support")]
+ type XcmExecutionManager = ();
+ #[cfg(feature = "xcm-support")]
+ type NormalDmpHandler = NormalDmpHandler;
+ #[cfg(feature = "xcm-support")]
+ type MaintenanceDmpHandler = MaintenanceDmpHandler;
+ type NormalExecutiveHooks = NormalHooks;
+ type MaintenanceExecutiveHooks = MaintenanceHooks;
+}
+
+/// Externality builder for pallet maintenance mode's mock runtime
+pub(crate) struct ExtBuilder {
+ maintenance_mode: bool,
+}
+
+impl Default for ExtBuilder {
+ fn default() -> ExtBuilder {
+ ExtBuilder {
+ maintenance_mode: false,
+ }
+ }
+}
+
+impl ExtBuilder {
+ pub(crate) fn with_maintenance_mode(mut self, m: bool) -> Self {
+ self.maintenance_mode = m;
+ self
+ }
+
+ pub(crate) fn build(self) -> sp_io::TestExternalities {
+ let mut t = frame_system::GenesisConfig::default()
+ .build_storage::()
+ .expect("Frame system builds valid default genesis config");
+
+ GenesisBuild::::assimilate_storage(
+ &pallet_maintenance_mode::GenesisConfig {
+ start_in_maintenance_mode: self.maintenance_mode,
+ },
+ &mut t,
+ )
+ .expect("Pallet maintenance mode storage can be assimilated");
+
+ let mut ext = sp_io::TestExternalities::new(t);
+ ext.execute_with(|| System::set_block_number(1));
+ ext
+ }
+}
+
+pub(crate) fn events() -> Vec {
+ System::events()
+ .into_iter()
+ .map(|r| r.event)
+ .filter_map(|e| {
+ if let Event::MaintenanceMode(inner) = e {
+ Some(inner)
+ } else {
+ None
+ }
+ })
+ .collect::>()
+}
+
+pub(crate) fn mock_events() -> Vec {
+ System::events()
+ .into_iter()
+ .map(|r| r.event)
+ .filter_map(|e| {
+ if let Event::MockPalletMaintenanceHooks(inner) = e {
+ Some(inner)
+ } else {
+ None
+ }
+ })
+ .collect::>()
+}
diff --git a/pallets/maintenance-mode/src/tests.rs b/pallets/maintenance-mode/src/tests.rs
new file mode 100644
index 00000000..557ad503
--- /dev/null
+++ b/pallets/maintenance-mode/src/tests.rs
@@ -0,0 +1,203 @@
+// 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 .
+
+//! Unit testing
+use crate::mock::{
+ events, mock_events, Call as OuterCall, ExtBuilder, MaintenanceMode, Origin, Test,
+};
+use crate::{Call, Error, Event, ExecutiveHooks};
+use cumulus_primitives_core::DmpMessageHandler;
+use frame_support::{
+ assert_noop, assert_ok,
+ dispatch::Dispatchable,
+ traits::{OffchainWorker, OnFinalize, OnIdle, OnInitialize, OnRuntimeUpgrade},
+};
+
+#[test]
+fn can_remark_during_normal_operation() {
+ ExtBuilder::default().build().execute_with(|| {
+ let call: OuterCall = frame_system::Call::remark { remark: vec![] }.into();
+ assert_ok!(call.dispatch(Origin::signed(1)));
+ })
+}
+
+#[test]
+fn cannot_remark_during_maintenance_mode() {
+ ExtBuilder::default()
+ .with_maintenance_mode(true)
+ .build()
+ .execute_with(|| {
+ let call: OuterCall = frame_system::Call::remark { remark: vec![] }.into();
+ assert_noop!(
+ call.dispatch(Origin::signed(1)),
+ frame_system::Error::::CallFiltered
+ );
+ })
+}
+
+#[test]
+fn can_enter_maintenance_mode() {
+ ExtBuilder::default().build().execute_with(|| {
+ let call: OuterCall = Call::enter_maintenance_mode {}.into();
+ assert_ok!(call.dispatch(Origin::root()));
+
+ assert_eq!(events(), vec![Event::EnteredMaintenanceMode,]);
+ })
+}
+
+#[test]
+fn cannot_enter_maintenance_mode_from_wrong_origin() {
+ ExtBuilder::default()
+ .with_maintenance_mode(true)
+ .build()
+ .execute_with(|| {
+ let call: OuterCall = Call::enter_maintenance_mode {}.into();
+ assert_noop!(
+ call.dispatch(Origin::signed(1)),
+ frame_system::Error::::CallFiltered
+ );
+ })
+}
+
+#[test]
+fn cannot_enter_maintenance_mode_when_already_in_it() {
+ ExtBuilder::default()
+ .with_maintenance_mode(true)
+ .build()
+ .execute_with(|| {
+ let call: OuterCall = Call::enter_maintenance_mode {}.into();
+ assert_noop!(
+ call.dispatch(Origin::root()),
+ Error::::AlreadyInMaintenanceMode
+ );
+ })
+}
+
+#[test]
+fn can_resume_normal_operation() {
+ ExtBuilder::default()
+ .with_maintenance_mode(true)
+ .build()
+ .execute_with(|| {
+ let call: OuterCall = Call::resume_normal_operation {}.into();
+ assert_ok!(call.dispatch(Origin::root()));
+
+ assert_eq!(events(), vec![Event::NormalOperationResumed,]);
+ })
+}
+
+#[test]
+fn cannot_resume_normal_operation_from_wrong_origin() {
+ ExtBuilder::default()
+ .with_maintenance_mode(true)
+ .build()
+ .execute_with(|| {
+ let call: OuterCall = Call::resume_normal_operation {}.into();
+ assert_noop!(
+ call.dispatch(Origin::signed(1)),
+ frame_system::Error::::CallFiltered
+ );
+ })
+}
+
+#[test]
+fn cannot_resume_normal_operation_while_already_operating_normally() {
+ ExtBuilder::default().build().execute_with(|| {
+ let call: OuterCall = Call::resume_normal_operation {}.into();
+ assert_noop!(
+ call.dispatch(Origin::root()),
+ Error::::NotInMaintenanceMode
+ );
+ })
+}
+
+#[cfg(feature = "xcm-support")]
+#[test]
+fn normal_dmp_in_non_maintenance() {
+ ExtBuilder::default()
+ .with_maintenance_mode(false)
+ .build()
+ .execute_with(|| {
+ assert_eq!(
+ MaintenanceMode::handle_dmp_messages(vec![].into_iter(), 1),
+ 0
+ );
+ })
+}
+
+#[cfg(feature = "xcm-support")]
+#[test]
+fn maintenance_dmp_in_maintenance() {
+ ExtBuilder::default()
+ .with_maintenance_mode(true)
+ .build()
+ .execute_with(|| {
+ assert_eq!(
+ MaintenanceMode::handle_dmp_messages(vec![].into_iter(), 1),
+ 1
+ );
+ })
+}
+
+#[test]
+fn normal_hooks_in_non_maintenance() {
+ ExtBuilder::default()
+ .with_maintenance_mode(false)
+ .build()
+ .execute_with(|| {
+ assert_eq!(ExecutiveHooks::::on_idle(0, 0), 0);
+ assert_eq!(ExecutiveHooks::::on_initialize(0), 0);
+ assert_eq!(ExecutiveHooks::::on_runtime_upgrade(), 0);
+ ExecutiveHooks::::on_finalize(0);
+ ExecutiveHooks::::offchain_worker(0);
+
+ assert_eq!(
+ mock_events(),
+ [
+ crate::mock::mock_pallet_maintenance_hooks::Event::NormalOnIdle,
+ crate::mock::mock_pallet_maintenance_hooks::Event::NormalOnInitialize,
+ crate::mock::mock_pallet_maintenance_hooks::Event::NormalOnRuntimeUpgrade,
+ crate::mock::mock_pallet_maintenance_hooks::Event::NormalOnFinalize,
+ crate::mock::mock_pallet_maintenance_hooks::Event::NormalOffchainWorker
+ ]
+ );
+ })
+}
+
+#[test]
+fn maintenance_hooks_in_maintenance() {
+ ExtBuilder::default()
+ .with_maintenance_mode(true)
+ .build()
+ .execute_with(|| {
+ assert_eq!(ExecutiveHooks::::on_idle(0, 0), 1);
+ assert_eq!(ExecutiveHooks::::on_initialize(0), 1);
+ assert_eq!(ExecutiveHooks::::on_runtime_upgrade(), 1);
+
+ ExecutiveHooks::::on_finalize(0);
+ ExecutiveHooks::::offchain_worker(0);
+ assert_eq!(
+ mock_events(),
+ [
+ crate::mock::mock_pallet_maintenance_hooks::Event::MaintenanceOnIdle,
+ crate::mock::mock_pallet_maintenance_hooks::Event::MaintenanceOnInitialize,
+ crate::mock::mock_pallet_maintenance_hooks::Event::MaintenanceOnRuntimeUpgrade,
+ crate::mock::mock_pallet_maintenance_hooks::Event::MaintenanceOnFinalize,
+ crate::mock::mock_pallet_maintenance_hooks::Event::MaintenanceOffchainWorker
+ ]
+ );
+ })
+}
diff --git a/pallets/maintenance-mode/src/types.rs b/pallets/maintenance-mode/src/types.rs
new file mode 100644
index 00000000..ca87cadb
--- /dev/null
+++ b/pallets/maintenance-mode/src/types.rs
@@ -0,0 +1,111 @@
+// 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 .
+
+// This file contains the ExecutiveHooks type which is intended to be used
+// with frame_executive::Executive. This instructs which pallets execute
+// hooks in each of the normal and maintenance modes.
+use super::*;
+use frame_support::{
+ traits::{OffchainWorker, OnFinalize, OnIdle, OnInitialize, OnRuntimeUpgrade},
+ weights::Weight,
+};
+use sp_std::marker::PhantomData;
+
+pub struct ExecutiveHooks(PhantomData);
+type BlockNumberOf = ::BlockNumber;
+
+impl OnIdle> for ExecutiveHooks
+where
+ T: Config,
+{
+ fn on_idle(n: BlockNumberOf, remaining_weight: Weight) -> Weight {
+ if Pallet::::maintenance_mode() {
+ T::MaintenanceExecutiveHooks::on_idle(n, remaining_weight)
+ } else {
+ T::NormalExecutiveHooks::on_idle(n, remaining_weight)
+ }
+ }
+}
+
+impl OnInitialize> for ExecutiveHooks
+where
+ T: Config,
+{
+ fn on_initialize(n: BlockNumberOf) -> Weight {
+ if Pallet::::maintenance_mode() {
+ T::MaintenanceExecutiveHooks::on_initialize(n)
+ } else {
+ T::NormalExecutiveHooks::on_initialize(n)
+ }
+ }
+}
+
+impl OnFinalize> for ExecutiveHooks
+where
+ T: Config,
+{
+ fn on_finalize(n: BlockNumberOf) {
+ if Pallet::::maintenance_mode() {
+ T::MaintenanceExecutiveHooks::on_finalize(n)
+ } else {
+ T::NormalExecutiveHooks::on_finalize(n)
+ }
+ }
+}
+
+impl OffchainWorker> for ExecutiveHooks
+where
+ T: Config,
+{
+ fn offchain_worker(n: BlockNumberOf) {
+ if Pallet::::maintenance_mode() {
+ T::MaintenanceExecutiveHooks::offchain_worker(n)
+ } else {
+ T::NormalExecutiveHooks::offchain_worker(n)
+ }
+ }
+}
+
+impl OnRuntimeUpgrade for ExecutiveHooks
+where
+ T: Config,
+{
+ fn on_runtime_upgrade() -> Weight {
+ if Pallet::::maintenance_mode() {
+ T::MaintenanceExecutiveHooks::on_runtime_upgrade()
+ } else {
+ T::NormalExecutiveHooks::on_runtime_upgrade()
+ }
+ }
+
+ #[cfg(feature = "try-runtime")]
+ fn pre_upgrade() -> Result<(), &'static str> {
+ if Pallet::::maintenance_mode() {
+ T::MaintenanceExecutiveHooks::pre_upgrade()
+ } else {
+ T::NormalExecutiveHooks::pre_upgrade()
+ }
+ }
+
+ #[cfg(feature = "try-runtime")]
+ fn post_upgrade() -> Result<(), &'static str> {
+ if Pallet::::maintenance_mode() {
+ T::MaintenanceExecutiveHooks::post_upgrade()
+ } else {
+ T::NormalExecutiveHooks::post_upgrade()
+ }
+ }
+}
diff --git a/runtime/tinkernet/Cargo.toml b/runtime/tinkernet/Cargo.toml
index 59a7c7dc..80e1dfa3 100644
--- a/runtime/tinkernet/Cargo.toml
+++ b/runtime/tinkernet/Cargo.toml
@@ -30,6 +30,8 @@ pallet-inv4 = { git = "https://github.com/InvArch/InvArch-Frames", branch = "mai
#pallet-inv4 = { path = "../../../InvArch-Pallet-Library/INV4/pallet-inv4", default-features = false }
#pallet-ipf = { path = "../../../InvArch-Pallet-Library/INV4/pallet-ipf", default-features = false }
+pallet-maintenance-mode = { path = "../../pallets/maintenance-mode", default-features = false }
+
## Parity's Unique + RMRK Pallets
pallet-rmrk-core = { git = "https://github.com/rmrk-team/rmrk-substrate", default-features = false, rev = "405af2499ca2a9b3f4717eec238fc80dd6abc3f7" }
pallet-rmrk-equip = { git = "https://github.com/rmrk-team/rmrk-substrate", default-features = false, rev = "405af2499ca2a9b3f4717eec238fc80dd6abc3f7" }
@@ -170,6 +172,7 @@ std = [
"orml-vesting/std",
"pallet-ipf/std",
"pallet-inv4/std",
+ "pallet-maintenance-mode/std"
]
runtime-benchmarks = [
diff --git a/runtime/tinkernet/src/lib.rs b/runtime/tinkernet/src/lib.rs
index 49f254c9..d068016c 100644
--- a/runtime/tinkernet/src/lib.rs
+++ b/runtime/tinkernet/src/lib.rs
@@ -187,7 +187,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("tinkernet_node"),
impl_name: create_runtime_str!("tinkernet_node"),
authoring_version: 1,
- spec_version: 3,
+ spec_version: 4,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
@@ -281,6 +281,70 @@ impl Contains for BaseFilter {
}
}
+pub struct MaintenanceFilter;
+impl Contains for MaintenanceFilter {
+ fn contains(c: &Call) -> bool {
+ match c {
+ Call::Balances(_) => false,
+ Call::Vesting(_) => false,
+ _ => true,
+ }
+ }
+}
+
+/// The hooks we want to run in Maintenance Mode
+pub struct MaintenanceHooks;
+
+impl frame_support::traits::OnInitialize for MaintenanceHooks {
+ fn on_initialize(n: BlockNumber) -> Weight {
+ AllPalletsReversedWithSystemFirst::on_initialize(n)
+ }
+}
+
+impl frame_support::traits::OnRuntimeUpgrade for MaintenanceHooks {
+ fn on_runtime_upgrade() -> Weight {
+ AllPalletsReversedWithSystemFirst::on_runtime_upgrade()
+ }
+ #[cfg(feature = "try-runtime")]
+ fn pre_upgrade() -> Result<(), &'static str> {
+ AllPalletsReversedWithSystemFirst::pre_upgrade()
+ }
+
+ #[cfg(feature = "try-runtime")]
+ fn post_upgrade() -> Result<(), &'static str> {
+ AllPalletsReversedWithSystemFirst::post_upgrade()
+ }
+}
+
+impl frame_support::traits::OnFinalize for MaintenanceHooks {
+ fn on_finalize(n: BlockNumber) {
+ AllPalletsReversedWithSystemFirst::on_finalize(n)
+ }
+}
+
+impl frame_support::traits::OnIdle for MaintenanceHooks {
+ fn on_idle(_n: BlockNumber, _max_weight: Weight) -> Weight {
+ 0
+ }
+}
+
+impl frame_support::traits::OffchainWorker for MaintenanceHooks {
+ fn offchain_worker(n: BlockNumber) {
+ AllPalletsReversedWithSystemFirst::offchain_worker(n)
+ }
+}
+
+impl pallet_maintenance_mode::Config for Runtime {
+ type Event = Event;
+ type NormalCallFilter = BaseFilter;
+ type MaintenanceCallFilter = MaintenanceFilter;
+ type MaintenanceOrigin = EnsureRoot;
+ // We use AllPalletsReversedWithSystemFirst because we dont want to change the hooks in normal
+ // operation
+ type NormalExecutiveHooks = AllPalletsReversedWithSystemFirst;
+ type MaintenanceExecutiveHooks = MaintenanceHooks;
+}
+
// Configure FRAME pallets to include in runtime.
impl frame_system::Config for Runtime {
@@ -321,7 +385,7 @@ impl frame_system::Config for Runtime {
/// The weight of database operations that the runtime can invoke.
type DbWeight = RocksDbWeight;
/// The basic call filter to use in dispatchable.
- type BaseCallFilter = Everything;
+ type BaseCallFilter = MaintenanceMode;
/// Weight information for the extrinsics of this pallet.
type SystemWeightInfo = frame_system::weights::SubstrateWeight;
/// Block & extrinsics weights: base values and limits.
@@ -1102,6 +1166,7 @@ construct_runtime!(
ParachainInfo: parachain_info::{Pallet, Storage, Config} = 4,
Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 5,
Preimage: pallet_preimage::{Pallet, Call, Storage, Event} = 6,
+ MaintenanceMode: pallet_maintenance_mode::{Pallet, Call, Config, Storage, Event} = 7,
// Monetary stuff
Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10,