diff --git a/polkadot/runtime/common/src/assigned_slots/mod.rs b/polkadot/runtime/common/src/assigned_slots/mod.rs index dd39789e10cfd..67e5784962ff1 100644 --- a/polkadot/runtime/common/src/assigned_slots/mod.rs +++ b/polkadot/runtime/common/src/assigned_slots/mod.rs @@ -719,6 +719,8 @@ mod tests { type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); type AssignCoretime = (); + type UnbrickOrigin = EnsureRoot; + type MinTimeToAllowUnbrick = ConstU32<5>; } impl parachains_shared::Config for Test { diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index 7a689a517eaa2..37f6c155d6634 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -205,6 +205,8 @@ impl paras::Config for Test { type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); type AssignCoretime = (); + type UnbrickOrigin = EnsureRoot; + type MinTimeToAllowUnbrick = ConstU32<5>; } parameter_types! { diff --git a/polkadot/runtime/common/src/paras_registrar/mod.rs b/polkadot/runtime/common/src/paras_registrar/mod.rs index 07f02e9265612..abf4c9018b22a 100644 --- a/polkadot/runtime/common/src/paras_registrar/mod.rs +++ b/polkadot/runtime/common/src/paras_registrar/mod.rs @@ -723,7 +723,7 @@ mod tests { assert_noop, assert_ok, derive_impl, parameter_types, traits::{OnFinalize, OnInitialize}, }; - use frame_system::limits; + use frame_system::{limits, EnsureRoot}; use pallet_balances::Error as BalancesError; use polkadot_primitives::{Balance, BlockNumber, SessionIndex, MAX_CODE_SIZE}; use polkadot_runtime_parachains::{configuration, origin, shared}; @@ -823,6 +823,8 @@ mod tests { type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); type AssignCoretime = (); + type UnbrickOrigin = EnsureRoot; + type MinTimeToAllowUnbrick = frame_support::traits::ConstU32<5>; } impl configuration::Config for Test { @@ -989,7 +991,7 @@ mod tests { assert!(Parachains::is_parathread(para_id)); assert!(!Parachains::is_parachain(para_id)); // Deregister it - assert_ok!(Registrar::deregister(RuntimeOrigin::root(), para_id,)); + assert_ok!(Registrar::deregister(RuntimeOrigin::root(), para_id)); run_to_session(START_SESSION_INDEX + 8); // It is nothing assert!(!Parachains::is_parathread(para_id)); @@ -1180,7 +1182,7 @@ mod tests { run_to_session(START_SESSION_INDEX + 2); assert!(Parachains::is_parathread(para_id)); - assert_ok!(Registrar::deregister(RuntimeOrigin::root(), para_id,)); + assert_ok!(Registrar::deregister(RuntimeOrigin::root(), para_id)); run_to_session(START_SESSION_INDEX + 4); assert!(paras::Pallet::::lifecycle(para_id).is_none()); assert_eq!(Balances::reserved_balance(&1), 0); diff --git a/polkadot/runtime/parachains/src/mock.rs b/polkadot/runtime/parachains/src/mock.rs index fbe9ebf809b35..63381fdbac4fc 100644 --- a/polkadot/runtime/parachains/src/mock.rs +++ b/polkadot/runtime/parachains/src/mock.rs @@ -30,7 +30,7 @@ use polkadot_primitives::CoreIndex; use codec::Decode; use frame_support::{ - assert_ok, derive_impl, parameter_types, + assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::{ Currency, ProcessMessage, ProcessMessageError, ValidatorSet, ValidatorSetWithIdentification, }, @@ -38,7 +38,7 @@ use frame_support::{ PalletId, }; use frame_support_test::TestRandomness; -use frame_system::limits; +use frame_system::{limits, EnsureSignedBy}; use polkadot_primitives::{ AuthorityDiscoveryId, Balance, BlockNumber, CandidateHash, Moment, SessionIndex, UpwardMessage, ValidationCode, ValidatorIndex, @@ -229,6 +229,10 @@ impl frame_support::traits::EstimateNextSessionRotation for TestNextSession } } +ord_parameter_types! { + pub const One: u64 = 1; +} + impl crate::paras::Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = crate::paras::TestWeightInfo; @@ -237,6 +241,8 @@ impl crate::paras::Config for Test { type NextSessionRotation = TestNextSessionRotation; type OnNewHead = (); type AssignCoretime = (); + type UnbrickOrigin = EnsureSignedBy; + type MinTimeToAllowUnbrick = ConstU32<5>; } impl crate::dmp::Config for Test {} diff --git a/polkadot/runtime/parachains/src/paras/mod.rs b/polkadot/runtime/parachains/src/paras/mod.rs index a4c404de2a651..80834016d5a47 100644 --- a/polkadot/runtime/parachains/src/paras/mod.rs +++ b/polkadot/runtime/parachains/src/paras/mod.rs @@ -545,6 +545,7 @@ pub trait WeightInfo { fn force_queue_action() -> Weight; fn add_trusted_validation_code(c: u32) -> Weight; fn poke_unused_validation_code() -> Weight; + fn unbrick() -> Weight; fn include_pvf_check_statement_finalize_upgrade_accept() -> Weight; fn include_pvf_check_statement_finalize_upgrade_reject() -> Weight; @@ -596,6 +597,10 @@ impl WeightInfo for TestWeightInfo { // This special value is to distinguish from the finalizing variants above in tests. Weight::MAX - Weight::from_parts(1, 1) } + fn unbrick() -> Weight { + // This special value is to distinguish from the finalizing variants above in tests. + Weight::MAX - Weight::from_parts(1, 1) + } } #[frame_support::pallet] @@ -642,6 +647,13 @@ pub mod pallet { /// /// TODO: Remove once coretime is the standard across all chains. type AssignCoretime: AssignCoretime; + + /// A valid origin able to call the `unbrick` method. + type UnbrickOrigin: EnsureOrigin; + + /// The minimum time (in blocks) that needs to have passed since the + /// last head update to allow unbricking a parachain. + type MinTimeToAllowUnbrick: Get>; } #[pallet::event] @@ -696,6 +708,8 @@ pub mod pallet { CannotUpgradeCode, /// Invalid validation code size. InvalidCode, + /// Para cannot be considered as bricked + ParaNotBricked, } /// All currently active PVF pre-checking votes. @@ -1149,6 +1163,43 @@ pub mod pallet { MostRecentContext::::insert(¶, context); Ok(()) } + + /// Allows a given origin to update the head and validation code for a para + /// if certain time has elapsed since last noted head. + #[pallet::call_index(9)] + #[pallet::weight(::WeightInfo::force_set_most_recent_context())] + pub fn unbrick( + origin: OriginFor, + para: ParaId, + maybe_new_code: Option, + maybe_new_head: Option, + ) -> DispatchResult { + T::UnbrickOrigin::ensure_origin(origin)?; + ensure!( + frame_system::Pallet::::block_number() - + MostRecentContext::::get(para) + .unwrap_or(frame_system::Pallet::::block_number()) > + T::MinTimeToAllowUnbrick::get(), + Error::::ParaNotBricked + ); + + if let Some(new_code) = maybe_new_code { + let new_code_hash = new_code.hash(); + Self::increase_code_ref(&new_code_hash, &new_code); + Self::set_current_code( + para, + new_code_hash, + frame_system::Pallet::::block_number(), + ); + Self::deposit_event(Event::CurrentCodeUpdated(para)); + } + + if let Some(new_head) = maybe_new_head { + Self::set_current_head(para, new_head); + } + + Ok(()) + } } #[pallet::validate_unsigned] diff --git a/polkadot/runtime/parachains/src/paras/tests.rs b/polkadot/runtime/parachains/src/paras/tests.rs index 732b75417387a..c88b7356218c8 100644 --- a/polkadot/runtime/parachains/src/paras/tests.rs +++ b/polkadot/runtime/parachains/src/paras/tests.rs @@ -15,7 +15,7 @@ // along with Polkadot. If not, see . use super::*; -use frame_support::{assert_err, assert_ok, assert_storage_noop}; +use frame_support::{assert_err, assert_noop, assert_ok, assert_storage_noop}; use polkadot_primitives::{vstaging::SchedulerParams, BlockNumber, PARACHAIN_KEY_TYPE_ID}; use polkadot_primitives_test_helpers::{dummy_head_data, dummy_validation_code, validator_pubkeys}; use sc_keystore::LocalKeystore; @@ -2013,3 +2013,101 @@ fn parachains_cache_preserves_order() { assert_eq!(Parachains::::get(), vec![a, c]); }); } + +fn setup_para_unbrick_tests(para_id: ParaId) -> sp_io::TestExternalities { + let validation_code = test_validation_code_1(); + + let genesis_config = MockGenesisConfig::default(); + + let mut t = new_test_ext(genesis_config); + + t.execute_with(|| { + const EXPECTED_SESSION: SessionIndex = 1; + run_to_block(1, Some(vec![1])); + + assert_ok!(Paras::schedule_para_initialize( + para_id, + ParaGenesisArgs { + para_kind: ParaKind::Parachain, + genesis_head: vec![1].into(), + validation_code: validation_code.clone(), + }, + )); + submit_super_majority_pvf_votes(&validation_code, EXPECTED_SESSION, true); + + // Two sessions pass, so action queue is triggered. + run_to_block(4, Some(vec![3, 4])); + + // Progress para to the new head and check that the recent context is updated. + Paras::note_new_head(para_id, vec![4, 5, 6].into(), 3); + }); + + t +} + +#[test] +fn para_unbrick_fails_if_invalid_origin() { + let para_id = ParaId::from(111); + + setup_para_unbrick_tests(para_id).execute_with(|| { + // Unbrick Fails + assert_noop!( + Paras::unbrick(RuntimeOrigin::signed(2), para_id, None, None), + DispatchError::BadOrigin + ); + }) +} + +#[test] +fn para_unbrick_fails_if_minimum_time_not_met() { + let para_id = ParaId::from(111); + + setup_para_unbrick_tests(para_id).execute_with(|| { + run_to_block(8, Some(vec![6])); + + // Unbrick Fails + assert_noop!( + Paras::unbrick(RuntimeOrigin::signed(1), para_id, None, None), + paras::Error::::ParaNotBricked + ); + }) +} + +#[test] +fn para_unbrick_works_updates_code() { + let para_id = ParaId::from(111); + let new_validation_code = test_validation_code_2(); + + setup_para_unbrick_tests(para_id).execute_with(|| { + run_to_block(9, Some(vec![9, 10])); + + // Unbrick Fails + assert_ok!(Paras::unbrick( + RuntimeOrigin::signed(1), + para_id, + Some(new_validation_code), + None + )); + + System::assert_has_event(paras::Event::CurrentCodeUpdated(para_id).into()); + }) +} + +#[test] +fn para_unbrick_works_notes_head() { + let para_id = ParaId::from(111); + + setup_para_unbrick_tests(para_id).execute_with(|| { + run_to_block(9, Some(vec![9, 10])); + + // Unbrick Fails + assert_ok!(Paras::unbrick( + RuntimeOrigin::signed(1), + para_id, + None, + Some(vec![7, 8, 9].into()) + )); + + System::assert_has_event(paras::Event::CurrentHeadUpdated(para_id).into()); + }) +} diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 31713755b9b20..40bb3ffb59ee2 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -978,6 +978,8 @@ impl parachains_paras::Config for Runtime { type NextSessionRotation = Babe; type OnNewHead = Registrar; type AssignCoretime = CoretimeAssignmentProvider; + type UnbrickOrigin = EnsureNever<()>; + type MinTimeToAllowUnbrick = ConstU32<{ 2 * HOURS }>; } parameter_types! { diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 8e34320d38f2f..b6bf41a96e716 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -28,6 +28,7 @@ use alloc::{ vec::Vec, }; use codec::Encode; +use frame_system::{EnsureNever, EnsureRoot}; use pallet_transaction_payment::FungibleAdapter; use polkadot_runtime_parachains::{ @@ -550,6 +551,8 @@ impl parachains_paras::Config for Runtime { type NextSessionRotation = Babe; type OnNewHead = (); type AssignCoretime = (); + type UnbrickOrigin = EnsureRoot; + type MinTimeToAllowUnbrick = ConstU64<{ 2 * HOURS }>; } parameter_types! { diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 519c7dcde54eb..92baacc9b43fb 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -35,10 +35,10 @@ use frame_support::{ genesis_builder_helper::{build_state, get_preset}, parameter_types, traits::{ - fungible::HoldConsideration, tokens::UnityOrOuterConversion, ConstU32, Contains, EitherOf, - EitherOfDiverse, EnsureOriginWithArg, EverythingBut, FromContains, InstanceFilter, - KeyOwnerProofSystem, LinearStoragePrice, ProcessMessage, ProcessMessageError, - VariantCountOf, WithdrawReasons, + fungible::HoldConsideration, tokens::UnityOrOuterConversion, ConstU32, ConstU64, Contains, + EitherOf, EitherOfDiverse, EnsureOriginWithArg, EverythingBut, FromContains, + InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, ProcessMessage, + ProcessMessageError, VariantCountOf, WithdrawReasons, }, weights::{ConstantMultiplier, WeightMeter, WeightToFee as _}, PalletId, @@ -1109,7 +1109,8 @@ impl InstanceFilter for ProxyType { matches!( c, RuntimeCall::Staking(..) | - RuntimeCall::Session(..) | RuntimeCall::Utility(..) | + RuntimeCall::Session(..) | + RuntimeCall::Utility(..) | RuntimeCall::FastUnstake(..) | RuntimeCall::VoterList(..) | RuntimeCall::NominationPools(..) @@ -1209,6 +1210,8 @@ impl parachains_paras::Config for Runtime { type NextSessionRotation = Babe; type OnNewHead = (); type AssignCoretime = CoretimeAssignmentProvider; + type UnbrickOrigin = EnsureRoot; + type MinTimeToAllowUnbrick = ConstU64<{ 2 * HOURS }>; } parameter_types! {