Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions polkadot/runtime/common/src/assigned_slots/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,8 @@ mod tests {
type NextSessionRotation = crate::mock::TestNextSessionRotation;
type OnNewHead = ();
type AssignCoretime = ();
type UnbrickOrigin = EnsureRoot<Self::AccountId>;
type MinTimeToAllowUnbrick = ConstU32<5>;
}

impl parachains_shared::Config for Test {
Expand Down
2 changes: 2 additions & 0 deletions polkadot/runtime/common/src/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ impl paras::Config for Test {
type NextSessionRotation = crate::mock::TestNextSessionRotation;
type OnNewHead = ();
type AssignCoretime = ();
type UnbrickOrigin = EnsureRoot<AccountId>;
type MinTimeToAllowUnbrick = ConstU32<5>;
}

parameter_types! {
Expand Down
8 changes: 5 additions & 3 deletions polkadot/runtime/common/src/paras_registrar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -823,6 +823,8 @@ mod tests {
type NextSessionRotation = crate::mock::TestNextSessionRotation;
type OnNewHead = ();
type AssignCoretime = ();
type UnbrickOrigin = EnsureRoot<Self::AccountId>;
type MinTimeToAllowUnbrick = frame_support::traits::ConstU32<5>;
}

impl configuration::Config for Test {
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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::<Test>::lifecycle(para_id).is_none());
assert_eq!(Balances::reserved_balance(&1), 0);
Expand Down
10 changes: 8 additions & 2 deletions polkadot/runtime/parachains/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ 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,
},
weights::{Weight, WeightMeter},
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,
Expand Down Expand Up @@ -229,6 +229,10 @@ impl frame_support::traits::EstimateNextSessionRotation<u32> for TestNextSession
}
}

ord_parameter_types! {
pub const One: u64 = 1;
}

impl crate::paras::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = crate::paras::TestWeightInfo;
Expand All @@ -237,6 +241,8 @@ impl crate::paras::Config for Test {
type NextSessionRotation = TestNextSessionRotation;
type OnNewHead = ();
type AssignCoretime = ();
type UnbrickOrigin = EnsureSignedBy<One, AccountId>;
type MinTimeToAllowUnbrick = ConstU32<5>;
}

impl crate::dmp::Config for Test {}
Expand Down
51 changes: 51 additions & 0 deletions polkadot/runtime/parachains/src/paras/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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<Self::RuntimeOrigin>;

/// The minimum time (in blocks) that needs to have passed since the
/// last head update to allow unbricking a parachain.
type MinTimeToAllowUnbrick: Get<BlockNumberFor<Self>>;
}

#[pallet::event]
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1149,6 +1163,43 @@ pub mod pallet {
MostRecentContext::<T>::insert(&para, 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(<T as Config>::WeightInfo::force_set_most_recent_context())]
pub fn unbrick(
origin: OriginFor<T>,
para: ParaId,
maybe_new_code: Option<ValidationCode>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work if called from Collectives (para) to the relay chain — but there is an alternative implemented here:
#7592

The fn unbrick function isn't really necessary and could be replaced with two batched calls:

batch(
   Paras::authorize_force_set_current_code_hash(new_validation_code),
   Paras::force_set_current_head(...)
)

We might just need to add some custom origin validation to force_set_current_head instead of using ensure_root, but that's a very minor change.

More context: polkadot-fellows/RFCs#117 (comment)

maybe_new_head: Option<HeadData>,
) -> DispatchResult {
T::UnbrickOrigin::ensure_origin(origin)?;
ensure!(
frame_system::Pallet::<T>::block_number() -
MostRecentContext::<T>::get(para)
.unwrap_or(frame_system::Pallet::<T>::block_number()) >
T::MinTimeToAllowUnbrick::get(),
Error::<T>::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::<T>::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]
Expand Down
100 changes: 99 additions & 1 deletion polkadot/runtime/parachains/src/paras/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

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;
Expand Down Expand Up @@ -2013,3 +2013,101 @@ fn parachains_cache_preserves_order() {
assert_eq!(Parachains::<Test>::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::<Test>::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());
})
}
2 changes: 2 additions & 0 deletions polkadot/runtime/rococo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand Down
3 changes: 3 additions & 0 deletions polkadot/runtime/test-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -550,6 +551,8 @@ impl parachains_paras::Config for Runtime {
type NextSessionRotation = Babe;
type OnNewHead = ();
type AssignCoretime = ();
type UnbrickOrigin = EnsureRoot<AccountId>;
type MinTimeToAllowUnbrick = ConstU64<{ 2 * HOURS }>;
}

parameter_types! {
Expand Down
13 changes: 8 additions & 5 deletions polkadot/runtime/westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1109,7 +1109,8 @@ impl InstanceFilter<RuntimeCall> for ProxyType {
matches!(
c,
RuntimeCall::Staking(..) |
RuntimeCall::Session(..) | RuntimeCall::Utility(..) |
RuntimeCall::Session(..) |
RuntimeCall::Utility(..) |
RuntimeCall::FastUnstake(..) |
RuntimeCall::VoterList(..) |
RuntimeCall::NominationPools(..)
Expand Down Expand Up @@ -1209,6 +1210,8 @@ impl parachains_paras::Config for Runtime {
type NextSessionRotation = Babe;
type OnNewHead = ();
type AssignCoretime = CoretimeAssignmentProvider;
type UnbrickOrigin = EnsureRoot<AccountId>;
type MinTimeToAllowUnbrick = ConstU64<{ 2 * HOURS }>;
}

parameter_types! {
Expand Down