diff --git a/Cargo.lock b/Cargo.lock
index 197d05e26339b71c8c9e9c793b2deb0c57072fd3..3838ed0e94a1ad6319e95938100b831fe74747ee 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -322,6 +322,20 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "aquamarine"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1da02abba9f9063d786eab1509833ebb2fac0f966862ca59439c76b9c566760"
+dependencies = [
+ "include_dir",
+ "itertools 0.10.5",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "aquamarine"
 version = "0.5.0"
@@ -4055,7 +4069,7 @@ dependencies = [
  "cumulus-primitives-core",
  "cumulus-primitives-proof-size-hostfunction",
  "cumulus-test-runtime",
- "docify",
+ "docify 0.2.7",
  "frame-support",
  "frame-system",
  "log",
@@ -4736,13 +4750,39 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
 
+[[package]]
+name = "docify"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1b04e6ef3d21119d3eb7b032bca17f99fe041e9c072f30f32cc0e1a2b1f3c4"
+dependencies = [
+ "docify_macros 0.1.16",
+]
+
 [[package]]
 name = "docify"
 version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7cc4fd38aaa9fb98ac70794c82a00360d1e165a87fbf96a8a91f9dfc602aaee2"
 dependencies = [
- "docify_macros",
+ "docify_macros 0.2.7",
+]
+
+[[package]]
+name = "docify_macros"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b5610df7f2acf89a1bb5d1a66ae56b1c7fcdcfe3948856fb3ace3f644d70eb7"
+dependencies = [
+ "common-path",
+ "derive-syn-parse",
+ "lazy_static",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn 2.0.50",
+ "termcolor",
+ "walkdir",
 ]
 
 [[package]]
@@ -5451,7 +5491,7 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
 name = "frame"
 version = "0.0.1-dev"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "frame-executive",
  "frame-support",
  "frame-system",
@@ -5619,6 +5659,7 @@ dependencies = [
 name = "frame-executive"
 version = "28.0.0"
 dependencies = [
+ "aquamarine 0.3.3",
  "array-bytes 6.1.0",
  "frame-support",
  "frame-system",
@@ -5675,11 +5716,11 @@ dependencies = [
 name = "frame-support"
 version = "28.0.0"
 dependencies = [
- "aquamarine",
+ "aquamarine 0.5.0",
  "array-bytes 6.1.0",
  "assert_matches",
  "bitflags 1.3.2",
- "docify",
+ "docify 0.2.7",
  "environmental",
  "frame-metadata",
  "frame-support-procedural",
@@ -5822,7 +5863,7 @@ version = "28.0.0"
 dependencies = [
  "cfg-if",
  "criterion 0.4.0",
- "docify",
+ "docify 0.2.7",
  "frame-support",
  "log",
  "parity-scale-codec",
@@ -7081,6 +7122,7 @@ dependencies = [
  "pallet-lottery",
  "pallet-membership",
  "pallet-message-queue",
+ "pallet-migrations",
  "pallet-mixnet",
  "pallet-mmr",
  "pallet-multisig",
@@ -9301,8 +9343,8 @@ dependencies = [
 name = "pallet-bags-list"
 version = "27.0.0"
 dependencies = [
- "aquamarine",
- "docify",
+ "aquamarine 0.5.0",
+ "docify 0.2.7",
  "frame-benchmarking",
  "frame-election-provider-support",
  "frame-support",
@@ -9350,7 +9392,7 @@ dependencies = [
 name = "pallet-balances"
 version = "28.0.0"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
@@ -9969,7 +10011,7 @@ dependencies = [
 name = "pallet-example-single-block-migrations"
 version = "0.0.1"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "frame-executive",
  "frame-support",
  "frame-system",
@@ -10035,7 +10077,7 @@ dependencies = [
 name = "pallet-fast-unstake"
 version = "27.0.0"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "frame-benchmarking",
  "frame-election-provider-support",
  "frame-support",
@@ -10232,6 +10274,30 @@ dependencies = [
  "sp-weights",
 ]
 
+[[package]]
+name = "pallet-migrations"
+version = "1.0.0"
+dependencies = [
+ "docify 0.1.16",
+ "frame-benchmarking",
+ "frame-executive",
+ "frame-support",
+ "frame-system",
+ "impl-trait-for-tuples",
+ "log",
+ "parity-scale-codec",
+ "pretty_assertions",
+ "scale-info",
+ "sp-api",
+ "sp-block-builder",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std 14.0.0",
+ "sp-tracing 16.0.0",
+ "sp-version",
+]
+
 [[package]]
 name = "pallet-mixnet"
 version = "0.4.0"
@@ -10507,7 +10573,7 @@ dependencies = [
 name = "pallet-paged-list"
 version = "0.6.0"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
@@ -10550,7 +10616,7 @@ dependencies = [
 name = "pallet-parameters"
 version = "0.0.1"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
@@ -10711,7 +10777,7 @@ dependencies = [
 name = "pallet-safe-mode"
 version = "9.0.0"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
@@ -10768,7 +10834,7 @@ dependencies = [
 name = "pallet-scheduler"
 version = "29.0.0"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
@@ -10981,7 +11047,7 @@ dependencies = [
 name = "pallet-sudo"
 version = "28.0.0"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
@@ -11012,7 +11078,7 @@ dependencies = [
 name = "pallet-timestamp"
 version = "27.0.0"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
@@ -11116,7 +11182,7 @@ dependencies = [
 name = "pallet-treasury"
 version = "27.0.0"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
@@ -11136,7 +11202,7 @@ dependencies = [
 name = "pallet-tx-pause"
 version = "9.0.0"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
@@ -13404,7 +13470,7 @@ version = "0.0.1"
 dependencies = [
  "cumulus-pallet-aura-ext",
  "cumulus-pallet-parachain-system",
- "docify",
+ "docify 0.2.7",
  "frame",
  "frame-executive",
  "frame-support",
@@ -15665,7 +15731,7 @@ name = "sc-chain-spec"
 version = "27.0.0"
 dependencies = [
  "array-bytes 6.1.0",
- "docify",
+ "docify 0.2.7",
  "log",
  "memmap2 0.9.3",
  "parity-scale-codec",
@@ -18931,7 +18997,7 @@ dependencies = [
 name = "sp-runtime"
 version = "31.0.1"
 dependencies = [
- "docify",
+ "docify 0.2.7",
  "either",
  "hash256-std-hasher",
  "impl-trait-for-tuples",
diff --git a/Cargo.toml b/Cargo.toml
index 654367cc1e214fdcf994fe511af8475b995f1a08..48aa25f5c5a98ba951ffcb7525c1b0abb13b5611 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -352,6 +352,7 @@ members = [
 	"substrate/frame/membership",
 	"substrate/frame/merkle-mountain-range",
 	"substrate/frame/message-queue",
+	"substrate/frame/migrations",
 	"substrate/frame/mixnet",
 	"substrate/frame/multisig",
 	"substrate/frame/nft-fractionalization",
diff --git a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs
index 110f611c6766020039bd1f73def900914da8cae2..749fb0367f332d743b01ad9d56238106ced36e72 100644
--- a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs
+++ b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs
@@ -3,7 +3,7 @@
 use super::*;
 
 use frame_support::{
-	parameter_types,
+	derive_impl, parameter_types,
 	traits::{ConstU128, ConstU32, Everything},
 	weights::IdentityFee,
 };
@@ -47,10 +47,9 @@ parameter_types! {
 
 type Balance = u128;
 
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
 impl frame_system::Config for Test {
 	type BaseCallFilter = Everything;
-	type BlockWeights = ();
-	type BlockLength = ();
 	type RuntimeOrigin = RuntimeOrigin;
 	type RuntimeCall = RuntimeCall;
 	type RuntimeTask = RuntimeTask;
@@ -60,16 +59,8 @@ impl frame_system::Config for Test {
 	type Lookup = IdentityLookup<Self::AccountId>;
 	type RuntimeEvent = RuntimeEvent;
 	type BlockHashCount = BlockHashCount;
-	type DbWeight = ();
-	type Version = ();
 	type PalletInfo = PalletInfo;
 	type AccountData = pallet_balances::AccountData<u128>;
-	type OnNewAccount = ();
-	type OnKilledAccount = ();
-	type SystemWeightInfo = ();
-	type SS58Prefix = ();
-	type OnSetCode = ();
-	type MaxConsumers = frame_support::traits::ConstU32<16>;
 	type Nonce = u64;
 	type Block = Block;
 }
diff --git a/bridges/snowbridge/pallets/outbound-queue/src/mock.rs b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs
index dd8fee4e2ed08ec0f3090b765fa882b063a98300..6e78fb4467210e3cb5e1eb581b377cbbfeac74ad 100644
--- a/bridges/snowbridge/pallets/outbound-queue/src/mock.rs
+++ b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs
@@ -3,7 +3,7 @@
 use super::*;
 
 use frame_support::{
-	parameter_types,
+	derive_impl, parameter_types,
 	traits::{Everything, Hooks},
 	weights::IdentityFee,
 };
@@ -37,10 +37,9 @@ parameter_types! {
 	pub const BlockHashCount: u64 = 250;
 }
 
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
 impl frame_system::Config for Test {
 	type BaseCallFilter = Everything;
-	type BlockWeights = ();
-	type BlockLength = ();
 	type RuntimeOrigin = RuntimeOrigin;
 	type RuntimeCall = RuntimeCall;
 	type RuntimeTask = RuntimeTask;
@@ -50,16 +49,7 @@ impl frame_system::Config for Test {
 	type Lookup = IdentityLookup<Self::AccountId>;
 	type RuntimeEvent = RuntimeEvent;
 	type BlockHashCount = BlockHashCount;
-	type DbWeight = ();
-	type Version = ();
 	type PalletInfo = PalletInfo;
-	type AccountData = ();
-	type OnNewAccount = ();
-	type OnKilledAccount = ();
-	type SystemWeightInfo = ();
-	type SS58Prefix = ();
-	type OnSetCode = ();
-	type MaxConsumers = frame_support::traits::ConstU32<16>;
 	type Nonce = u64;
 	type Block = Block;
 }
diff --git a/bridges/snowbridge/pallets/system/src/mock.rs b/bridges/snowbridge/pallets/system/src/mock.rs
index edc3f141b0735d7439b120c51da836fb8a77bd04..de2970dd550ba75fe42de08dc4d297cd5cccdf1f 100644
--- a/bridges/snowbridge/pallets/system/src/mock.rs
+++ b/bridges/snowbridge/pallets/system/src/mock.rs
@@ -2,8 +2,8 @@
 // SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
 use crate as snowbridge_system;
 use frame_support::{
-	parameter_types,
-	traits::{tokens::fungible::Mutate, ConstU128, ConstU16, ConstU64, ConstU8},
+	derive_impl, parameter_types,
+	traits::{tokens::fungible::Mutate, ConstU128, ConstU64, ConstU8},
 	weights::IdentityFee,
 	PalletId,
 };
@@ -95,11 +95,9 @@ frame_support::construct_runtime!(
 	}
 );
 
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
 impl frame_system::Config for Test {
 	type BaseCallFilter = frame_support::traits::Everything;
-	type BlockWeights = ();
-	type BlockLength = ();
-	type DbWeight = ();
 	type RuntimeOrigin = RuntimeOrigin;
 	type RuntimeCall = RuntimeCall;
 	type RuntimeTask = RuntimeTask;
@@ -109,15 +107,8 @@ impl frame_system::Config for Test {
 	type Lookup = IdentityLookup<Self::AccountId>;
 	type RuntimeEvent = RuntimeEvent;
 	type BlockHashCount = ConstU64<250>;
-	type Version = ();
 	type PalletInfo = PalletInfo;
 	type AccountData = pallet_balances::AccountData<u128>;
-	type OnNewAccount = ();
-	type OnKilledAccount = ();
-	type SystemWeightInfo = ();
-	type SS58Prefix = ConstU16<42>;
-	type OnSetCode = ();
-	type MaxConsumers = frame_support::traits::ConstU32<16>;
 	type Nonce = u64;
 	type Block = Block;
 }
diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/mock.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/mock.rs
new file mode 100644
index 0000000000000000000000000000000000000000..77b5c1aa631db89a986837f258ee7dea45a580d0
--- /dev/null
+++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/mock.rs
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
+use crate as ethereum_beacon_client;
+use frame_support::parameter_types;
+use pallet_timestamp;
+use primitives::{Fork, ForkVersions};
+use sp_core::H256;
+use sp_runtime::traits::{BlakeTwo256, IdentityLookup};
+
+#[cfg(not(feature = "beacon-spec-mainnet"))]
+pub mod minimal {
+	use super::*;
+
+	use crate::config;
+	use frame_support::derive_impl;
+	use hex_literal::hex;
+	use primitives::CompactExecutionHeader;
+	use snowbridge_core::inbound::{Log, Proof};
+	use sp_runtime::BuildStorage;
+	use std::{fs::File, path::PathBuf};
+
+	type Block = frame_system::mocking::MockBlock<Test>;
+
+	frame_support::construct_runtime!(
+		pub enum Test {
+			System: frame_system::{Pallet, Call, Storage, Event<T>},
+			Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent},
+			EthereumBeaconClient: ethereum_beacon_client::{Pallet, Call, Storage, Event<T>},
+		}
+	);
+
+	parameter_types! {
+		pub const BlockHashCount: u64 = 250;
+		pub const SS58Prefix: u8 = 42;
+	}
+
+	#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+	impl frame_system::Config for Test {
+		type BaseCallFilter = frame_support::traits::Everything;
+		type RuntimeOrigin = RuntimeOrigin;
+		type RuntimeCall = RuntimeCall;
+		type RuntimeTask = RuntimeTask;
+		type Hash = H256;
+		type Hashing = BlakeTwo256;
+		type AccountId = u64;
+		type Lookup = IdentityLookup<Self::AccountId>;
+		type RuntimeEvent = RuntimeEvent;
+		type BlockHashCount = BlockHashCount;
+		type PalletInfo = PalletInfo;
+		type SS58Prefix = SS58Prefix;
+		type Nonce = u64;
+		type Block = Block;
+	}
+
+	impl pallet_timestamp::Config for Test {
+		type Moment = u64;
+		type OnTimestampSet = ();
+		type MinimumPeriod = ();
+		type WeightInfo = ();
+	}
+
+	parameter_types! {
+		pub const ExecutionHeadersPruneThreshold: u32 = 10;
+		pub const ChainForkVersions: ForkVersions = ForkVersions{
+			genesis: Fork {
+				version: [0, 0, 0, 1], // 0x00000001
+				epoch: 0,
+			},
+			altair: Fork {
+				version: [1, 0, 0, 1], // 0x01000001
+				epoch: 0,
+			},
+			bellatrix: Fork {
+				version: [2, 0, 0, 1], // 0x02000001
+				epoch: 0,
+			},
+			capella: Fork {
+				version: [3, 0, 0, 1], // 0x03000001
+				epoch: 0,
+			},
+		};
+	}
+
+	impl ethereum_beacon_client::Config for Test {
+		type RuntimeEvent = RuntimeEvent;
+		type ForkVersions = ChainForkVersions;
+		type MaxExecutionHeadersToKeep = ExecutionHeadersPruneThreshold;
+		type WeightInfo = ();
+	}
+
+	// Build genesis storage according to the mock runtime.
+	pub fn new_tester() -> sp_io::TestExternalities {
+		let t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
+		let mut ext = sp_io::TestExternalities::new(t);
+		let _ = ext.execute_with(|| Timestamp::set(RuntimeOrigin::signed(1), 30_000));
+		ext
+	}
+
+	fn load_fixture<T>(basename: &str) -> Result<T, serde_json::Error>
+	where
+		T: for<'de> serde::Deserialize<'de>,
+	{
+		let filepath: PathBuf =
+			[env!("CARGO_MANIFEST_DIR"), "tests", "fixtures", basename].iter().collect();
+		serde_json::from_reader(File::open(filepath).unwrap())
+	}
+
+	pub fn load_execution_header_update_fixture() -> primitives::ExecutionHeaderUpdate {
+		load_fixture("execution-header-update.minimal.json").unwrap()
+	}
+
+	pub fn load_checkpoint_update_fixture(
+	) -> primitives::CheckpointUpdate<{ config::SYNC_COMMITTEE_SIZE }> {
+		load_fixture("initial-checkpoint.minimal.json").unwrap()
+	}
+
+	pub fn load_sync_committee_update_fixture(
+	) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> {
+		load_fixture("sync-committee-update.minimal.json").unwrap()
+	}
+
+	pub fn load_finalized_header_update_fixture(
+	) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> {
+		load_fixture("finalized-header-update.minimal.json").unwrap()
+	}
+
+	pub fn load_next_sync_committee_update_fixture(
+	) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> {
+		load_fixture("next-sync-committee-update.minimal.json").unwrap()
+	}
+
+	pub fn load_next_finalized_header_update_fixture(
+	) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> {
+		load_fixture("next-finalized-header-update.minimal.json").unwrap()
+	}
+
+	pub fn get_message_verification_payload() -> (Log, Proof) {
+		(
+			Log {
+				address: hex!("ee9170abfbf9421ad6dd07f6bdec9d89f2b581e0").into(),
+				topics: vec![
+					hex!("1b11dcf133cc240f682dab2d3a8e4cd35c5da8c9cf99adac4336f8512584c5ad").into(),
+					hex!("00000000000000000000000000000000000000000000000000000000000003e8").into(),
+					hex!("0000000000000000000000000000000000000000000000000000000000000001").into(),
+				],
+				data: hex!("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004b000f000000000000000100d184c103f7acc340847eee82a0b909e3358bc28d440edffa1352b13227e8ee646f3ea37456dec701345772617070656420457468657210574554481235003511000000000000000000000000000000000000000000").into(),
+			},
+			Proof {
+				block_hash: hex!("05aaa60b0f27cce9e71909508527264b77ee14da7b5bf915fcc4e32715333213").into(),
+				tx_index: 0,
+				data: (vec![
+					hex!("cf0d1c1ba57d1e0edfb59786c7e30c2b7e12bd54612b00cd21c4eaeecedf44fb").to_vec(),
+					hex!("d21fc4f68ab05bc4dcb23c67008e92c4d466437cdd6ed7aad0c008944c185510").to_vec(),
+					hex!("b9890f91ca0d77aa2a4adfaf9b9e40c94cac9e638b6d9797923865872944b646").to_vec(),
+				], vec![
+					hex!("f90131a0b601337b3aa10a671caa724eba641e759399979856141d3aea6b6b4ac59b889ba00c7d5dd48be9060221a02fb8fa213860b4c50d47046c8fa65ffaba5737d569e0a094601b62a1086cd9c9cb71a7ebff9e718f3217fd6e837efe4246733c0a196f63a06a4b0dd0aefc37b3c77828c8f07d1b7a2455ceb5dbfd3c77d7d6aeeddc2f7e8ca0d6e8e23142cdd8ec219e1f5d8b56aa18e456702b195deeaa210327284d42ade4a08a313d4c87023005d1ab631bbfe3f5de1e405d0e66d0bef3e033f1e5711b5521a0bf09a5d9a48b10ade82b8d6a5362a15921c8b5228a3487479b467db97411d82fa0f95cccae2a7c572ef3c566503e30bac2b2feb2d2f26eebf6d870dcf7f8cf59cea0d21fc4f68ab05bc4dcb23c67008e92c4d466437cdd6ed7aad0c008944c1855108080808080808080").to_vec(),
+					hex!("f851a0b9890f91ca0d77aa2a4adfaf9b9e40c94cac9e638b6d9797923865872944b646a060a634b9280e3a23fb63375e7bbdd9ab07fd379ab6a67e2312bbc112195fa358808080808080808080808080808080").to_vec(),
+					hex!("f9030820b9030402f90300018301d6e2b9010000000000000800000000000020040008000000000000000000000000400000008000000000000000000000000000000000000000000000000000000000042010000000001000000000000000000000000000000000040000000000000000000000000000000000000000000000008000000000000000002000000000000000000000000200000000000000200000000000100000000040000001000200008000000000000200000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000f901f5f87a942ffa5ecdbe006d30397c7636d3e015eee251369ff842a0c965575a00553e094ca7c5d14f02e107c258dda06867cbf9e0e69f80e71bbcc1a000000000000000000000000000000000000000000000000000000000000003e8a000000000000000000000000000000000000000000000000000000000000003e8f9011c94ee9170abfbf9421ad6dd07f6bdec9d89f2b581e0f863a01b11dcf133cc240f682dab2d3a8e4cd35c5da8c9cf99adac4336f8512584c5ada000000000000000000000000000000000000000000000000000000000000003e8a00000000000000000000000000000000000000000000000000000000000000001b8a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004b000f000000000000000100d184c103f7acc340847eee82a0b909e3358bc28d440edffa1352b13227e8ee646f3ea37456dec701345772617070656420457468657210574554481235003511000000000000000000000000000000000000000000f858948cf6147918a5cbb672703f879f385036f8793a24e1a01449abf21e49fd025f33495e77f7b1461caefdd3d4bb646424a3f445c4576a5ba0000000000000000000000000440edffa1352b13227e8ee646f3ea37456dec701").to_vec(),
+				]),
+			}
+		)
+	}
+
+	pub fn get_message_verification_header() -> CompactExecutionHeader {
+		CompactExecutionHeader {
+			parent_hash: hex!("04a7f6ab8282203562c62f38b0ab41d32aaebe2c7ea687702b463148a6429e04")
+				.into(),
+			block_number: 55,
+			state_root: hex!("894d968712976d613519f973a317cb0781c7b039c89f27ea2b7ca193f7befdb3")
+				.into(),
+			receipts_root: hex!("cf0d1c1ba57d1e0edfb59786c7e30c2b7e12bd54612b00cd21c4eaeecedf44fb")
+				.into(),
+		}
+	}
+}
+
+#[cfg(feature = "beacon-spec-mainnet")]
+pub mod mainnet {
+	use super::*;
+	use frame_support::derive_impl;
+
+	type Block = frame_system::mocking::MockBlock<Test>;
+	use sp_runtime::BuildStorage;
+
+	frame_support::construct_runtime!(
+		pub enum Test {
+			System: frame_system::{Pallet, Call, Storage, Event<T>},
+			Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent},
+			EthereumBeaconClient: ethereum_beacon_client::{Pallet, Call, Storage, Event<T>},
+		}
+	);
+
+	parameter_types! {
+		pub const BlockHashCount: u64 = 250;
+		pub const SS58Prefix: u8 = 42;
+	}
+
+	#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+	impl frame_system::Config for Test {
+		type BaseCallFilter = frame_support::traits::Everything;
+		type RuntimeOrigin = RuntimeOrigin;
+		type RuntimeCall = RuntimeCall;
+		type RuntimeTask = RuntimeTask;
+		type Hash = H256;
+		type Hashing = BlakeTwo256;
+		type AccountId = u64;
+		type Lookup = IdentityLookup<Self::AccountId>;
+		type RuntimeEvent = RuntimeEvent;
+		type BlockHashCount = BlockHashCount;
+		type PalletInfo = PalletInfo;
+		type SS58Prefix = SS58Prefix;
+		type Nonce = u64;
+		type Block = Block;
+	}
+
+	impl pallet_timestamp::Config for Test {
+		type Moment = u64;
+		type OnTimestampSet = ();
+		type MinimumPeriod = ();
+		type WeightInfo = ();
+	}
+
+	parameter_types! {
+		pub const ChainForkVersions: ForkVersions = ForkVersions{
+			genesis: Fork {
+				version: [0, 0, 16, 32], // 0x00001020
+				epoch: 0,
+			},
+			altair: Fork {
+				version: [1, 0, 16, 32], // 0x01001020
+				epoch: 36660,
+			},
+			bellatrix: Fork {
+				version: [2, 0, 16, 32], // 0x02001020
+				epoch: 112260,
+			},
+			capella: Fork {
+				version: [3, 0, 16, 32], // 0x03001020
+				epoch: 162304,
+			},
+		};
+		pub const ExecutionHeadersPruneThreshold: u32 = 10;
+	}
+
+	impl ethereum_beacon_client::Config for Test {
+		type RuntimeEvent = RuntimeEvent;
+		type ForkVersions = ChainForkVersions;
+		type MaxExecutionHeadersToKeep = ExecutionHeadersPruneThreshold;
+		type WeightInfo = ();
+	}
+
+	// Build genesis storage according to the mock runtime.
+	pub fn new_tester() -> sp_io::TestExternalities {
+		let t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
+		let mut ext = sp_io::TestExternalities::new(t);
+		let _ = ext.execute_with(|| Timestamp::set(RuntimeOrigin::signed(1), 30_000));
+		ext
+	}
+}
diff --git a/cumulus/parachain-template/runtime/src/lib.rs b/cumulus/parachain-template/runtime/src/lib.rs
index cee9b33bf37cca9cab905223406453c5424a5894..004a5d70ebc9a5a3c5d65b487a7281c38b2ca657 100644
--- a/cumulus/parachain-template/runtime/src/lib.rs
+++ b/cumulus/parachain-template/runtime/src/lib.rs
@@ -560,7 +560,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
index 6791dc4064ed7eb2ca780f75797d71d52bab3401..1c4f49fea0ed2d0e9e703753334ac29d23f44756 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
@@ -1093,7 +1093,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
index 5352eeb1a1b793007f4dcf3041a006a7de6e6000..e015c7bfca352aa4599b868c2272f90417f22a23 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
@@ -1124,7 +1124,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs
index db1dfbd45d63169a9f0cd87b3c578d41a3eb00c5..0f6af1b51f1e3c4d2e904fd3088379b4ea71f4b6 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs
@@ -816,7 +816,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs
index 1c8c9aa20755d73d81f10cb95e1c9ffb716eea55..206743ba6aca7a35a1f7b8570059ec9110ed645b 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs
@@ -566,7 +566,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs
index 36eb0e07f87c16704ab27745c2f19efc113884e3..3ed9d6e8e1685709f0e2dd320dc15ccadb9c03d6 100644
--- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs
@@ -803,7 +803,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs
index 541978098caaefaccc7724f56d8ff1b819fe42ec..c4df5b9a56522077d0c36c7387001686dab07cde 100644
--- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs
+++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs
@@ -466,7 +466,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs
index fcd7576b1e17aafcf9f97d13cfe7c1a1e37f14b0..0cdfebce742dcf9d63756617e73e1bb57993191d 100644
--- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs
+++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs
@@ -521,7 +521,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs
index 80eb7863803b62ed09d67ab703df7507360e5333..7797329b5266bf47c7018ce2fcf5638daf022dfa 100644
--- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs
@@ -512,7 +512,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs
index 10408aaf39a7611f93178802e17403ed4fd90838..e09ad42ea9f20277fae45893cf5d47da1f232f5e 100644
--- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs
@@ -332,7 +332,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs
index 90c398917695a0ef1150e42c6b61430bbb6de783..616da5519a5bb0a5e7be8297aec8fc11dcfc3f44 100644
--- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs
+++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs
@@ -495,7 +495,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs
index a904f7c3521be8b7122eb36fc0a1baa053106b38..9f572330cac8ce4d73d55517b3e50d1ec8cd37c5 100644
--- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs
@@ -495,7 +495,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/starters/seedling/src/lib.rs b/cumulus/parachains/runtimes/starters/seedling/src/lib.rs
index ba077ef887947f6f0f2b639f85c10fcb13a06fcb..4cc0a81ef49a7ce96f74583a10a0d18506a79b28 100644
--- a/cumulus/parachains/runtimes/starters/seedling/src/lib.rs
+++ b/cumulus/parachains/runtimes/starters/seedling/src/lib.rs
@@ -300,7 +300,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/starters/shell/src/lib.rs b/cumulus/parachains/runtimes/starters/shell/src/lib.rs
index 457394760d989db4dcd5ba335e1ff2dad46eaaba..829754731a421f1d95e8c207180d159999f9898b 100644
--- a/cumulus/parachains/runtimes/starters/shell/src/lib.rs
+++ b/cumulus/parachains/runtimes/starters/shell/src/lib.rs
@@ -357,7 +357,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs
index bf8dcbc24c8d51d61c70ea6a56fa8865ea32c893..0030287edb3b6c5c087c65f2507a62b6d2de3e08 100644
--- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs
+++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs
@@ -699,7 +699,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs
index 57969d9a4f1882d218fdc97374f52f467f109398..b69c2341d6b0dc1035710af30a85c5af8e764311 100644
--- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs
+++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs
@@ -689,7 +689,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block);
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/cumulus/polkadot-parachain/src/fake_runtime_api/asset_hub_polkadot_aura.rs b/cumulus/polkadot-parachain/src/fake_runtime_api/asset_hub_polkadot_aura.rs
index 76dd7347ccbc35127747e144af7c8705a15022e4..7778d1bf7d2dc0187fe6a0684023a9a4648be596 100644
--- a/cumulus/polkadot-parachain/src/fake_runtime_api/asset_hub_polkadot_aura.rs
+++ b/cumulus/polkadot-parachain/src/fake_runtime_api/asset_hub_polkadot_aura.rs
@@ -39,7 +39,7 @@ sp_api::impl_runtime_apis! {
 			unimplemented!()
 		}
 
-		fn initialize_block(_: &<Block as BlockT>::Header) {
+		fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			unimplemented!()
 		}
 	}
diff --git a/cumulus/polkadot-parachain/src/fake_runtime_api/aura.rs b/cumulus/polkadot-parachain/src/fake_runtime_api/aura.rs
index 0f01b85ebcf6fa63aabd9115c2ef553c18badea8..880f5d760c74559db87597bce158f8f5e62dbd82 100644
--- a/cumulus/polkadot-parachain/src/fake_runtime_api/aura.rs
+++ b/cumulus/polkadot-parachain/src/fake_runtime_api/aura.rs
@@ -39,7 +39,7 @@ sp_api::impl_runtime_apis! {
 			unimplemented!()
 		}
 
-		fn initialize_block(_: &<Block as BlockT>::Header) {
+		fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			unimplemented!()
 		}
 	}
diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs
index 5fb3141098449ffa26f7e7ca09f30adee610f9e4..5ccec8983e91fa2e50a242e191ee79ec8e9655cf 100644
--- a/cumulus/test/runtime/src/lib.rs
+++ b/cumulus/test/runtime/src/lib.rs
@@ -377,7 +377,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block)
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/polkadot/node/service/src/fake_runtime_api.rs b/polkadot/node/service/src/fake_runtime_api.rs
index ccc3da22400dfc38f5e94aa4f6e89969499dbee8..085ea93fdc78526f793b726bf385124ec6418e5d 100644
--- a/polkadot/node/service/src/fake_runtime_api.rs
+++ b/polkadot/node/service/src/fake_runtime_api.rs
@@ -60,7 +60,7 @@ sp_api::impl_runtime_apis! {
 			unimplemented!()
 		}
 
-		fn initialize_block(_: &<Block as BlockT>::Header) {
+		fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			unimplemented!()
 		}
 	}
diff --git a/polkadot/runtime/common/src/claims.rs b/polkadot/runtime/common/src/claims.rs
index 86550ea8b4ebf97054ce6713fffcdd0dd2cf628e..68f42914e4471531e4c85314c61ca7d41a672c6d 100644
--- a/polkadot/runtime/common/src/claims.rs
+++ b/polkadot/runtime/common/src/claims.rs
@@ -702,7 +702,6 @@ mod tests {
 	use secp_utils::*;
 
 	use parity_scale_codec::Encode;
-	use sp_core::H256;
 	// The testing primitives are very useful for avoiding having to work with signatures
 	// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required.
 	use crate::claims;
@@ -715,11 +714,8 @@ mod tests {
 	};
 	use pallet_balances;
 	use sp_runtime::{
-		traits::{BlakeTwo256, Identity, IdentityLookup},
-		transaction_validity::TransactionLongevity,
-		BuildStorage,
-		DispatchError::BadOrigin,
-		TokenError,
+		traits::Identity, transaction_validity::TransactionLongevity, BuildStorage,
+		DispatchError::BadOrigin, TokenError,
 	};
 
 	type Block = frame_system::mocking::MockBlock<Test>;
@@ -734,34 +730,13 @@ mod tests {
 		}
 	);
 
-	parameter_types! {
-		pub const BlockHashCount: u32 = 250;
-	}
-
 	#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
 	impl frame_system::Config for Test {
-		type BaseCallFilter = frame_support::traits::Everything;
-		type BlockWeights = ();
-		type BlockLength = ();
-		type DbWeight = ();
 		type RuntimeOrigin = RuntimeOrigin;
 		type RuntimeCall = RuntimeCall;
-		type Nonce = u64;
-		type Hash = H256;
-		type Hashing = BlakeTwo256;
-		type AccountId = u64;
-		type Lookup = IdentityLookup<u64>;
 		type Block = Block;
 		type RuntimeEvent = RuntimeEvent;
-		type BlockHashCount = BlockHashCount;
-		type Version = ();
-		type PalletInfo = PalletInfo;
 		type AccountData = pallet_balances::AccountData<u64>;
-		type OnNewAccount = ();
-		type OnKilledAccount = ();
-		type SystemWeightInfo = ();
-		type SS58Prefix = ();
-		type OnSetCode = ();
 		type MaxConsumers = frame_support::traits::ConstU32<16>;
 	}
 
diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs
index bbc79a13d37f4987eb7bfc4f1619b8141255467c..173301e1ad915dd2889ebd1cc2a40ea626c3ec71 100644
--- a/polkadot/runtime/rococo/src/lib.rs
+++ b/polkadot/runtime/rococo/src/lib.rs
@@ -1768,7 +1768,7 @@ sp_api::impl_runtime_apis! {
 			Executive::execute_block(block);
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs
index b74def5de8aadd718df48b27aff1a7ca324389c3..6c899c5270156dbbdcad0108f7e1ffb69069c288 100644
--- a/polkadot/runtime/test-runtime/src/lib.rs
+++ b/polkadot/runtime/test-runtime/src/lib.rs
@@ -767,7 +767,7 @@ sp_api::impl_runtime_apis! {
 			Executive::execute_block(block);
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs
index 574710b3110d59ba941b334b4910cdaa973eceed..91047d953f54cffff82f9d1022d6c9cf78e20e41 100644
--- a/polkadot/runtime/westend/src/lib.rs
+++ b/polkadot/runtime/westend/src/lib.rs
@@ -1793,7 +1793,7 @@ sp_api::impl_runtime_apis! {
 			Executive::execute_block(block);
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/prdoc/pr_1781.prdoc b/prdoc/pr_1781.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..e3560842d15ab249539ac2de172dc5914e92cc19
--- /dev/null
+++ b/prdoc/pr_1781.prdoc
@@ -0,0 +1,45 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: "Multi-Block-Migrations, `poll` hook and new System Callbacks"
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      The major things that this MR touches are:
+
+      **Multi-Block-Migrations**: `pallet-migrations` is introduced that can be configured in the
+      `System` of a runtime to act as multi-block migrator. The `migrations` pallet then in turn
+      receives the list of MBMs as config parameter. The list of migrations can be an aggregated
+      tuple of `SteppedMigration` trait implementation.  
+      It is paramount that the `migrations` pallet is configured in `System` once it is deployed. A
+      test is in place to double check this.
+
+      To integrate this into your runtime, it is only necessary to change the return type of
+      `initialize_block` to `RuntimeExecutiveMode`. For extended info please see
+      https://github.com/paritytech/polkadot-sdk/pull/1781.
+
+      **poll**: a new pallet hook named `poll` is added. This can be used for places where the code
+      that should be executed is not deadline critical. Runtime devs are advised to skim their usage
+      of `on_initialize` and `on_finalize` to see whether they can be replace with `poll`. `poll` is
+      not guaranteed to be called each block. In fact it will not be called when MBMs are ongoing.
+
+      **System Callbacks**: The `system` pallet gets five new config items - all of which can be
+      safely set to `()` as default. They are:  
+      - `SingleBlockMigrations`: replaces the `Executive` now for configuring migrations.
+      - `MultiBlockMigrator`: the `pallet-migrations` would be set here, if deployed.
+      - `PreInherents`: a hook that runs before any inherent.
+      - `PostInherents`: a hook to run between inherents and `poll`/MBM logic.
+      - `PostTransactions`: a hook to run after all transactions but before `on_idle`.
+
+crates:
+  - name: frame-executive
+  - name: frame-system
+  - name: frame-support
+  - name: frame-support-procedural
+  - name: pallet-migrations
+  - name: sc-basic-authorship
+  - name: sc-block-builder
+  - name: sp-api
+  - name: sp-api-proc-macro
+  - name: sp-runtime
diff --git a/substrate/bin/minimal/runtime/src/lib.rs b/substrate/bin/minimal/runtime/src/lib.rs
index 610289693d91b778295b3e48569fc887f805a204..fb996aef46b8ed90b041aa0e0aa1be119438b60f 100644
--- a/substrate/bin/minimal/runtime/src/lib.rs
+++ b/substrate/bin/minimal/runtime/src/lib.rs
@@ -26,7 +26,8 @@ use frame::{
 	prelude::*,
 	runtime::{
 		apis::{
-			self, impl_runtime_apis, ApplyExtrinsicResult, CheckInherentsResult, OpaqueMetadata,
+			self, impl_runtime_apis, ApplyExtrinsicResult, CheckInherentsResult,
+			ExtrinsicInclusionMode, OpaqueMetadata,
 		},
 		prelude::*,
 	},
@@ -121,7 +122,7 @@ impl_runtime_apis! {
 			RuntimeExecutive::execute_block(block)
 		}
 
-		fn initialize_block(header: &Header) {
+		fn initialize_block(header: &Header) -> ExtrinsicInclusionMode {
 			RuntimeExecutive::initialize_block(header)
 		}
 	}
diff --git a/substrate/bin/node-template/runtime/src/lib.rs b/substrate/bin/node-template/runtime/src/lib.rs
index 3b6a74be2512c6214b2fadada68bb22d746f07a9..159697f427f6d29d041b7110cf7a2d9896d149a0 100644
--- a/substrate/bin/node-template/runtime/src/lib.rs
+++ b/substrate/bin/node-template/runtime/src/lib.rs
@@ -331,7 +331,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block);
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml
index aa13c3d292be6af6d6dba12e5b043a9069eab9a3..9607d05daf0e6d4daa93a3c4f5479ab4b5e48ee8 100644
--- a/substrate/bin/node/runtime/Cargo.toml
+++ b/substrate/bin/node/runtime/Cargo.toml
@@ -88,6 +88,7 @@ pallet-election-provider-support-benchmarking = { path = "../../../frame/electio
 pallet-elections-phragmen = { path = "../../../frame/elections-phragmen", default-features = false }
 pallet-example-tasks = { path = "../../../frame/examples/tasks", default-features = false }
 pallet-fast-unstake = { path = "../../../frame/fast-unstake", default-features = false }
+pallet-migrations = { path = "../../../frame/migrations", default-features = false }
 pallet-nis = { path = "../../../frame/nis", default-features = false }
 pallet-grandpa = { path = "../../../frame/grandpa", default-features = false }
 pallet-im-online = { path = "../../../frame/im-online", default-features = false }
@@ -198,6 +199,7 @@ std = [
 	"pallet-lottery/std",
 	"pallet-membership/std",
 	"pallet-message-queue/std",
+	"pallet-migrations/std",
 	"pallet-mixnet/std",
 	"pallet-mmr/std",
 	"pallet-multisig/std",
@@ -302,6 +304,7 @@ runtime-benchmarks = [
 	"pallet-lottery/runtime-benchmarks",
 	"pallet-membership/runtime-benchmarks",
 	"pallet-message-queue/runtime-benchmarks",
+	"pallet-migrations/runtime-benchmarks",
 	"pallet-mixnet/runtime-benchmarks",
 	"pallet-mmr/runtime-benchmarks",
 	"pallet-multisig/runtime-benchmarks",
@@ -381,6 +384,7 @@ try-runtime = [
 	"pallet-lottery/try-runtime",
 	"pallet-membership/try-runtime",
 	"pallet-message-queue/try-runtime",
+	"pallet-migrations/try-runtime",
 	"pallet-mixnet/try-runtime",
 	"pallet-mmr/try-runtime",
 	"pallet-multisig/try-runtime",
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index b34d8c89e568ab78622010ea1c7aac35126bd2cd..5431628c747d483d23c7f2f9a4a488b1b5188fe5 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -310,6 +310,7 @@ impl frame_system::Config for Runtime {
 	type SystemWeightInfo = frame_system::weights::SubstrateWeight<Runtime>;
 	type SS58Prefix = ConstU16<42>;
 	type MaxConsumers = ConstU32<16>;
+	type MultiBlockMigrator = MultiBlockMigrations;
 }
 
 impl pallet_insecure_randomness_collective_flip::Config for Runtime {}
@@ -2007,6 +2008,25 @@ impl pallet_statement::Config for Runtime {
 	type MaxAllowedBytes = MaxAllowedBytes;
 }
 
+parameter_types! {
+	pub MbmServiceWeight: Weight = Perbill::from_percent(80) * RuntimeBlockWeights::get().max_block;
+}
+
+impl pallet_migrations::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	#[cfg(not(feature = "runtime-benchmarks"))]
+	type Migrations = ();
+	// Benchmarks need mocked migrations to guarantee that they succeed.
+	#[cfg(feature = "runtime-benchmarks")]
+	type Migrations = pallet_migrations::mock_helpers::MockedMigrations;
+	type CursorMaxLen = ConstU32<65_536>;
+	type IdentifierMaxLen = ConstU32<256>;
+	type MigrationStatusHandler = ();
+	type FailedMigrationHandler = frame_support::migrations::FreezeChainOnFailedMigration;
+	type MaxServiceWeight = MbmServiceWeight;
+	type WeightInfo = pallet_migrations::weights::SubstrateWeight<Runtime>;
+}
+
 parameter_types! {
 	pub const BrokerPalletId: PalletId = PalletId(*b"py/broke");
 }
@@ -2241,6 +2261,7 @@ construct_runtime!(
 		TxPause: pallet_tx_pause,
 		SafeMode: pallet_safe_mode,
 		Statement: pallet_statement,
+		MultiBlockMigrations: pallet_migrations,
 		Broker: pallet_broker,
 		TasksExample: pallet_example_tasks,
 		Mixnet: pallet_mixnet,
@@ -2372,6 +2393,7 @@ mod benches {
 		[pallet_lottery, Lottery]
 		[pallet_membership, TechnicalMembership]
 		[pallet_message_queue, MessageQueue]
+		[pallet_migrations, MultiBlockMigrations]
 		[pallet_mmr, Mmr]
 		[pallet_multisig, Multisig]
 		[pallet_nomination_pools, NominationPoolsBench::<Runtime>]
@@ -2417,7 +2439,7 @@ impl_runtime_apis! {
 			Executive::execute_block(block);
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			Executive::initialize_block(header)
 		}
 	}
diff --git a/substrate/client/basic-authorship/src/basic_authorship.rs b/substrate/client/basic-authorship/src/basic_authorship.rs
index fdc3d4918de32c9263286a0b3bfe73a01d26538b..932287ac86577b0e1a9b2c057320aeda9d12319a 100644
--- a/substrate/client/basic-authorship/src/basic_authorship.rs
+++ b/substrate/client/basic-authorship/src/basic_authorship.rs
@@ -38,7 +38,7 @@ use sp_core::traits::SpawnNamed;
 use sp_inherents::InherentData;
 use sp_runtime::{
 	traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Header as HeaderT},
-	Digest, Percent, SaturatedConversion,
+	Digest, ExtrinsicInclusionMode, Percent, SaturatedConversion,
 };
 use std::{marker::PhantomData, pin::Pin, sync::Arc, time};
 
@@ -335,11 +335,12 @@ where
 
 		self.apply_inherents(&mut block_builder, inherent_data)?;
 
-		// TODO call `after_inherents` and check if we should apply extrinsincs here
-		// <https://github.com/paritytech/substrate/pull/14275/>
-
-		let end_reason =
-			self.apply_extrinsics(&mut block_builder, deadline, block_size_limit).await?;
+		let mode = block_builder.extrinsic_inclusion_mode();
+		let end_reason = match mode {
+			ExtrinsicInclusionMode::AllExtrinsics =>
+				self.apply_extrinsics(&mut block_builder, deadline, block_size_limit).await?,
+			ExtrinsicInclusionMode::OnlyInherents => EndProposingReason::TransactionForbidden,
+		};
 		let (block, storage_changes, proof) = block_builder.build()?.into_inner();
 		let block_took = block_timer.elapsed();
 
diff --git a/substrate/client/block-builder/src/lib.rs b/substrate/client/block-builder/src/lib.rs
index 258e39d962b2de2397d85a54223c155e821ddfa3..2f22cd42591fc60bf1665d00a1c5b2b548ad3aea 100644
--- a/substrate/client/block-builder/src/lib.rs
+++ b/substrate/client/block-builder/src/lib.rs
@@ -37,7 +37,7 @@ use sp_core::traits::CallContext;
 use sp_runtime::{
 	legacy,
 	traits::{Block as BlockT, Hash, HashingFor, Header as HeaderT, NumberFor, One},
-	Digest,
+	Digest, ExtrinsicInclusionMode,
 };
 use std::marker::PhantomData;
 
@@ -198,10 +198,12 @@ pub struct BlockBuilder<'a, Block: BlockT, C: ProvideRuntimeApi<Block> + 'a> {
 	extrinsics: Vec<Block::Extrinsic>,
 	api: ApiRef<'a, C::Api>,
 	call_api_at: &'a C,
+	/// Version of the [`BlockBuilderApi`] runtime API.
 	version: u32,
 	parent_hash: Block::Hash,
 	/// The estimated size of the block header.
 	estimated_header_size: usize,
+	extrinsic_inclusion_mode: ExtrinsicInclusionMode,
 }
 
 impl<'a, Block, C> BlockBuilder<'a, Block, C>
@@ -244,9 +246,19 @@ where
 
 		api.set_call_context(CallContext::Onchain);
 
-		api.initialize_block(parent_hash, &header)?;
+		let core_version = api
+			.api_version::<dyn Core<Block>>(parent_hash)?
+			.ok_or_else(|| Error::VersionInvalid("Core".to_string()))?;
 
-		let version = api
+		let extrinsic_inclusion_mode = if core_version >= 5 {
+			api.initialize_block(parent_hash, &header)?
+		} else {
+			#[allow(deprecated)]
+			api.initialize_block_before_version_5(parent_hash, &header)?;
+			ExtrinsicInclusionMode::AllExtrinsics
+		};
+
+		let bb_version = api
 			.api_version::<dyn BlockBuilderApi<Block>>(parent_hash)?
 			.ok_or_else(|| Error::VersionInvalid("BlockBuilderApi".to_string()))?;
 
@@ -254,12 +266,18 @@ where
 			parent_hash,
 			extrinsics: Vec::new(),
 			api,
-			version,
+			version: bb_version,
 			estimated_header_size,
 			call_api_at,
+			extrinsic_inclusion_mode,
 		})
 	}
 
+	/// The extrinsic inclusion mode of the runtime for this block.
+	pub fn extrinsic_inclusion_mode(&self) -> ExtrinsicInclusionMode {
+		self.extrinsic_inclusion_mode
+	}
+
 	/// Push onto the block's list of extrinsics.
 	///
 	/// This will ensure the extrinsic can be validly executed (by executing it).
diff --git a/substrate/client/proposer-metrics/src/lib.rs b/substrate/client/proposer-metrics/src/lib.rs
index 012e8ca769a96cb51141c5a13ca7ba04d76a8123..2856300cf8027b45bb0ef32b8781c533ae2e340c 100644
--- a/substrate/client/proposer-metrics/src/lib.rs
+++ b/substrate/client/proposer-metrics/src/lib.rs
@@ -44,11 +44,14 @@ impl MetricsLink {
 }
 
 /// The reason why proposing a block ended.
+#[derive(Clone, Copy, PartialEq, Eq)]
 pub enum EndProposingReason {
 	NoMoreTransactions,
 	HitDeadline,
 	HitBlockSizeLimit,
 	HitBlockWeightLimit,
+	/// No transactions are allowed in the block.
+	TransactionForbidden,
 }
 
 /// Authorship metrics.
@@ -112,6 +115,7 @@ impl Metrics {
 			EndProposingReason::NoMoreTransactions => "no_more_transactions",
 			EndProposingReason::HitBlockSizeLimit => "hit_block_size_limit",
 			EndProposingReason::HitBlockWeightLimit => "hit_block_weight_limit",
+			EndProposingReason::TransactionForbidden => "transactions_forbidden",
 		};
 
 		self.end_proposing_reason.with_label_values(&[reason]).inc();
diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs
index 9544736d84c8e634613d0a7e363e9398f1aa0b9d..89d8c4ce2713d6b0b01411a3bf3928aa42eb363b 100644
--- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs
+++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs
@@ -242,12 +242,12 @@ async fn follow_with_runtime() {
 	let event: FollowEvent<String> = get_next_event(&mut sub).await;
 
 	// it is basically json-encoded substrate_test_runtime_client::runtime::VERSION
-	let runtime_str = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":0,\
-		\"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",4],\
+	let runtime_str = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\
+		\"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",5],\
 		[\"0x37e397fc7c91f5e4\",2],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\
 		[\"0xbc9d89904f5b923f\",1],[\"0xc6e9a76309f39b09\",2],[\"0xdd718d5cc53262d4\",1],\
 		[\"0xcbca25e39f142387\",2],[\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],\
-		[\"0xed99c5acb25eedf5\",3],[\"0xfbc577b9d747efd6\",1]],\"transactionVersion\":1,\"stateVersion\":0}";
+		[\"0xed99c5acb25eedf5\",3],[\"0xfbc577b9d747efd6\",1]],\"transactionVersion\":1,\"stateVersion\":1}";
 
 	let runtime: RuntimeVersion = serde_json::from_str(runtime_str).unwrap();
 
diff --git a/substrate/client/rpc/src/state/tests.rs b/substrate/client/rpc/src/state/tests.rs
index 96f4c1be960faa5f15508f19a9dd5665d8b684c1..dd866e671c5095dca9b5851111340a4e32f71e67 100644
--- a/substrate/client/rpc/src/state/tests.rs
+++ b/substrate/client/rpc/src/state/tests.rs
@@ -475,7 +475,7 @@ async fn should_return_runtime_version() {
 
 	// it is basically json-encoded substrate_test_runtime_client::runtime::VERSION
 	let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\
-		\"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",4],\
+		\"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",5],\
 		[\"0x37e397fc7c91f5e4\",2],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\
 		[\"0xbc9d89904f5b923f\",1],[\"0xc6e9a76309f39b09\",2],[\"0xdd718d5cc53262d4\",1],\
 		[\"0xcbca25e39f142387\",2],[\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],\
diff --git a/substrate/frame/executive/Cargo.toml b/substrate/frame/executive/Cargo.toml
index a4ca265f6178218791bf83f6b8f259821e086a98..63285e4cb4939c10db3342b5d59eeffc63103d48 100644
--- a/substrate/frame/executive/Cargo.toml
+++ b/substrate/frame/executive/Cargo.toml
@@ -16,6 +16,7 @@ workspace = true
 targets = ["x86_64-unknown-linux-gnu"]
 
 [dependencies]
+aquamarine = "0.3.2"
 codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [
 	"derive",
 ] }
@@ -44,6 +45,7 @@ default = ["std"]
 with-tracing = ["sp-tracing/with-tracing"]
 std = [
 	"codec/std",
+	"frame-support/experimental",
 	"frame-support/std",
 	"frame-system/std",
 	"frame-try-runtime/std",
diff --git a/substrate/frame/executive/src/lib.rs b/substrate/frame/executive/src/lib.rs
index 48ff675f8082ddd0e7779c946b32dde66b171078..3028eaf318e0881c1b6f4d4ea6ac17adfe99a75f 100644
--- a/substrate/frame/executive/src/lib.rs
+++ b/substrate/frame/executive/src/lib.rs
@@ -15,6 +15,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![cfg_attr(not(feature = "std"), no_std)]
+
 //! # Executive Module
 //!
 //! The Executive module acts as the orchestration layer for the runtime. It dispatches incoming
@@ -35,6 +37,8 @@
 //! - Finalize a block.
 //! - Start an off-chain worker.
 //!
+//! The flow of their application in a block is explained in the [block flowchart](block_flowchart).
+//!
 //! ### Implementations
 //!
 //! The Executive module provides the following implementations:
@@ -114,17 +118,51 @@
 //! pub type Executive = executive::Executive<Runtime, Block, Context, Runtime, AllPalletsWithSystem, CustomOnRuntimeUpgrade>;
 //! ```
 
-#![cfg_attr(not(feature = "std"), no_std)]
+#[cfg(doc)]
+#[cfg_attr(doc, aquamarine::aquamarine)]
+/// # Block Execution
+///
+/// These are the steps of block execution as done by [`Executive::execute_block`]. A block is
+/// invalid if any of them fail.
+///
+/// ```mermaid
+/// flowchart TD
+///     Executive::execute_block --> on_runtime_upgrade
+///     on_runtime_upgrade --> System::initialize
+///     Executive::initialize_block --> System::initialize
+///     System::initialize --> on_initialize
+///     on_initialize --> PreInherents[System::PreInherents]
+///     PreInherents --> Inherents[Apply Inherents]
+///     Inherents --> PostInherents[System::PostInherents]
+///     PostInherents --> Check{MBM ongoing?}
+///     Check -->|No| poll
+///     Check -->|Yes| post_transactions_2[System::PostTransaction]
+///     post_transactions_2 --> Step[MBMs::step]
+///     Step --> on_finalize
+///     poll --> transactions[Apply Transactions]
+///     transactions --> post_transactions_1[System::PostTransaction]
+///     post_transactions_1 --> CheckIdle{Weight remaining?}
+///     CheckIdle -->|Yes| on_idle
+///     CheckIdle -->|No| on_finalize
+///     on_idle --> on_finalize
+/// ```
+pub mod block_flowchart {}
+
+#[cfg(test)]
+mod tests;
 
 use codec::{Codec, Encode};
 use frame_support::{
+	defensive_assert,
 	dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo},
+	migrations::MultiStepMigrator,
 	pallet_prelude::InvalidTransaction,
 	traits::{
 		BeforeAllRuntimeMigrations, EnsureInherentsAreFirst, ExecuteBlock, OffchainWorker,
-		OnFinalize, OnIdle, OnInitialize, OnRuntimeUpgrade,
+		OnFinalize, OnIdle, OnInitialize, OnPoll, OnRuntimeUpgrade, PostInherents,
+		PostTransactions, PreInherents,
 	},
-	weights::Weight,
+	weights::{Weight, WeightMeter},
 };
 use frame_system::pallet_prelude::BlockNumberFor;
 use sp_runtime::{
@@ -134,7 +172,7 @@ use sp_runtime::{
 		ValidateUnsigned, Zero,
 	},
 	transaction_validity::{TransactionSource, TransactionValidity},
-	ApplyExtrinsicResult,
+	ApplyExtrinsicResult, ExtrinsicInclusionMode,
 };
 use sp_std::{marker::PhantomData, prelude::*};
 
@@ -198,7 +236,8 @@ impl<
 			+ OnInitialize<BlockNumberFor<System>>
 			+ OnIdle<BlockNumberFor<System>>
 			+ OnFinalize<BlockNumberFor<System>>
-			+ OffchainWorker<BlockNumberFor<System>>,
+			+ OffchainWorker<BlockNumberFor<System>>
+			+ OnPoll<BlockNumberFor<System>>,
 		COnRuntimeUpgrade: OnRuntimeUpgrade,
 	> ExecuteBlock<Block>
 	for Executive<System, Block, Context, UnsignedValidator, AllPalletsWithSystem, COnRuntimeUpgrade>
@@ -237,6 +276,7 @@ impl<
 			+ OnIdle<BlockNumberFor<System>>
 			+ OnFinalize<BlockNumberFor<System>>
 			+ OffchainWorker<BlockNumberFor<System>>
+			+ OnPoll<BlockNumberFor<System>>
 			+ TryState<BlockNumberFor<System>>
 			+ TryDecodeEntireStorage,
 		COnRuntimeUpgrade: OnRuntimeUpgrade,
@@ -272,36 +312,50 @@ where
 			select,
 		);
 
-		Self::initialize_block(block.header());
-		Self::initial_checks(&block);
-
+		let mode = Self::initialize_block(block.header());
+		let num_inherents = Self::initial_checks(&block) as usize;
 		let (header, extrinsics) = block.deconstruct();
 
+		// Check if there are any forbidden non-inherents in the block.
+		if mode == ExtrinsicInclusionMode::OnlyInherents && extrinsics.len() > num_inherents {
+			return Err("Only inherents allowed".into())
+		}
+
 		let try_apply_extrinsic = |uxt: Block::Extrinsic| -> ApplyExtrinsicResult {
 			sp_io::init_tracing();
 			let encoded = uxt.encode();
 			let encoded_len = encoded.len();
 
+			let is_inherent = System::is_inherent(&uxt);
 			// skip signature verification.
 			let xt = if signature_check {
 				uxt.check(&Default::default())
 			} else {
 				uxt.unchecked_into_checked_i_know_what_i_am_doing(&Default::default())
 			}?;
-			<frame_system::Pallet<System>>::note_extrinsic(encoded);
 
 			let dispatch_info = xt.get_dispatch_info();
+			if !is_inherent && !<frame_system::Pallet<System>>::inherents_applied() {
+				Self::inherents_applied();
+			}
+
+			<frame_system::Pallet<System>>::note_extrinsic(encoded);
 			let r = Applyable::apply::<UnsignedValidator>(xt, &dispatch_info, encoded_len)?;
 
+			if r.is_err() && dispatch_info.class == DispatchClass::Mandatory {
+				return Err(InvalidTransaction::BadMandatory.into())
+			}
+
 			<frame_system::Pallet<System>>::note_applied_extrinsic(&r, dispatch_info);
 
 			Ok(r.map(|_| ()).map_err(|e| e.error))
 		};
 
-		for e in extrinsics {
+		// Apply extrinsics:
+		for e in extrinsics.iter() {
 			if let Err(err) = try_apply_extrinsic(e.clone()) {
 				log::error!(
-					target: LOG_TARGET, "executing transaction {:?} failed due to {:?}. Aborting the rest of the block execution.",
+					target: LOG_TARGET, "transaction {:?} failed due to {:?}. Aborting the rest of the block execution.",
 					e,
 					err,
 				);
@@ -309,9 +363,17 @@ where
 			}
 		}
 
+		// In this case there were no transactions to trigger this state transition:
+		if !<frame_system::Pallet<System>>::inherents_applied() {
+			Self::inherents_applied();
+		}
+
 		// post-extrinsics book-keeping
 		<frame_system::Pallet<System>>::note_finished_extrinsics();
-		Self::idle_and_finalize_hook(*header.number());
+		<System as frame_system::Config>::PostTransactions::post_transactions();
+
+		Self::on_idle_hook(*header.number());
+		Self::on_finalize_hook(*header.number());
 
 		// run the try-state checks of all pallets, ensuring they don't alter any state.
 		let _guard = frame_support::StorageNoopGuard::default();
@@ -449,7 +511,8 @@ impl<
 			+ OnInitialize<BlockNumberFor<System>>
 			+ OnIdle<BlockNumberFor<System>>
 			+ OnFinalize<BlockNumberFor<System>>
-			+ OffchainWorker<BlockNumberFor<System>>,
+			+ OffchainWorker<BlockNumberFor<System>>
+			+ OnPoll<BlockNumberFor<System>>,
 		COnRuntimeUpgrade: OnRuntimeUpgrade,
 	> Executive<System, Block, Context, UnsignedValidator, AllPalletsWithSystem, COnRuntimeUpgrade>
 where
@@ -464,16 +527,36 @@ where
 	pub fn execute_on_runtime_upgrade() -> Weight {
 		let before_all_weight =
 			<AllPalletsWithSystem as BeforeAllRuntimeMigrations>::before_all_runtime_migrations();
-		<(COnRuntimeUpgrade, AllPalletsWithSystem) as OnRuntimeUpgrade>::on_runtime_upgrade()
-			.saturating_add(before_all_weight)
+
+		let runtime_upgrade_weight = <(
+			COnRuntimeUpgrade,
+			<System as frame_system::Config>::SingleBlockMigrations,
+			// We want to run the migrations before we call into the pallets as they may
+			// access any state that would then not be migrated.
+			AllPalletsWithSystem,
+		) as OnRuntimeUpgrade>::on_runtime_upgrade();
+
+		before_all_weight.saturating_add(runtime_upgrade_weight)
 	}
 
 	/// Start the execution of a particular block.
-	pub fn initialize_block(header: &frame_system::pallet_prelude::HeaderFor<System>) {
+	pub fn initialize_block(
+		header: &frame_system::pallet_prelude::HeaderFor<System>,
+	) -> ExtrinsicInclusionMode {
 		sp_io::init_tracing();
 		sp_tracing::enter_span!(sp_tracing::Level::TRACE, "init_block");
 		let digests = Self::extract_pre_digest(header);
 		Self::initialize_block_impl(header.number(), header.parent_hash(), &digests);
+
+		Self::extrinsic_mode()
+	}
+
+	fn extrinsic_mode() -> ExtrinsicInclusionMode {
+		if <System as frame_system::Config>::MultiBlockMigrator::ongoing() {
+			ExtrinsicInclusionMode::OnlyInherents
+		} else {
+			ExtrinsicInclusionMode::AllExtrinsics
+		}
 	}
 
 	fn extract_pre_digest(header: &frame_system::pallet_prelude::HeaderFor<System>) -> Digest {
@@ -519,6 +602,7 @@ where
 		);
 
 		frame_system::Pallet::<System>::note_finished_initialize();
+		<System as frame_system::Config>::PreInherents::pre_inherents();
 	}
 
 	/// Returns if the runtime has been upgraded, based on [`frame_system::LastRuntimeUpgrade`].
@@ -529,7 +613,8 @@ where
 		last.map(|v| v.was_upgraded(&current)).unwrap_or(true)
 	}
 
-	fn initial_checks(block: &Block) {
+	/// Returns the number of inherents in the block.
+	fn initial_checks(block: &Block) -> u32 {
 		sp_tracing::enter_span!(sp_tracing::Level::TRACE, "initial_checks");
 		let header = block.header();
 
@@ -542,8 +627,9 @@ where
 			"Parent hash should be valid.",
 		);
 
-		if let Err(i) = System::ensure_inherents_are_first(block) {
-			panic!("Invalid inherent position for extrinsic at index {}", i);
+		match System::ensure_inherents_are_first(block) {
+			Ok(num) => num,
+			Err(i) => panic!("Invalid inherent position for extrinsic at index {}", i),
 		}
 	}
 
@@ -552,53 +638,90 @@ where
 		sp_io::init_tracing();
 		sp_tracing::within_span! {
 			sp_tracing::info_span!("execute_block", ?block);
+			// Execute `on_runtime_upgrade` and `on_initialize`.
+			let mode = Self::initialize_block(block.header());
+			let num_inherents = Self::initial_checks(&block) as usize;
+			let (header, extrinsics) = block.deconstruct();
+			let num_extrinsics = extrinsics.len();
 
-			Self::initialize_block(block.header());
+			if mode == ExtrinsicInclusionMode::OnlyInherents && num_extrinsics > num_inherents {
+				// Invalid block
+				panic!("Only inherents are allowed in this block")
+			}
 
-			// any initial checks
-			Self::initial_checks(&block);
+			Self::apply_extrinsics(extrinsics.into_iter());
 
-			// execute extrinsics
-			let (header, extrinsics) = block.deconstruct();
-			Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number());
+			// In this case there were no transactions to trigger this state transition:
+			if !<frame_system::Pallet<System>>::inherents_applied() {
+				defensive_assert!(num_inherents == num_extrinsics);
+				Self::inherents_applied();
+			}
 
-			// any final checks
+			<frame_system::Pallet<System>>::note_finished_extrinsics();
+			<System as frame_system::Config>::PostTransactions::post_transactions();
+
+			Self::on_idle_hook(*header.number());
+			Self::on_finalize_hook(*header.number());
 			Self::final_checks(&header);
 		}
 	}
 
-	/// Execute given extrinsics and take care of post-extrinsics book-keeping.
-	fn execute_extrinsics_with_book_keeping(
-		extrinsics: Vec<Block::Extrinsic>,
-		block_number: NumberFor<Block>,
-	) {
+	/// Logic that runs directly after inherent application.
+	///
+	/// It advances the Multi-Block-Migrations or runs the `on_poll` hook.
+	pub fn inherents_applied() {
+		<frame_system::Pallet<System>>::note_inherents_applied();
+		<System as frame_system::Config>::PostInherents::post_inherents();
+
+		if <System as frame_system::Config>::MultiBlockMigrator::ongoing() {
+			let used_weight = <System as frame_system::Config>::MultiBlockMigrator::step();
+			<frame_system::Pallet<System>>::register_extra_weight_unchecked(
+				used_weight,
+				DispatchClass::Mandatory,
+			);
+		} else {
+			let block_number = <frame_system::Pallet<System>>::block_number();
+			Self::on_poll_hook(block_number);
+		}
+	}
+
+	/// Execute given extrinsics.
+	fn apply_extrinsics(extrinsics: impl Iterator<Item = Block::Extrinsic>) {
 		extrinsics.into_iter().for_each(|e| {
 			if let Err(e) = Self::apply_extrinsic(e) {
 				let err: &'static str = e.into();
 				panic!("{}", err)
 			}
 		});
-
-		// post-extrinsics book-keeping
-		<frame_system::Pallet<System>>::note_finished_extrinsics();
-
-		Self::idle_and_finalize_hook(block_number);
 	}
 
 	/// Finalize the block - it is up the caller to ensure that all header fields are valid
 	/// except state-root.
+	// Note: Only used by the block builder - not Executive itself.
 	pub fn finalize_block() -> frame_system::pallet_prelude::HeaderFor<System> {
 		sp_io::init_tracing();
 		sp_tracing::enter_span!(sp_tracing::Level::TRACE, "finalize_block");
-		<frame_system::Pallet<System>>::note_finished_extrinsics();
-		let block_number = <frame_system::Pallet<System>>::block_number();
 
-		Self::idle_and_finalize_hook(block_number);
+		// In this case there were no transactions to trigger this state transition:
+		if !<frame_system::Pallet<System>>::inherents_applied() {
+			Self::inherents_applied();
+		}
 
+		<frame_system::Pallet<System>>::note_finished_extrinsics();
+		<System as frame_system::Config>::PostTransactions::post_transactions();
+		let block_number = <frame_system::Pallet<System>>::block_number();
+		Self::on_idle_hook(block_number);
+		Self::on_finalize_hook(block_number);
 		<frame_system::Pallet<System>>::finalize()
 	}
 
-	fn idle_and_finalize_hook(block_number: NumberFor<Block>) {
+	/// Run the `on_idle` hook of all pallet, but only if there is weight remaining and there are no
+	/// ongoing MBMs.
+	fn on_idle_hook(block_number: NumberFor<Block>) {
+		if <System as frame_system::Config>::MultiBlockMigrator::ongoing() {
+			return
+		}
+
 		let weight = <frame_system::Pallet<System>>::block_weight();
 		let max_weight = <System::BlockWeights as frame_support::traits::Get<_>>::get().max_block;
 		let remaining_weight = max_weight.saturating_sub(weight.total());
@@ -613,7 +736,33 @@ where
 				DispatchClass::Mandatory,
 			);
 		}
+	}
+
+	fn on_poll_hook(block_number: NumberFor<Block>) {
+		defensive_assert!(
+			!<System as frame_system::Config>::MultiBlockMigrator::ongoing(),
+			"on_poll should not be called during migrations"
+		);
 
+		let weight = <frame_system::Pallet<System>>::block_weight();
+		let max_weight = <System::BlockWeights as frame_support::traits::Get<_>>::get().max_block;
+		let remaining = max_weight.saturating_sub(weight.total());
+
+		if remaining.all_gt(Weight::zero()) {
+			let mut meter = WeightMeter::with_limit(remaining);
+			<AllPalletsWithSystem as OnPoll<BlockNumberFor<System>>>::on_poll(
+				block_number,
+				&mut meter,
+			);
+			<frame_system::Pallet<System>>::register_extra_weight_unchecked(
+				meter.consumed(),
+				DispatchClass::Mandatory,
+			);
+		}
+	}
+
+	/// Run the `on_finalize` hook of all pallet.
+	fn on_finalize_hook(block_number: NumberFor<Block>) {
 		<AllPalletsWithSystem as OnFinalize<BlockNumberFor<System>>>::on_finalize(block_number);
 	}
 
@@ -627,8 +776,18 @@ where
 		let encoded_len = encoded.len();
 		sp_tracing::enter_span!(sp_tracing::info_span!("apply_extrinsic",
 				ext=?sp_core::hexdisplay::HexDisplay::from(&encoded)));
+
+		// We use the dedicated `is_inherent` check here, since just relying on `Mandatory` dispatch
+		// class does not capture optional inherents.
+		let is_inherent = System::is_inherent(&uxt);
+
 		// Verify that the signature is good.
 		let xt = uxt.check(&Default::default())?;
+		let dispatch_info = xt.get_dispatch_info();
+
+		if !is_inherent && !<frame_system::Pallet<System>>::inherents_applied() {
+			Self::inherents_applied();
+		}
 
 		// We don't need to make sure to `note_extrinsic` only after we know it's going to be
 		// executed to prevent it from leaking in storage since at this point, it will either
@@ -637,8 +796,6 @@ where
 
 		// AUDIT: Under no circumstances may this function panic from here onwards.
 
-		// Decode parameters and dispatch
-		let dispatch_info = xt.get_dispatch_info();
 		let r = Applyable::apply::<UnsignedValidator>(xt, &dispatch_info, encoded_len)?;
 
 		// Mandatory(inherents) are not allowed to fail.
@@ -745,956 +902,3 @@ where
 		)
 	}
 }
-
-#[cfg(test)]
-mod tests {
-	use super::*;
-
-	use sp_core::H256;
-	use sp_runtime::{
-		generic::{DigestItem, Era},
-		testing::{Block, Digest, Header},
-		traits::{BlakeTwo256, Block as BlockT, Header as HeaderT, IdentityLookup},
-		transaction_validity::{
-			InvalidTransaction, TransactionValidityError, UnknownTransaction, ValidTransaction,
-		},
-		BuildStorage, DispatchError,
-	};
-
-	use frame_support::{
-		assert_err, derive_impl, parameter_types,
-		traits::{fungible, ConstU32, ConstU64, ConstU8, Currency},
-		weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight, WeightToFee},
-	};
-	use frame_system::{ChainContext, LastRuntimeUpgrade, LastRuntimeUpgradeInfo};
-	use pallet_balances::Call as BalancesCall;
-	use pallet_transaction_payment::CurrencyAdapter;
-
-	const TEST_KEY: &[u8] = b":test:key:";
-
-	#[frame_support::pallet(dev_mode)]
-	mod custom {
-		use frame_support::pallet_prelude::*;
-		use frame_system::pallet_prelude::*;
-
-		#[pallet::pallet]
-		pub struct Pallet<T>(_);
-
-		#[pallet::config]
-		pub trait Config: frame_system::Config {}
-
-		#[pallet::hooks]
-		impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
-			// module hooks.
-			// one with block number arg and one without
-			fn on_initialize(n: BlockNumberFor<T>) -> Weight {
-				println!("on_initialize({})", n);
-				Weight::from_parts(175, 0)
-			}
-
-			fn on_idle(n: BlockNumberFor<T>, remaining_weight: Weight) -> Weight {
-				println!("on_idle{}, {})", n, remaining_weight);
-				Weight::from_parts(175, 0)
-			}
-
-			fn on_finalize(n: BlockNumberFor<T>) {
-				println!("on_finalize({})", n);
-			}
-
-			fn on_runtime_upgrade() -> Weight {
-				sp_io::storage::set(super::TEST_KEY, "module".as_bytes());
-				Weight::from_parts(200, 0)
-			}
-
-			fn offchain_worker(n: BlockNumberFor<T>) {
-				assert_eq!(BlockNumberFor::<T>::from(1u32), n);
-			}
-		}
-
-		#[pallet::call]
-		impl<T: Config> Pallet<T> {
-			pub fn some_function(origin: OriginFor<T>) -> DispatchResult {
-				// NOTE: does not make any different.
-				frame_system::ensure_signed(origin)?;
-				Ok(())
-			}
-
-			#[pallet::weight((200, DispatchClass::Operational))]
-			pub fn some_root_operation(origin: OriginFor<T>) -> DispatchResult {
-				frame_system::ensure_root(origin)?;
-				Ok(())
-			}
-
-			pub fn some_unsigned_message(origin: OriginFor<T>) -> DispatchResult {
-				frame_system::ensure_none(origin)?;
-				Ok(())
-			}
-
-			pub fn allowed_unsigned(origin: OriginFor<T>) -> DispatchResult {
-				frame_system::ensure_root(origin)?;
-				Ok(())
-			}
-
-			pub fn unallowed_unsigned(origin: OriginFor<T>) -> DispatchResult {
-				frame_system::ensure_root(origin)?;
-				Ok(())
-			}
-
-			#[pallet::weight((0, DispatchClass::Mandatory))]
-			pub fn inherent_call(origin: OriginFor<T>) -> DispatchResult {
-				frame_system::ensure_none(origin)?;
-				Ok(())
-			}
-
-			pub fn calculate_storage_root(_origin: OriginFor<T>) -> DispatchResult {
-				let root = sp_io::storage::root(sp_runtime::StateVersion::V1);
-				sp_io::storage::set("storage_root".as_bytes(), &root);
-				Ok(())
-			}
-		}
-
-		#[pallet::inherent]
-		impl<T: Config> ProvideInherent for Pallet<T> {
-			type Call = Call<T>;
-
-			type Error = sp_inherents::MakeFatalError<()>;
-
-			const INHERENT_IDENTIFIER: [u8; 8] = *b"test1234";
-
-			fn create_inherent(_data: &InherentData) -> Option<Self::Call> {
-				None
-			}
-
-			fn is_inherent(call: &Self::Call) -> bool {
-				*call == Call::<T>::inherent_call {}
-			}
-		}
-
-		#[pallet::validate_unsigned]
-		impl<T: Config> ValidateUnsigned for Pallet<T> {
-			type Call = Call<T>;
-
-			// Inherent call is accepted for being dispatched
-			fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
-				match call {
-					Call::allowed_unsigned { .. } => Ok(()),
-					Call::inherent_call { .. } => Ok(()),
-					_ => Err(UnknownTransaction::NoUnsignedValidator.into()),
-				}
-			}
-
-			// Inherent call is not validated as unsigned
-			fn validate_unsigned(
-				_source: TransactionSource,
-				call: &Self::Call,
-			) -> TransactionValidity {
-				match call {
-					Call::allowed_unsigned { .. } => Ok(Default::default()),
-					_ => UnknownTransaction::NoUnsignedValidator.into(),
-				}
-			}
-		}
-	}
-
-	frame_support::construct_runtime!(
-		pub enum Runtime {
-			System: frame_system,
-			Balances: pallet_balances,
-			TransactionPayment: pallet_transaction_payment,
-			Custom: custom,
-		}
-	);
-
-	parameter_types! {
-		pub BlockWeights: frame_system::limits::BlockWeights =
-			frame_system::limits::BlockWeights::builder()
-				.base_block(Weight::from_parts(10, 0))
-				.for_class(DispatchClass::all(), |weights| weights.base_extrinsic = Weight::from_parts(5, 0))
-				.for_class(DispatchClass::non_mandatory(), |weights| weights.max_total = Weight::from_parts(1024, u64::MAX).into())
-				.build_or_panic();
-		pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight {
-			read: 10,
-			write: 100,
-		};
-	}
-	#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
-	impl frame_system::Config for Runtime {
-		type BaseCallFilter = frame_support::traits::Everything;
-		type BlockWeights = BlockWeights;
-		type BlockLength = ();
-		type DbWeight = ();
-		type RuntimeOrigin = RuntimeOrigin;
-		type Nonce = u64;
-		type RuntimeCall = RuntimeCall;
-		type Hash = sp_core::H256;
-		type Hashing = BlakeTwo256;
-		type AccountId = u64;
-		type Lookup = IdentityLookup<u64>;
-		type Block = TestBlock;
-		type RuntimeEvent = RuntimeEvent;
-		type BlockHashCount = ConstU64<250>;
-		type Version = RuntimeVersion;
-		type PalletInfo = PalletInfo;
-		type AccountData = pallet_balances::AccountData<Balance>;
-		type OnNewAccount = ();
-		type OnKilledAccount = ();
-		type SystemWeightInfo = ();
-		type SS58Prefix = ();
-		type OnSetCode = ();
-		type MaxConsumers = ConstU32<16>;
-	}
-
-	type Balance = u64;
-	impl pallet_balances::Config for Runtime {
-		type Balance = Balance;
-		type RuntimeEvent = RuntimeEvent;
-		type DustRemoval = ();
-		type ExistentialDeposit = ConstU64<1>;
-		type AccountStore = System;
-		type MaxLocks = ();
-		type MaxReserves = ();
-		type ReserveIdentifier = [u8; 8];
-		type WeightInfo = ();
-		type FreezeIdentifier = ();
-		type MaxFreezes = ConstU32<1>;
-		type RuntimeHoldReason = ();
-		type RuntimeFreezeReason = ();
-	}
-
-	parameter_types! {
-		pub const TransactionByteFee: Balance = 0;
-	}
-	impl pallet_transaction_payment::Config for Runtime {
-		type RuntimeEvent = RuntimeEvent;
-		type OnChargeTransaction = CurrencyAdapter<Balances, ()>;
-		type OperationalFeeMultiplier = ConstU8<5>;
-		type WeightToFee = IdentityFee<Balance>;
-		type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
-		type FeeMultiplierUpdate = ();
-	}
-	impl custom::Config for Runtime {}
-
-	pub struct RuntimeVersion;
-	impl frame_support::traits::Get<sp_version::RuntimeVersion> for RuntimeVersion {
-		fn get() -> sp_version::RuntimeVersion {
-			RuntimeVersionTestValues::get().clone()
-		}
-	}
-
-	parameter_types! {
-		pub static RuntimeVersionTestValues: sp_version::RuntimeVersion =
-			Default::default();
-	}
-
-	type SignedExtra = (
-		frame_system::CheckEra<Runtime>,
-		frame_system::CheckNonce<Runtime>,
-		frame_system::CheckWeight<Runtime>,
-		pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
-	);
-	type TestXt = sp_runtime::testing::TestXt<RuntimeCall, SignedExtra>;
-	type TestBlock = Block<TestXt>;
-
-	// Will contain `true` when the custom runtime logic was called.
-	const CUSTOM_ON_RUNTIME_KEY: &[u8] = b":custom:on_runtime";
-
-	struct CustomOnRuntimeUpgrade;
-	impl OnRuntimeUpgrade for CustomOnRuntimeUpgrade {
-		fn on_runtime_upgrade() -> Weight {
-			sp_io::storage::set(TEST_KEY, "custom_upgrade".as_bytes());
-			sp_io::storage::set(CUSTOM_ON_RUNTIME_KEY, &true.encode());
-			System::deposit_event(frame_system::Event::CodeUpdated);
-
-			assert_eq!(0, System::last_runtime_upgrade_spec_version());
-
-			Weight::from_parts(100, 0)
-		}
-	}
-
-	type Executive = super::Executive<
-		Runtime,
-		Block<TestXt>,
-		ChainContext<Runtime>,
-		Runtime,
-		AllPalletsWithSystem,
-		CustomOnRuntimeUpgrade,
-	>;
-
-	fn extra(nonce: u64, fee: Balance) -> SignedExtra {
-		(
-			frame_system::CheckEra::from(Era::Immortal),
-			frame_system::CheckNonce::from(nonce),
-			frame_system::CheckWeight::new(),
-			pallet_transaction_payment::ChargeTransactionPayment::from(fee),
-		)
-	}
-
-	fn sign_extra(who: u64, nonce: u64, fee: Balance) -> Option<(u64, SignedExtra)> {
-		Some((who, extra(nonce, fee)))
-	}
-
-	fn call_transfer(dest: u64, value: u64) -> RuntimeCall {
-		RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value })
-	}
-
-	#[test]
-	fn balance_transfer_dispatch_works() {
-		let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
-		pallet_balances::GenesisConfig::<Runtime> { balances: vec![(1, 211)] }
-			.assimilate_storage(&mut t)
-			.unwrap();
-		let xt = TestXt::new(call_transfer(2, 69), sign_extra(1, 0, 0));
-		let weight = xt.get_dispatch_info().weight +
-			<Runtime as frame_system::Config>::BlockWeights::get()
-				.get(DispatchClass::Normal)
-				.base_extrinsic;
-		let fee: Balance =
-			<Runtime as pallet_transaction_payment::Config>::WeightToFee::weight_to_fee(&weight);
-		let mut t = sp_io::TestExternalities::new(t);
-		t.execute_with(|| {
-			Executive::initialize_block(&Header::new(
-				1,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-			let r = Executive::apply_extrinsic(xt);
-			assert!(r.is_ok());
-			assert_eq!(<pallet_balances::Pallet<Runtime>>::total_balance(&1), 142 - fee);
-			assert_eq!(<pallet_balances::Pallet<Runtime>>::total_balance(&2), 69);
-		});
-	}
-
-	fn new_test_ext(balance_factor: Balance) -> sp_io::TestExternalities {
-		let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
-		pallet_balances::GenesisConfig::<Runtime> { balances: vec![(1, 111 * balance_factor)] }
-			.assimilate_storage(&mut t)
-			.unwrap();
-		t.into()
-	}
-
-	fn new_test_ext_v0(balance_factor: Balance) -> sp_io::TestExternalities {
-		let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
-		pallet_balances::GenesisConfig::<Runtime> { balances: vec![(1, 111 * balance_factor)] }
-			.assimilate_storage(&mut t)
-			.unwrap();
-		(t, sp_runtime::StateVersion::V0).into()
-	}
-
-	#[test]
-	fn block_import_works() {
-		block_import_works_inner(
-			new_test_ext_v0(1),
-			array_bytes::hex_n_into_unchecked(
-				"65e953676859e7a33245908af7ad3637d6861eb90416d433d485e95e2dd174a1",
-			),
-		);
-		block_import_works_inner(
-			new_test_ext(1),
-			array_bytes::hex_n_into_unchecked(
-				"5a19b3d6fdb7241836349fdcbe2d9df4d4f945b949d979e31ad50bff1cbcd1c2",
-			),
-		);
-	}
-	fn block_import_works_inner(mut ext: sp_io::TestExternalities, state_root: H256) {
-		ext.execute_with(|| {
-			Executive::execute_block(Block {
-				header: Header {
-					parent_hash: [69u8; 32].into(),
-					number: 1,
-					state_root,
-					extrinsics_root: array_bytes::hex_n_into_unchecked(
-						"03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314",
-					),
-					digest: Digest { logs: vec![] },
-				},
-				extrinsics: vec![],
-			});
-		});
-	}
-
-	#[test]
-	#[should_panic]
-	fn block_import_of_bad_state_root_fails() {
-		new_test_ext(1).execute_with(|| {
-			Executive::execute_block(Block {
-				header: Header {
-					parent_hash: [69u8; 32].into(),
-					number: 1,
-					state_root: [0u8; 32].into(),
-					extrinsics_root: array_bytes::hex_n_into_unchecked(
-						"03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314",
-					),
-					digest: Digest { logs: vec![] },
-				},
-				extrinsics: vec![],
-			});
-		});
-	}
-
-	#[test]
-	#[should_panic]
-	fn block_import_of_bad_extrinsic_root_fails() {
-		new_test_ext(1).execute_with(|| {
-			Executive::execute_block(Block {
-				header: Header {
-					parent_hash: [69u8; 32].into(),
-					number: 1,
-					state_root: array_bytes::hex_n_into_unchecked(
-						"75e7d8f360d375bbe91bcf8019c01ab6362448b4a89e3b329717eb9d910340e5",
-					),
-					extrinsics_root: [0u8; 32].into(),
-					digest: Digest { logs: vec![] },
-				},
-				extrinsics: vec![],
-			});
-		});
-	}
-
-	#[test]
-	fn bad_extrinsic_not_inserted() {
-		let mut t = new_test_ext(1);
-		// bad nonce check!
-		let xt = TestXt::new(call_transfer(33, 69), sign_extra(1, 30, 0));
-		t.execute_with(|| {
-			Executive::initialize_block(&Header::new(
-				1,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-			assert_err!(
-				Executive::apply_extrinsic(xt),
-				TransactionValidityError::Invalid(InvalidTransaction::Future)
-			);
-			assert_eq!(<frame_system::Pallet<Runtime>>::extrinsic_index(), Some(0));
-		});
-	}
-
-	#[test]
-	fn block_weight_limit_enforced() {
-		let mut t = new_test_ext(10000);
-		// given: TestXt uses the encoded len as fixed Len:
-		let xt = TestXt::new(
-			RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
-			sign_extra(1, 0, 0),
-		);
-		let encoded = xt.encode();
-		let encoded_len = encoded.len() as u64;
-		// on_initialize weight + base block execution weight
-		let block_weights = <Runtime as frame_system::Config>::BlockWeights::get();
-		let base_block_weight = Weight::from_parts(175, 0) + block_weights.base_block;
-		let limit = block_weights.get(DispatchClass::Normal).max_total.unwrap() - base_block_weight;
-		let num_to_exhaust_block = limit.ref_time() / (encoded_len + 5);
-		t.execute_with(|| {
-			Executive::initialize_block(&Header::new(
-				1,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-			// Base block execution weight + `on_initialize` weight from the custom module.
-			assert_eq!(<frame_system::Pallet<Runtime>>::block_weight().total(), base_block_weight);
-
-			for nonce in 0..=num_to_exhaust_block {
-				let xt = TestXt::new(
-					RuntimeCall::Balances(BalancesCall::transfer_allow_death {
-						dest: 33,
-						value: 0,
-					}),
-					sign_extra(1, nonce.into(), 0),
-				);
-				let res = Executive::apply_extrinsic(xt);
-				if nonce != num_to_exhaust_block {
-					assert!(res.is_ok());
-					assert_eq!(
-						<frame_system::Pallet<Runtime>>::block_weight().total(),
-						//--------------------- on_initialize + block_execution + extrinsic_base weight
-						Weight::from_parts((encoded_len + 5) * (nonce + 1), 0) + base_block_weight,
-					);
-					assert_eq!(
-						<frame_system::Pallet<Runtime>>::extrinsic_index(),
-						Some(nonce as u32 + 1)
-					);
-				} else {
-					assert_eq!(res, Err(InvalidTransaction::ExhaustsResources.into()));
-				}
-			}
-		});
-	}
-
-	#[test]
-	fn block_weight_and_size_is_stored_per_tx() {
-		let xt = TestXt::new(
-			RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
-			sign_extra(1, 0, 0),
-		);
-		let x1 = TestXt::new(
-			RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
-			sign_extra(1, 1, 0),
-		);
-		let x2 = TestXt::new(
-			RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
-			sign_extra(1, 2, 0),
-		);
-		let len = xt.clone().encode().len() as u32;
-		let mut t = new_test_ext(1);
-		t.execute_with(|| {
-			// Block execution weight + on_initialize weight from custom module
-			let base_block_weight = Weight::from_parts(175, 0) +
-				<Runtime as frame_system::Config>::BlockWeights::get().base_block;
-
-			Executive::initialize_block(&Header::new(
-				1,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-
-			assert_eq!(<frame_system::Pallet<Runtime>>::block_weight().total(), base_block_weight);
-			assert_eq!(<frame_system::Pallet<Runtime>>::all_extrinsics_len(), 0);
-
-			assert!(Executive::apply_extrinsic(xt.clone()).unwrap().is_ok());
-			assert!(Executive::apply_extrinsic(x1.clone()).unwrap().is_ok());
-			assert!(Executive::apply_extrinsic(x2.clone()).unwrap().is_ok());
-
-			// default weight for `TestXt` == encoded length.
-			let extrinsic_weight = Weight::from_parts(len as u64, 0) +
-				<Runtime as frame_system::Config>::BlockWeights::get()
-					.get(DispatchClass::Normal)
-					.base_extrinsic;
-			assert_eq!(
-				<frame_system::Pallet<Runtime>>::block_weight().total(),
-				base_block_weight + 3u64 * extrinsic_weight,
-			);
-			assert_eq!(<frame_system::Pallet<Runtime>>::all_extrinsics_len(), 3 * len);
-
-			let _ = <frame_system::Pallet<Runtime>>::finalize();
-			// All extrinsics length cleaned on `System::finalize`
-			assert_eq!(<frame_system::Pallet<Runtime>>::all_extrinsics_len(), 0);
-
-			// New Block
-			Executive::initialize_block(&Header::new(
-				2,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-
-			// Block weight cleaned up on `System::initialize`
-			assert_eq!(<frame_system::Pallet<Runtime>>::block_weight().total(), base_block_weight);
-		});
-	}
-
-	#[test]
-	fn validate_unsigned() {
-		let valid = TestXt::new(RuntimeCall::Custom(custom::Call::allowed_unsigned {}), None);
-		let invalid = TestXt::new(RuntimeCall::Custom(custom::Call::unallowed_unsigned {}), None);
-		let mut t = new_test_ext(1);
-
-		t.execute_with(|| {
-			assert_eq!(
-				Executive::validate_transaction(
-					TransactionSource::InBlock,
-					valid.clone(),
-					Default::default(),
-				),
-				Ok(ValidTransaction::default()),
-			);
-			assert_eq!(
-				Executive::validate_transaction(
-					TransactionSource::InBlock,
-					invalid.clone(),
-					Default::default(),
-				),
-				Err(TransactionValidityError::Unknown(UnknownTransaction::NoUnsignedValidator)),
-			);
-			assert_eq!(Executive::apply_extrinsic(valid), Ok(Err(DispatchError::BadOrigin)));
-			assert_eq!(
-				Executive::apply_extrinsic(invalid),
-				Err(TransactionValidityError::Unknown(UnknownTransaction::NoUnsignedValidator))
-			);
-		});
-	}
-
-	#[test]
-	fn can_not_pay_for_tx_fee_on_full_lock() {
-		let mut t = new_test_ext(1);
-		t.execute_with(|| {
-			<pallet_balances::Pallet<Runtime> as fungible::MutateFreeze<u64>>::set_freeze(
-				&(),
-				&1,
-				110,
-			)
-			.unwrap();
-			let xt = TestXt::new(
-				RuntimeCall::System(frame_system::Call::remark { remark: vec![1u8] }),
-				sign_extra(1, 0, 0),
-			);
-			Executive::initialize_block(&Header::new(
-				1,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-
-			assert_eq!(Executive::apply_extrinsic(xt), Err(InvalidTransaction::Payment.into()),);
-			assert_eq!(<pallet_balances::Pallet<Runtime>>::total_balance(&1), 111);
-		});
-	}
-
-	#[test]
-	fn block_hooks_weight_is_stored() {
-		new_test_ext(1).execute_with(|| {
-			Executive::initialize_block(&Header::new_from_number(1));
-			Executive::finalize_block();
-			// NOTE: might need updates over time if new weights are introduced.
-			// For now it only accounts for the base block execution weight and
-			// the `on_initialize` weight defined in the custom test module.
-			assert_eq!(
-				<frame_system::Pallet<Runtime>>::block_weight().total(),
-				Weight::from_parts(175 + 175 + 10, 0)
-			);
-		})
-	}
-
-	#[test]
-	fn runtime_upgraded_should_work() {
-		new_test_ext(1).execute_with(|| {
-			RuntimeVersionTestValues::mutate(|v| *v = Default::default());
-			// It should be added at genesis
-			assert!(LastRuntimeUpgrade::<Runtime>::exists());
-			assert!(!Executive::runtime_upgraded());
-
-			RuntimeVersionTestValues::mutate(|v| {
-				*v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() }
-			});
-			assert!(Executive::runtime_upgraded());
-
-			RuntimeVersionTestValues::mutate(|v| {
-				*v = sp_version::RuntimeVersion {
-					spec_version: 1,
-					spec_name: "test".into(),
-					..Default::default()
-				}
-			});
-			assert!(Executive::runtime_upgraded());
-
-			RuntimeVersionTestValues::mutate(|v| {
-				*v = sp_version::RuntimeVersion {
-					spec_version: 0,
-					impl_version: 2,
-					..Default::default()
-				}
-			});
-			assert!(!Executive::runtime_upgraded());
-
-			LastRuntimeUpgrade::<Runtime>::take();
-			assert!(Executive::runtime_upgraded());
-		})
-	}
-
-	#[test]
-	fn last_runtime_upgrade_was_upgraded_works() {
-		let test_data = vec![
-			(0, "", 1, "", true),
-			(1, "", 1, "", false),
-			(1, "", 1, "test", true),
-			(1, "", 0, "", false),
-			(1, "", 0, "test", true),
-		];
-
-		for (spec_version, spec_name, c_spec_version, c_spec_name, result) in test_data {
-			let current = sp_version::RuntimeVersion {
-				spec_version: c_spec_version,
-				spec_name: c_spec_name.into(),
-				..Default::default()
-			};
-
-			let last = LastRuntimeUpgradeInfo {
-				spec_version: spec_version.into(),
-				spec_name: spec_name.into(),
-			};
-
-			assert_eq!(result, last.was_upgraded(&current));
-		}
-	}
-
-	#[test]
-	fn custom_runtime_upgrade_is_called_before_modules() {
-		new_test_ext(1).execute_with(|| {
-			// Make sure `on_runtime_upgrade` is called.
-			RuntimeVersionTestValues::mutate(|v| {
-				*v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() }
-			});
-
-			Executive::initialize_block(&Header::new(
-				1,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-
-			assert_eq!(&sp_io::storage::get(TEST_KEY).unwrap()[..], *b"module");
-			assert_eq!(sp_io::storage::get(CUSTOM_ON_RUNTIME_KEY).unwrap(), true.encode());
-			assert_eq!(
-				Some(RuntimeVersionTestValues::get().into()),
-				LastRuntimeUpgrade::<Runtime>::get(),
-			)
-		});
-	}
-
-	#[test]
-	fn event_from_runtime_upgrade_is_included() {
-		new_test_ext(1).execute_with(|| {
-			// Make sure `on_runtime_upgrade` is called.
-			RuntimeVersionTestValues::mutate(|v| {
-				*v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() }
-			});
-
-			// set block number to non zero so events are not excluded
-			System::set_block_number(1);
-
-			Executive::initialize_block(&Header::new(
-				2,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-
-			System::assert_last_event(frame_system::Event::<Runtime>::CodeUpdated.into());
-		});
-	}
-
-	/// Regression test that ensures that the custom on runtime upgrade is called when executive is
-	/// used through the `ExecuteBlock` trait.
-	#[test]
-	fn custom_runtime_upgrade_is_called_when_using_execute_block_trait() {
-		let xt = TestXt::new(
-			RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
-			sign_extra(1, 0, 0),
-		);
-
-		let header = new_test_ext(1).execute_with(|| {
-			// Make sure `on_runtime_upgrade` is called.
-			RuntimeVersionTestValues::mutate(|v| {
-				*v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() }
-			});
-
-			// Let's build some fake block.
-			Executive::initialize_block(&Header::new(
-				1,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-
-			Executive::apply_extrinsic(xt.clone()).unwrap().unwrap();
-
-			Executive::finalize_block()
-		});
-
-		// Reset to get the correct new genesis below.
-		RuntimeVersionTestValues::mutate(|v| {
-			*v = sp_version::RuntimeVersion { spec_version: 0, ..Default::default() }
-		});
-
-		new_test_ext(1).execute_with(|| {
-			// Make sure `on_runtime_upgrade` is called.
-			RuntimeVersionTestValues::mutate(|v| {
-				*v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() }
-			});
-
-			<Executive as ExecuteBlock<Block<TestXt>>>::execute_block(Block::new(header, vec![xt]));
-
-			assert_eq!(&sp_io::storage::get(TEST_KEY).unwrap()[..], *b"module");
-			assert_eq!(sp_io::storage::get(CUSTOM_ON_RUNTIME_KEY).unwrap(), true.encode());
-		});
-	}
-
-	#[test]
-	fn all_weights_are_recorded_correctly() {
-		// Reset to get the correct new genesis below.
-		RuntimeVersionTestValues::take();
-
-		new_test_ext(1).execute_with(|| {
-			// Make sure `on_runtime_upgrade` is called for maximum complexity
-			RuntimeVersionTestValues::mutate(|v| {
-				*v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() }
-			});
-
-			let block_number = 1;
-
-			Executive::initialize_block(&Header::new(
-				block_number,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-
-			// Reset the last runtime upgrade info, to make the second call to `on_runtime_upgrade`
-			// succeed.
-			LastRuntimeUpgrade::<Runtime>::take();
-
-			// All weights that show up in the `initialize_block_impl`
-			let custom_runtime_upgrade_weight = CustomOnRuntimeUpgrade::on_runtime_upgrade();
-			let runtime_upgrade_weight =
-				<AllPalletsWithSystem as OnRuntimeUpgrade>::on_runtime_upgrade();
-			let on_initialize_weight =
-				<AllPalletsWithSystem as OnInitialize<u64>>::on_initialize(block_number);
-			let base_block_weight =
-				<Runtime as frame_system::Config>::BlockWeights::get().base_block;
-
-			// Weights are recorded correctly
-			assert_eq!(
-				frame_system::Pallet::<Runtime>::block_weight().total(),
-				custom_runtime_upgrade_weight +
-					runtime_upgrade_weight +
-					on_initialize_weight + base_block_weight,
-			);
-		});
-	}
-
-	#[test]
-	fn offchain_worker_works_as_expected() {
-		new_test_ext(1).execute_with(|| {
-			let parent_hash = sp_core::H256::from([69u8; 32]);
-			let mut digest = Digest::default();
-			digest.push(DigestItem::Seal([1, 2, 3, 4], vec![5, 6, 7, 8]));
-
-			let header =
-				Header::new(1, H256::default(), H256::default(), parent_hash, digest.clone());
-
-			Executive::offchain_worker(&header);
-
-			assert_eq!(digest, System::digest());
-			assert_eq!(parent_hash, System::block_hash(0));
-			assert_eq!(header.hash(), System::block_hash(1));
-		});
-	}
-
-	#[test]
-	fn calculating_storage_root_twice_works() {
-		let call = RuntimeCall::Custom(custom::Call::calculate_storage_root {});
-		let xt = TestXt::new(call, sign_extra(1, 0, 0));
-
-		let header = new_test_ext(1).execute_with(|| {
-			// Let's build some fake block.
-			Executive::initialize_block(&Header::new(
-				1,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-
-			Executive::apply_extrinsic(xt.clone()).unwrap().unwrap();
-
-			Executive::finalize_block()
-		});
-
-		new_test_ext(1).execute_with(|| {
-			Executive::execute_block(Block::new(header, vec![xt]));
-		});
-	}
-
-	#[test]
-	#[should_panic(expected = "Invalid inherent position for extrinsic at index 1")]
-	fn invalid_inherent_position_fail() {
-		let xt1 = TestXt::new(
-			RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
-			sign_extra(1, 0, 0),
-		);
-		let xt2 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent_call {}), None);
-
-		let header = new_test_ext(1).execute_with(|| {
-			// Let's build some fake block.
-			Executive::initialize_block(&Header::new(
-				1,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-
-			Executive::apply_extrinsic(xt1.clone()).unwrap().unwrap();
-			Executive::apply_extrinsic(xt2.clone()).unwrap().unwrap();
-
-			Executive::finalize_block()
-		});
-
-		new_test_ext(1).execute_with(|| {
-			Executive::execute_block(Block::new(header, vec![xt1, xt2]));
-		});
-	}
-
-	#[test]
-	fn valid_inherents_position_works() {
-		let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent_call {}), None);
-		let xt2 = TestXt::new(call_transfer(33, 0), sign_extra(1, 0, 0));
-
-		let header = new_test_ext(1).execute_with(|| {
-			// Let's build some fake block.
-			Executive::initialize_block(&Header::new(
-				1,
-				H256::default(),
-				H256::default(),
-				[69u8; 32].into(),
-				Digest::default(),
-			));
-
-			Executive::apply_extrinsic(xt1.clone()).unwrap().unwrap();
-			Executive::apply_extrinsic(xt2.clone()).unwrap().unwrap();
-
-			Executive::finalize_block()
-		});
-
-		new_test_ext(1).execute_with(|| {
-			Executive::execute_block(Block::new(header, vec![xt1, xt2]));
-		});
-	}
-
-	#[test]
-	#[should_panic(expected = "A call was labelled as mandatory, but resulted in an Error.")]
-	fn invalid_inherents_fail_block_execution() {
-		let xt1 =
-			TestXt::new(RuntimeCall::Custom(custom::Call::inherent_call {}), sign_extra(1, 0, 0));
-
-		new_test_ext(1).execute_with(|| {
-			Executive::execute_block(Block::new(
-				Header::new(
-					1,
-					H256::default(),
-					H256::default(),
-					[69u8; 32].into(),
-					Digest::default(),
-				),
-				vec![xt1],
-			));
-		});
-	}
-
-	// Inherents are created by the runtime and don't need to be validated.
-	#[test]
-	fn inherents_fail_validate_block() {
-		let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent_call {}), None);
-
-		new_test_ext(1).execute_with(|| {
-			assert_eq!(
-				Executive::validate_transaction(TransactionSource::External, xt1, H256::random())
-					.unwrap_err(),
-				InvalidTransaction::MandatoryValidation.into()
-			);
-		})
-	}
-}
diff --git a/substrate/frame/executive/src/tests.rs b/substrate/frame/executive/src/tests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..70b55f6e85537246398b10f0880ec14681194b68
--- /dev/null
+++ b/substrate/frame/executive/src/tests.rs
@@ -0,0 +1,1389 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Test the `frame-executive` crate.
+
+use super::*;
+
+use sp_core::H256;
+use sp_runtime::{
+	generic::{DigestItem, Era},
+	testing::{Block, Digest, Header},
+	traits::{Block as BlockT, Header as HeaderT},
+	transaction_validity::{
+		InvalidTransaction, TransactionValidityError, UnknownTransaction, ValidTransaction,
+	},
+	BuildStorage, DispatchError,
+};
+
+use frame_support::{
+	assert_err, assert_ok, derive_impl,
+	migrations::MultiStepMigrator,
+	pallet_prelude::*,
+	parameter_types,
+	traits::{fungible, ConstU8, Currency, IsInherent},
+	weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight, WeightMeter, WeightToFee},
+};
+use frame_system::{pallet_prelude::*, ChainContext, LastRuntimeUpgrade, LastRuntimeUpgradeInfo};
+use pallet_balances::Call as BalancesCall;
+use pallet_transaction_payment::CurrencyAdapter;
+
+const TEST_KEY: &[u8] = b":test:key:";
+
+#[frame_support::pallet(dev_mode)]
+mod custom {
+	use super::*;
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::hooks]
+	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
+		// module hooks.
+		// one with block number arg and one without
+		fn on_initialize(_: BlockNumberFor<T>) -> Weight {
+			Weight::from_parts(175, 0)
+		}
+
+		fn on_idle(_: BlockNumberFor<T>, _: Weight) -> Weight {
+			Weight::from_parts(175, 0)
+		}
+
+		fn on_poll(_: BlockNumberFor<T>, _: &mut WeightMeter) {}
+
+		fn on_finalize(_: BlockNumberFor<T>) {}
+
+		fn on_runtime_upgrade() -> Weight {
+			sp_io::storage::set(super::TEST_KEY, "module".as_bytes());
+			Weight::from_parts(200, 0)
+		}
+
+		fn offchain_worker(n: BlockNumberFor<T>) {
+			assert_eq!(BlockNumberFor::<T>::from(1u32), n);
+		}
+	}
+
+	#[pallet::call]
+	impl<T: Config> Pallet<T> {
+		pub fn some_function(origin: OriginFor<T>) -> DispatchResult {
+			// NOTE: does not make any difference.
+			frame_system::ensure_signed(origin)?;
+			Ok(())
+		}
+
+		#[pallet::weight((200, DispatchClass::Operational))]
+		pub fn some_root_operation(origin: OriginFor<T>) -> DispatchResult {
+			frame_system::ensure_root(origin)?;
+			Ok(())
+		}
+
+		pub fn some_unsigned_message(origin: OriginFor<T>) -> DispatchResult {
+			frame_system::ensure_none(origin)?;
+			Ok(())
+		}
+
+		pub fn allowed_unsigned(origin: OriginFor<T>) -> DispatchResult {
+			frame_system::ensure_root(origin)?;
+			Ok(())
+		}
+
+		pub fn unallowed_unsigned(origin: OriginFor<T>) -> DispatchResult {
+			frame_system::ensure_root(origin)?;
+			Ok(())
+		}
+
+		#[pallet::weight((0, DispatchClass::Mandatory))]
+		pub fn inherent(origin: OriginFor<T>) -> DispatchResult {
+			frame_system::ensure_none(origin)?;
+			Ok(())
+		}
+
+		pub fn calculate_storage_root(_origin: OriginFor<T>) -> DispatchResult {
+			let root = sp_io::storage::root(sp_runtime::StateVersion::V1);
+			sp_io::storage::set("storage_root".as_bytes(), &root);
+			Ok(())
+		}
+	}
+
+	#[pallet::inherent]
+	impl<T: Config> ProvideInherent for Pallet<T> {
+		type Call = Call<T>;
+
+		type Error = sp_inherents::MakeFatalError<()>;
+
+		const INHERENT_IDENTIFIER: [u8; 8] = *b"test1234";
+
+		fn create_inherent(_data: &InherentData) -> Option<Self::Call> {
+			None
+		}
+
+		fn is_inherent(call: &Self::Call) -> bool {
+			*call == Call::<T>::inherent {}
+		}
+	}
+
+	#[pallet::validate_unsigned]
+	impl<T: Config> ValidateUnsigned for Pallet<T> {
+		type Call = Call<T>;
+
+		// Inherent call is accepted for being dispatched
+		fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
+			match call {
+				Call::allowed_unsigned { .. } => Ok(()),
+				Call::inherent { .. } => Ok(()),
+				_ => Err(UnknownTransaction::NoUnsignedValidator.into()),
+			}
+		}
+
+		// Inherent call is not validated as unsigned
+		fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
+			match call {
+				Call::allowed_unsigned { .. } => Ok(Default::default()),
+				_ => UnknownTransaction::NoUnsignedValidator.into(),
+			}
+		}
+	}
+}
+
+#[frame_support::pallet(dev_mode)]
+mod custom2 {
+	use super::*;
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::hooks]
+	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
+		// module hooks.
+		// one with block number arg and one without
+		fn on_initialize(_: BlockNumberFor<T>) -> Weight {
+			assert!(
+				!MockedSystemCallbacks::pre_inherent_called(),
+				"Pre inherent hook goes after on_initialize"
+			);
+
+			Weight::from_parts(0, 0)
+		}
+
+		fn on_idle(_: BlockNumberFor<T>, _: Weight) -> Weight {
+			assert!(
+				MockedSystemCallbacks::post_transactions_called(),
+				"Post transactions hook goes before after on_idle"
+			);
+			Weight::from_parts(0, 0)
+		}
+
+		fn on_finalize(_: BlockNumberFor<T>) {
+			assert!(
+				MockedSystemCallbacks::post_transactions_called(),
+				"Post transactions hook goes before after on_finalize"
+			);
+		}
+
+		fn on_runtime_upgrade() -> Weight {
+			sp_io::storage::set(super::TEST_KEY, "module".as_bytes());
+			Weight::from_parts(0, 0)
+		}
+	}
+
+	#[pallet::call]
+	impl<T: Config> Pallet<T> {
+		pub fn allowed_unsigned(origin: OriginFor<T>) -> DispatchResult {
+			frame_system::ensure_root(origin)?;
+			Ok(())
+		}
+
+		pub fn some_call(_: OriginFor<T>) -> DispatchResult {
+			assert!(MockedSystemCallbacks::post_inherent_called());
+			assert!(!MockedSystemCallbacks::post_transactions_called());
+			assert!(System::inherents_applied());
+
+			Ok(())
+		}
+
+		#[pallet::weight({0})]
+		pub fn optional_inherent(origin: OriginFor<T>) -> DispatchResult {
+			frame_system::ensure_none(origin)?;
+
+			assert!(MockedSystemCallbacks::pre_inherent_called());
+			assert!(!MockedSystemCallbacks::post_inherent_called(), "Should not already be called");
+			assert!(!System::inherents_applied());
+
+			Ok(())
+		}
+
+		#[pallet::weight((0, DispatchClass::Mandatory))]
+		pub fn inherent(origin: OriginFor<T>) -> DispatchResult {
+			frame_system::ensure_none(origin)?;
+
+			assert!(MockedSystemCallbacks::pre_inherent_called());
+			assert!(!MockedSystemCallbacks::post_inherent_called(), "Should not already be called");
+			assert!(!System::inherents_applied());
+
+			Ok(())
+		}
+	}
+
+	#[pallet::inherent]
+	impl<T: Config> ProvideInherent for Pallet<T> {
+		type Call = Call<T>;
+
+		type Error = sp_inherents::MakeFatalError<()>;
+
+		const INHERENT_IDENTIFIER: [u8; 8] = *b"test1235";
+
+		fn create_inherent(_data: &InherentData) -> Option<Self::Call> {
+			None
+		}
+
+		fn is_inherent(call: &Self::Call) -> bool {
+			matches!(call, Call::<T>::inherent {} | Call::<T>::optional_inherent {})
+		}
+	}
+
+	#[pallet::validate_unsigned]
+	impl<T: Config> ValidateUnsigned for Pallet<T> {
+		type Call = Call<T>;
+
+		// Inherent call is accepted for being dispatched
+		fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
+			match call {
+				Call::allowed_unsigned { .. } |
+				Call::optional_inherent { .. } |
+				Call::inherent { .. } => Ok(()),
+				_ => Err(UnknownTransaction::NoUnsignedValidator.into()),
+			}
+		}
+
+		// Inherent call is not validated as unsigned
+		fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
+			match call {
+				Call::allowed_unsigned { .. } => Ok(Default::default()),
+				_ => UnknownTransaction::NoUnsignedValidator.into(),
+			}
+		}
+	}
+}
+
+frame_support::construct_runtime!(
+	pub struct Runtime
+	{
+		System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
+		Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
+		TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
+		Custom: custom::{Pallet, Call, ValidateUnsigned, Inherent},
+		Custom2: custom2::{Pallet, Call, ValidateUnsigned, Inherent},
+	}
+);
+
+parameter_types! {
+	pub BlockWeights: frame_system::limits::BlockWeights =
+		frame_system::limits::BlockWeights::builder()
+			.base_block(Weight::from_parts(10, 0))
+			.for_class(DispatchClass::all(), |weights| weights.base_extrinsic = Weight::from_parts(5, 0))
+			.for_class(DispatchClass::non_mandatory(), |weights| weights.max_total = Weight::from_parts(1024, u64::MAX).into())
+			.build_or_panic();
+	pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight {
+		read: 10,
+		write: 100,
+	};
+}
+
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+impl frame_system::Config for Runtime {
+	type BlockWeights = BlockWeights;
+	type RuntimeOrigin = RuntimeOrigin;
+	type Nonce = u64;
+	type RuntimeCall = RuntimeCall;
+	type Block = TestBlock;
+	type RuntimeEvent = RuntimeEvent;
+	type Version = RuntimeVersion;
+	type AccountData = pallet_balances::AccountData<Balance>;
+	type PreInherents = MockedSystemCallbacks;
+	type PostInherents = MockedSystemCallbacks;
+	type PostTransactions = MockedSystemCallbacks;
+	type MultiBlockMigrator = MockedModeGetter;
+}
+
+type Balance = u64;
+
+#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
+impl pallet_balances::Config for Runtime {
+	type Balance = Balance;
+	type AccountStore = System;
+}
+
+parameter_types! {
+	pub const TransactionByteFee: Balance = 0;
+}
+impl pallet_transaction_payment::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type OnChargeTransaction = CurrencyAdapter<Balances, ()>;
+	type OperationalFeeMultiplier = ConstU8<5>;
+	type WeightToFee = IdentityFee<Balance>;
+	type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
+	type FeeMultiplierUpdate = ();
+}
+
+impl custom::Config for Runtime {}
+impl custom2::Config for Runtime {}
+
+pub struct RuntimeVersion;
+impl frame_support::traits::Get<sp_version::RuntimeVersion> for RuntimeVersion {
+	fn get() -> sp_version::RuntimeVersion {
+		RuntimeVersionTestValues::get().clone()
+	}
+}
+
+parameter_types! {
+	pub static RuntimeVersionTestValues: sp_version::RuntimeVersion =
+		Default::default();
+}
+
+type SignedExtra = (
+	frame_system::CheckEra<Runtime>,
+	frame_system::CheckNonce<Runtime>,
+	frame_system::CheckWeight<Runtime>,
+	pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
+);
+type TestXt = sp_runtime::testing::TestXt<RuntimeCall, SignedExtra>;
+type TestBlock = Block<TestXt>;
+
+// Will contain `true` when the custom runtime logic was called.
+const CUSTOM_ON_RUNTIME_KEY: &[u8] = b":custom:on_runtime";
+
+struct CustomOnRuntimeUpgrade;
+impl OnRuntimeUpgrade for CustomOnRuntimeUpgrade {
+	fn on_runtime_upgrade() -> Weight {
+		sp_io::storage::set(TEST_KEY, "custom_upgrade".as_bytes());
+		sp_io::storage::set(CUSTOM_ON_RUNTIME_KEY, &true.encode());
+		System::deposit_event(frame_system::Event::CodeUpdated);
+
+		assert_eq!(0, System::last_runtime_upgrade_spec_version());
+
+		Weight::from_parts(100, 0)
+	}
+}
+
+type Executive = super::Executive<
+	Runtime,
+	Block<TestXt>,
+	ChainContext<Runtime>,
+	Runtime,
+	AllPalletsWithSystem,
+	CustomOnRuntimeUpgrade,
+>;
+
+parameter_types! {
+	pub static SystemCallbacksCalled: u32 = 0;
+}
+
+pub struct MockedSystemCallbacks;
+impl PreInherents for MockedSystemCallbacks {
+	fn pre_inherents() {
+		assert_eq!(SystemCallbacksCalled::get(), 0);
+		SystemCallbacksCalled::set(1);
+		// Change the storage to modify the root hash:
+		frame_support::storage::unhashed::put(b":pre_inherent", b"0");
+	}
+}
+
+impl PostInherents for MockedSystemCallbacks {
+	fn post_inherents() {
+		assert_eq!(SystemCallbacksCalled::get(), 1);
+		SystemCallbacksCalled::set(2);
+		// Change the storage to modify the root hash:
+		frame_support::storage::unhashed::put(b":post_inherent", b"0");
+	}
+}
+
+impl PostTransactions for MockedSystemCallbacks {
+	fn post_transactions() {
+		assert_eq!(SystemCallbacksCalled::get(), 2);
+		SystemCallbacksCalled::set(3);
+		// Change the storage to modify the root hash:
+		frame_support::storage::unhashed::put(b":post_transaction", b"0");
+	}
+}
+
+impl MockedSystemCallbacks {
+	fn pre_inherent_called() -> bool {
+		SystemCallbacksCalled::get() >= 1
+	}
+
+	fn post_inherent_called() -> bool {
+		SystemCallbacksCalled::get() >= 2
+	}
+
+	fn post_transactions_called() -> bool {
+		SystemCallbacksCalled::get() >= 3
+	}
+
+	fn reset() {
+		SystemCallbacksCalled::set(0);
+		frame_support::storage::unhashed::kill(b":pre_inherent");
+		frame_support::storage::unhashed::kill(b":post_inherent");
+		frame_support::storage::unhashed::kill(b":post_transaction");
+	}
+}
+
+parameter_types! {
+	pub static MbmActive: bool = false;
+}
+
+pub struct MockedModeGetter;
+impl MultiStepMigrator for MockedModeGetter {
+	fn ongoing() -> bool {
+		MbmActive::get()
+	}
+
+	fn step() -> Weight {
+		Weight::zero()
+	}
+}
+
+fn extra(nonce: u64, fee: Balance) -> SignedExtra {
+	(
+		frame_system::CheckEra::from(Era::Immortal),
+		frame_system::CheckNonce::from(nonce),
+		frame_system::CheckWeight::new(),
+		pallet_transaction_payment::ChargeTransactionPayment::from(fee),
+	)
+}
+
+fn sign_extra(who: u64, nonce: u64, fee: Balance) -> Option<(u64, SignedExtra)> {
+	Some((who, extra(nonce, fee)))
+}
+
+fn call_transfer(dest: u64, value: u64) -> RuntimeCall {
+	RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value })
+}
+
+#[test]
+fn balance_transfer_dispatch_works() {
+	let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
+	pallet_balances::GenesisConfig::<Runtime> { balances: vec![(1, 211)] }
+		.assimilate_storage(&mut t)
+		.unwrap();
+	let xt = TestXt::new(call_transfer(2, 69), sign_extra(1, 0, 0));
+	let weight = xt.get_dispatch_info().weight +
+		<Runtime as frame_system::Config>::BlockWeights::get()
+			.get(DispatchClass::Normal)
+			.base_extrinsic;
+	let fee: Balance =
+		<Runtime as pallet_transaction_payment::Config>::WeightToFee::weight_to_fee(&weight);
+	let mut t = sp_io::TestExternalities::new(t);
+	t.execute_with(|| {
+		Executive::initialize_block(&Header::new_from_number(1));
+		let r = Executive::apply_extrinsic(xt);
+		assert!(r.is_ok());
+		assert_eq!(<pallet_balances::Pallet<Runtime>>::total_balance(&1), 142 - fee);
+		assert_eq!(<pallet_balances::Pallet<Runtime>>::total_balance(&2), 69);
+	});
+}
+
+fn new_test_ext(balance_factor: Balance) -> sp_io::TestExternalities {
+	let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
+	pallet_balances::GenesisConfig::<Runtime> { balances: vec![(1, 111 * balance_factor)] }
+		.assimilate_storage(&mut t)
+		.unwrap();
+	let mut ext: sp_io::TestExternalities = t.into();
+	ext.execute_with(|| {
+		SystemCallbacksCalled::set(0);
+	});
+	ext
+}
+
+fn new_test_ext_v0(balance_factor: Balance) -> sp_io::TestExternalities {
+	let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
+	pallet_balances::GenesisConfig::<Runtime> { balances: vec![(1, 111 * balance_factor)] }
+		.assimilate_storage(&mut t)
+		.unwrap();
+	(t, sp_runtime::StateVersion::V0).into()
+}
+
+#[test]
+fn block_import_works() {
+	block_import_works_inner(
+		new_test_ext_v0(1),
+		array_bytes::hex_n_into_unchecked(
+			"4826d3bdf87dbbc883d2ab274cbe272f58ed94a904619b59953e48294d1142d2",
+		),
+	);
+	block_import_works_inner(
+		new_test_ext(1),
+		array_bytes::hex_n_into_unchecked(
+			"d6b465f5a50c9f8d5a6edc0f01d285a6b19030f097d3aaf1649b7be81649f118",
+		),
+	);
+}
+fn block_import_works_inner(mut ext: sp_io::TestExternalities, state_root: H256) {
+	ext.execute_with(|| {
+		Executive::execute_block(Block {
+			header: Header {
+				parent_hash: [69u8; 32].into(),
+				number: 1,
+				state_root,
+				extrinsics_root: array_bytes::hex_n_into_unchecked(
+					"03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314",
+				),
+				digest: Digest { logs: vec![] },
+			},
+			extrinsics: vec![],
+		});
+	});
+}
+
+#[test]
+#[should_panic]
+fn block_import_of_bad_state_root_fails() {
+	new_test_ext(1).execute_with(|| {
+		Executive::execute_block(Block {
+			header: Header {
+				parent_hash: [69u8; 32].into(),
+				number: 1,
+				state_root: [0u8; 32].into(),
+				extrinsics_root: array_bytes::hex_n_into_unchecked(
+					"03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314",
+				),
+				digest: Digest { logs: vec![] },
+			},
+			extrinsics: vec![],
+		});
+	});
+}
+
+#[test]
+#[should_panic]
+fn block_import_of_bad_extrinsic_root_fails() {
+	new_test_ext(1).execute_with(|| {
+		Executive::execute_block(Block {
+			header: Header {
+				parent_hash: [69u8; 32].into(),
+				number: 1,
+				state_root: array_bytes::hex_n_into_unchecked(
+					"75e7d8f360d375bbe91bcf8019c01ab6362448b4a89e3b329717eb9d910340e5",
+				),
+				extrinsics_root: [0u8; 32].into(),
+				digest: Digest { logs: vec![] },
+			},
+			extrinsics: vec![],
+		});
+	});
+}
+
+#[test]
+fn bad_extrinsic_not_inserted() {
+	let mut t = new_test_ext(1);
+	// bad nonce check!
+	let xt = TestXt::new(call_transfer(33, 69), sign_extra(1, 30, 0));
+	t.execute_with(|| {
+		Executive::initialize_block(&Header::new_from_number(1));
+		assert_err!(
+			Executive::apply_extrinsic(xt),
+			TransactionValidityError::Invalid(InvalidTransaction::Future)
+		);
+		assert_eq!(<frame_system::Pallet<Runtime>>::extrinsic_index(), Some(0));
+	});
+}
+
+#[test]
+fn block_weight_limit_enforced() {
+	let mut t = new_test_ext(10000);
+	// given: TestXt uses the encoded len as fixed Len:
+	let xt = TestXt::new(
+		RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
+		sign_extra(1, 0, 0),
+	);
+	let encoded = xt.encode();
+	let encoded_len = encoded.len() as u64;
+	// on_initialize weight + base block execution weight
+	let block_weights = <Runtime as frame_system::Config>::BlockWeights::get();
+	let base_block_weight = Weight::from_parts(175, 0) + block_weights.base_block;
+	let limit = block_weights.get(DispatchClass::Normal).max_total.unwrap() - base_block_weight;
+	let num_to_exhaust_block = limit.ref_time() / (encoded_len + 5);
+	t.execute_with(|| {
+		Executive::initialize_block(&Header::new_from_number(1));
+		// Base block execution weight + `on_initialize` weight from the custom module.
+		assert_eq!(<frame_system::Pallet<Runtime>>::block_weight().total(), base_block_weight);
+
+		for nonce in 0..=num_to_exhaust_block {
+			let xt = TestXt::new(
+				RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
+				sign_extra(1, nonce.into(), 0),
+			);
+			let res = Executive::apply_extrinsic(xt);
+			if nonce != num_to_exhaust_block {
+				assert!(res.is_ok());
+				assert_eq!(
+					<frame_system::Pallet<Runtime>>::block_weight().total(),
+					//--------------------- on_initialize + block_execution + extrinsic_base weight
+					Weight::from_parts((encoded_len + 5) * (nonce + 1), 0) + base_block_weight,
+				);
+				assert_eq!(
+					<frame_system::Pallet<Runtime>>::extrinsic_index(),
+					Some(nonce as u32 + 1)
+				);
+			} else {
+				assert_eq!(res, Err(InvalidTransaction::ExhaustsResources.into()));
+			}
+		}
+	});
+}
+
+#[test]
+fn block_weight_and_size_is_stored_per_tx() {
+	let xt = TestXt::new(
+		RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
+		sign_extra(1, 0, 0),
+	);
+	let x1 = TestXt::new(
+		RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
+		sign_extra(1, 1, 0),
+	);
+	let x2 = TestXt::new(
+		RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
+		sign_extra(1, 2, 0),
+	);
+	let len = xt.clone().encode().len() as u32;
+	let mut t = new_test_ext(1);
+	t.execute_with(|| {
+		// Block execution weight + on_initialize weight from custom module
+		let base_block_weight = Weight::from_parts(175, 0) +
+			<Runtime as frame_system::Config>::BlockWeights::get().base_block;
+
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		assert_eq!(<frame_system::Pallet<Runtime>>::block_weight().total(), base_block_weight);
+		assert_eq!(<frame_system::Pallet<Runtime>>::all_extrinsics_len(), 0);
+
+		assert!(Executive::apply_extrinsic(xt.clone()).unwrap().is_ok());
+		assert!(Executive::apply_extrinsic(x1.clone()).unwrap().is_ok());
+		assert!(Executive::apply_extrinsic(x2.clone()).unwrap().is_ok());
+
+		// default weight for `TestXt` == encoded length.
+		let extrinsic_weight = Weight::from_parts(len as u64, 0) +
+			<Runtime as frame_system::Config>::BlockWeights::get()
+				.get(DispatchClass::Normal)
+				.base_extrinsic;
+		assert_eq!(
+			<frame_system::Pallet<Runtime>>::block_weight().total(),
+			base_block_weight + 3u64 * extrinsic_weight,
+		);
+		assert_eq!(<frame_system::Pallet<Runtime>>::all_extrinsics_len(), 3 * len);
+
+		let _ = <frame_system::Pallet<Runtime>>::finalize();
+		// All extrinsics length cleaned on `System::finalize`
+		assert_eq!(<frame_system::Pallet<Runtime>>::all_extrinsics_len(), 0);
+
+		// Reset to a new block.
+		SystemCallbacksCalled::take();
+		Executive::initialize_block(&Header::new_from_number(2));
+
+		// Block weight cleaned up on `System::initialize`
+		assert_eq!(<frame_system::Pallet<Runtime>>::block_weight().total(), base_block_weight);
+	});
+}
+
+#[test]
+fn validate_unsigned() {
+	let valid = TestXt::new(RuntimeCall::Custom(custom::Call::allowed_unsigned {}), None);
+	let invalid = TestXt::new(RuntimeCall::Custom(custom::Call::unallowed_unsigned {}), None);
+	let mut t = new_test_ext(1);
+
+	t.execute_with(|| {
+		assert_eq!(
+			Executive::validate_transaction(
+				TransactionSource::InBlock,
+				valid.clone(),
+				Default::default(),
+			),
+			Ok(ValidTransaction::default()),
+		);
+		assert_eq!(
+			Executive::validate_transaction(
+				TransactionSource::InBlock,
+				invalid.clone(),
+				Default::default(),
+			),
+			Err(TransactionValidityError::Unknown(UnknownTransaction::NoUnsignedValidator)),
+		);
+		// Need to initialize the block before applying extrinsics for the `MockedSystemCallbacks`
+		// check.
+		Executive::initialize_block(&Header::new_from_number(1));
+		assert_eq!(Executive::apply_extrinsic(valid), Ok(Err(DispatchError::BadOrigin)));
+		assert_eq!(
+			Executive::apply_extrinsic(invalid),
+			Err(TransactionValidityError::Unknown(UnknownTransaction::NoUnsignedValidator))
+		);
+	});
+}
+
+#[test]
+fn can_not_pay_for_tx_fee_on_full_lock() {
+	let mut t = new_test_ext(1);
+	t.execute_with(|| {
+		<pallet_balances::Pallet<Runtime> as fungible::MutateFreeze<u64>>::set_freeze(&(), &1, 110)
+			.unwrap();
+		let xt = TestXt::new(
+			RuntimeCall::System(frame_system::Call::remark { remark: vec![1u8] }),
+			sign_extra(1, 0, 0),
+		);
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		assert_eq!(Executive::apply_extrinsic(xt), Err(InvalidTransaction::Payment.into()),);
+		assert_eq!(<pallet_balances::Pallet<Runtime>>::total_balance(&1), 111);
+	});
+}
+
+#[test]
+fn block_hooks_weight_is_stored() {
+	new_test_ext(1).execute_with(|| {
+		Executive::initialize_block(&Header::new_from_number(1));
+		Executive::finalize_block();
+		// NOTE: might need updates over time if new weights are introduced.
+		// For now it only accounts for the base block execution weight and
+		// the `on_initialize` weight defined in the custom test module.
+		assert_eq!(
+			<frame_system::Pallet<Runtime>>::block_weight().total(),
+			Weight::from_parts(175 + 175 + 10, 0)
+		);
+	})
+}
+
+#[test]
+fn runtime_upgraded_should_work() {
+	new_test_ext(1).execute_with(|| {
+		RuntimeVersionTestValues::mutate(|v| *v = Default::default());
+		// It should be added at genesis
+		assert!(LastRuntimeUpgrade::<Runtime>::exists());
+		assert!(!Executive::runtime_upgraded());
+
+		RuntimeVersionTestValues::mutate(|v| {
+			*v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() }
+		});
+		assert!(Executive::runtime_upgraded());
+
+		RuntimeVersionTestValues::mutate(|v| {
+			*v = sp_version::RuntimeVersion {
+				spec_version: 1,
+				spec_name: "test".into(),
+				..Default::default()
+			}
+		});
+		assert!(Executive::runtime_upgraded());
+
+		RuntimeVersionTestValues::mutate(|v| {
+			*v = sp_version::RuntimeVersion {
+				spec_version: 0,
+				impl_version: 2,
+				..Default::default()
+			}
+		});
+		assert!(!Executive::runtime_upgraded());
+
+		LastRuntimeUpgrade::<Runtime>::take();
+		assert!(Executive::runtime_upgraded());
+	})
+}
+
+#[test]
+fn last_runtime_upgrade_was_upgraded_works() {
+	let test_data = vec![
+		(0, "", 1, "", true),
+		(1, "", 1, "", false),
+		(1, "", 1, "test", true),
+		(1, "", 0, "", false),
+		(1, "", 0, "test", true),
+	];
+
+	for (spec_version, spec_name, c_spec_version, c_spec_name, result) in test_data {
+		let current = sp_version::RuntimeVersion {
+			spec_version: c_spec_version,
+			spec_name: c_spec_name.into(),
+			..Default::default()
+		};
+
+		let last = LastRuntimeUpgradeInfo {
+			spec_version: spec_version.into(),
+			spec_name: spec_name.into(),
+		};
+
+		assert_eq!(result, last.was_upgraded(&current));
+	}
+}
+
+#[test]
+fn custom_runtime_upgrade_is_called_before_modules() {
+	new_test_ext(1).execute_with(|| {
+		// Make sure `on_runtime_upgrade` is called.
+		RuntimeVersionTestValues::mutate(|v| {
+			*v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() }
+		});
+
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		assert_eq!(&sp_io::storage::get(TEST_KEY).unwrap()[..], *b"module");
+		assert_eq!(sp_io::storage::get(CUSTOM_ON_RUNTIME_KEY).unwrap(), true.encode());
+		assert_eq!(
+			Some(RuntimeVersionTestValues::get().into()),
+			LastRuntimeUpgrade::<Runtime>::get(),
+		)
+	});
+}
+
+#[test]
+fn event_from_runtime_upgrade_is_included() {
+	new_test_ext(1).execute_with(|| {
+		// Make sure `on_runtime_upgrade` is called.
+		RuntimeVersionTestValues::mutate(|v| {
+			*v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() }
+		});
+
+		// set block number to non zero so events are not excluded
+		System::set_block_number(1);
+
+		Executive::initialize_block(&Header::new_from_number(2));
+		System::assert_last_event(frame_system::Event::<Runtime>::CodeUpdated.into());
+	});
+}
+
+/// Regression test that ensures that the custom on runtime upgrade is called when executive is
+/// used through the `ExecuteBlock` trait.
+#[test]
+fn custom_runtime_upgrade_is_called_when_using_execute_block_trait() {
+	let xt = TestXt::new(
+		RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
+		sign_extra(1, 0, 0),
+	);
+
+	let header = new_test_ext(1).execute_with(|| {
+		// Make sure `on_runtime_upgrade` is called.
+		RuntimeVersionTestValues::mutate(|v| {
+			*v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() }
+		});
+
+		// Let's build some fake block.
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		Executive::apply_extrinsic(xt.clone()).unwrap().unwrap();
+
+		Executive::finalize_block()
+	});
+
+	// Reset to get the correct new genesis below.
+	RuntimeVersionTestValues::mutate(|v| {
+		*v = sp_version::RuntimeVersion { spec_version: 0, ..Default::default() }
+	});
+
+	new_test_ext(1).execute_with(|| {
+		// Make sure `on_runtime_upgrade` is called.
+		RuntimeVersionTestValues::mutate(|v| {
+			*v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() }
+		});
+
+		<Executive as ExecuteBlock<Block<TestXt>>>::execute_block(Block::new(header, vec![xt]));
+
+		assert_eq!(&sp_io::storage::get(TEST_KEY).unwrap()[..], *b"module");
+		assert_eq!(sp_io::storage::get(CUSTOM_ON_RUNTIME_KEY).unwrap(), true.encode());
+	});
+}
+
+#[test]
+fn all_weights_are_recorded_correctly() {
+	// Reset to get the correct new genesis below.
+	RuntimeVersionTestValues::take();
+
+	new_test_ext(1).execute_with(|| {
+		// Make sure `on_runtime_upgrade` is called for maximum complexity
+		RuntimeVersionTestValues::mutate(|v| {
+			*v = sp_version::RuntimeVersion { spec_version: 1, ..Default::default() }
+		});
+
+		let block_number = 1;
+
+		Executive::initialize_block(&Header::new_from_number(block_number));
+
+		// Reset the last runtime upgrade info, to make the second call to `on_runtime_upgrade`
+		// succeed.
+		LastRuntimeUpgrade::<Runtime>::take();
+		MockedSystemCallbacks::reset();
+
+		// All weights that show up in the `initialize_block_impl`
+		let custom_runtime_upgrade_weight = CustomOnRuntimeUpgrade::on_runtime_upgrade();
+		let runtime_upgrade_weight =
+			<AllPalletsWithSystem as OnRuntimeUpgrade>::on_runtime_upgrade();
+		let on_initialize_weight =
+			<AllPalletsWithSystem as OnInitialize<u64>>::on_initialize(block_number);
+		let base_block_weight = <Runtime as frame_system::Config>::BlockWeights::get().base_block;
+
+		// Weights are recorded correctly
+		assert_eq!(
+			frame_system::Pallet::<Runtime>::block_weight().total(),
+			custom_runtime_upgrade_weight +
+				runtime_upgrade_weight +
+				on_initialize_weight +
+				base_block_weight,
+		);
+	});
+}
+
+#[test]
+fn offchain_worker_works_as_expected() {
+	new_test_ext(1).execute_with(|| {
+		let parent_hash = sp_core::H256::from([69u8; 32]);
+		let mut digest = Digest::default();
+		digest.push(DigestItem::Seal([1, 2, 3, 4], vec![5, 6, 7, 8]));
+
+		let header = Header::new(1, H256::default(), H256::default(), parent_hash, digest.clone());
+
+		Executive::offchain_worker(&header);
+
+		assert_eq!(digest, System::digest());
+		assert_eq!(parent_hash, System::block_hash(0));
+		assert_eq!(header.hash(), System::block_hash(1));
+	});
+}
+
+#[test]
+fn calculating_storage_root_twice_works() {
+	let call = RuntimeCall::Custom(custom::Call::calculate_storage_root {});
+	let xt = TestXt::new(call, sign_extra(1, 0, 0));
+
+	let header = new_test_ext(1).execute_with(|| {
+		// Let's build some fake block.
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		Executive::apply_extrinsic(xt.clone()).unwrap().unwrap();
+
+		Executive::finalize_block()
+	});
+
+	new_test_ext(1).execute_with(|| {
+		Executive::execute_block(Block::new(header, vec![xt]));
+	});
+}
+
+#[test]
+#[should_panic(expected = "Invalid inherent position for extrinsic at index 1")]
+fn invalid_inherent_position_fail() {
+	let xt1 = TestXt::new(
+		RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }),
+		sign_extra(1, 0, 0),
+	);
+	let xt2 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent {}), None);
+
+	let header = new_test_ext(1).execute_with(|| {
+		// Let's build some fake block.
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		Executive::apply_extrinsic(xt1.clone()).unwrap().unwrap();
+		Executive::apply_extrinsic(xt2.clone()).unwrap().unwrap();
+
+		Executive::finalize_block()
+	});
+
+	new_test_ext(1).execute_with(|| {
+		Executive::execute_block(Block::new(header, vec![xt1, xt2]));
+	});
+}
+
+#[test]
+fn valid_inherents_position_works() {
+	let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent {}), None);
+	let xt2 = TestXt::new(call_transfer(33, 0), sign_extra(1, 0, 0));
+
+	let header = new_test_ext(1).execute_with(|| {
+		// Let's build some fake block.
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		Executive::apply_extrinsic(xt1.clone()).unwrap().unwrap();
+		Executive::apply_extrinsic(xt2.clone()).unwrap().unwrap();
+
+		Executive::finalize_block()
+	});
+
+	new_test_ext(1).execute_with(|| {
+		Executive::execute_block(Block::new(header, vec![xt1, xt2]));
+	});
+}
+
+#[test]
+#[should_panic(expected = "A call was labelled as mandatory, but resulted in an Error.")]
+fn invalid_inherents_fail_block_execution() {
+	let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent {}), sign_extra(1, 0, 0));
+
+	new_test_ext(1).execute_with(|| {
+		Executive::execute_block(Block::new(
+			Header::new(1, H256::default(), H256::default(), [69u8; 32].into(), Digest::default()),
+			vec![xt1],
+		));
+	});
+}
+
+// Inherents are created by the runtime and don't need to be validated.
+#[test]
+fn inherents_fail_validate_block() {
+	let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent {}), None);
+
+	new_test_ext(1).execute_with(|| {
+		assert_eq!(
+			Executive::validate_transaction(TransactionSource::External, xt1, H256::random())
+				.unwrap_err(),
+			InvalidTransaction::MandatoryValidation.into()
+		);
+	})
+}
+
+/// Inherents still work while `initialize_block` forbids transactions.
+#[test]
+fn inherents_ok_while_exts_forbidden_works() {
+	let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent {}), None);
+
+	let header = new_test_ext(1).execute_with(|| {
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		Executive::apply_extrinsic(xt1.clone()).unwrap().unwrap();
+		// This is not applied:
+		Executive::finalize_block()
+	});
+
+	new_test_ext(1).execute_with(|| {
+		// Tell `initialize_block` to forbid extrinsics:
+		Executive::execute_block(Block::new(header, vec![xt1]));
+	});
+}
+
+/// Refuses to import blocks with transactions during `OnlyInherents` mode.
+#[test]
+#[should_panic = "Only inherents are allowed in this block"]
+fn transactions_in_only_inherents_block_errors() {
+	let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent {}), None);
+	let xt2 = TestXt::new(call_transfer(33, 0), sign_extra(1, 0, 0));
+
+	let header = new_test_ext(1).execute_with(|| {
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		Executive::apply_extrinsic(xt1.clone()).unwrap().unwrap();
+		Executive::apply_extrinsic(xt2.clone()).unwrap().unwrap();
+
+		Executive::finalize_block()
+	});
+
+	new_test_ext(1).execute_with(|| {
+		MbmActive::set(true);
+		Executive::execute_block(Block::new(header, vec![xt1, xt2]));
+	});
+}
+
+/// Same as above but no error.
+#[test]
+fn transactions_in_normal_block_works() {
+	let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent {}), None);
+	let xt2 = TestXt::new(call_transfer(33, 0), sign_extra(1, 0, 0));
+
+	let header = new_test_ext(1).execute_with(|| {
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		Executive::apply_extrinsic(xt1.clone()).unwrap().unwrap();
+		Executive::apply_extrinsic(xt2.clone()).unwrap().unwrap();
+
+		Executive::finalize_block()
+	});
+
+	new_test_ext(1).execute_with(|| {
+		// Tell `initialize_block` to forbid extrinsics:
+		Executive::execute_block(Block::new(header, vec![xt1, xt2]));
+	});
+}
+
+#[test]
+#[cfg(feature = "try-runtime")]
+fn try_execute_block_works() {
+	let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent {}), None);
+	let xt2 = TestXt::new(call_transfer(33, 0), sign_extra(1, 0, 0));
+
+	let header = new_test_ext(1).execute_with(|| {
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		Executive::apply_extrinsic(xt1.clone()).unwrap().unwrap();
+		Executive::apply_extrinsic(xt2.clone()).unwrap().unwrap();
+
+		Executive::finalize_block()
+	});
+
+	new_test_ext(1).execute_with(|| {
+		Executive::try_execute_block(
+			Block::new(header, vec![xt1, xt2]),
+			true,
+			true,
+			frame_try_runtime::TryStateSelect::All,
+		)
+		.unwrap();
+	});
+}
+
+/// Same as `extrinsic_while_exts_forbidden_errors` but using the try-runtime function.
+#[test]
+#[cfg(feature = "try-runtime")]
+#[should_panic = "Only inherents allowed"]
+fn try_execute_tx_forbidden_errors() {
+	let xt1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent {}), None);
+	let xt2 = TestXt::new(call_transfer(33, 0), sign_extra(1, 0, 0));
+
+	let header = new_test_ext(1).execute_with(|| {
+		// Let's build some fake block.
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		Executive::apply_extrinsic(xt1.clone()).unwrap().unwrap();
+		Executive::apply_extrinsic(xt2.clone()).unwrap().unwrap();
+
+		Executive::finalize_block()
+	});
+
+	new_test_ext(1).execute_with(|| {
+		MbmActive::set(true);
+		Executive::try_execute_block(
+			Block::new(header, vec![xt1, xt2]),
+			true,
+			true,
+			frame_try_runtime::TryStateSelect::All,
+		)
+		.unwrap();
+	});
+}
+
+/// Check that `ensure_inherents_are_first` reports the correct indices.
+#[test]
+fn ensure_inherents_are_first_works() {
+	let in1 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent {}), None);
+	let in2 = TestXt::new(RuntimeCall::Custom2(custom2::Call::inherent {}), None);
+	let xt2 = TestXt::new(call_transfer(33, 0), sign_extra(1, 0, 0));
+
+	// Mocked empty header:
+	let header = new_test_ext(1).execute_with(|| {
+		Executive::initialize_block(&Header::new_from_number(1));
+		Executive::finalize_block()
+	});
+
+	new_test_ext(1).execute_with(|| {
+		assert_ok!(Runtime::ensure_inherents_are_first(&Block::new(header.clone(), vec![]),), 0);
+		assert_ok!(
+			Runtime::ensure_inherents_are_first(&Block::new(header.clone(), vec![xt2.clone()]),),
+			0
+		);
+		assert_ok!(
+			Runtime::ensure_inherents_are_first(&Block::new(header.clone(), vec![in1.clone()])),
+			1
+		);
+		assert_ok!(
+			Runtime::ensure_inherents_are_first(&Block::new(
+				header.clone(),
+				vec![in1.clone(), xt2.clone()]
+			),),
+			1
+		);
+		assert_ok!(
+			Runtime::ensure_inherents_are_first(&Block::new(
+				header.clone(),
+				vec![in2.clone(), in1.clone(), xt2.clone()]
+			),),
+			2
+		);
+
+		assert_eq!(
+			Runtime::ensure_inherents_are_first(&Block::new(
+				header.clone(),
+				vec![xt2.clone(), in1.clone()]
+			),),
+			Err(1)
+		);
+		assert_eq!(
+			Runtime::ensure_inherents_are_first(&Block::new(
+				header.clone(),
+				vec![xt2.clone(), xt2.clone(), in1.clone()]
+			),),
+			Err(2)
+		);
+		assert_eq!(
+			Runtime::ensure_inherents_are_first(&Block::new(
+				header.clone(),
+				vec![xt2.clone(), xt2.clone(), xt2.clone(), in2.clone()]
+			),),
+			Err(3)
+		);
+	});
+}
+
+/// Check that block execution rejects blocks with transactions in them while MBMs are active and
+/// also that all the system callbacks are called correctly.
+#[test]
+fn callbacks_in_block_execution_works() {
+	callbacks_in_block_execution_works_inner(false);
+	callbacks_in_block_execution_works_inner(true);
+}
+
+fn callbacks_in_block_execution_works_inner(mbms_active: bool) {
+	MbmActive::set(mbms_active);
+
+	for (n_in, n_tx) in (0..10usize).zip(0..10usize) {
+		let mut extrinsics = Vec::new();
+
+		let header = new_test_ext(10).execute_with(|| {
+			MockedSystemCallbacks::reset();
+			Executive::initialize_block(&Header::new_from_number(1));
+			assert_eq!(SystemCallbacksCalled::get(), 1);
+
+			for i in 0..n_in {
+				let xt = if i % 2 == 0 {
+					TestXt::new(RuntimeCall::Custom(custom::Call::inherent {}), None)
+				} else {
+					TestXt::new(RuntimeCall::Custom2(custom2::Call::optional_inherent {}), None)
+				};
+				Executive::apply_extrinsic(xt.clone()).unwrap().unwrap();
+				extrinsics.push(xt);
+			}
+
+			for t in 0..n_tx {
+				let xt = TestXt::new(
+					RuntimeCall::Custom2(custom2::Call::some_call {}),
+					sign_extra(1, t as u64, 0),
+				);
+				// Extrinsics can be applied even when MBMs are active. Only the `execute_block`
+				// will reject it.
+				Executive::apply_extrinsic(xt.clone()).unwrap().unwrap();
+				extrinsics.push(xt);
+			}
+
+			Executive::finalize_block()
+		});
+		assert_eq!(SystemCallbacksCalled::get(), 3);
+
+		new_test_ext(10).execute_with(|| {
+			let header = std::panic::catch_unwind(|| {
+				Executive::execute_block(Block::new(header, extrinsics));
+			});
+
+			match header {
+				Err(e) => {
+					let err = e.downcast::<&str>().unwrap();
+					assert_eq!(*err, "Only inherents are allowed in this block");
+					assert!(
+						MbmActive::get() && n_tx > 0,
+						"Transactions should be rejected when MBMs are active"
+					);
+				},
+				Ok(_) => {
+					assert_eq!(SystemCallbacksCalled::get(), 3);
+					assert!(
+						!MbmActive::get() || n_tx == 0,
+						"MBMs should be deactivated after finalization"
+					);
+				},
+			}
+		});
+	}
+}
+
+#[test]
+fn post_inherent_called_after_all_inherents() {
+	let in1 = TestXt::new(RuntimeCall::Custom2(custom2::Call::inherent {}), None);
+	let xt1 = TestXt::new(RuntimeCall::Custom2(custom2::Call::some_call {}), sign_extra(1, 0, 0));
+
+	let header = new_test_ext(1).execute_with(|| {
+		// Let's build some fake block.
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		Executive::apply_extrinsic(in1.clone()).unwrap().unwrap();
+		Executive::apply_extrinsic(xt1.clone()).unwrap().unwrap();
+
+		Executive::finalize_block()
+	});
+
+	#[cfg(feature = "try-runtime")]
+	new_test_ext(1).execute_with(|| {
+		Executive::try_execute_block(
+			Block::new(header.clone(), vec![in1.clone(), xt1.clone()]),
+			true,
+			true,
+			frame_try_runtime::TryStateSelect::All,
+		)
+		.unwrap();
+		assert!(MockedSystemCallbacks::post_transactions_called());
+	});
+
+	new_test_ext(1).execute_with(|| {
+		MockedSystemCallbacks::reset();
+		Executive::execute_block(Block::new(header, vec![in1, xt1]));
+		assert!(MockedSystemCallbacks::post_transactions_called());
+	});
+}
+
+/// Regression test for AppSec finding #40.
+#[test]
+fn post_inherent_called_after_all_optional_inherents() {
+	let in1 = TestXt::new(RuntimeCall::Custom2(custom2::Call::optional_inherent {}), None);
+	let xt1 = TestXt::new(RuntimeCall::Custom2(custom2::Call::some_call {}), sign_extra(1, 0, 0));
+
+	let header = new_test_ext(1).execute_with(|| {
+		// Let's build some fake block.
+		Executive::initialize_block(&Header::new_from_number(1));
+
+		Executive::apply_extrinsic(in1.clone()).unwrap().unwrap();
+		Executive::apply_extrinsic(xt1.clone()).unwrap().unwrap();
+
+		Executive::finalize_block()
+	});
+
+	#[cfg(feature = "try-runtime")]
+	new_test_ext(1).execute_with(|| {
+		Executive::try_execute_block(
+			Block::new(header.clone(), vec![in1.clone(), xt1.clone()]),
+			true,
+			true,
+			frame_try_runtime::TryStateSelect::All,
+		)
+		.unwrap();
+		assert!(MockedSystemCallbacks::post_transactions_called());
+	});
+
+	new_test_ext(1).execute_with(|| {
+		MockedSystemCallbacks::reset();
+		Executive::execute_block(Block::new(header, vec![in1, xt1]));
+		assert!(MockedSystemCallbacks::post_transactions_called());
+	});
+}
+
+#[test]
+fn is_inherent_works() {
+	let ext = TestXt::new(RuntimeCall::Custom2(custom2::Call::inherent {}), None);
+	assert!(Runtime::is_inherent(&ext));
+	let ext = TestXt::new(RuntimeCall::Custom2(custom2::Call::optional_inherent {}), None);
+	assert!(Runtime::is_inherent(&ext));
+
+	let ext = TestXt::new(call_transfer(33, 0), sign_extra(1, 0, 0));
+	assert!(!Runtime::is_inherent(&ext));
+
+	let ext = TestXt::new(RuntimeCall::Custom2(custom2::Call::allowed_unsigned {}), None);
+	assert!(!Runtime::is_inherent(&ext), "Unsigned ext are not automatically inherents");
+}
diff --git a/substrate/frame/migrations/Cargo.toml b/substrate/frame/migrations/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..2914aa9ea973eafc3eee5419b3b8d8562b23c4f4
--- /dev/null
+++ b/substrate/frame/migrations/Cargo.toml
@@ -0,0 +1,64 @@
+[package]
+name = "pallet-migrations"
+version = "1.0.0"
+description = "FRAME pallet to execute multi-block migrations."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] }
+docify = "0.1.14"
+impl-trait-for-tuples = "0.2.2"
+log = "0.4.18"
+scale-info = { version = "2.0.0", default-features = false, features = ["derive"] }
+
+frame-benchmarking = { default-features = false, optional = true, path = "../benchmarking" }
+frame-support = { default-features = false, path = "../support" }
+frame-system = { default-features = false, path = "../system" }
+sp-core = { path = "../../primitives/core", default-features = false }
+sp-std = { path = "../../primitives/std", default-features = false }
+sp-runtime = { path = "../../primitives/runtime", default-features = false }
+
+[dev-dependencies]
+frame-executive = { path = "../executive" }
+sp-api = { path = "../../primitives/api", features = ["std"] }
+sp-block-builder = { path = "../../primitives/block-builder", features = ["std"] }
+sp-io = { path = "../../primitives/io", features = ["std"] }
+sp-tracing = { path = "../../primitives/tracing", features = ["std"] }
+sp-version = { path = "../../primitives/version", features = ["std"] }
+
+pretty_assertions = "1.3.0"
+
+[features]
+default = ["std"]
+
+std = [
+	"codec/std",
+	"frame-benchmarking?/std",
+	"frame-support/std",
+	"frame-system/std",
+	"log/std",
+	"scale-info/std",
+	"sp-core/std",
+	"sp-runtime/std",
+	"sp-std/std",
+]
+
+runtime-benchmarks = [
+	"frame-benchmarking/runtime-benchmarks",
+	"frame-support/runtime-benchmarks",
+	"frame-system/runtime-benchmarks",
+	"sp-runtime/runtime-benchmarks",
+]
+
+try-runtime = [
+	"frame-executive/try-runtime",
+	"frame-support/try-runtime",
+	"frame-system/try-runtime",
+	"sp-runtime/try-runtime",
+]
diff --git a/substrate/frame/migrations/src/benchmarking.rs b/substrate/frame/migrations/src/benchmarking.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8ad1fa50d14985842051c9ac6fb3f95e30bdb030
--- /dev/null
+++ b/substrate/frame/migrations/src/benchmarking.rs
@@ -0,0 +1,222 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![cfg(feature = "runtime-benchmarks")]
+
+use super::*;
+
+use frame_benchmarking::{v2::*, BenchmarkError};
+use frame_system::{Pallet as System, RawOrigin};
+use sp_runtime::traits::One;
+
+fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
+	frame_system::Pallet::<T>::assert_last_event(generic_event.into());
+}
+
+#[benchmarks]
+mod benches {
+	use super::*;
+	use frame_support::traits::Hooks;
+
+	#[benchmark]
+	fn onboard_new_mbms() {
+		T::Migrations::set_fail_after(0); // Should not be called anyway.
+		assert!(!Cursor::<T>::exists());
+
+		#[block]
+		{
+			Pallet::<T>::onboard_new_mbms();
+		}
+
+		assert_last_event::<T>(Event::UpgradeStarted { migrations: 1 }.into());
+	}
+
+	#[benchmark]
+	fn progress_mbms_none() {
+		T::Migrations::set_fail_after(0); // Should not be called anyway.
+		assert!(!Cursor::<T>::exists());
+
+		#[block]
+		{
+			Pallet::<T>::progress_mbms(One::one());
+		}
+	}
+
+	/// All migrations completed.
+	#[benchmark]
+	fn exec_migration_completed() -> Result<(), BenchmarkError> {
+		T::Migrations::set_fail_after(0); // Should not be called anyway.
+		assert_eq!(T::Migrations::len(), 1, "Setup failed");
+		let c = ActiveCursor { index: 1, inner_cursor: None, started_at: 0u32.into() };
+		let mut meter = WeightMeter::with_limit(T::MaxServiceWeight::get());
+		System::<T>::set_block_number(1u32.into());
+
+		#[block]
+		{
+			Pallet::<T>::exec_migration(c, false, &mut meter);
+		}
+
+		assert_last_event::<T>(Event::UpgradeCompleted {}.into());
+
+		Ok(())
+	}
+
+	/// No migration runs since it is skipped as historic.
+	#[benchmark]
+	fn exec_migration_skipped_historic() -> Result<(), BenchmarkError> {
+		T::Migrations::set_fail_after(0); // Should not be called anyway.
+		assert_eq!(T::Migrations::len(), 1, "Setup failed");
+		let c = ActiveCursor { index: 0, inner_cursor: None, started_at: 0u32.into() };
+
+		let id: IdentifierOf<T> = T::Migrations::nth_id(0).unwrap().try_into().unwrap();
+		Historic::<T>::insert(id, ());
+
+		let mut meter = WeightMeter::with_limit(T::MaxServiceWeight::get());
+		System::<T>::set_block_number(1u32.into());
+
+		#[block]
+		{
+			Pallet::<T>::exec_migration(c, false, &mut meter);
+		}
+
+		assert_last_event::<T>(Event::MigrationSkipped { index: 0 }.into());
+
+		Ok(())
+	}
+
+	/// Advance a migration by one step.
+	#[benchmark]
+	fn exec_migration_advance() -> Result<(), BenchmarkError> {
+		T::Migrations::set_success_after(1);
+		assert_eq!(T::Migrations::len(), 1, "Setup failed");
+		let c = ActiveCursor { index: 0, inner_cursor: None, started_at: 0u32.into() };
+		let mut meter = WeightMeter::with_limit(T::MaxServiceWeight::get());
+		System::<T>::set_block_number(1u32.into());
+
+		#[block]
+		{
+			Pallet::<T>::exec_migration(c, false, &mut meter);
+		}
+
+		assert_last_event::<T>(Event::MigrationAdvanced { index: 0, took: One::one() }.into());
+
+		Ok(())
+	}
+
+	/// Successfully complete a migration.
+	#[benchmark]
+	fn exec_migration_complete() -> Result<(), BenchmarkError> {
+		T::Migrations::set_success_after(0);
+		assert_eq!(T::Migrations::len(), 1, "Setup failed");
+		let c = ActiveCursor { index: 0, inner_cursor: None, started_at: 0u32.into() };
+		let mut meter = WeightMeter::with_limit(T::MaxServiceWeight::get());
+		System::<T>::set_block_number(1u32.into());
+
+		#[block]
+		{
+			Pallet::<T>::exec_migration(c, false, &mut meter);
+		}
+
+		assert_last_event::<T>(Event::MigrationCompleted { index: 0, took: One::one() }.into());
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn exec_migration_fail() -> Result<(), BenchmarkError> {
+		T::Migrations::set_fail_after(0);
+		assert_eq!(T::Migrations::len(), 1, "Setup failed");
+		let c = ActiveCursor { index: 0, inner_cursor: None, started_at: 0u32.into() };
+		let mut meter = WeightMeter::with_limit(T::MaxServiceWeight::get());
+		System::<T>::set_block_number(1u32.into());
+
+		#[block]
+		{
+			Pallet::<T>::exec_migration(c, false, &mut meter);
+		}
+
+		assert_last_event::<T>(Event::UpgradeFailed {}.into());
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn on_init_loop() {
+		T::Migrations::set_fail_after(0); // Should not be called anyway.
+		System::<T>::set_block_number(1u32.into());
+		Pallet::<T>::on_runtime_upgrade();
+
+		#[block]
+		{
+			Pallet::<T>::on_initialize(1u32.into());
+		}
+	}
+
+	#[benchmark]
+	fn force_set_cursor() {
+		#[extrinsic_call]
+		_(RawOrigin::Root, Some(cursor::<T>()));
+	}
+
+	#[benchmark]
+	fn force_set_active_cursor() {
+		#[extrinsic_call]
+		_(RawOrigin::Root, 0, None, None);
+	}
+
+	#[benchmark]
+	fn force_onboard_mbms() {
+		#[extrinsic_call]
+		_(RawOrigin::Root);
+	}
+
+	#[benchmark]
+	fn clear_historic(n: Linear<0, { DEFAULT_HISTORIC_BATCH_CLEAR_SIZE * 2 }>) {
+		let id_max_len = <T as Config>::IdentifierMaxLen::get();
+		assert!(id_max_len >= 4, "Precondition violated");
+
+		for i in 0..DEFAULT_HISTORIC_BATCH_CLEAR_SIZE * 2 {
+			let id = IdentifierOf::<T>::truncate_from(
+				i.encode().into_iter().cycle().take(id_max_len as usize).collect::<Vec<_>>(),
+			);
+
+			Historic::<T>::insert(&id, ());
+		}
+
+		#[extrinsic_call]
+		_(
+			RawOrigin::Root,
+			HistoricCleanupSelector::Wildcard { limit: n.into(), previous_cursor: None },
+		);
+	}
+
+	fn cursor<T: Config>() -> CursorOf<T> {
+		// Note: The weight of a function can depend on the weight of reading the `inner_cursor`.
+		// `Cursor` is a user provided type. Now instead of requiring something like `Cursor:
+		// From<u32>`, we instead rely on the fact that it is MEL and the PoV benchmarking will
+		// therefore already take the MEL bound, even when the cursor in storage is `None`.
+		MigrationCursor::Active(ActiveCursor {
+			index: u32::MAX,
+			inner_cursor: None,
+			started_at: 0u32.into(),
+		})
+	}
+
+	// Implements a test for each benchmark. Execute with:
+	// `cargo test -p pallet-migrations --features runtime-benchmarks`.
+	impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
+}
diff --git a/substrate/frame/migrations/src/lib.rs b/substrate/frame/migrations/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..cd57d89f440f79d39f98a3da81ed57b6cb5bfcf5
--- /dev/null
+++ b/substrate/frame/migrations/src/lib.rs
@@ -0,0 +1,746 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![deny(missing_docs)]
+#![deny(rustdoc::broken_intra_doc_links)]
+
+//! # `pallet-migrations`
+//!
+//! Provides multi block migrations for FRAME runtimes.
+//!
+//! ## Overview
+//!
+//! The pallet takes care of executing a batch of multi-step migrations over multiple blocks. The
+//! process starts on each runtime upgrade. Normal and operational transactions are paused while
+//! migrations are on-going.
+//!
+//! ### Example
+//!
+//! This example demonstrates a simple mocked walk through of a basic success scenario. The pallet
+//! is configured with two migrations: one succeeding after just one step, and the second one
+//! succeeding after two steps. A runtime upgrade is then enacted and the block number is advanced
+//! until all migrations finish executing. Afterwards, the recorded historic migrations are
+//! checked and events are asserted.
+#![doc = docify::embed!("substrate/frame/migrations/src/tests.rs", simple_works)]
+//!
+//! ## Pallet API
+//!
+//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
+//! including its configuration trait, dispatchables, storage items, events and errors.
+//!
+//! Otherwise noteworthy API of this pallet include its implementation of the
+//! [`MultiStepMigrator`] trait. This must be plugged into
+//! [`frame_system::Config::MultiBlockMigrator`] for proper function.
+//!
+//! The API contains some calls for emergency management. They are all prefixed with `force_` and
+//! should normally not be needed. Pay special attention prior to using them.
+//!
+//! ### Design Goals
+//!
+//! 1. Must automatically execute migrations over multiple blocks.
+//! 2. Must expose information about whether migrations are ongoing.
+//! 3. Must respect pessimistic weight bounds of migrations.
+//! 4. Must execute migrations in order. Skipping is not allowed; migrations are run on a
+//! all-or-nothing basis.
+//! 5. Must prevent re-execution of past migrations.
+//! 6. Must provide transactional storage semantics for migrations.
+//! 7. Must guarantee progress.
+//!
+//! ### Design
+//!
+//! Migrations are provided to the pallet through the associated type [`Config::Migrations`] of type
+//! [`SteppedMigrations`]. This allows multiple migrations to be aggregated through a tuple. It
+//! simplifies the trait bounds since all associated types of the trait must be provided by the
+//! pallet. The actual progress of the pallet is stored in the [`Cursor`] storage item. This can
+//! either be [`MigrationCursor::Active`] or [`MigrationCursor::Stuck`]. In the active case it
+//! points to the currently active migration and stores its inner cursor. The inner cursor can then
+//! be used by the migration to store its inner state and advance. Each time when the migration
+//! returns `Some(cursor)`, it signals the pallet that it is not done yet.  
+//! The cursor is reset on each runtime upgrade. This ensures that it starts to execute at the
+//! first migration in the vector. The pallets cursor is only ever incremented or set to `Stuck`
+//! once it encounters an error (Goal 4). Once in the stuck state, the pallet will stay stuck until
+//! it is fixed through manual governance intervention.  
+//! As soon as the cursor of the pallet becomes `Some(_)`; [`MultiStepMigrator::ongoing`] returns
+//! `true` (Goal 2). This can be used by upstream code to possibly pause transactions.
+//! In `on_initialize` the pallet will load the current migration and check whether it was already
+//! executed in the past by checking for membership of its ID in the [`Historic`] set. Historic
+//! migrations are skipped without causing an error. Each successfully executed migration is added
+//! to this set (Goal 5).  
+//! This proceeds until no more migrations remain. At that point, the event `UpgradeCompleted` is
+//! emitted (Goal 1).  
+//! The execution of each migration happens by calling [`SteppedMigration::transactional_step`].
+//! This function wraps the inner `step` function into a transactional layer to allow rollback in
+//! the error case (Goal 6).  
+//! Weight limits must be checked by the migration itself. The pallet provides a [`WeightMeter`] for
+//! that purpose. The pallet may return [`SteppedMigrationError::InsufficientWeight`] at any point.
+//! In that scenario, one of two things will happen: if that migration was exclusively executed
+//! in this block, and therefore required more than the maximum amount of weight possible, the
+//! process becomes `Stuck`. Otherwise, one re-attempt is executed with the same logic in the next
+//! block (Goal 3). Progress through the migrations is guaranteed by providing a timeout for each
+//! migration via [`SteppedMigration::max_steps`]. The pallet **ONLY** guarantees progress if this
+//! is set to sensible limits (Goal 7).
+//!
+//! ### Scenario: Governance cleanup
+//!
+//! Every now and then, governance can make use of the [`clear_historic`][Pallet::clear_historic]
+//! call. This ensures that no old migrations pile up in the [`Historic`] set. This can be done very
+//! rarely, since the storage should not grow quickly and the lookup weight does not suffer much.
+//! Another possibility would be to have a synchronous single-block migration perpetually deployed
+//! that cleans them up before the MBMs start.
+//!
+//! ### Scenario: Successful upgrade
+//!
+//! The standard procedure for a successful runtime upgrade can look like this:
+//! 1. Migrations are configured in the `Migrations` config item. All migrations expose
+//! [`max_steps`][SteppedMigration::max_steps], are error tolerant, check their weight bounds and
+//! have a unique identifier.
+//! 2. The runtime upgrade is enacted. An `UpgradeStarted` event is
+//! followed by lots of `MigrationAdvanced` and `MigrationCompleted` events. Finally
+//! `UpgradeCompleted` is emitted.
+//! 3. Cleanup as described in the governance scenario be executed at any time after the migrations
+//! completed.
+//!
+//! ### Advice: Failed upgrades
+//!
+//! Failed upgrades cannot be recovered from automatically and require governance intervention. Set
+//! up monitoring for `UpgradeFailed` events to be made aware of any failures. The hook
+//! [`FailedMigrationHandler::failed`] should be setup in a way that it allows governance to act,
+//! but still prevent other transactions from interacting with the inconsistent storage state. Note
+//! that this is paramount, since the inconsistent state might contain a faulty balance amount or
+//! similar that could cause great harm if user transactions don't remain suspended. One way to
+//! implement this would be to use the `SafeMode` or `TxPause` pallets that can prevent most user
+//! interactions but still allow a whitelisted set of governance calls.
+//!
+//! ### Remark: Failed migrations
+//!
+//! Failed migrations are not added to the `Historic` set. This means that an erroneous
+//! migration must be removed and fixed manually. This already applies, even before considering the
+//! historic set.
+//!
+//! ### Remark: Transactional processing
+//!
+//! You can see the transactional semantics for migration steps as mostly useless, since in the
+//! stuck case the state is already messed up. This just prevents it from becoming even more messed
+//! up, but doesn't prevent it in the first place.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+mod benchmarking;
+mod mock;
+pub mod mock_helpers;
+mod tests;
+pub mod weights;
+
+pub use pallet::*;
+pub use weights::WeightInfo;
+
+use codec::{Decode, Encode, MaxEncodedLen};
+use core::ops::ControlFlow;
+use frame_support::{
+	defensive, defensive_assert,
+	migrations::*,
+	traits::Get,
+	weights::{Weight, WeightMeter},
+	BoundedVec,
+};
+use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System};
+use sp_runtime::Saturating;
+use sp_std::vec::Vec;
+
+/// Points to the next migration to execute.
+#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
+pub enum MigrationCursor<Cursor, BlockNumber> {
+	/// Points to the currently active migration and its inner cursor.
+	Active(ActiveCursor<Cursor, BlockNumber>),
+
+	/// Migration got stuck and cannot proceed. This is bad.
+	Stuck,
+}
+
+impl<Cursor, BlockNumber> MigrationCursor<Cursor, BlockNumber> {
+	/// Try to return self as an [`ActiveCursor`].
+	pub fn as_active(&self) -> Option<&ActiveCursor<Cursor, BlockNumber>> {
+		match self {
+			MigrationCursor::Active(active) => Some(active),
+			MigrationCursor::Stuck => None,
+		}
+	}
+}
+
+impl<Cursor, BlockNumber> From<ActiveCursor<Cursor, BlockNumber>>
+	for MigrationCursor<Cursor, BlockNumber>
+{
+	fn from(active: ActiveCursor<Cursor, BlockNumber>) -> Self {
+		MigrationCursor::Active(active)
+	}
+}
+
+/// Points to the currently active migration and its inner cursor.
+#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
+pub struct ActiveCursor<Cursor, BlockNumber> {
+	/// The index of the migration in the MBM tuple.
+	pub index: u32,
+	/// The cursor of the migration that is referenced by `index`.
+	pub inner_cursor: Option<Cursor>,
+	/// The block number that the migration started at.
+	///
+	/// This is used to calculate how many blocks it took.
+	pub started_at: BlockNumber,
+}
+
+impl<Cursor, BlockNumber> ActiveCursor<Cursor, BlockNumber> {
+	/// Advance the overarching cursor to the next migration.
+	pub(crate) fn goto_next_migration(&mut self, current_block: BlockNumber) {
+		self.index.saturating_inc();
+		self.inner_cursor = None;
+		self.started_at = current_block;
+	}
+}
+
+/// How to clear the records of historic migrations.
+#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo)]
+pub enum HistoricCleanupSelector<Id> {
+	/// Clear exactly these entries.
+	///
+	/// This is the advised way of doing it.
+	Specific(Vec<Id>),
+
+	/// Clear up to this many entries
+	Wildcard {
+		/// How many should be cleared in this call at most.
+		limit: Option<u32>,
+		/// The cursor that was emitted from any previous `HistoricCleared`.
+		///
+		/// Does not need to be passed when clearing the first batch.
+		previous_cursor: Option<Vec<u8>>,
+	},
+}
+
+/// The default number of entries that should be cleared by a `HistoricCleanupSelector::Wildcard`.
+///
+/// The caller can explicitly specify a higher amount. Benchmarks are run with twice this value.
+const DEFAULT_HISTORIC_BATCH_CLEAR_SIZE: u32 = 128;
+
+impl<Id> HistoricCleanupSelector<Id> {
+	/// The maximal number of entries that this will remove.
+	///
+	/// Needed for weight calculation.
+	pub fn limit(&self) -> u32 {
+		match self {
+			Self::Specific(ids) => ids.len() as u32,
+			Self::Wildcard { limit, .. } => limit.unwrap_or(DEFAULT_HISTORIC_BATCH_CLEAR_SIZE),
+		}
+	}
+}
+
+/// Convenience alias for [`MigrationCursor`].
+pub type CursorOf<T> = MigrationCursor<RawCursorOf<T>, BlockNumberFor<T>>;
+
+/// Convenience alias for the raw inner cursor of a migration.
+pub type RawCursorOf<T> = BoundedVec<u8, <T as Config>::CursorMaxLen>;
+
+/// Convenience alias for the identifier of a migration.
+pub type IdentifierOf<T> = BoundedVec<u8, <T as Config>::IdentifierMaxLen>;
+
+/// Convenience alias for [`ActiveCursor`].
+pub type ActiveCursorOf<T> = ActiveCursor<RawCursorOf<T>, BlockNumberFor<T>>;
+
+/// Trait for a tuple of No-OP migrations with one element.
+pub trait MockedMigrations: SteppedMigrations {
+	/// The migration should fail after `n` steps.
+	fn set_fail_after(n: u32);
+	/// The migration should succeed after `n` steps.
+	fn set_success_after(n: u32);
+}
+
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+	use frame_support::pallet_prelude::*;
+	use frame_system::pallet_prelude::*;
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {
+		/// The overarching event type of the runtime.
+		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
+
+		/// All the multi-block migrations to run.
+		///
+		/// Should only be updated in a runtime-upgrade once all the old migrations have completed.
+		/// (Check that [`Cursor`] is `None`).
+		#[cfg(not(feature = "runtime-benchmarks"))]
+		type Migrations: SteppedMigrations;
+
+		/// Mocked migrations for benchmarking only.
+		///
+		/// Should be configured to [`crate::mock_helpers::MockedMigrations`] in benchmarks.
+		#[cfg(feature = "runtime-benchmarks")]
+		type Migrations: MockedMigrations;
+
+		/// The maximal length of an encoded cursor.
+		///
+		/// A good default needs to selected such that no migration will ever have a cursor with MEL
+		/// above this limit. This is statically checked in `integrity_test`.
+		#[pallet::constant]
+		type CursorMaxLen: Get<u32>;
+
+		/// The maximal length of an encoded identifier.
+		///
+		/// A good default needs to selected such that no migration will ever have an identifier
+		/// with MEL above this limit. This is statically checked in `integrity_test`.
+		#[pallet::constant]
+		type IdentifierMaxLen: Get<u32>;
+
+		/// Notifications for status updates of a runtime upgrade.
+		///
+		/// Could be used to pause XCM etc.
+		type MigrationStatusHandler: MigrationStatusHandler;
+
+		/// Handler for failed migrations.
+		type FailedMigrationHandler: FailedMigrationHandler;
+
+		/// The maximum weight to spend each block to execute migrations.
+		type MaxServiceWeight: Get<Weight>;
+
+		/// Weight information for the calls and functions of this pallet.
+		type WeightInfo: WeightInfo;
+	}
+
+	/// The currently active migration to run and its cursor.
+	///
+	/// `None` indicates that no migration is running.
+	#[pallet::storage]
+	pub type Cursor<T: Config> = StorageValue<_, CursorOf<T>, OptionQuery>;
+
+	/// Set of all successfully executed migrations.
+	///
+	/// This is used as blacklist, to not re-execute migrations that have not been removed from the
+	/// codebase yet. Governance can regularly clear this out via `clear_historic`.
+	#[pallet::storage]
+	pub type Historic<T: Config> = StorageMap<_, Twox64Concat, IdentifierOf<T>, (), OptionQuery>;
+
+	#[pallet::event]
+	#[pallet::generate_deposit(pub(super) fn deposit_event)]
+	pub enum Event<T: Config> {
+		/// A Runtime upgrade started.
+		///
+		/// Its end is indicated by `UpgradeCompleted` or `UpgradeFailed`.
+		UpgradeStarted {
+			/// The number of migrations that this upgrade contains.
+			///
+			/// This can be used to design a progress indicator in combination with counting the
+			/// `MigrationCompleted` and `MigrationSkipped` events.
+			migrations: u32,
+		},
+		/// The current runtime upgrade completed.
+		///
+		/// This implies that all of its migrations completed successfully as well.
+		UpgradeCompleted,
+		/// Runtime upgrade failed.
+		///
+		/// This is very bad and will require governance intervention.
+		UpgradeFailed,
+		/// A migration was skipped since it was already executed in the past.
+		MigrationSkipped {
+			/// The index of the skipped migration within the [`Config::Migrations`] list.
+			index: u32,
+		},
+		/// A migration progressed.
+		MigrationAdvanced {
+			/// The index of the migration within the [`Config::Migrations`] list.
+			index: u32,
+			/// The number of blocks that this migration took so far.
+			took: BlockNumberFor<T>,
+		},
+		/// A Migration completed.
+		MigrationCompleted {
+			/// The index of the migration within the [`Config::Migrations`] list.
+			index: u32,
+			/// The number of blocks that this migration took so far.
+			took: BlockNumberFor<T>,
+		},
+		/// A Migration failed.
+		///
+		/// This implies that the whole upgrade failed and governance intervention is required.
+		MigrationFailed {
+			/// The index of the migration within the [`Config::Migrations`] list.
+			index: u32,
+			/// The number of blocks that this migration took so far.
+			took: BlockNumberFor<T>,
+		},
+		/// The set of historical migrations has been cleared.
+		HistoricCleared {
+			/// Should be passed to `clear_historic` in a successive call.
+			next_cursor: Option<Vec<u8>>,
+		},
+	}
+
+	#[pallet::error]
+	pub enum Error<T> {
+		/// The operation cannot complete since some MBMs are ongoing.
+		Ongoing,
+	}
+
+	#[pallet::hooks]
+	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
+		fn on_runtime_upgrade() -> Weight {
+			Self::onboard_new_mbms()
+		}
+
+		#[cfg(feature = "std")]
+		fn integrity_test() {
+			// Check that the migrations tuple is legit.
+			frame_support::assert_ok!(T::Migrations::integrity_test());
+
+			// Very important! Ensure that the pallet is configured in `System::Config`.
+			{
+				assert!(!Cursor::<T>::exists(), "Externalities storage should be clean");
+				assert!(!<T as frame_system::Config>::MultiBlockMigrator::ongoing());
+
+				Cursor::<T>::put(MigrationCursor::Stuck);
+				assert!(<T as frame_system::Config>::MultiBlockMigrator::ongoing());
+
+				Cursor::<T>::kill();
+			}
+
+			// The per-block service weight is sane.
+			#[cfg(not(test))]
+			{
+				let want = T::MaxServiceWeight::get();
+				let max = <T as frame_system::Config>::BlockWeights::get().max_block;
+
+				assert!(want.all_lte(max), "Service weight is larger than a block: {want} > {max}",);
+			}
+
+			// Cursor MEL
+			{
+				let mel = T::Migrations::cursor_max_encoded_len();
+				let max_mel = T::CursorMaxLen::get() as usize;
+				assert!(
+					mel <= max_mel,
+					"A Cursor is not guaranteed to fit into the storage: {mel} > {max_mel}",
+				);
+			}
+
+			// Identifier MEL
+			{
+				let mel = T::Migrations::identifier_max_encoded_len();
+				let max_mel = T::IdentifierMaxLen::get() as usize;
+				assert!(
+					mel <= max_mel,
+					"An Identifier is not guaranteed to fit into the storage: {mel} > {max_mel}",
+				);
+			}
+		}
+	}
+
+	#[pallet::call(weight = T::WeightInfo)]
+	impl<T: Config> Pallet<T> {
+		/// Allows root to set a cursor to forcefully start, stop or forward the migration process.
+		///
+		/// Should normally not be needed and is only in place as emergency measure. Note that
+		/// restarting the migration process in this manner will not call the
+		/// [`MigrationStatusHandler::started`] hook or emit an `UpgradeStarted` event.
+		#[pallet::call_index(0)]
+		pub fn force_set_cursor(
+			origin: OriginFor<T>,
+			cursor: Option<CursorOf<T>>,
+		) -> DispatchResult {
+			ensure_root(origin)?;
+
+			Cursor::<T>::set(cursor);
+
+			Ok(())
+		}
+
+		/// Allows root to set an active cursor to forcefully start/forward the migration process.
+		///
+		/// This is an edge-case version of [`Self::force_set_cursor`] that allows to set the
+		/// `started_at` value to the next block number. Otherwise this would not be possible, since
+		/// `force_set_cursor` takes an absolute block number. Setting `started_at` to `None`
+		/// indicates that the current block number plus one should be used.
+		#[pallet::call_index(1)]
+		pub fn force_set_active_cursor(
+			origin: OriginFor<T>,
+			index: u32,
+			inner_cursor: Option<RawCursorOf<T>>,
+			started_at: Option<BlockNumberFor<T>>,
+		) -> DispatchResult {
+			ensure_root(origin)?;
+
+			let started_at = started_at.unwrap_or(
+				System::<T>::block_number().saturating_add(sp_runtime::traits::One::one()),
+			);
+			Cursor::<T>::put(MigrationCursor::Active(ActiveCursor {
+				index,
+				inner_cursor,
+				started_at,
+			}));
+
+			Ok(())
+		}
+
+		/// Forces the onboarding of the migrations.
+		///
+		/// This process happens automatically on a runtime upgrade. It is in place as an emergency
+		/// measurement. The cursor needs to be `None` for this to succeed.
+		#[pallet::call_index(2)]
+		pub fn force_onboard_mbms(origin: OriginFor<T>) -> DispatchResult {
+			ensure_root(origin)?;
+
+			ensure!(!Cursor::<T>::exists(), Error::<T>::Ongoing);
+			Self::onboard_new_mbms();
+
+			Ok(())
+		}
+
+		/// Clears the `Historic` set.
+		///
+		/// `map_cursor` must be set to the last value that was returned by the
+		/// `HistoricCleared` event. The first time `None` can be used. `limit` must be chosen in a
+		/// way that will result in a sensible weight.
+		#[pallet::call_index(3)]
+		#[pallet::weight(T::WeightInfo::clear_historic(selector.limit()))]
+		pub fn clear_historic(
+			origin: OriginFor<T>,
+			selector: HistoricCleanupSelector<IdentifierOf<T>>,
+		) -> DispatchResult {
+			ensure_root(origin)?;
+
+			match &selector {
+				HistoricCleanupSelector::Specific(ids) => {
+					for id in ids {
+						Historic::<T>::remove(id);
+					}
+					Self::deposit_event(Event::HistoricCleared { next_cursor: None });
+				},
+				HistoricCleanupSelector::Wildcard { previous_cursor, .. } => {
+					let next = Historic::<T>::clear(selector.limit(), previous_cursor.as_deref());
+					Self::deposit_event(Event::HistoricCleared { next_cursor: next.maybe_cursor });
+				},
+			}
+
+			Ok(())
+		}
+	}
+}
+
+impl<T: Config> Pallet<T> {
+	/// Onboard all new Multi-Block-Migrations and start the process of executing them.
+	///
+	/// Should only be called once all previous migrations completed.
+	fn onboard_new_mbms() -> Weight {
+		if let Some(cursor) = Cursor::<T>::get() {
+			log::error!("Ongoing migrations interrupted - chain stuck");
+
+			let maybe_index = cursor.as_active().map(|c| c.index);
+			Self::upgrade_failed(maybe_index);
+			return T::WeightInfo::onboard_new_mbms()
+		}
+
+		let migrations = T::Migrations::len();
+		log::debug!("Onboarding {migrations} new MBM migrations");
+
+		if migrations > 0 {
+			// Set the cursor to the first migration:
+			Cursor::<T>::set(Some(
+				ActiveCursor {
+					index: 0,
+					inner_cursor: None,
+					started_at: System::<T>::block_number(),
+				}
+				.into(),
+			));
+			Self::deposit_event(Event::UpgradeStarted { migrations });
+			T::MigrationStatusHandler::started();
+		}
+
+		T::WeightInfo::onboard_new_mbms()
+	}
+
+	/// Tries to make progress on the Multi-Block-Migrations process.
+	fn progress_mbms(n: BlockNumberFor<T>) -> Weight {
+		let mut meter = WeightMeter::with_limit(T::MaxServiceWeight::get());
+		meter.consume(T::WeightInfo::progress_mbms_none());
+
+		let mut cursor = match Cursor::<T>::get() {
+			None => {
+				log::trace!("[Block {n:?}] Waiting for cursor to become `Some`.");
+				return meter.consumed()
+			},
+			Some(MigrationCursor::Active(cursor)) => {
+				log::debug!("Progressing MBM #{}", cursor.index);
+				cursor
+			},
+			Some(MigrationCursor::Stuck) => {
+				log::error!("Migration stuck. Governance intervention required.");
+				return meter.consumed()
+			},
+		};
+		debug_assert!(Self::ongoing());
+
+		// The limit here is a defensive measure to prevent an infinite loop. It expresses that we
+		// allow no more than 8 MBMs to finish in a single block. This should be harmless, since we
+		// generally expect *Multi*-Block-Migrations to take *multiple* blocks.
+		for i in 0..8 {
+			match Self::exec_migration(cursor, i == 0, &mut meter) {
+				None => return meter.consumed(),
+				Some(ControlFlow::Continue(next_cursor)) => {
+					cursor = next_cursor;
+				},
+				Some(ControlFlow::Break(last_cursor)) => {
+					cursor = last_cursor;
+					break
+				},
+			}
+		}
+
+		Cursor::<T>::set(Some(cursor.into()));
+
+		meter.consumed()
+	}
+
+	/// Try to make progress on the current migration.
+	///
+	/// Returns whether processing should continue or break for this block. The return value means:
+	/// - `None`: The migration process is completely finished.
+	/// - `ControlFlow::Break`: Continue in the *next* block with the given cursor.
+	/// - `ControlFlow::Continue`: Continue in the *current* block with the given cursor.
+	fn exec_migration(
+		mut cursor: ActiveCursorOf<T>,
+		is_first: bool,
+		meter: &mut WeightMeter,
+	) -> Option<ControlFlow<ActiveCursorOf<T>, ActiveCursorOf<T>>> {
+		// The differences between the single branches' weights is not that big. And since we do
+		// only one step per block, we can just use the maximum instead of more precise accounting.
+		if meter.try_consume(Self::exec_migration_max_weight()).is_err() {
+			defensive_assert!(!is_first, "There should be enough weight to do this at least once");
+			return Some(ControlFlow::Break(cursor))
+		}
+
+		let Some(id) = T::Migrations::nth_id(cursor.index) else {
+			// No more migrations in the tuple - we are done.
+			defensive_assert!(cursor.index == T::Migrations::len(), "Inconsistent MBMs tuple");
+			Self::deposit_event(Event::UpgradeCompleted);
+			Cursor::<T>::kill();
+			T::MigrationStatusHandler::completed();
+			return None;
+		};
+
+		let Ok(bounded_id): Result<IdentifierOf<T>, _> = id.try_into() else {
+			defensive!("integrity_test ensures that all identifiers' MEL bounds fit into CursorMaxLen; qed.");
+			Self::upgrade_failed(Some(cursor.index));
+			return None
+		};
+
+		if Historic::<T>::contains_key(&bounded_id) {
+			Self::deposit_event(Event::MigrationSkipped { index: cursor.index });
+			cursor.goto_next_migration(System::<T>::block_number());
+			return Some(ControlFlow::Continue(cursor))
+		}
+
+		let max_steps = T::Migrations::nth_max_steps(cursor.index);
+		let next_cursor = T::Migrations::nth_transactional_step(
+			cursor.index,
+			cursor.inner_cursor.clone().map(|c| c.into_inner()),
+			meter,
+		);
+		let Some((max_steps, next_cursor)) = max_steps.zip(next_cursor) else {
+			defensive!("integrity_test ensures that the tuple is valid; qed");
+			Self::upgrade_failed(Some(cursor.index));
+			return None
+		};
+
+		let took = System::<T>::block_number().saturating_sub(cursor.started_at);
+		match next_cursor {
+			Ok(Some(next_cursor)) => {
+				let Ok(bound_next_cursor) = next_cursor.try_into() else {
+					defensive!("The integrity check ensures that all cursors' MEL bound fits into CursorMaxLen; qed");
+					Self::upgrade_failed(Some(cursor.index));
+					return None
+				};
+
+				Self::deposit_event(Event::MigrationAdvanced { index: cursor.index, took });
+				cursor.inner_cursor = Some(bound_next_cursor);
+
+				if max_steps.map_or(false, |max| took > max.into()) {
+					Self::deposit_event(Event::MigrationFailed { index: cursor.index, took });
+					Self::upgrade_failed(Some(cursor.index));
+					None
+				} else {
+					// A migration cannot progress more than one step per block, we therefore break.
+					Some(ControlFlow::Break(cursor))
+				}
+			},
+			Ok(None) => {
+				// A migration is done when it returns cursor `None`.
+				Self::deposit_event(Event::MigrationCompleted { index: cursor.index, took });
+				Historic::<T>::insert(&bounded_id, ());
+				cursor.goto_next_migration(System::<T>::block_number());
+				Some(ControlFlow::Continue(cursor))
+			},
+			Err(SteppedMigrationError::InsufficientWeight { required }) => {
+				if is_first || required.any_gt(meter.limit()) {
+					Self::deposit_event(Event::MigrationFailed { index: cursor.index, took });
+					Self::upgrade_failed(Some(cursor.index));
+					None
+				} else {
+					// Retry and hope that there is more weight in the next block.
+					Some(ControlFlow::Break(cursor))
+				}
+			},
+			Err(SteppedMigrationError::InvalidCursor | SteppedMigrationError::Failed) => {
+				Self::deposit_event(Event::MigrationFailed { index: cursor.index, took });
+				Self::upgrade_failed(Some(cursor.index));
+				None
+			},
+		}
+	}
+
+	/// Fail the current runtime upgrade, caused by `migration`.
+	fn upgrade_failed(migration: Option<u32>) {
+		use FailedMigrationHandling::*;
+		Self::deposit_event(Event::UpgradeFailed);
+
+		match T::FailedMigrationHandler::failed(migration) {
+			KeepStuck => Cursor::<T>::set(Some(MigrationCursor::Stuck)),
+			ForceUnstuck => Cursor::<T>::kill(),
+			Ignore => {},
+		}
+	}
+
+	fn exec_migration_max_weight() -> Weight {
+		T::WeightInfo::exec_migration_complete()
+			.max(T::WeightInfo::exec_migration_completed())
+			.max(T::WeightInfo::exec_migration_skipped_historic())
+			.max(T::WeightInfo::exec_migration_advance())
+			.max(T::WeightInfo::exec_migration_fail())
+	}
+}
+
+impl<T: Config> MultiStepMigrator for Pallet<T> {
+	fn ongoing() -> bool {
+		Cursor::<T>::exists()
+	}
+
+	fn step() -> Weight {
+		Self::progress_mbms(System::<T>::block_number())
+	}
+}
diff --git a/substrate/frame/migrations/src/mock.rs b/substrate/frame/migrations/src/mock.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3729264755442a008641668b0a14214764d255b9
--- /dev/null
+++ b/substrate/frame/migrations/src/mock.rs
@@ -0,0 +1,163 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Mocked runtime for testing the migrations pallet.
+
+#![cfg(test)]
+
+use crate::{mock_helpers::*, Event, Historic};
+
+use frame_support::{
+	derive_impl,
+	migrations::*,
+	traits::{OnFinalize, OnInitialize},
+	weights::Weight,
+};
+use frame_system::EventRecord;
+use sp_core::{ConstU32, H256};
+
+type Block = frame_system::mocking::MockBlock<Test>;
+
+// Configure a mock runtime to test the pallet.
+frame_support::construct_runtime!(
+	pub enum Test {
+		System: frame_system,
+		Migrations: crate,
+	}
+);
+
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+impl frame_system::Config for Test {
+	type Block = Block;
+	type PalletInfo = PalletInfo;
+	type MultiBlockMigrator = Migrations;
+}
+
+frame_support::parameter_types! {
+	pub const MaxServiceWeight: Weight = Weight::MAX.div(10);
+}
+
+impl crate::Config for Test {
+	type RuntimeEvent = RuntimeEvent;
+	type Migrations = MockedMigrations;
+	type CursorMaxLen = ConstU32<65_536>;
+	type IdentifierMaxLen = ConstU32<256>;
+	type MigrationStatusHandler = MockedMigrationStatusHandler;
+	type FailedMigrationHandler = MockedFailedMigrationHandler;
+	type MaxServiceWeight = MaxServiceWeight;
+	type WeightInfo = ();
+}
+
+frame_support::parameter_types! {
+	/// The number of started upgrades.
+	pub static UpgradesStarted: u32 = 0;
+	/// The number of completed upgrades.
+	pub static UpgradesCompleted: u32 = 0;
+	/// The migrations that failed.
+	pub static UpgradesFailed: Vec<Option<u32>> = vec![];
+	/// Return value of [`MockedFailedMigrationHandler::failed`].
+	pub static FailedUpgradeResponse: FailedMigrationHandling = FailedMigrationHandling::KeepStuck;
+}
+
+/// Records all started and completed upgrades in `UpgradesStarted` and `UpgradesCompleted`.
+pub struct MockedMigrationStatusHandler;
+impl MigrationStatusHandler for MockedMigrationStatusHandler {
+	fn started() {
+		log::info!("MigrationStatusHandler started");
+		UpgradesStarted::mutate(|v| *v += 1);
+	}
+
+	fn completed() {
+		log::info!("MigrationStatusHandler completed");
+		UpgradesCompleted::mutate(|v| *v += 1);
+	}
+}
+
+/// Records all failed upgrades in `UpgradesFailed`.
+pub struct MockedFailedMigrationHandler;
+impl FailedMigrationHandler for MockedFailedMigrationHandler {
+	fn failed(migration: Option<u32>) -> FailedMigrationHandling {
+		UpgradesFailed::mutate(|v| v.push(migration));
+		let res = FailedUpgradeResponse::get();
+		log::error!("FailedMigrationHandler failed at: {migration:?}, handling as {res:?}");
+		res
+	}
+}
+
+/// Returns the number of `(started, completed, failed)` upgrades and resets their numbers.
+pub fn upgrades_started_completed_failed() -> (u32, u32, u32) {
+	(UpgradesStarted::take(), UpgradesCompleted::take(), UpgradesFailed::take().len() as u32)
+}
+
+/// Build genesis storage according to the mock runtime.
+pub fn new_test_ext() -> sp_io::TestExternalities {
+	sp_io::TestExternalities::new(Default::default())
+}
+
+/// Run this closure in test externalities.
+pub fn test_closure<R>(f: impl FnOnce() -> R) -> R {
+	let mut ext = new_test_ext();
+	ext.execute_with(f)
+}
+
+pub fn run_to_block(n: u32) {
+	while System::block_number() < n as u64 {
+		log::debug!("Block {}", System::block_number());
+		System::set_block_number(System::block_number() + 1);
+		System::on_initialize(System::block_number());
+		Migrations::on_initialize(System::block_number());
+		// Executive calls this:
+		<Migrations as MultiStepMigrator>::step();
+
+		Migrations::on_finalize(System::block_number());
+		System::on_finalize(System::block_number());
+	}
+}
+
+/// Returns the historic migrations, sorted by their identifier.
+pub fn historic() -> Vec<MockedIdentifier> {
+	let mut historic = Historic::<Test>::iter_keys().collect::<Vec<_>>();
+	historic.sort();
+	historic
+}
+
+// Traits to make using events less insufferable:
+pub trait IntoRecord {
+	fn into_record(self) -> EventRecord<<Test as frame_system::Config>::RuntimeEvent, H256>;
+}
+
+impl IntoRecord for Event<Test> {
+	fn into_record(self) -> EventRecord<<Test as frame_system::Config>::RuntimeEvent, H256> {
+		let re: <Test as frame_system::Config>::RuntimeEvent = self.into();
+		EventRecord { phase: frame_system::Phase::Initialization, event: re, topics: vec![] }
+	}
+}
+
+pub trait IntoRecords {
+	fn into_records(self) -> Vec<EventRecord<<Test as frame_system::Config>::RuntimeEvent, H256>>;
+}
+
+impl<E: IntoRecord> IntoRecords for Vec<E> {
+	fn into_records(self) -> Vec<EventRecord<<Test as frame_system::Config>::RuntimeEvent, H256>> {
+		self.into_iter().map(|e| e.into_record()).collect()
+	}
+}
+
+pub fn assert_events<E: IntoRecord>(events: Vec<E>) {
+	pretty_assertions::assert_eq!(events.into_records(), System::events());
+	System::reset_events();
+}
diff --git a/substrate/frame/migrations/src/mock_helpers.rs b/substrate/frame/migrations/src/mock_helpers.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c5e23efb4e314e6f5562c50521a533c82f94669e
--- /dev/null
+++ b/substrate/frame/migrations/src/mock_helpers.rs
@@ -0,0 +1,142 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Test helpers for internal and external usage.
+
+#![allow(missing_docs)]
+
+use codec::{Decode, Encode};
+use frame_support::{
+	migrations::*,
+	weights::{Weight, WeightMeter},
+};
+use sp_core::ConstU32;
+use sp_runtime::BoundedVec;
+use sp_std::{vec, vec::Vec};
+
+/// Opaque identifier of a migration.
+pub type MockedIdentifier = BoundedVec<u8, ConstU32<256>>;
+
+/// How a mocked migration should behave.
+#[derive(Debug, Clone, Copy, Encode, Decode)]
+pub enum MockedMigrationKind {
+	/// Succeed after its number of steps elapsed.
+	SucceedAfter,
+	/// Fail after its number of steps elapsed.
+	FailAfter,
+	/// Never terminate.
+	TimeoutAfter,
+	/// Cause an [`SteppedMigrationError::InsufficientWeight`] error after its number of steps
+	/// elapsed.
+	HighWeightAfter(Weight),
+}
+use MockedMigrationKind::*; // C style
+
+/// Creates a migration identifier with a specific `kind` and `steps`.
+pub fn mocked_id(kind: MockedMigrationKind, steps: u32) -> MockedIdentifier {
+	(b"MockedMigration", kind, steps).encode().try_into().unwrap()
+}
+
+frame_support::parameter_types! {
+	/// The configs for the migrations to run.
+	storage MIGRATIONS: Vec<(MockedMigrationKind, u32)> = vec![];
+}
+
+/// Allows to set the migrations to run at runtime instead of compile-time.
+///
+/// It achieves this by using the storage to store the migrations to run.
+pub struct MockedMigrations;
+impl SteppedMigrations for MockedMigrations {
+	fn len() -> u32 {
+		MIGRATIONS::get().len() as u32
+	}
+
+	fn nth_id(n: u32) -> Option<Vec<u8>> {
+		let k = MIGRATIONS::get().get(n as usize).copied();
+		k.map(|(kind, steps)| mocked_id(kind, steps).into_inner())
+	}
+
+	fn nth_step(
+		n: u32,
+		cursor: Option<Vec<u8>>,
+		_meter: &mut WeightMeter,
+	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
+		let (kind, steps) = MIGRATIONS::get()[n as usize];
+
+		let mut count: u32 =
+			cursor.as_ref().and_then(|c| Decode::decode(&mut &c[..]).ok()).unwrap_or(0);
+		log::debug!("MockedMigration: Step {}", count);
+		if count != steps || matches!(kind, TimeoutAfter) {
+			count += 1;
+			return Some(Ok(Some(count.encode())))
+		}
+
+		Some(match kind {
+			SucceedAfter => {
+				log::debug!("MockedMigration: Succeeded after {} steps", count);
+				Ok(None)
+			},
+			HighWeightAfter(required) => {
+				log::debug!("MockedMigration: Not enough weight after {} steps", count);
+				Err(SteppedMigrationError::InsufficientWeight { required })
+			},
+			FailAfter => {
+				log::debug!("MockedMigration: Failed after {} steps", count);
+				Err(SteppedMigrationError::Failed)
+			},
+			TimeoutAfter => unreachable!(),
+		})
+	}
+
+	fn nth_transactional_step(
+		n: u32,
+		cursor: Option<Vec<u8>>,
+		meter: &mut WeightMeter,
+	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
+		// This is a hack but should be fine. We dont need it in testing.
+		Self::nth_step(n, cursor, meter)
+	}
+
+	fn nth_max_steps(n: u32) -> Option<Option<u32>> {
+		MIGRATIONS::get().get(n as usize).map(|(_, s)| Some(*s))
+	}
+
+	fn cursor_max_encoded_len() -> usize {
+		65_536
+	}
+
+	fn identifier_max_encoded_len() -> usize {
+		256
+	}
+}
+
+impl MockedMigrations {
+	/// Set the migrations to run.
+	pub fn set(migrations: Vec<(MockedMigrationKind, u32)>) {
+		MIGRATIONS::set(&migrations);
+	}
+}
+
+impl crate::MockedMigrations for MockedMigrations {
+	fn set_fail_after(steps: u32) {
+		MIGRATIONS::set(&vec![(FailAfter, steps)]);
+	}
+
+	fn set_success_after(steps: u32) {
+		MIGRATIONS::set(&vec![(SucceedAfter, steps)]);
+	}
+}
diff --git a/substrate/frame/migrations/src/tests.rs b/substrate/frame/migrations/src/tests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9c9043d37a62e5f1df3d12da979b68251621c9d0
--- /dev/null
+++ b/substrate/frame/migrations/src/tests.rs
@@ -0,0 +1,335 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![cfg(test)]
+
+use crate::{
+	mock::{Test as T, *},
+	mock_helpers::{MockedMigrationKind::*, *},
+	Cursor, Event, FailedMigrationHandling, MigrationCursor,
+};
+use frame_support::{pallet_prelude::Weight, traits::OnRuntimeUpgrade};
+
+#[docify::export]
+#[test]
+fn simple_works() {
+	use Event::*;
+	test_closure(|| {
+		// Add three migrations, each taking one block longer than the previous.
+		MockedMigrations::set(vec![(SucceedAfter, 0), (SucceedAfter, 1), (SucceedAfter, 2)]);
+
+		System::set_block_number(1);
+		Migrations::on_runtime_upgrade();
+		run_to_block(10);
+
+		// Check that the executed migrations are recorded in `Historical`.
+		assert_eq!(
+			historic(),
+			vec![
+				mocked_id(SucceedAfter, 0),
+				mocked_id(SucceedAfter, 1),
+				mocked_id(SucceedAfter, 2),
+			]
+		);
+
+		// Check that we got all events.
+		assert_events(vec![
+			UpgradeStarted { migrations: 3 },
+			MigrationCompleted { index: 0, took: 1 },
+			MigrationAdvanced { index: 1, took: 0 },
+			MigrationCompleted { index: 1, took: 1 },
+			MigrationAdvanced { index: 2, took: 0 },
+			MigrationAdvanced { index: 2, took: 1 },
+			MigrationCompleted { index: 2, took: 2 },
+			UpgradeCompleted,
+		]);
+	});
+}
+
+#[test]
+fn failing_migration_sets_cursor_to_stuck() {
+	test_closure(|| {
+		FailedUpgradeResponse::set(FailedMigrationHandling::KeepStuck);
+		MockedMigrations::set(vec![(FailAfter, 2)]);
+
+		System::set_block_number(1);
+		Migrations::on_runtime_upgrade();
+		run_to_block(10);
+
+		// Failed migrations are not recorded in `Historical`.
+		assert!(historic().is_empty());
+		// Check that we got all events.
+		assert_events(vec![
+			Event::UpgradeStarted { migrations: 1 },
+			Event::MigrationAdvanced { index: 0, took: 1 },
+			Event::MigrationAdvanced { index: 0, took: 2 },
+			Event::MigrationFailed { index: 0, took: 3 },
+			Event::UpgradeFailed,
+		]);
+
+		// Check that the handler was called correctly.
+		assert_eq!(UpgradesStarted::take(), 1);
+		assert_eq!(UpgradesCompleted::take(), 0);
+		assert_eq!(UpgradesFailed::take(), vec![Some(0)]);
+
+		assert_eq!(Cursor::<T>::get(), Some(MigrationCursor::Stuck), "Must stuck the chain");
+	});
+}
+
+#[test]
+fn failing_migration_force_unstuck_works() {
+	test_closure(|| {
+		FailedUpgradeResponse::set(FailedMigrationHandling::ForceUnstuck);
+		MockedMigrations::set(vec![(FailAfter, 2)]);
+
+		System::set_block_number(1);
+		Migrations::on_runtime_upgrade();
+		run_to_block(10);
+
+		// Failed migrations are not recorded in `Historical`.
+		assert!(historic().is_empty());
+		// Check that we got all events.
+		assert_events(vec![
+			Event::UpgradeStarted { migrations: 1 },
+			Event::MigrationAdvanced { index: 0, took: 1 },
+			Event::MigrationAdvanced { index: 0, took: 2 },
+			Event::MigrationFailed { index: 0, took: 3 },
+			Event::UpgradeFailed,
+		]);
+
+		// Check that the handler was called correctly.
+		assert_eq!(UpgradesStarted::take(), 1);
+		assert_eq!(UpgradesCompleted::take(), 0);
+		assert_eq!(UpgradesFailed::take(), vec![Some(0)]);
+
+		assert!(Cursor::<T>::get().is_none(), "Must unstuck the chain");
+	});
+}
+
+/// A migration that reports not getting enough weight errors if it is the first one to run in that
+/// block.
+#[test]
+fn high_weight_migration_singular_fails() {
+	test_closure(|| {
+		MockedMigrations::set(vec![(HighWeightAfter(Weight::zero()), 2)]);
+
+		System::set_block_number(1);
+		Migrations::on_runtime_upgrade();
+		run_to_block(10);
+
+		// Failed migrations are not recorded in `Historical`.
+		assert!(historic().is_empty());
+		// Check that we got all events.
+		assert_events(vec![
+			Event::UpgradeStarted { migrations: 1 },
+			Event::MigrationAdvanced { index: 0, took: 1 },
+			Event::MigrationAdvanced { index: 0, took: 2 },
+			Event::MigrationFailed { index: 0, took: 3 },
+			Event::UpgradeFailed,
+		]);
+
+		// Check that the handler was called correctly.
+		assert_eq!(upgrades_started_completed_failed(), (1, 0, 1));
+		assert_eq!(Cursor::<T>::get(), Some(MigrationCursor::Stuck));
+	});
+}
+
+/// A migration that reports of not getting enough weight is retried once, if it is not the first
+/// one to run in a block.
+#[test]
+fn high_weight_migration_retries_once() {
+	test_closure(|| {
+		MockedMigrations::set(vec![(SucceedAfter, 0), (HighWeightAfter(Weight::zero()), 0)]);
+
+		System::set_block_number(1);
+		Migrations::on_runtime_upgrade();
+		run_to_block(10);
+
+		assert_eq!(historic(), vec![mocked_id(SucceedAfter, 0)]);
+		// Check that we got all events.
+		assert_events::<Event<T>>(vec![
+			Event::UpgradeStarted { migrations: 2 },
+			Event::MigrationCompleted { index: 0, took: 1 },
+			// `took=1` means that it was retried once.
+			Event::MigrationFailed { index: 1, took: 1 },
+			Event::UpgradeFailed,
+		]);
+
+		// Check that the handler was called correctly.
+		assert_eq!(upgrades_started_completed_failed(), (1, 0, 1));
+		assert_eq!(Cursor::<T>::get(), Some(MigrationCursor::Stuck));
+	});
+}
+
+/// If a migration uses more weight than the limit, then it will not retry but fail even when it is
+/// not the first one in the block.
+// Note: Same as `high_weight_migration_retries_once` but with different required weight for the
+// migration.
+#[test]
+fn high_weight_migration_permanently_overweight_fails() {
+	test_closure(|| {
+		MockedMigrations::set(vec![(SucceedAfter, 0), (HighWeightAfter(Weight::MAX), 0)]);
+
+		System::set_block_number(1);
+		Migrations::on_runtime_upgrade();
+		run_to_block(10);
+
+		assert_eq!(historic(), vec![mocked_id(SucceedAfter, 0)]);
+		// Check that we got all events.
+		assert_events::<Event<T>>(vec![
+			Event::UpgradeStarted { migrations: 2 },
+			Event::MigrationCompleted { index: 0, took: 1 },
+			// `blocks=0` means that it was not retried.
+			Event::MigrationFailed { index: 1, took: 0 },
+			Event::UpgradeFailed,
+		]);
+
+		// Check that the handler was called correctly.
+		assert_eq!(upgrades_started_completed_failed(), (1, 0, 1));
+		assert_eq!(Cursor::<T>::get(), Some(MigrationCursor::Stuck));
+	});
+}
+
+#[test]
+fn historic_skipping_works() {
+	test_closure(|| {
+		MockedMigrations::set(vec![
+			(SucceedAfter, 0),
+			(SucceedAfter, 0), // duplicate
+			(SucceedAfter, 1),
+			(SucceedAfter, 2),
+			(SucceedAfter, 1), // duplicate
+		]);
+
+		System::set_block_number(1);
+		Migrations::on_runtime_upgrade();
+		run_to_block(10);
+
+		// Just three historical ones, since two were added twice.
+		assert_eq!(
+			historic(),
+			vec![
+				mocked_id(SucceedAfter, 0),
+				mocked_id(SucceedAfter, 1),
+				mocked_id(SucceedAfter, 2),
+			]
+		);
+		// Events received.
+		assert_events(vec![
+			Event::UpgradeStarted { migrations: 5 },
+			Event::MigrationCompleted { index: 0, took: 1 },
+			Event::MigrationSkipped { index: 1 },
+			Event::MigrationAdvanced { index: 2, took: 0 },
+			Event::MigrationCompleted { index: 2, took: 1 },
+			Event::MigrationAdvanced { index: 3, took: 0 },
+			Event::MigrationAdvanced { index: 3, took: 1 },
+			Event::MigrationCompleted { index: 3, took: 2 },
+			Event::MigrationSkipped { index: 4 },
+			Event::UpgradeCompleted,
+		]);
+		assert_eq!(upgrades_started_completed_failed(), (1, 1, 0));
+
+		// Now go for another upgrade; just to make sure that it wont execute again.
+		System::reset_events();
+		Migrations::on_runtime_upgrade();
+		run_to_block(20);
+
+		// Same historical ones as before.
+		assert_eq!(
+			historic(),
+			vec![
+				mocked_id(SucceedAfter, 0),
+				mocked_id(SucceedAfter, 1),
+				mocked_id(SucceedAfter, 2),
+			]
+		);
+
+		// Everything got skipped.
+		assert_events(vec![
+			Event::UpgradeStarted { migrations: 5 },
+			Event::MigrationSkipped { index: 0 },
+			Event::MigrationSkipped { index: 1 },
+			Event::MigrationSkipped { index: 2 },
+			Event::MigrationSkipped { index: 3 },
+			Event::MigrationSkipped { index: 4 },
+			Event::UpgradeCompleted,
+		]);
+		assert_eq!(upgrades_started_completed_failed(), (1, 1, 0));
+	});
+}
+
+/// When another upgrade happens while a migration is still running, it should set the cursor to
+/// stuck.
+#[test]
+fn upgrade_fails_when_migration_active() {
+	test_closure(|| {
+		MockedMigrations::set(vec![(SucceedAfter, 10)]);
+
+		System::set_block_number(1);
+		Migrations::on_runtime_upgrade();
+		run_to_block(3);
+
+		// Events received.
+		assert_events(vec![
+			Event::UpgradeStarted { migrations: 1 },
+			Event::MigrationAdvanced { index: 0, took: 1 },
+			Event::MigrationAdvanced { index: 0, took: 2 },
+		]);
+		assert_eq!(upgrades_started_completed_failed(), (1, 0, 0));
+
+		// Upgrade again.
+		Migrations::on_runtime_upgrade();
+		// -- Defensive path --
+		assert_eq!(Cursor::<T>::get(), Some(MigrationCursor::Stuck));
+		assert_events(vec![Event::UpgradeFailed]);
+		assert_eq!(upgrades_started_completed_failed(), (0, 0, 1));
+	});
+}
+
+#[test]
+fn migration_timeout_errors() {
+	test_closure(|| {
+		MockedMigrations::set(vec![(TimeoutAfter, 3)]);
+
+		System::set_block_number(1);
+		Migrations::on_runtime_upgrade();
+		run_to_block(5);
+
+		// Times out after taking more than 3 steps.
+		assert_events(vec![
+			Event::UpgradeStarted { migrations: 1 },
+			Event::MigrationAdvanced { index: 0, took: 1 },
+			Event::MigrationAdvanced { index: 0, took: 2 },
+			Event::MigrationAdvanced { index: 0, took: 3 },
+			Event::MigrationAdvanced { index: 0, took: 4 },
+			Event::MigrationFailed { index: 0, took: 4 },
+			Event::UpgradeFailed,
+		]);
+		assert_eq!(upgrades_started_completed_failed(), (1, 0, 1));
+
+		// Failed migrations are not black-listed.
+		assert!(historic().is_empty());
+		assert_eq!(Cursor::<T>::get(), Some(MigrationCursor::Stuck));
+
+		Migrations::on_runtime_upgrade();
+		run_to_block(6);
+
+		assert_events(vec![Event::UpgradeFailed]);
+		assert_eq!(Cursor::<T>::get(), Some(MigrationCursor::Stuck));
+		assert_eq!(upgrades_started_completed_failed(), (0, 0, 1));
+	});
+}
diff --git a/substrate/frame/migrations/src/weights.rs b/substrate/frame/migrations/src/weights.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c9b63258c44b7948f113161be6452b9a0f7348cc
--- /dev/null
+++ b/substrate/frame/migrations/src/weights.rs
@@ -0,0 +1,358 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Autogenerated weights for `pallet_migrations`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2023-12-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `loud1`, CPU: `AMD EPYC 7282 16-Core Processor`
+//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024`
+
+// Executed Command:
+// target/release/substrate-node
+// benchmark
+// pallet
+// --chain
+// dev
+// --pallet
+// pallet-migrations
+// --extrinsic
+// 
+// --output
+// weight.rs
+// --template
+// ../../polkadot-sdk/substrate/.maintain/frame-weight-template.hbs
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
+use core::marker::PhantomData;
+
+/// Weight functions needed for `pallet_migrations`.
+pub trait WeightInfo {
+	fn onboard_new_mbms() -> Weight;
+	fn progress_mbms_none() -> Weight;
+	fn exec_migration_completed() -> Weight;
+	fn exec_migration_skipped_historic() -> Weight;
+	fn exec_migration_advance() -> Weight;
+	fn exec_migration_complete() -> Weight;
+	fn exec_migration_fail() -> Weight;
+	fn on_init_loop() -> Weight;
+	fn force_set_cursor() -> Weight;
+	fn force_set_active_cursor() -> Weight;
+	fn force_onboard_mbms() -> Weight;
+	fn clear_historic(n: u32, ) -> Weight;
+}
+
+/// Weights for `pallet_migrations` using the Substrate node and recommended hardware.
+pub struct SubstrateWeight<T>(PhantomData<T>);
+impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
+	/// Storage: `MultiBlockMigrations::Cursor` (r:1 w:1)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	fn onboard_new_mbms() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `243`
+		//  Estimated: `67035`
+		// Minimum execution time: 13_980_000 picoseconds.
+		Weight::from_parts(14_290_000, 67035)
+			.saturating_add(T::DbWeight::get().reads(2_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	fn progress_mbms_none() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `109`
+		//  Estimated: `67035`
+		// Minimum execution time: 3_770_000 picoseconds.
+		Weight::from_parts(4_001_000, 67035)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	fn exec_migration_completed() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `134`
+		//  Estimated: `3599`
+		// Minimum execution time: 10_900_000 picoseconds.
+		Weight::from_parts(11_251_000, 3599)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Storage: `MultiBlockMigrations::Historic` (r:1 w:0)
+	/// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`)
+	fn exec_migration_skipped_historic() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `297`
+		//  Estimated: `3762`
+		// Minimum execution time: 17_891_000 picoseconds.
+		Weight::from_parts(18_501_000, 3762)
+			.saturating_add(T::DbWeight::get().reads(2_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Storage: `MultiBlockMigrations::Historic` (r:1 w:0)
+	/// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`)
+	fn exec_migration_advance() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `243`
+		//  Estimated: `3731`
+		// Minimum execution time: 18_271_000 picoseconds.
+		Weight::from_parts(18_740_000, 3731)
+			.saturating_add(T::DbWeight::get().reads(2_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Storage: `MultiBlockMigrations::Historic` (r:1 w:1)
+	/// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`)
+	fn exec_migration_complete() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `243`
+		//  Estimated: `3731`
+		// Minimum execution time: 21_241_000 picoseconds.
+		Weight::from_parts(21_911_000, 3731)
+			.saturating_add(T::DbWeight::get().reads(2_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Storage: `MultiBlockMigrations::Historic` (r:1 w:0)
+	/// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`)
+	/// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	fn exec_migration_fail() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `243`
+		//  Estimated: `3731`
+		// Minimum execution time: 22_740_000 picoseconds.
+		Weight::from_parts(23_231_000, 3731)
+			.saturating_add(T::DbWeight::get().reads(2_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	fn on_init_loop() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 440_000 picoseconds.
+		Weight::from_parts(500_000, 0)
+	}
+	/// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	fn force_set_cursor() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 5_751_000 picoseconds.
+		Weight::from_parts(5_950_000, 0)
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	fn force_set_active_cursor() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 6_350_000 picoseconds.
+		Weight::from_parts(6_560_000, 0)
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	fn force_onboard_mbms() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `218`
+		//  Estimated: `67035`
+		// Minimum execution time: 11_121_000 picoseconds.
+		Weight::from_parts(11_530_000, 67035)
+			.saturating_add(T::DbWeight::get().reads(2_u64))
+	}
+	/// Storage: `MultiBlockMigrations::Historic` (r:256 w:256)
+	/// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`)
+	/// The range of component `n` is `[0, 256]`.
+	fn clear_historic(n: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1089 + n * (271 ±0)`
+		//  Estimated: `3834 + n * (2740 ±0)`
+		// Minimum execution time: 21_891_000 picoseconds.
+		Weight::from_parts(18_572_306, 3834)
+			// Standard Error: 3_236
+			.saturating_add(Weight::from_parts(1_648_429, 0).saturating_mul(n.into()))
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into())))
+			.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into())))
+			.saturating_add(Weight::from_parts(0, 2740).saturating_mul(n.into()))
+	}
+}
+
+// For backwards compatibility and tests.
+impl WeightInfo for () {
+	/// Storage: `MultiBlockMigrations::Cursor` (r:1 w:1)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	fn onboard_new_mbms() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `243`
+		//  Estimated: `67035`
+		// Minimum execution time: 13_980_000 picoseconds.
+		Weight::from_parts(14_290_000, 67035)
+			.saturating_add(RocksDbWeight::get().reads(2_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	fn progress_mbms_none() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `109`
+		//  Estimated: `67035`
+		// Minimum execution time: 3_770_000 picoseconds.
+		Weight::from_parts(4_001_000, 67035)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	fn exec_migration_completed() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `134`
+		//  Estimated: `3599`
+		// Minimum execution time: 10_900_000 picoseconds.
+		Weight::from_parts(11_251_000, 3599)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Storage: `MultiBlockMigrations::Historic` (r:1 w:0)
+	/// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`)
+	fn exec_migration_skipped_historic() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `297`
+		//  Estimated: `3762`
+		// Minimum execution time: 17_891_000 picoseconds.
+		Weight::from_parts(18_501_000, 3762)
+			.saturating_add(RocksDbWeight::get().reads(2_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Storage: `MultiBlockMigrations::Historic` (r:1 w:0)
+	/// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`)
+	fn exec_migration_advance() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `243`
+		//  Estimated: `3731`
+		// Minimum execution time: 18_271_000 picoseconds.
+		Weight::from_parts(18_740_000, 3731)
+			.saturating_add(RocksDbWeight::get().reads(2_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Storage: `MultiBlockMigrations::Historic` (r:1 w:1)
+	/// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`)
+	fn exec_migration_complete() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `243`
+		//  Estimated: `3731`
+		// Minimum execution time: 21_241_000 picoseconds.
+		Weight::from_parts(21_911_000, 3731)
+			.saturating_add(RocksDbWeight::get().reads(2_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Storage: `MultiBlockMigrations::Historic` (r:1 w:0)
+	/// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`)
+	/// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	fn exec_migration_fail() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `243`
+		//  Estimated: `3731`
+		// Minimum execution time: 22_740_000 picoseconds.
+		Weight::from_parts(23_231_000, 3731)
+			.saturating_add(RocksDbWeight::get().reads(2_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	fn on_init_loop() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 440_000 picoseconds.
+		Weight::from_parts(500_000, 0)
+	}
+	/// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	fn force_set_cursor() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 5_751_000 picoseconds.
+		Weight::from_parts(5_950_000, 0)
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	fn force_set_active_cursor() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 6_350_000 picoseconds.
+		Weight::from_parts(6_560_000, 0)
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0)
+	/// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`)
+	/// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0)
+	fn force_onboard_mbms() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `218`
+		//  Estimated: `67035`
+		// Minimum execution time: 11_121_000 picoseconds.
+		Weight::from_parts(11_530_000, 67035)
+			.saturating_add(RocksDbWeight::get().reads(2_u64))
+	}
+	/// Storage: `MultiBlockMigrations::Historic` (r:256 w:256)
+	/// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`)
+	/// The range of component `n` is `[0, 256]`.
+	fn clear_historic(n: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1089 + n * (271 ±0)`
+		//  Estimated: `3834 + n * (2740 ±0)`
+		// Minimum execution time: 21_891_000 picoseconds.
+		Weight::from_parts(18_572_306, 3834)
+			// Standard Error: 3_236
+			.saturating_add(Weight::from_parts(1_648_429, 0).saturating_mul(n.into()))
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into())))
+			.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into())))
+			.saturating_add(Weight::from_parts(0, 2740).saturating_mul(n.into()))
+	}
+}
diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs
index a2e5d726fdd003b691e692e25ac2c738758b6b89..52db7c34bfdc46efcb4008c04ae4f8b185fb3ffb 100644
--- a/substrate/frame/src/lib.rs
+++ b/substrate/frame/src/lib.rs
@@ -197,7 +197,7 @@ pub mod runtime {
 		// Types often used in the runtime APIs.
 		pub use sp_core::OpaqueMetadata;
 		pub use sp_inherents::{CheckInherentsResult, InherentData};
-		pub use sp_runtime::ApplyExtrinsicResult;
+		pub use sp_runtime::{ApplyExtrinsicResult, ExtrinsicInclusionMode};
 
 		pub use frame_system_rpc_runtime_api::*;
 		pub use sp_api::{self, *};
diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs
index 34b9d21d8ce84fafb7958278227a3f572d8d2f74..da483fa6cf0b6aaa724792f724482e88027fa696 100644
--- a/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs
+++ b/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs
@@ -204,47 +204,50 @@ pub fn expand_outer_inherent(
 			}
 		}
 
+		impl #scrate::traits::IsInherent<<#block as #scrate::sp_runtime::traits::Block>::Extrinsic> for #runtime {
+			fn is_inherent(ext: &<#block as #scrate::sp_runtime::traits::Block>::Extrinsic) -> bool {
+				use #scrate::inherent::ProvideInherent;
+				use #scrate::traits::{IsSubType, ExtrinsicCall};
+
+				if #scrate::sp_runtime::traits::Extrinsic::is_signed(ext).unwrap_or(false) {
+					// Signed extrinsics are never inherents.
+					return false
+				}
+
+				#(
+					#pallet_attrs
+					{
+						let call = <#unchecked_extrinsic as ExtrinsicCall>::call(ext);
+						if let Some(call) = IsSubType::<_>::is_sub_type(call) {
+							if <#pallet_names as ProvideInherent>::is_inherent(&call) {
+								return true;
+							}
+						}
+					}
+				)*
+				false
+			}
+		}
+
 		impl #scrate::traits::EnsureInherentsAreFirst<#block> for #runtime {
-			fn ensure_inherents_are_first(block: &#block) -> Result<(), u32> {
+			fn ensure_inherents_are_first(block: &#block) -> Result<u32, u32> {
 				use #scrate::inherent::ProvideInherent;
 				use #scrate::traits::{IsSubType, ExtrinsicCall};
 				use #scrate::sp_runtime::traits::Block as _;
 
-				let mut first_signed_observed = false;
+				let mut num_inherents = 0u32;
 
 				for (i, xt) in block.extrinsics().iter().enumerate() {
-					let is_signed = #scrate::sp_runtime::traits::Extrinsic::is_signed(xt)
-						.unwrap_or(false);
-
-					let is_inherent = if is_signed {
-						// Signed extrinsics are not inherents.
-						false
-					} else {
-						let mut is_inherent = false;
-						#(
-							#pallet_attrs
-							{
-								let call = <#unchecked_extrinsic as ExtrinsicCall>::call(xt);
-								if let Some(call) = IsSubType::<_>::is_sub_type(call) {
-									if #pallet_names::is_inherent(&call) {
-										is_inherent = true;
-									}
-								}
-							}
-						)*
-						is_inherent
-					};
-
-					if !is_inherent {
-						first_signed_observed = true;
-					}
+					if <Self as #scrate::traits::IsInherent<_>>::is_inherent(xt) {
+						if num_inherents != i as u32 {
+							return Err(i as u32);
+						}
 
-					if first_signed_observed && is_inherent {
-						return Err(i as u32)
+						num_inherents += 1; // Safe since we are in an `enumerate` loop.
 					}
 				}
 
-				Ok(())
+				Ok(num_inherents)
 			}
 		}
 	}
diff --git a/substrate/frame/support/procedural/src/pallet/expand/hooks.rs b/substrate/frame/support/procedural/src/pallet/expand/hooks.rs
index 6b25ddcba1a7a6ac46a68355a0465a100e98503b..3623b595268d081ee7b5750a5431f208383f8517 100644
--- a/substrate/frame/support/procedural/src/pallet/expand/hooks.rs
+++ b/substrate/frame/support/procedural/src/pallet/expand/hooks.rs
@@ -175,6 +175,22 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream {
 			}
 		}
 
+		impl<#type_impl_gen>
+			#frame_support::traits::OnPoll<#frame_system::pallet_prelude::BlockNumberFor::<T>>
+			for #pallet_ident<#type_use_gen> #where_clause
+		{
+			fn on_poll(
+				n: #frame_system::pallet_prelude::BlockNumberFor::<T>,
+				weight: &mut #frame_support::weights::WeightMeter
+			) {
+				<
+					Self as #frame_support::traits::Hooks<
+						#frame_system::pallet_prelude::BlockNumberFor::<T>
+					>
+				>::on_poll(n, weight);
+			}
+		}
+
 		impl<#type_impl_gen>
 			#frame_support::traits::OnInitialize<#frame_system::pallet_prelude::BlockNumberFor::<T>>
 			for #pallet_ident<#type_use_gen> #where_clause
diff --git a/substrate/frame/support/src/migrations.rs b/substrate/frame/support/src/migrations.rs
index f754105902323893bab0dd0d16b6de20e29f7e4b..2ceab44cb16bd4bcf356caa07a3506aa76ba79db 100644
--- a/substrate/frame/support/src/migrations.rs
+++ b/substrate/frame/support/src/migrations.rs
@@ -16,13 +16,21 @@
 // limitations under the License.
 
 use crate::{
-	traits::{GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, StorageVersion},
-	weights::{RuntimeDbWeight, Weight},
+	defensive,
+	storage::transactional::with_transaction_opaque_err,
+	traits::{
+		Defensive, GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, SafeMode,
+		StorageVersion,
+	},
+	weights::{RuntimeDbWeight, Weight, WeightMeter},
 };
+use codec::{Decode, Encode, MaxEncodedLen};
 use impl_trait_for_tuples::impl_for_tuples;
+use sp_arithmetic::traits::Bounded;
 use sp_core::Get;
 use sp_io::{hashing::twox_128, storage::clear_prefix, KillStorageResult};
-use sp_std::marker::PhantomData;
+use sp_runtime::traits::Zero;
+use sp_std::{marker::PhantomData, vec::Vec};
 
 /// Handles storage migration pallet versioning.
 ///
@@ -91,7 +99,7 @@ pub struct VersionedMigration<const FROM: u16, const TO: u16, Inner, Pallet, Wei
 
 /// A helper enum to wrap the pre_upgrade bytes like an Option before passing them to post_upgrade.
 /// This enum is used rather than an Option to make the API clearer to the developer.
-#[derive(codec::Encode, codec::Decode)]
+#[derive(Encode, Decode)]
 pub enum VersionedPostUpgradeData {
 	/// The migration ran, inner vec contains pre_upgrade data.
 	MigrationExecuted(sp_std::vec::Vec<u8>),
@@ -118,7 +126,6 @@ impl<
 	/// migration ran or not.
 	#[cfg(feature = "try-runtime")]
 	fn pre_upgrade() -> Result<sp_std::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
-		use codec::Encode;
 		let on_chain_version = Pallet::on_chain_storage_version();
 		if on_chain_version == FROM {
 			Ok(VersionedPostUpgradeData::MigrationExecuted(Inner::pre_upgrade()?).encode())
@@ -361,3 +368,622 @@ impl<P: Get<&'static str>, DbWeight: Get<RuntimeDbWeight>> frame_support::traits
 		Ok(())
 	}
 }
+
+/// A migration that can proceed in multiple steps.
+pub trait SteppedMigration {
+	/// The cursor type that stores the progress (aka. state) of this migration.
+	type Cursor: codec::FullCodec + codec::MaxEncodedLen;
+
+	/// The unique identifier type of this migration.
+	type Identifier: codec::FullCodec + codec::MaxEncodedLen;
+
+	/// The unique identifier of this migration.
+	///
+	/// If two migrations have the same identifier, then they are assumed to be identical.
+	fn id() -> Self::Identifier;
+
+	/// The maximum number of steps that this migration can take.
+	///
+	/// This can be used to enforce progress and prevent migrations becoming stuck forever. A
+	/// migration that exceeds its max steps is treated as failed. `None` means that there is no
+	/// limit.
+	fn max_steps() -> Option<u32> {
+		None
+	}
+
+	/// Try to migrate as much as possible with the given weight.
+	///
+	/// **ANY STORAGE CHANGES MUST BE ROLLED-BACK BY THE CALLER UPON ERROR.** This is necessary
+	/// since the caller cannot return a cursor in the error case. [`Self::transactional_step`] is
+	/// provided as convenience for a caller. A cursor of `None` implies that the migration is at
+	/// its end. A migration that once returned `Nonce` is guaranteed to never be called again.
+	fn step(
+		cursor: Option<Self::Cursor>,
+		meter: &mut WeightMeter,
+	) -> Result<Option<Self::Cursor>, SteppedMigrationError>;
+
+	/// Same as [`Self::step`], but rolls back pending changes in the error case.
+	fn transactional_step(
+		mut cursor: Option<Self::Cursor>,
+		meter: &mut WeightMeter,
+	) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
+		with_transaction_opaque_err(move || match Self::step(cursor, meter) {
+			Ok(new_cursor) => {
+				cursor = new_cursor;
+				sp_runtime::TransactionOutcome::Commit(Ok(cursor))
+			},
+			Err(err) => sp_runtime::TransactionOutcome::Rollback(Err(err)),
+		})
+		.map_err(|()| SteppedMigrationError::Failed)?
+	}
+}
+
+/// Error that can occur during a [`SteppedMigration`].
+#[derive(Debug, Encode, Decode, MaxEncodedLen, scale_info::TypeInfo)]
+pub enum SteppedMigrationError {
+	// Transient errors:
+	/// The remaining weight is not enough to do anything.
+	///
+	/// Can be resolved by calling with at least `required` weight. Note that calling it with
+	/// exactly `required` weight could cause it to not make any progress.
+	InsufficientWeight {
+		/// Amount of weight required to make progress.
+		required: Weight,
+	},
+	// Permanent errors:
+	/// The migration cannot decode its cursor and therefore not proceed.
+	///
+	/// This should not happen unless (1) the migration itself returned an invalid cursor in a
+	/// previous iteration, (2) the storage got corrupted or (3) there is a bug in the caller's
+	/// code.
+	InvalidCursor,
+	/// The migration encountered a permanent error and cannot continue.
+	Failed,
+}
+
+/// Notification handler for status updates regarding Multi-Block-Migrations.
+#[impl_trait_for_tuples::impl_for_tuples(8)]
+pub trait MigrationStatusHandler {
+	/// Notifies of the start of a runtime migration.
+	fn started() {}
+
+	/// Notifies of the completion of a runtime migration.
+	fn completed() {}
+}
+
+/// Handles a failed runtime migration.
+///
+/// This should never happen, but is here for completeness.
+pub trait FailedMigrationHandler {
+	/// Infallibly handle a failed runtime migration.
+	///
+	/// Gets passed in the optional index of the migration in the batch that caused the failure.
+	/// Returning `None` means that no automatic handling should take place and the callee decides
+	/// in the implementation what to do.
+	fn failed(migration: Option<u32>) -> FailedMigrationHandling;
+}
+
+/// Do now allow any transactions to be processed after a runtime upgrade failed.
+///
+/// This is **not a sane default**, since it prevents governance intervention.
+pub struct FreezeChainOnFailedMigration;
+
+impl FailedMigrationHandler for FreezeChainOnFailedMigration {
+	fn failed(_migration: Option<u32>) -> FailedMigrationHandling {
+		FailedMigrationHandling::KeepStuck
+	}
+}
+
+/// Enter safe mode on a failed runtime upgrade.
+///
+/// This can be very useful to manually intervene and fix the chain state. `Else` is used in case
+/// that the safe mode could not be entered.
+pub struct EnterSafeModeOnFailedMigration<SM, Else: FailedMigrationHandler>(
+	PhantomData<(SM, Else)>,
+);
+
+impl<Else: FailedMigrationHandler, SM: SafeMode> FailedMigrationHandler
+	for EnterSafeModeOnFailedMigration<SM, Else>
+where
+	<SM as SafeMode>::BlockNumber: Bounded,
+{
+	fn failed(migration: Option<u32>) -> FailedMigrationHandling {
+		let entered = if SM::is_entered() {
+			SM::extend(Bounded::max_value())
+		} else {
+			SM::enter(Bounded::max_value())
+		};
+
+		// If we could not enter or extend safe mode (for whatever reason), then we try the next.
+		if entered.is_err() {
+			Else::failed(migration)
+		} else {
+			FailedMigrationHandling::KeepStuck
+		}
+	}
+}
+
+/// How to proceed after a runtime upgrade failed.
+///
+/// There is NO SANE DEFAULT HERE. All options are very dangerous and should be used with care.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FailedMigrationHandling {
+	/// Resume extrinsic processing of the chain. This will not resume the upgrade.
+	///
+	/// This should be supplemented with additional measures to ensure that the broken chain state
+	/// does not get further messed up by user extrinsics.
+	ForceUnstuck,
+	/// Set the cursor to `Stuck` and keep blocking extrinsics.
+	KeepStuck,
+	/// Don't do anything with the cursor and let the handler decide.
+	///
+	/// This can be useful in cases where the other two options would overwrite any changes that
+	/// were done by the handler to the cursor.
+	Ignore,
+}
+
+/// Something that can do multi step migrations.
+pub trait MultiStepMigrator {
+	/// Hint for whether [`Self::step`] should be called.
+	fn ongoing() -> bool;
+
+	/// Do the next step in the MBM process.
+	///
+	/// Must gracefully handle the case that it is currently not upgrading.
+	fn step() -> Weight;
+}
+
+impl MultiStepMigrator for () {
+	fn ongoing() -> bool {
+		false
+	}
+
+	fn step() -> Weight {
+		Weight::zero()
+	}
+}
+
+/// Multiple [`SteppedMigration`].
+pub trait SteppedMigrations {
+	/// The number of migrations that `Self` aggregates.
+	fn len() -> u32;
+
+	/// The `n`th [`SteppedMigration::id`].
+	///
+	/// Is guaranteed to return `Some` if `n < Self::len()`.
+	fn nth_id(n: u32) -> Option<Vec<u8>>;
+
+	/// The [`SteppedMigration::max_steps`] of the `n`th migration.
+	///
+	/// Is guaranteed to return `Some` if `n < Self::len()`.
+	fn nth_max_steps(n: u32) -> Option<Option<u32>>;
+
+	/// Do a [`SteppedMigration::step`] on the `n`th migration.
+	///
+	/// Is guaranteed to return `Some` if `n < Self::len()`.
+	fn nth_step(
+		n: u32,
+		cursor: Option<Vec<u8>>,
+		meter: &mut WeightMeter,
+	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>>;
+
+	/// Do a [`SteppedMigration::transactional_step`] on the `n`th migration.
+	///
+	/// Is guaranteed to return `Some` if `n < Self::len()`.
+	fn nth_transactional_step(
+		n: u32,
+		cursor: Option<Vec<u8>>,
+		meter: &mut WeightMeter,
+	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>>;
+
+	/// The maximal encoded length across all cursors.
+	fn cursor_max_encoded_len() -> usize;
+
+	/// The maximal encoded length across all identifiers.
+	fn identifier_max_encoded_len() -> usize;
+
+	/// Assert the integrity of the migrations.
+	///
+	/// Should be executed as part of a test prior to runtime usage. May or may not need
+	/// externalities.
+	#[cfg(feature = "std")]
+	fn integrity_test() -> Result<(), &'static str> {
+		use crate::ensure;
+		let l = Self::len();
+
+		for n in 0..l {
+			ensure!(Self::nth_id(n).is_some(), "id is None");
+			ensure!(Self::nth_max_steps(n).is_some(), "steps is None");
+
+			// The cursor that we use does not matter. Hence use empty.
+			ensure!(
+				Self::nth_step(n, Some(vec![]), &mut WeightMeter::new()).is_some(),
+				"steps is None"
+			);
+			ensure!(
+				Self::nth_transactional_step(n, Some(vec![]), &mut WeightMeter::new()).is_some(),
+				"steps is None"
+			);
+		}
+
+		Ok(())
+	}
+}
+
+impl SteppedMigrations for () {
+	fn len() -> u32 {
+		0
+	}
+
+	fn nth_id(_n: u32) -> Option<Vec<u8>> {
+		None
+	}
+
+	fn nth_max_steps(_n: u32) -> Option<Option<u32>> {
+		None
+	}
+
+	fn nth_step(
+		_n: u32,
+		_cursor: Option<Vec<u8>>,
+		_meter: &mut WeightMeter,
+	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
+		None
+	}
+
+	fn nth_transactional_step(
+		_n: u32,
+		_cursor: Option<Vec<u8>>,
+		_meter: &mut WeightMeter,
+	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
+		None
+	}
+
+	fn cursor_max_encoded_len() -> usize {
+		0
+	}
+
+	fn identifier_max_encoded_len() -> usize {
+		0
+	}
+}
+
+// A collection consisting of only a single migration.
+impl<T: SteppedMigration> SteppedMigrations for T {
+	fn len() -> u32 {
+		1
+	}
+
+	fn nth_id(_n: u32) -> Option<Vec<u8>> {
+		Some(T::id().encode())
+	}
+
+	fn nth_max_steps(n: u32) -> Option<Option<u32>> {
+		// It should be generally fine to call with n>0, but the code should not attempt to.
+		n.is_zero()
+			.then_some(T::max_steps())
+			.defensive_proof("nth_max_steps should only be called with n==0")
+	}
+
+	fn nth_step(
+		_n: u32,
+		cursor: Option<Vec<u8>>,
+		meter: &mut WeightMeter,
+	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
+		if !_n.is_zero() {
+			defensive!("nth_step should only be called with n==0");
+			return None
+		}
+
+		let cursor = match cursor {
+			Some(cursor) => match T::Cursor::decode(&mut &cursor[..]) {
+				Ok(cursor) => Some(cursor),
+				Err(_) => return Some(Err(SteppedMigrationError::InvalidCursor)),
+			},
+			None => None,
+		};
+
+		Some(T::step(cursor, meter).map(|cursor| cursor.map(|cursor| cursor.encode())))
+	}
+
+	fn nth_transactional_step(
+		n: u32,
+		cursor: Option<Vec<u8>>,
+		meter: &mut WeightMeter,
+	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
+		if n != 0 {
+			defensive!("nth_transactional_step should only be called with n==0");
+			return None
+		}
+
+		let cursor = match cursor {
+			Some(cursor) => match T::Cursor::decode(&mut &cursor[..]) {
+				Ok(cursor) => Some(cursor),
+				Err(_) => return Some(Err(SteppedMigrationError::InvalidCursor)),
+			},
+			None => None,
+		};
+
+		Some(
+			T::transactional_step(cursor, meter).map(|cursor| cursor.map(|cursor| cursor.encode())),
+		)
+	}
+
+	fn cursor_max_encoded_len() -> usize {
+		T::Cursor::max_encoded_len()
+	}
+
+	fn identifier_max_encoded_len() -> usize {
+		T::Identifier::max_encoded_len()
+	}
+}
+
+#[impl_trait_for_tuples::impl_for_tuples(1, 30)]
+impl SteppedMigrations for Tuple {
+	fn len() -> u32 {
+		for_tuples!( #( Tuple::len() )+* )
+	}
+
+	fn nth_id(n: u32) -> Option<Vec<u8>> {
+		let mut i = 0;
+
+		for_tuples!( #(
+			if (i + Tuple::len()) > n {
+				return Tuple::nth_id(n - i)
+			}
+
+			i += Tuple::len();
+		)* );
+
+		None
+	}
+
+	fn nth_step(
+		n: u32,
+		cursor: Option<Vec<u8>>,
+		meter: &mut WeightMeter,
+	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
+		let mut i = 0;
+
+		for_tuples!( #(
+			if (i + Tuple::len()) > n {
+				return Tuple::nth_step(n - i, cursor, meter)
+			}
+
+			i += Tuple::len();
+		)* );
+
+		None
+	}
+
+	fn nth_transactional_step(
+		n: u32,
+		cursor: Option<Vec<u8>>,
+		meter: &mut WeightMeter,
+	) -> Option<Result<Option<Vec<u8>>, SteppedMigrationError>> {
+		let mut i = 0;
+
+		for_tuples! ( #(
+			if (i + Tuple::len()) > n {
+				return Tuple::nth_transactional_step(n - i, cursor, meter)
+			}
+
+			i += Tuple::len();
+		)* );
+
+		None
+	}
+
+	fn nth_max_steps(n: u32) -> Option<Option<u32>> {
+		let mut i = 0;
+
+		for_tuples!( #(
+			if (i + Tuple::len()) > n {
+				return Tuple::nth_max_steps(n - i)
+			}
+
+			i += Tuple::len();
+		)* );
+
+		None
+	}
+
+	fn cursor_max_encoded_len() -> usize {
+		let mut max_len = 0;
+
+		for_tuples!( #(
+			max_len = max_len.max(Tuple::cursor_max_encoded_len());
+		)* );
+
+		max_len
+	}
+
+	fn identifier_max_encoded_len() -> usize {
+		let mut max_len = 0;
+
+		for_tuples!( #(
+			max_len = max_len.max(Tuple::identifier_max_encoded_len());
+		)* );
+
+		max_len
+	}
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+	use crate::{assert_ok, storage::unhashed};
+
+	#[derive(Decode, Encode, MaxEncodedLen, Eq, PartialEq)]
+	pub enum Either<L, R> {
+		Left(L),
+		Right(R),
+	}
+
+	pub struct M0;
+	impl SteppedMigration for M0 {
+		type Cursor = ();
+		type Identifier = u8;
+
+		fn id() -> Self::Identifier {
+			0
+		}
+
+		fn step(
+			_cursor: Option<Self::Cursor>,
+			_meter: &mut WeightMeter,
+		) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
+			log::info!("M0");
+			unhashed::put(&[0], &());
+			Ok(None)
+		}
+	}
+
+	pub struct M1;
+	impl SteppedMigration for M1 {
+		type Cursor = ();
+		type Identifier = u8;
+
+		fn id() -> Self::Identifier {
+			1
+		}
+
+		fn step(
+			_cursor: Option<Self::Cursor>,
+			_meter: &mut WeightMeter,
+		) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
+			log::info!("M1");
+			unhashed::put(&[1], &());
+			Ok(None)
+		}
+
+		fn max_steps() -> Option<u32> {
+			Some(1)
+		}
+	}
+
+	pub struct M2;
+	impl SteppedMigration for M2 {
+		type Cursor = ();
+		type Identifier = u8;
+
+		fn id() -> Self::Identifier {
+			2
+		}
+
+		fn step(
+			_cursor: Option<Self::Cursor>,
+			_meter: &mut WeightMeter,
+		) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
+			log::info!("M2");
+			unhashed::put(&[2], &());
+			Ok(None)
+		}
+
+		fn max_steps() -> Option<u32> {
+			Some(2)
+		}
+	}
+
+	pub struct F0;
+	impl SteppedMigration for F0 {
+		type Cursor = ();
+		type Identifier = u8;
+
+		fn id() -> Self::Identifier {
+			3
+		}
+
+		fn step(
+			_cursor: Option<Self::Cursor>,
+			_meter: &mut WeightMeter,
+		) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
+			log::info!("F0");
+			unhashed::put(&[3], &());
+			Err(SteppedMigrationError::Failed)
+		}
+	}
+
+	// Three migrations combined to execute in order:
+	type Triple = (M0, (M1, M2));
+	// Six migrations, just concatenating the ones from before:
+	type Hextuple = (Triple, Triple);
+
+	#[test]
+	fn singular_migrations_work() {
+		assert_eq!(M0::max_steps(), None);
+		assert_eq!(M1::max_steps(), Some(1));
+		assert_eq!(M2::max_steps(), Some(2));
+
+		assert_eq!(<(M0, M1)>::nth_max_steps(0), Some(None));
+		assert_eq!(<(M0, M1)>::nth_max_steps(1), Some(Some(1)));
+		assert_eq!(<(M0, M1, M2)>::nth_max_steps(2), Some(Some(2)));
+
+		assert_eq!(<(M0, M1)>::nth_max_steps(2), None);
+	}
+
+	#[test]
+	fn tuple_migrations_work() {
+		assert_eq!(<() as SteppedMigrations>::len(), 0);
+		assert_eq!(<((), ((), ())) as SteppedMigrations>::len(), 0);
+		assert_eq!(<Triple as SteppedMigrations>::len(), 3);
+		assert_eq!(<Hextuple as SteppedMigrations>::len(), 6);
+
+		// Check the IDs. The index specific functions all return an Option,
+		// to account for the out-of-range case.
+		assert_eq!(<Triple as SteppedMigrations>::nth_id(0), Some(0u8.encode()));
+		assert_eq!(<Triple as SteppedMigrations>::nth_id(1), Some(1u8.encode()));
+		assert_eq!(<Triple as SteppedMigrations>::nth_id(2), Some(2u8.encode()));
+
+		sp_io::TestExternalities::default().execute_with(|| {
+			for n in 0..3 {
+				<Triple as SteppedMigrations>::nth_step(
+					n,
+					Default::default(),
+					&mut WeightMeter::new(),
+				);
+			}
+		});
+	}
+
+	#[test]
+	fn integrity_test_works() {
+		sp_io::TestExternalities::default().execute_with(|| {
+			assert_ok!(<() as SteppedMigrations>::integrity_test());
+			assert_ok!(<M0 as SteppedMigrations>::integrity_test());
+			assert_ok!(<M1 as SteppedMigrations>::integrity_test());
+			assert_ok!(<M2 as SteppedMigrations>::integrity_test());
+			assert_ok!(<Triple as SteppedMigrations>::integrity_test());
+			assert_ok!(<Hextuple as SteppedMigrations>::integrity_test());
+		});
+	}
+
+	#[test]
+	fn transactional_rollback_works() {
+		sp_io::TestExternalities::default().execute_with(|| {
+			assert_ok!(<(M0, F0) as SteppedMigrations>::nth_transactional_step(
+				0,
+				Default::default(),
+				&mut WeightMeter::new()
+			)
+			.unwrap());
+			assert!(unhashed::exists(&[0]));
+
+			let _g = crate::StorageNoopGuard::new();
+			assert!(<(M0, F0) as SteppedMigrations>::nth_transactional_step(
+				1,
+				Default::default(),
+				&mut WeightMeter::new()
+			)
+			.unwrap()
+			.is_err());
+			assert!(<(F0, M1) as SteppedMigrations>::nth_transactional_step(
+				0,
+				Default::default(),
+				&mut WeightMeter::new()
+			)
+			.unwrap()
+			.is_err());
+		});
+	}
+}
diff --git a/substrate/frame/support/src/storage/transactional.rs b/substrate/frame/support/src/storage/transactional.rs
index d42e1809e91292a8e0ad367af4e203b8b1b17f87..0671db4a3a86bc76f7706df6eefe28a8c34c0701 100644
--- a/substrate/frame/support/src/storage/transactional.rs
+++ b/substrate/frame/support/src/storage/transactional.rs
@@ -127,6 +127,22 @@ where
 	}
 }
 
+/// Same as [`with_transaction`] but casts any internal error to `()`.
+///
+/// This rids `E` of the `From<DispatchError>` bound that is required by `with_transaction`.
+pub fn with_transaction_opaque_err<T, E, F>(f: F) -> Result<Result<T, E>, ()>
+where
+	F: FnOnce() -> TransactionOutcome<Result<T, E>>,
+{
+	with_transaction(move || -> TransactionOutcome<Result<Result<T, E>, DispatchError>> {
+		match f() {
+			TransactionOutcome::Commit(res) => TransactionOutcome::Commit(Ok(res)),
+			TransactionOutcome::Rollback(res) => TransactionOutcome::Rollback(Ok(res)),
+		}
+	})
+	.map_err(|_| ())
+}
+
 /// Same as [`with_transaction`] but without a limit check on nested transactional layers.
 ///
 /// This is mostly for backwards compatibility before there was a transactional layer limit.
diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs
index 3d0429f71b11d6c74f87ef33521634f46b0d591c..1997d8fc223efe7ff1d1be17e2bc10fb973d2cb9 100644
--- a/substrate/frame/support/src/traits.rs
+++ b/substrate/frame/support/src/traits.rs
@@ -59,10 +59,10 @@ pub use misc::{
 	AccountTouch, Backing, ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128,
 	ConstU16, ConstU32, ConstU64, ConstU8, DefensiveMax, DefensiveMin, DefensiveSaturating,
 	DefensiveTruncateFrom, EnsureInherentsAreFirst, EqualPrivilegeOnly, EstimateCallFee,
-	ExecuteBlock, ExtrinsicCall, Get, GetBacking, GetDefault, HandleLifetime, IsSubType, IsType,
-	Len, OffchainWorker, OnKilledAccount, OnNewAccount, PrivilegeCmp, SameOrOther, Time,
-	TryCollect, TryDrop, TypedGet, UnixTime, VariantCount, VariantCountOf, WrapperKeepOpaque,
-	WrapperOpaque,
+	ExecuteBlock, ExtrinsicCall, Get, GetBacking, GetDefault, HandleLifetime, IsInherent,
+	IsSubType, IsType, Len, OffchainWorker, OnKilledAccount, OnNewAccount, PrivilegeCmp,
+	SameOrOther, Time, TryCollect, TryDrop, TypedGet, UnixTime, VariantCount, VariantCountOf,
+	WrapperKeepOpaque, WrapperOpaque,
 };
 #[allow(deprecated)]
 pub use misc::{PreimageProvider, PreimageRecipient};
@@ -86,7 +86,8 @@ mod hooks;
 pub use hooks::GenesisBuild;
 pub use hooks::{
 	BeforeAllRuntimeMigrations, BuildGenesisConfig, Hooks, IntegrityTest, OnFinalize, OnGenesis,
-	OnIdle, OnInitialize, OnRuntimeUpgrade, OnTimestampSet,
+	OnIdle, OnInitialize, OnPoll, OnRuntimeUpgrade, OnTimestampSet, PostInherents,
+	PostTransactions, PreInherents,
 };
 
 pub mod schedule;
diff --git a/substrate/frame/support/src/traits/hooks.rs b/substrate/frame/support/src/traits/hooks.rs
index c37fb0f54bcd534330dbeda9a53cf2096ef96a1d..7d0e5aa1e89ca1d76e81d526881041c09c6e6cf3 100644
--- a/substrate/frame/support/src/traits/hooks.rs
+++ b/substrate/frame/support/src/traits/hooks.rs
@@ -25,10 +25,74 @@ use crate::weights::Weight;
 use impl_trait_for_tuples::impl_for_tuples;
 use sp_runtime::traits::AtLeast32BitUnsigned;
 use sp_std::prelude::*;
+use sp_weights::WeightMeter;
 
 #[cfg(feature = "try-runtime")]
 use sp_runtime::TryRuntimeError;
 
+/// Provides a callback to execute logic before the all inherents.
+pub trait PreInherents {
+	/// Called before all inherents were applied but after `on_initialize`.
+	fn pre_inherents() {}
+}
+
+#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
+#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
+#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))]
+impl PreInherents for Tuple {
+	fn pre_inherents() {
+		for_tuples!( #( Tuple::pre_inherents(); )* );
+	}
+}
+
+/// Provides a callback to execute logic after the all inherents.
+pub trait PostInherents {
+	/// Called after all inherents were applied.
+	fn post_inherents() {}
+}
+
+#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
+#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
+#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))]
+impl PostInherents for Tuple {
+	fn post_inherents() {
+		for_tuples!( #( Tuple::post_inherents(); )* );
+	}
+}
+
+/// Provides a callback to execute logic before the all transactions.
+pub trait PostTransactions {
+	/// Called after all transactions were applied but before `on_finalize`.
+	fn post_transactions() {}
+}
+
+#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
+#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
+#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))]
+impl PostTransactions for Tuple {
+	fn post_transactions() {
+		for_tuples!( #( Tuple::post_transactions(); )* );
+	}
+}
+
+/// Periodically executes logic. Is not guaranteed to run within a specific timeframe and should
+/// only be used on logic that has no deadline.
+pub trait OnPoll<BlockNumber> {
+	/// Code to execute every now and then at the beginning of the block after inherent application.
+	///
+	/// The remaining weight limit must be respected.
+	fn on_poll(_n: BlockNumber, _weight: &mut WeightMeter) {}
+}
+
+#[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))]
+#[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))]
+#[cfg_attr(feature = "tuples-128", impl_for_tuples(128))]
+impl<BlockNumber: Clone> OnPoll<BlockNumber> for Tuple {
+	fn on_poll(n: BlockNumber, weight: &mut WeightMeter) {
+		for_tuples!( #( Tuple::on_poll(n.clone(), weight); )* );
+	}
+}
+
 /// See [`Hooks::on_initialize`].
 pub trait OnInitialize<BlockNumber> {
 	/// See [`Hooks::on_initialize`].
@@ -374,6 +438,12 @@ pub trait Hooks<BlockNumber> {
 		Weight::zero()
 	}
 
+	/// A hook to run logic after inherent application.
+	///
+	/// Is not guaranteed to execute in a block and should therefore only be used in no-deadline
+	/// scenarios.
+	fn on_poll(_n: BlockNumber, _weight: &mut WeightMeter) {}
+
 	/// Hook executed when a code change (aka. a "runtime upgrade") is detected by the FRAME
 	/// `Executive` pallet.
 	///
diff --git a/substrate/frame/support/src/traits/misc.rs b/substrate/frame/support/src/traits/misc.rs
index eafd9c8abdd2dfc4b4174723678d94aa963ad738..1f634a642829c1d6a3e72e0e76ef25bdf2bef55a 100644
--- a/substrate/frame/support/src/traits/misc.rs
+++ b/substrate/frame/support/src/traits/misc.rs
@@ -23,6 +23,7 @@ use impl_trait_for_tuples::impl_for_tuples;
 use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter};
 use sp_arithmetic::traits::{CheckedAdd, CheckedMul, CheckedSub, One, Saturating};
 use sp_core::bounded::bounded_vec::TruncateFrom;
+
 #[doc(hidden)]
 pub use sp_runtime::traits::{
 	ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, ConstU16, ConstU32,
@@ -895,11 +896,21 @@ pub trait GetBacking {
 /// A trait to ensure the inherent are before non-inherent in a block.
 ///
 /// This is typically implemented on runtime, through `construct_runtime!`.
-pub trait EnsureInherentsAreFirst<Block> {
+pub trait EnsureInherentsAreFirst<Block: sp_runtime::traits::Block>:
+	IsInherent<<Block as sp_runtime::traits::Block>::Extrinsic>
+{
 	/// Ensure the position of inherent is correct, i.e. they are before non-inherents.
 	///
-	/// On error return the index of the inherent with invalid position (counting from 0).
-	fn ensure_inherents_are_first(block: &Block) -> Result<(), u32>;
+	/// On error return the index of the inherent with invalid position (counting from 0). On
+	/// success it returns the index of the last inherent. `0` therefore means that there are no
+	/// inherents.
+	fn ensure_inherents_are_first(block: &Block) -> Result<u32, u32>;
+}
+
+/// A trait to check if an extrinsic is an inherent.
+pub trait IsInherent<Extrinsic> {
+	/// Whether this extrinsic is an inherent.
+	fn is_inherent(ext: &Extrinsic) -> bool;
 }
 
 /// An extrinsic on which we can get access to call.
diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr
index a4c7ecf786588a782f96b3ae890c67973c2841fc..30005c07cb631dd48425117507006faa870866cd 100644
--- a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr
+++ b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr
@@ -53,8 +53,9 @@ error[E0599]: no function or associated item named `is_inherent` found for struc
    | |_^ function or associated item not found in `Pallet<Runtime>`
    |
    = help: items from traits can only be used if the trait is implemented and in scope
-   = note: the following trait defines an item `is_inherent`, perhaps you need to implement it:
+   = note: the following traits define an item `is_inherent`, perhaps you need to implement one of them:
            candidate #1: `ProvideInherent`
+           candidate #2: `IsInherent`
    = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error[E0599]: no function or associated item named `check_inherent` found for struct `pallet::Pallet` in the current scope
@@ -119,3 +120,23 @@ error[E0599]: no function or associated item named `is_inherent_required` found
    = note: the following trait defines an item `is_inherent_required`, perhaps you need to implement it:
            candidate #1: `ProvideInherent`
    = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0277]: the trait bound `pallet::Pallet<Runtime>: ProvideInherent` is not satisfied
+  --> tests/construct_runtime_ui/undefined_inherent_part.rs:70:3
+   |
+70 |         Pallet: pallet expanded::{}::{Pallet, Inherent},
+   |         ^^^^^^ the trait `ProvideInherent` is not implemented for `pallet::Pallet<Runtime>`
+
+error[E0277]: the trait bound `pallet::Pallet<Runtime>: ProvideInherent` is not satisfied
+  --> tests/construct_runtime_ui/undefined_inherent_part.rs:66:1
+   |
+66 | / construct_runtime! {
+67 | |     pub struct Runtime
+68 | |     {
+69 | |         System: frame_system expanded::{}::{Pallet, Call, Storage, Config<T>, Event<T>},
+70 | |         Pallet: pallet expanded::{}::{Pallet, Inherent},
+71 | |     }
+72 | | }
+   | |_^ the trait `ProvideInherent` is not implemented for `pallet::Pallet<Runtime>`
+   |
+   = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs b/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs
index 79e9d6786717a5a27717cda754575c2ecd478aa3..33b96dea9486396629e19274de8437f0ca3c3162 100644
--- a/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs
+++ b/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs
@@ -90,6 +90,7 @@ fn module_error_outer_enum_expand_explicit() {
 			frame_system::Error::NonDefaultComposite => (),
 			frame_system::Error::NonZeroRefCount => (),
 			frame_system::Error::CallFiltered => (),
+			frame_system::Error::MultiBlockMigrationsOngoing => (),
 			#[cfg(feature = "experimental")]
 			frame_system::Error::InvalidTask => (),
 			#[cfg(feature = "experimental")]
diff --git a/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs b/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs
index 4bd8ee0bb39a574b2ff3f59b571c1209fc675da4..db006fe79359aa9f096963ce9333933329c23508 100644
--- a/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs
+++ b/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs
@@ -90,6 +90,7 @@ fn module_error_outer_enum_expand_implicit() {
 			frame_system::Error::NonDefaultComposite => (),
 			frame_system::Error::NonZeroRefCount => (),
 			frame_system::Error::CallFiltered => (),
+			frame_system::Error::MultiBlockMigrationsOngoing => (),
 			#[cfg(feature = "experimental")]
 			frame_system::Error::InvalidTask => (),
 			#[cfg(feature = "experimental")]
diff --git a/substrate/frame/support/test/tests/runtime_metadata.rs b/substrate/frame/support/test/tests/runtime_metadata.rs
index bb7f7d2822e7cf1a15c41de1ca47686dd8440bfc..40e70b219ba94062af2bb75391628c73bc3a3233 100644
--- a/substrate/frame/support/test/tests/runtime_metadata.rs
+++ b/substrate/frame/support/test/tests/runtime_metadata.rs
@@ -101,7 +101,7 @@ sp_api::impl_runtime_apis! {
 		fn execute_block(_: Block) {
 			unimplemented!()
 		}
-		fn initialize_block(_: &<Block as BlockT>::Header) {
+		fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			unimplemented!()
 		}
 	}
@@ -200,8 +200,8 @@ fn runtime_metadata() {
 						name: "header",
 						ty: meta_type::<&<Block as BlockT>::Header>(),
 					}],
-					output: meta_type::<()>(),
-					docs: maybe_docs(vec![" Initialize a block with the given header."]),
+					output: meta_type::<sp_runtime::ExtrinsicInclusionMode>(),
+					docs: maybe_docs(vec![" Initialize a block with the given header and return the runtime executive mode."]),
 				},
 			],
 			docs: maybe_docs(vec![
diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs
index 01df09106d99ec668caab9824c4120740c7a6899..3b659916379e9bc823f9fea3b4984dd2c6142cb5 100644
--- a/substrate/frame/system/src/lib.rs
+++ b/substrate/frame/system/src/lib.rs
@@ -130,11 +130,13 @@ use frame_support::{
 		DispatchResult, DispatchResultWithPostInfo, PerDispatchClass, PostDispatchInfo,
 	},
 	ensure, impl_ensure_origin_with_arg_ignoring_arg,
+	migrations::MultiStepMigrator,
 	pallet_prelude::Pays,
 	storage::{self, StorageStreamIter},
 	traits::{
 		ConstU32, Contains, EnsureOrigin, EnsureOriginWithArg, Get, HandleLifetime,
-		OnKilledAccount, OnNewAccount, OriginTrait, PalletInfo, SortedMembers, StoredMap, TypedGet,
+		OnKilledAccount, OnNewAccount, OnRuntimeUpgrade, OriginTrait, PalletInfo, SortedMembers,
+		StoredMap, TypedGet,
 	},
 	Parameter,
 };
@@ -169,6 +171,7 @@ pub use extensions::{
 // Backward compatible re-export.
 pub use extensions::check_mortality::CheckMortality as CheckEra;
 pub use frame_support::dispatch::RawOrigin;
+use frame_support::traits::{PostInherents, PostTransactions, PreInherents};
 pub use weights::WeightInfo;
 
 const LOG_TARGET: &str = "runtime::system";
@@ -299,6 +302,11 @@ pub mod pallet {
 			type BaseCallFilter = frame_support::traits::Everything;
 			type BlockHashCount = frame_support::traits::ConstU64<10>;
 			type OnSetCode = ();
+			type SingleBlockMigrations = ();
+			type MultiBlockMigrator = ();
+			type PreInherents = ();
+			type PostInherents = ();
+			type PostTransactions = ();
 		}
 
 		/// Default configurations of this pallet in a solo-chain environment.
@@ -393,6 +401,11 @@ pub mod pallet {
 
 			/// The set code logic, just the default since we're not a parachain.
 			type OnSetCode = ();
+			type SingleBlockMigrations = ();
+			type MultiBlockMigrator = ();
+			type PreInherents = ();
+			type PostInherents = ();
+			type PostTransactions = ();
 		}
 
 		/// Default configurations of this pallet in a relay-chain environment.
@@ -572,6 +585,35 @@ pub mod pallet {
 
 		/// The maximum number of consumers allowed on a single account.
 		type MaxConsumers: ConsumerLimits;
+
+		/// All migrations that should run in the next runtime upgrade.
+		///
+		/// These used to be formerly configured in `Executive`. Parachains need to ensure that
+		/// running all these migrations in one block will not overflow the weight limit of a block.
+		/// The migrations are run *before* the pallet `on_runtime_upgrade` hooks, just like the
+		/// `OnRuntimeUpgrade` migrations.
+		type SingleBlockMigrations: OnRuntimeUpgrade;
+
+		/// The migrator that is used to run Multi-Block-Migrations.
+		///
+		/// Can be set to [`pallet-migrations`] or an alternative implementation of the interface.
+		/// The diagram in `frame_executive::block_flowchart` explains when it runs.
+		type MultiBlockMigrator: MultiStepMigrator;
+
+		/// A callback that executes in *every block* directly before all inherents were applied.
+		///
+		/// See `frame_executive::block_flowchart` for a in-depth explanation when it runs.
+		type PreInherents: PreInherents;
+
+		/// A callback that executes in *every block* directly after all inherents were applied.
+		///
+		/// See `frame_executive::block_flowchart` for a in-depth explanation when it runs.
+		type PostInherents: PostInherents;
+
+		/// A callback that executes in *every block* directly after all transactions were applied.
+		///
+		/// See `frame_executive::block_flowchart` for a in-depth explanation when it runs.
+		type PostTransactions: PostTransactions;
 	}
 
 	#[pallet::pallet]
@@ -619,6 +661,9 @@ pub mod pallet {
 		}
 
 		/// Set the new runtime code without doing any checks of the given `code`.
+		///
+		/// Note that runtime upgrades will not run if this is called with a not-increasing spec
+		/// version!
 		#[pallet::call_index(3)]
 		#[pallet::weight((T::SystemWeightInfo::set_code(), DispatchClass::Operational))]
 		pub fn set_code_without_checks(
@@ -814,6 +859,8 @@ pub mod pallet {
 		NonZeroRefCount,
 		/// The origin filter prevent the call to be dispatched.
 		CallFiltered,
+		/// A multi-block migration is ongoing and prevents the current code from being replaced.
+		MultiBlockMigrationsOngoing,
 		#[cfg(feature = "experimental")]
 		/// The specified [`Task`] is not valid.
 		InvalidTask,
@@ -845,6 +892,10 @@ pub mod pallet {
 	#[pallet::storage]
 	pub(super) type ExtrinsicCount<T: Config> = StorageValue<_, u32>;
 
+	/// Whether all inherents have been applied.
+	#[pallet::storage]
+	pub type InherentsApplied<T: Config> = StorageValue<_, bool, ValueQuery>;
+
 	/// The current weight for the block.
 	#[pallet::storage]
 	#[pallet::whitelist_storage]
@@ -1373,6 +1424,19 @@ impl<T: Config> Pallet<T> {
 		Self::deposit_event(Event::CodeUpdated);
 	}
 
+	/// Whether all inherents have been applied.
+	pub fn inherents_applied() -> bool {
+		InherentsApplied::<T>::get()
+	}
+
+	/// Note that all inherents have been applied.
+	///
+	/// Should be called immediately after all inherents have been applied. Must be called at least
+	/// once per block.
+	pub fn note_inherents_applied() {
+		InherentsApplied::<T>::put(true);
+	}
+
 	/// Increment the reference counter on an account.
 	#[deprecated = "Use `inc_consumers` instead"]
 	pub fn inc_ref(who: &T::AccountId) {
@@ -1692,6 +1756,7 @@ impl<T: Config> Pallet<T> {
 		<Digest<T>>::put(digest);
 		<ParentHash<T>>::put(parent_hash);
 		<BlockHash<T>>::insert(*number - One::one(), parent_hash);
+		<InherentsApplied<T>>::kill();
 
 		// Remove previous block data from storage
 		BlockWeight::<T>::kill();
@@ -1738,6 +1803,7 @@ impl<T: Config> Pallet<T> {
 		ExecutionPhase::<T>::kill();
 		AllExtrinsicsLen::<T>::kill();
 		storage::unhashed::kill(well_known_keys::INTRABLOCK_ENTROPY);
+		InherentsApplied::<T>::kill();
 
 		// The following fields
 		//
@@ -1981,10 +2047,14 @@ impl<T: Config> Pallet<T> {
 
 	/// Determine whether or not it is possible to update the code.
 	///
-	/// Checks the given code if it is a valid runtime wasm blob by instantianting
+	/// Checks the given code if it is a valid runtime wasm blob by instantiating
 	/// it and extracting the runtime version of it. It checks that the runtime version
 	/// of the old and new runtime has the same spec name and that the spec version is increasing.
 	pub fn can_set_code(code: &[u8]) -> Result<(), sp_runtime::DispatchError> {
+		if T::MultiBlockMigrator::ongoing() {
+			return Err(Error::<T>::MultiBlockMigrationsOngoing.into())
+		}
+
 		let current_version = T::Version::get();
 		let new_version = sp_io::misc::runtime_version(code)
 			.and_then(|v| RuntimeVersion::decode(&mut &v[..]).ok())
diff --git a/substrate/frame/system/src/mock.rs b/substrate/frame/system/src/mock.rs
index c4108099e39ffdf306d7a9e5e0af8a619336eaea..7059845a7df33104a66348803fee6396bf2f93d2 100644
--- a/substrate/frame/system/src/mock.rs
+++ b/substrate/frame/system/src/mock.rs
@@ -86,6 +86,22 @@ impl Config for Test {
 	type Version = Version;
 	type AccountData = u32;
 	type OnKilledAccount = RecordKilled;
+	type MultiBlockMigrator = MockedMigrator;
+}
+
+parameter_types! {
+	pub static Ongoing: bool = false;
+}
+
+pub struct MockedMigrator;
+impl frame_support::migrations::MultiStepMigrator for MockedMigrator {
+	fn ongoing() -> bool {
+		Ongoing::get()
+	}
+
+	fn step() -> Weight {
+		Weight::zero()
+	}
 }
 
 pub type SysEvent = frame_system::Event<Test>;
diff --git a/substrate/frame/system/src/tests.rs b/substrate/frame/system/src/tests.rs
index e437e7f9f39b0845d9f7eef33e3c90546c8dbbd2..b889b5ca046efe557e7706870c11febce8a358b2 100644
--- a/substrate/frame/system/src/tests.rs
+++ b/substrate/frame/system/src/tests.rs
@@ -675,6 +675,28 @@ fn set_code_with_real_wasm_blob() {
 	});
 }
 
+#[test]
+fn set_code_rejects_during_mbm() {
+	Ongoing::set(true);
+
+	let executor = substrate_test_runtime_client::new_native_or_wasm_executor();
+	let mut ext = new_test_ext();
+	ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(executor));
+	ext.execute_with(|| {
+		System::set_block_number(1);
+		let res = System::set_code(
+			RawOrigin::Root.into(),
+			substrate_test_runtime_client::runtime::wasm_binary_unwrap().to_vec(),
+		);
+		assert_eq!(
+			res,
+			Err(DispatchErrorWithPostInfo::from(Error::<Test>::MultiBlockMigrationsOngoing))
+		);
+
+		assert!(System::events().is_empty());
+	});
+}
+
 #[test]
 fn set_code_via_authorization_works() {
 	let executor = substrate_test_runtime_client::new_native_or_wasm_executor();
diff --git a/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs
index 2b1e65ec88524a0236a464005fb2d1215c1157fe..e34e4c0e7672164afb41702ab3bae87082df9062 100644
--- a/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs
+++ b/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs
@@ -456,6 +456,7 @@ impl<'a> ToClientSideDecl<'a> {
 						|err| #crate_::ApiError::FailedToDecodeReturnValue {
 							function: #function_name,
 							error: err,
+							raw: r.clone(),
 						}
 					)
 				)
diff --git a/substrate/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs
index c1339ff6621b389e32c2f9ca2a7f19079c2d269e..1761e0ac9dbf450c15adae2659119c918f300334 100644
--- a/substrate/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs
+++ b/substrate/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs
@@ -158,7 +158,7 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result<To
 				&self,
 				_: <#block_type as #crate_::BlockT>::Hash,
 				_: &<#block_type as #crate_::BlockT>::Header,
-			) -> std::result::Result<(), #crate_::ApiError> {
+			) -> std::result::Result<#crate_::__private::ExtrinsicInclusionMode, #crate_::ApiError> {
 				unimplemented!("`Core::initialize_block` not implemented for runtime api mocks")
 			}
 		}
diff --git a/substrate/primitives/api/src/lib.rs b/substrate/primitives/api/src/lib.rs
index 190de1ab3fdee0611f27071b9c769a164c3aca9d..a945b9f21f3cff60d6a9d5e24aa07704a1dc8d31 100644
--- a/substrate/primitives/api/src/lib.rs
+++ b/substrate/primitives/api/src/lib.rs
@@ -101,7 +101,7 @@ pub mod __private {
 		generic::BlockId,
 		traits::{Block as BlockT, Hash as HashT, HashingFor, Header as HeaderT, NumberFor},
 		transaction_validity::TransactionValidity,
-		RuntimeString, TransactionOutcome,
+		ExtrinsicInclusionMode, RuntimeString, TransactionOutcome,
 	};
 	pub use sp_std::{mem, slice, vec};
 	pub use sp_version::{create_apis_vec, ApiId, ApisVec, RuntimeVersion};
@@ -115,11 +115,11 @@ pub use sp_core::traits::CallContext;
 use sp_core::OpaqueMetadata;
 #[cfg(feature = "std")]
 use sp_externalities::{Extension, Extensions};
-use sp_runtime::traits::Block as BlockT;
 #[cfg(feature = "std")]
 use sp_runtime::traits::HashingFor;
 #[cfg(feature = "std")]
 pub use sp_runtime::TransactionOutcome;
+use sp_runtime::{traits::Block as BlockT, ExtrinsicInclusionMode};
 #[cfg(feature = "std")]
 pub use sp_state_machine::StorageProof;
 #[cfg(feature = "std")]
@@ -280,7 +280,7 @@ pub use sp_api_proc_macro::decl_runtime_apis;
 /// ```rust
 /// use sp_version::create_runtime_str;
 /// #
-/// # use sp_runtime::traits::Block as BlockT;
+/// # use sp_runtime::{ExtrinsicInclusionMode, traits::Block as BlockT};
 /// # use sp_test_primitives::Block;
 /// #
 /// # /// The declaration of the `Runtime` type is done by the `construct_runtime!` macro
@@ -307,7 +307,9 @@ pub use sp_api_proc_macro::decl_runtime_apis;
 /// #           unimplemented!()
 /// #       }
 /// #       fn execute_block(_block: Block) {}
-/// #       fn initialize_block(_header: &<Block as BlockT>::Header) {}
+/// #       fn initialize_block(_header: &<Block as BlockT>::Header) -> ExtrinsicInclusionMode {
+/// #           unimplemented!()
+/// #       }
 /// #   }
 ///
 ///     impl self::Balance<Block> for Runtime {
@@ -540,11 +542,12 @@ pub fn init_runtime_logger() {
 #[cfg(feature = "std")]
 #[derive(Debug, thiserror::Error)]
 pub enum ApiError {
-	#[error("Failed to decode return value of {function}")]
+	#[error("Failed to decode return value of {function}: {error} raw data: {raw:?}")]
 	FailedToDecodeReturnValue {
 		function: &'static str,
 		#[source]
 		error: codec::Error,
+		raw: Vec<u8>,
 	},
 	#[error("Failed to convert return value from runtime to node of {function}")]
 	FailedToConvertReturnValue {
@@ -800,15 +803,18 @@ pub fn deserialize_runtime_api_info(bytes: [u8; RUNTIME_API_INFO_SIZE]) -> ([u8;
 decl_runtime_apis! {
 	/// The `Core` runtime api that every Substrate runtime needs to implement.
 	#[core_trait]
-	#[api_version(4)]
+	#[api_version(5)]
 	pub trait Core {
 		/// Returns the version of the runtime.
 		fn version() -> RuntimeVersion;
 		/// Execute the given block.
 		fn execute_block(block: Block);
 		/// Initialize a block with the given header.
+		#[changed_in(5)]
 		#[renamed("initialise_block", 2)]
 		fn initialize_block(header: &<Block as BlockT>::Header);
+		/// Initialize a block with the given header and return the runtime executive mode.
+		fn initialize_block(header: &<Block as BlockT>::Header) -> ExtrinsicInclusionMode;
 	}
 
 	/// The `Metadata` api trait that returns metadata for the runtime.
diff --git a/substrate/primitives/api/test/tests/decl_and_impl.rs b/substrate/primitives/api/test/tests/decl_and_impl.rs
index d68470551d20061aa09d277a9999db81654ff47d..211a08561fd4bcf601d9508d96447fc07f82657f 100644
--- a/substrate/primitives/api/test/tests/decl_and_impl.rs
+++ b/substrate/primitives/api/test/tests/decl_and_impl.rs
@@ -139,7 +139,7 @@ impl_runtime_apis! {
 		fn execute_block(_: Block) {
 			unimplemented!()
 		}
-		fn initialize_block(_: &<Block as BlockT>::Header) {
+		fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			unimplemented!()
 		}
 	}
diff --git a/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs b/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs
index 43718e4cd04a3f25fcc805edec3c14283f1f5f56..262a874213a5662c0c87a79989b620c661e707c7 100644
--- a/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs
+++ b/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs
@@ -40,7 +40,7 @@ sp_api::impl_runtime_apis! {
 		fn execute_block(_: Block) {
 			unimplemented!()
 		}
-		fn initialize_block(_: &<Block as BlockT>::Header) {
+		fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			unimplemented!()
 		}
 	}
diff --git a/substrate/primitives/api/test/tests/ui/impl_missing_version.rs b/substrate/primitives/api/test/tests/ui/impl_missing_version.rs
index 560257b5168c921ce112a56fcbd7e9c303363c77..58850ab343fbb7bcc2c04b01c7aec9cb881023d0 100644
--- a/substrate/primitives/api/test/tests/ui/impl_missing_version.rs
+++ b/substrate/primitives/api/test/tests/ui/impl_missing_version.rs
@@ -45,7 +45,7 @@ sp_api::impl_runtime_apis! {
 		fn execute_block(_: Block) {
 			unimplemented!()
 		}
-		fn initialize_block(_: &<Block as BlockT>::Header) {
+		fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			unimplemented!()
 		}
 	}
diff --git a/substrate/primitives/api/test/tests/ui/missing_versioned_method.rs b/substrate/primitives/api/test/tests/ui/missing_versioned_method.rs
index 6ead545f85a11e8f0382331e77145acb4a053d5e..70f75d065154afcb807e1712c95fc24eca5267b1 100644
--- a/substrate/primitives/api/test/tests/ui/missing_versioned_method.rs
+++ b/substrate/primitives/api/test/tests/ui/missing_versioned_method.rs
@@ -44,7 +44,7 @@ sp_api::impl_runtime_apis! {
 		fn execute_block(_: Block) {
 			unimplemented!()
 		}
-		fn initialize_block(_: &<Block as BlockT>::Header) {
+		fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			unimplemented!()
 		}
 	}
diff --git a/substrate/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs b/substrate/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs
index 8eebc1d79babcbd6fb95876a530a920397c035a6..63032000040b7d19f8e31fc4ac3221369aef9e7e 100644
--- a/substrate/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs
+++ b/substrate/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs
@@ -47,7 +47,7 @@ sp_api::impl_runtime_apis! {
 		fn execute_block(_: Block) {
 			unimplemented!()
 		}
-		fn initialize_block(_: &<Block as BlockT>::Header) {
+		fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			unimplemented!()
 		}
 	}
diff --git a/substrate/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs b/substrate/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs
index 594556d57be50dbd151de486dc22a63a733b933b..0858813bc99941be005e0c0141b2defc3a96a3d3 100644
--- a/substrate/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs
+++ b/substrate/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs
@@ -51,7 +51,7 @@ sp_api::impl_runtime_apis! {
 		fn execute_block(_: Block) {
 			unimplemented!()
 		}
-		fn initialize_block(_: &<Block as BlockT>::Header) {
+		fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			unimplemented!()
 		}
 	}
diff --git a/substrate/primitives/api/test/tests/ui/positive_cases/default_impls.rs b/substrate/primitives/api/test/tests/ui/positive_cases/default_impls.rs
index ae573238ffe144d26b7e5dd4528e8a9e5ab88a9e..3e0cb79156c8bf5c218e29e3024d181a69fc4da6 100644
--- a/substrate/primitives/api/test/tests/ui/positive_cases/default_impls.rs
+++ b/substrate/primitives/api/test/tests/ui/positive_cases/default_impls.rs
@@ -46,7 +46,7 @@ sp_api::impl_runtime_apis! {
 		fn execute_block(_: Block) {
 			unimplemented!()
 		}
-		fn initialize_block(_: &<Block as BlockT>::Header) {
+		fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			unimplemented!()
 		}
 	}
diff --git a/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs b/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs
index 921bf0d04351dac4ddcdd18030619f1ebcf15008..b2caea7ab7e44d910628d0ac834017e66e7e5d2e 100644
--- a/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs
+++ b/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs
@@ -42,7 +42,7 @@ sp_api::impl_runtime_apis! {
 		fn execute_block(_: Block) {
 			unimplemented!()
 		}
-		fn initialize_block(_: &<Block as BlockT>::Header) {
+		fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
 			unimplemented!()
 		}
 	}
diff --git a/substrate/primitives/runtime/src/lib.rs b/substrate/primitives/runtime/src/lib.rs
index ddf92554c83056f192015a26285408b7fdf33695..44bf3c969e5441245d1547e779195cdb68a83599 100644
--- a/substrate/primitives/runtime/src/lib.rs
+++ b/substrate/primitives/runtime/src/lib.rs
@@ -998,6 +998,16 @@ impl<R> TransactionOutcome<R> {
 	}
 }
 
+/// Confines the kind of extrinsics that can be included in a block.
+#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Encode, Decode, TypeInfo)]
+pub enum ExtrinsicInclusionMode {
+	/// All extrinsics are allowed to be included in this block.
+	#[default]
+	AllExtrinsics,
+	/// Inherents are allowed to be included.
+	OnlyInherents,
+}
+
 #[cfg(test)]
 mod tests {
 	use crate::traits::BlakeTwo256;
diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs
index db9ff187b707fa66c70be24844297b7f74844e4a..63e0aa6e13791cfa88c3c039b9ffcb76ab24e145 100644
--- a/substrate/test-utils/runtime/src/lib.rs
+++ b/substrate/test-utils/runtime/src/lib.rs
@@ -61,7 +61,7 @@ use sp_runtime::{
 	create_runtime_str, impl_opaque_keys,
 	traits::{BlakeTwo256, Block as BlockT, DispatchInfoOf, NumberFor, Verify},
 	transaction_validity::{TransactionSource, TransactionValidity, TransactionValidityError},
-	ApplyExtrinsicResult, Perbill,
+	ApplyExtrinsicResult, ExtrinsicInclusionMode, Perbill,
 };
 #[cfg(any(feature = "std", test))]
 use sp_version::NativeVersion;
@@ -480,9 +480,9 @@ impl_runtime_apis! {
 			Executive::execute_block(block);
 		}
 
-		fn initialize_block(header: &<Block as BlockT>::Header) {
+		fn initialize_block(header: &<Block as BlockT>::Header) -> ExtrinsicInclusionMode {
 			log::trace!(target: LOG_TARGET, "initialize_block: {header:#?}");
-			Executive::initialize_block(header);
+			Executive::initialize_block(header)
 		}
 	}