diff --git a/substrate-node/pallets/pallet-smart-contract/src/migrations/v10.rs b/substrate-node/pallets/pallet-smart-contract/src/migrations/v10.rs index ca180d44d..a1a8cb91e 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/migrations/v10.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/migrations/v10.rs @@ -81,3 +81,17 @@ pub fn rework_billing_loop_insertion() -> frame_support::weights::Wei Weight::zero() } } + +pub struct CheckStorageState(PhantomData); + +impl OnRuntimeUpgrade for CheckStorageState { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + info!("current pallet version: {:?}", PalletVersion::::get()); + assert!(PalletVersion::::get() == types::StorageVersion::V10); + + migrations::v9::check_pallet_smart_contract::(); + + Ok(vec![]) + } +} diff --git a/substrate-node/pallets/pallet-smart-contract/src/migrations/v11.rs b/substrate-node/pallets/pallet-smart-contract/src/migrations/v11.rs index 4ea467536..2136917e8 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/migrations/v11.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/migrations/v11.rs @@ -45,7 +45,7 @@ impl OnRuntimeUpgrade for ExtendContractLock { debug!("current pallet version: {:?}", PalletVersion::::get()); assert!(PalletVersion::::get() >= types::StorageVersion::V11); - check_contract_lock_v11::(); + check_contract_lock::(); debug!( "👥 Smart Contract pallet to {:?} passes POST migrate checks ✅", @@ -86,7 +86,35 @@ pub fn migrate_to_version_11() -> frame_support::weights::Weight { T::DbWeight::get().reads_writes(r, w) } -pub fn check_contract_lock_v11() { +pub struct CheckStorageState(PhantomData); + +impl OnRuntimeUpgrade for CheckStorageState { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + info!("current pallet version: {:?}", PalletVersion::::get()); + assert!(PalletVersion::::get() == types::StorageVersion::V11); + + check_pallet_smart_contract::(); + + Ok(vec![]) + } +} + +pub fn check_pallet_smart_contract() { + info!("💥💥💥💥💥 CHECKING PALLET SMART CONTRACT STORAGE 💥💥💥💥💥"); + migrations::v9::check_contracts::(); + migrations::v9::check_contracts_to_bill_at::(); + migrations::v9::check_active_node_contracts::(); + migrations::v9::check_active_rent_contract_for_node::(); + migrations::v9::check_contract_id_by_node_id_and_hash::(); + migrations::v9::check_contract_id_by_name_registration::(); + check_contract_lock::(); + migrations::v9::check_solution_providers::(); + migrations::v9::check_contract_billing_information_by_id::(); + migrations::v9::check_node_contract_resources::(); +} + +fn check_contract_lock() { debug!( "🔎 Smart Contract pallet {:?} checking ContractLock storage map START", PalletVersion::::get() diff --git a/substrate-node/pallets/pallet-smart-contract/src/migrations/v9.rs b/substrate-node/pallets/pallet-smart-contract/src/migrations/v9.rs index d420af067..c31e0cfbb 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/migrations/v9.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/migrations/v9.rs @@ -6,6 +6,20 @@ use scale_info::prelude::string::String; use sp_core::Get; use sp_std::{marker::PhantomData, vec, vec::Vec}; +pub struct CheckStorageState(PhantomData); + +impl OnRuntimeUpgrade for CheckStorageState { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + info!("current pallet version: {:?}", PalletVersion::::get()); + assert!(PalletVersion::::get() >= types::StorageVersion::V8); + + check_pallet_smart_contract::(); + + Ok(vec![]) + } +} + pub struct CleanStorageState(PhantomData); impl OnRuntimeUpgrade for CleanStorageState { @@ -22,16 +36,6 @@ impl OnRuntimeUpgrade for CleanStorageState { Weight::zero() } } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - info!("current pallet version: {:?}", PalletVersion::::get()); - assert!(PalletVersion::::get() == types::StorageVersion::V8 || PalletVersion::::get() == types::StorageVersion::V9); - - check_pallet_smart_contract::(); - - Ok(vec![]) - } } pub fn check_pallet_smart_contract() { diff --git a/substrate-node/pallets/pallet-tfgrid/src/migrations/mod.rs b/substrate-node/pallets/pallet-tfgrid/src/migrations/mod.rs index a387ddfea..60f8d6aeb 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/migrations/mod.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/migrations/mod.rs @@ -4,6 +4,6 @@ pub mod v11; pub mod v12; pub mod v13; pub mod v14; -//pub mod v15; +pub mod v15; pub mod v16; pub mod v17; diff --git a/substrate-node/pallets/pallet-tfgrid/src/migrations/v15.rs b/substrate-node/pallets/pallet-tfgrid/src/migrations/v15.rs index 341523adf..bd693145b 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/migrations/v15.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/migrations/v15.rs @@ -1,12 +1,13 @@ use crate::*; use frame_support::{traits::Get, traits::OnRuntimeUpgrade, weights::Weight}; use log::{debug, info}; -use sp_std::marker::PhantomData; +use scale_info::prelude::string::String; +use sp_std::{marker::PhantomData, vec::Vec}; #[cfg(feature = "try-runtime")] -use parity_scale_codec::Decode; +use parity_scale_codec::{Decode, Encode}; #[cfg(feature = "try-runtime")] -use sp_std::vec::Vec; +use sp_std::vec; pub struct MigrateTwinsV15(PhantomData); @@ -85,3 +86,496 @@ pub fn migrate_twins() -> frame_support::weights::Weight { // Return the weight consumed by the migration. T::DbWeight::get().reads_writes(read_writes, read_writes + 1) } + +pub struct CheckStorageState(PhantomData); + +impl OnRuntimeUpgrade for CheckStorageState { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + info!("current pallet version: {:?}", PalletVersion::::get()); + assert!(PalletVersion::::get() == types::StorageVersion::V15Struct); + + check_pallet_tfgrid::(); + + Ok(vec![]) + } +} + +pub fn check_pallet_tfgrid() { + info!("💥💥💥💥💥 CHECKING PALLET TFGRID STORAGE 💥💥💥💥💥"); + check_farms::(); + check_nodes_by_farm_id::(); + check_farm_id_by_name::(); + check_farm_payout_v2_address_by_farm_id::(); + check_nodes::(); + check_node_id_by_twin_id::(); + // check_entities::(); + // check_entity_id_by_account_id::(); + // check_entity_id_by_name::(); + check_twins::(); + check_twin_id_by_account_id::(); + check_twin_bounded_account_id::(); + check_pricing_policies::(); + check_pricing_policy_id_by_name::(); + check_farming_policies_map::(); + check_users_terms_and_conditions::(); +} + +// Farms +pub fn check_farms() { + debug!( + "🔎 TFGrid pallet {:?} checking Farms storage map START", + PalletVersion::::get() + ); + + let farm_id_range = 1..=FarmID::::get(); + + for (farm_id, farm) in Farms::::iter() { + if farm_id != farm.id { + debug!(" ⚠️ Farms[id: {}]: wrong id ({})", farm_id, farm.id); + } + if !farm_id_range.contains(&farm_id) { + debug!( + " ⚠️ Farms[id: {}]: id not in range {:?}", + farm_id, farm_id_range + ); + } + + // FarmIdByName + if !FarmIdByName::::contains_key(farm.name.clone().into()) { + debug!( + " ⚠️ Farm[id: {}]: farm (name: {}) not found", + farm_id, + String::from_utf8_lossy(&farm.name.into()) + ); + } + + // Twins + if !Twins::::contains_key(farm.twin_id) { + debug!( + " ⚠️ Farm[id: {}]: twin (twin_id: {}) not found", + farm_id, farm.twin_id + ); + } + + // PricingPolicies + if !PricingPolicies::::contains_key(farm.pricing_policy_id) { + debug!( + " ⚠️ Farm[id: {}]: pricing policy (pricing_policy_id: {}) not found", + farm_id, farm.pricing_policy_id + ); + } + + // FarmingPoliciesMap + if let Some(limits) = farm.farming_policy_limits { + if !FarmingPoliciesMap::::contains_key(limits.farming_policy_id) { + debug!( + " ⚠️ Farm[id: {}]: farming policy (farming_policy_id: {}) not found", + farm_id, limits.farming_policy_id + ); + } + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking Farms storage map END", + PalletVersion::::get() + ); +} + +// NodesByFarmID +pub fn check_nodes_by_farm_id() { + debug!( + "🔎 TFGrid pallet {:?} checking NodesByFarmID storage map START", + PalletVersion::::get() + ); + + for (farm_id, nodes) in NodesByFarmID::::iter() { + if Farms::::get(farm_id).is_none() { + debug!(" ⚠️ NodesByFarmID[farm: {}]: farm not exists", farm_id); + } + + for node_id in nodes { + if Nodes::::get(node_id).is_none() { + debug!( + " ⚠️ NodesByFarmID[farm: {}]: node {} not exists", + farm_id, node_id + ); + } + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking NodesByFarmID storage map END", + PalletVersion::::get() + ); +} + +// FarmIdByName +pub fn check_farm_id_by_name() { + debug!( + "🔎 TFGrid pallet {:?} checking FarmIdByName storage map START", + PalletVersion::::get() + ); + + for (farm_name, farm_id) in FarmIdByName::::iter() { + if let Some(farm) = Farms::::get(farm_id) { + if farm_name != farm.name.clone().into() { + debug!( + " ⚠️ FarmIdByName[name: {}]: name ({:?}) on farm {} not matching", + String::from_utf8_lossy(&farm_name), + String::from_utf8_lossy(&farm.name.into()), + farm_id, + ); + } + } else { + debug!( + " ⚠️ FarmIdByName[name: {}]: farm {} not exists", + String::from_utf8_lossy(&farm_name), + farm_id + ); + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking FarmIdByName storage map END", + PalletVersion::::get() + ); +} + +// FarmPayoutV2AddressByFarmID +pub fn check_farm_payout_v2_address_by_farm_id() { + debug!( + "🔎 TFGrid pallet {:?} checking FarmPayoutV2AddressByFarmID storage map START", + PalletVersion::::get() + ); + + for (farm_id, _stellar_address) in FarmPayoutV2AddressByFarmID::::iter() { + if Farms::::get(farm_id).is_none() { + debug!( + " ⚠️ FarmPayoutV2AddressByFarmID[farm id: {}]: farm not exists", + farm_id + ); + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking FarmPayoutV2AddressByFarmID storage map END", + PalletVersion::::get() + ); +} + +// Nodes +pub fn check_nodes() { + debug!( + "🔎 TFGrid pallet {:?} checking Nodes storage map START", + PalletVersion::::get() + ); + + let node_id_range = 1..=NodeID::::get(); + + for (node_id, node) in Nodes::::iter() { + if node_id != node.id { + debug!(" ⚠️ Nodes[id: {}]: wrong id ({})", node_id, node.id); + } + if !node_id_range.contains(&node_id) { + debug!( + " ⚠️ Nodes[id: {}]: id not in range {:?}", + node_id, node_id_range + ); + } + + // Farms + if !Farms::::contains_key(node.farm_id) { + debug!( + " ⚠️ Nodes[id: {}]: farm (farm_id: {}) not found", + node_id, node.farm_id + ); + } + + // Twins + if !Twins::::contains_key(node.twin_id) { + debug!( + " ⚠️ Nodes[id: {}]: twin (twin_id: {}) not found", + node_id, node.twin_id + ); + } + + // FarmingPoliciesMap + if !FarmingPoliciesMap::::contains_key(node.farming_policy_id) { + debug!( + " ⚠️ Node[id: {}]: farming policy (farming_policy_id: {}) not found", + node_id, node.farming_policy_id + ); + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking Nodes storage map END", + PalletVersion::::get() + ); +} + +// NodeIdByTwinID +pub fn check_node_id_by_twin_id() { + debug!( + "🔎 TFGrid pallet {:?} checking NodeIdByTwinID storage map START", + PalletVersion::::get() + ); + + for (twin_id, node_id) in NodeIdByTwinID::::iter() { + if Twins::::get(twin_id).is_none() { + debug!( + " ⚠️ NodeIdByTwinID[twin_id: {}]: twin not exists", + twin_id + ); + } + + if Nodes::::get(node_id).is_none() { + debug!( + " ⚠️ NodeIdByTwinID[twin_id: {}]: node {} not exists", + twin_id, node_id + ); + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking NodeIdByTwinID storage map END", + PalletVersion::::get() + ); +} + +// Entities +// pub type EntityIdByAccountID +// pub type EntityIdByName + +// Twins +pub fn check_twins() { + debug!( + "🔎 TFGrid pallet {:?} checking Twins storage map START", + PalletVersion::::get() + ); + + let twin_id_range = 1..=TwinID::::get(); + + for (twin_id, twin) in Twins::::iter() { + if twin_id != twin.id { + debug!(" ⚠️ Twins[id: {}]: wrong id ({})", twin_id, twin.id); + } + if !twin_id_range.contains(&twin_id) { + debug!( + " ⚠️ Twins[id: {}]: id not in range {:?}", + twin_id, twin_id_range + ); + } + + // TwinIdByAccountID + if !TwinIdByAccountID::::contains_key(&twin.account_id) { + debug!( + " ⚠️ Twins[id: {}]: account (account_id: {:?}) not found", + twin_id, &twin.account_id + ); + } + + // UsersTermsAndConditions + if !UsersTermsAndConditions::::contains_key(&twin.account_id) { + debug!( + " ⚠️ Twins[id: {}]: users terms and conditions (account_id: {:?}) not found", + twin_id, &twin.account_id + ); + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking Twins storage map END", + PalletVersion::::get() + ); +} + +// TwinIdByAccountID +pub fn check_twin_id_by_account_id() { + debug!( + "🔎 TFGrid pallet {:?} checking TwinIdByAccountID storage map START", + PalletVersion::::get() + ); + + for (account_id, twin_id) in TwinIdByAccountID::::iter() { + if let Some(twin) = Twins::::get(twin_id) { + if account_id != twin.account_id { + debug!( + " ⚠️ TwinIdByAccountID[account_id: {:?}]: account ({:?}) on twin {} not matching", + &account_id, &twin.account_id, twin_id, + ); + } + } else { + debug!( + " ⚠️ TwinIdByAccountID[account_id: {:?}]: twin {} not exists", + &account_id, twin_id + ); + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking TwinIdByAccountID storage map END", + PalletVersion::::get() + ); +} + +// TwinBoundedAccountID +pub fn check_twin_bounded_account_id() { + debug!( + "🔎 TFGrid pallet {:?} checking TwinBoundedAccountID storage map START", + PalletVersion::::get() + ); + + for (twin_id, _bounded_account_id) in TwinBoundedAccountID::::iter() { + if Twins::::get(twin_id).is_none() { + debug!( + " ⚠️ TwinBoundedAccountID[twin_id: {}]: twin not exists", + twin_id + ); + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking TwinBoundedAccountID storage map END", + PalletVersion::::get() + ); +} + +// PricingPolicies +pub fn check_pricing_policies() { + debug!( + "🔎 TFGrid pallet {:?} checking PricingPolicies storage map START", + PalletVersion::::get() + ); + + let pricing_policy_id_range = 1..=PricingPolicyID::::get(); + + for (pricing_policy_id, pricing_policy) in PricingPolicies::::iter() { + if pricing_policy_id != pricing_policy.id { + debug!( + " ⚠️ PricingPolicies[id: {}]: wrong id ({})", + pricing_policy_id, pricing_policy.id + ); + } + if !pricing_policy_id_range.contains(&pricing_policy_id) { + debug!( + " ⚠️ PricingPolicies[id: {}]: id not in range {:?}", + pricing_policy_id, pricing_policy_id_range + ); + } + + // PricingPolicyIdByName + if !PricingPolicyIdByName::::contains_key(&pricing_policy.name) { + debug!( + " ⚠️ PricingPolicies[id: {}]: : pricing policy (name: {}) not found", + pricing_policy_id, + String::from_utf8_lossy(&pricing_policy.name), + ); + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking PricingPolicies storage map END", + PalletVersion::::get() + ); +} + +// PricingPolicyIdByName +pub fn check_pricing_policy_id_by_name() { + debug!( + "🔎 TFGrid pallet {:?} checking PricingPolicyIdByName storage map START", + PalletVersion::::get() + ); + + for (pricing_policy_name, pricing_policy_id) in PricingPolicyIdByName::::iter() { + if let Some(pricing_policy) = PricingPolicies::::get(pricing_policy_id) { + if pricing_policy_name != pricing_policy.name { + debug!( + " ⚠️ PricingPolicyIdByName[name: {}]: name ({:?}) on pricing policy {} not matching", + String::from_utf8_lossy(&pricing_policy_name), + String::from_utf8_lossy(&pricing_policy.name), + pricing_policy_id, + ); + } + } else { + debug!( + " ⚠️ PricingPolicyIdByName[name: {}]: pricing policy {} not exists", + String::from_utf8_lossy(&pricing_policy_name), + pricing_policy_id + ); + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking Farms PricingPolicyIdByName map END", + PalletVersion::::get() + ); +} + +// FarmingPoliciesMap +pub fn check_farming_policies_map() { + debug!( + "🔎 TFGrid pallet {:?} checking FarmingPoliciesMap storage map START", + PalletVersion::::get() + ); + + let farming_policy_id_range = 1..=FarmingPolicyID::::get(); + + for (farming_policy_id, farming_policy) in FarmingPoliciesMap::::iter() { + if farming_policy_id != farming_policy.id { + debug!( + " ⚠️ FarmingPoliciesMap[id: {}]: wrong id ({})", + farming_policy_id, farming_policy.id + ); + } + if !farming_policy_id_range.contains(&farming_policy_id) { + debug!( + " ⚠️ FarmingPoliciesMap[id: {}]: id not in range {:?}", + farming_policy_id, farming_policy_id_range + ); + } + + // Nothing to add here + } + + debug!( + "🏁 TFGrid pallet {:?} checking FarmingPoliciesMap storage map END", + PalletVersion::::get() + ); +} + +// UsersTermsAndConditions +pub fn check_users_terms_and_conditions() { + debug!( + "🔎 TFGrid pallet {:?} checking UsersTermsAndConditions storage map START", + PalletVersion::::get() + ); + + // Nothing to do here + + debug!( + "🏁 TFGrid pallet {:?} checking UsersTermsAndConditions storage map END", + PalletVersion::::get() + ); +} + +// NodePower +pub fn check_node_power() { + debug!( + "🔎 TFGrid pallet {:?} checking NodePower storage map START", + PalletVersion::::get() + ); + + for (twin_id, _node_power) in NodePower::::iter() { + if Twins::::get(twin_id).is_none() { + debug!(" ⚠️ NodePower[twin_id: {}]: twin not exists", twin_id); + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking NodePower storage map END", + PalletVersion::::get() + ); +} diff --git a/substrate-node/pallets/pallet-tfgrid/src/migrations/v16.rs b/substrate-node/pallets/pallet-tfgrid/src/migrations/v16.rs index 8c51465d0..e69b41102 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/migrations/v16.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/migrations/v16.rs @@ -7,6 +7,9 @@ use log::info; use sp_core::Get; use sp_std::marker::PhantomData; +#[cfg(feature = "try-runtime")] +use sp_std::{vec, vec::Vec}; + pub struct KillNodeGpuStatus(PhantomData); impl OnRuntimeUpgrade for KillNodeGpuStatus { @@ -43,3 +46,21 @@ impl OnRuntimeUpgrade for KillNodeGpuStatus { } } } + +pub struct CheckStorageState(PhantomData); + +impl OnRuntimeUpgrade for CheckStorageState { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + info!("current pallet version: {:?}", PalletVersion::::get()); + assert!(PalletVersion::::get() == types::StorageVersion::V16Struct); + + check_pallet_tfgrid::(); + + Ok(vec![]) + } +} + +pub fn check_pallet_tfgrid() { + migrations::v15::check_pallet_tfgrid::(); +} diff --git a/substrate-node/pallets/pallet-tfgrid/src/migrations/v17.rs b/substrate-node/pallets/pallet-tfgrid/src/migrations/v17.rs index 7b21c5e62..789e8bc9f 100644 --- a/substrate-node/pallets/pallet-tfgrid/src/migrations/v17.rs +++ b/substrate-node/pallets/pallet-tfgrid/src/migrations/v17.rs @@ -9,6 +9,8 @@ use tfchain_support::types::{PublicIP, IP4}; #[cfg(feature = "try-runtime")] use parity_scale_codec::{Decode, Encode}; +#[cfg(feature = "try-runtime")] +use sp_std::vec; pub struct FixFarmPublicIps(PhantomData); @@ -121,3 +123,50 @@ fn public_ips_to_string(public_ips: BoundedVec>) -> Stri } s } + +pub struct CheckStorageState(PhantomData); + +impl OnRuntimeUpgrade for CheckStorageState { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + info!("current pallet version: {:?}", PalletVersion::::get()); + assert!(PalletVersion::::get() == types::StorageVersion::V17Struct); + + check_pallet_tfgrid::(); + + Ok(vec![]) + } +} + +pub fn check_pallet_tfgrid() { + migrations::v16::check_pallet_tfgrid::(); + check_farms_public_ips::(); +} + +// Check farms public ips +pub fn check_farms_public_ips() { + debug!( + "🔎 TFGrid pallet {:?} checking Farms storage map (public ips) START", + PalletVersion::::get() + ); + + for (farm_id, farm) in Farms::::iter() { + for pubip in farm.public_ips { + let ip4 = IP4 { + ip: pubip.ip.clone(), + gw: pubip.gateway.clone(), + }; + if ip4.is_valid().is_err() { + debug!( + " ⚠️ Farms[id: {}]: invalid public ip ({:?})", + farm_id, ip4 + ); + } + } + } + + debug!( + "🏁 TFGrid pallet {:?} checking Farms storage map (public ips) END", + PalletVersion::::get() + ); +} diff --git a/substrate-node/runtime/src/lib.rs b/substrate-node/runtime/src/lib.rs index 679bd3c86..97fce524d 100644 --- a/substrate-node/runtime/src/lib.rs +++ b/substrate-node/runtime/src/lib.rs @@ -770,7 +770,12 @@ pub type Executive = frame_executive::Executive< // All migrations executed on runtime upgrade as a nested tuple of types implementing // `OnRuntimeUpgrade`. -type Migrations = (pallet_tfgrid::migrations::v17::FixFarmPublicIps, pallet_tft_bridge::migrations::v2::MigrateBurnTransactionsV2); +type Migrations = ( + pallet_tfgrid::migrations::v17::FixFarmPublicIps, + pallet_tfgrid::migrations::v17::CheckStorageState, + pallet_smart_contract::migrations::v11::CheckStorageState, + pallet_tft_bridge::migrations::v2::MigrateBurnTransactionsV2 +); // follows Substrate's non destructive way of eliminating otherwise required // repetion: https://github.com/paritytech/substrate/pull/10592