From 841f4e4c4d842ba3d63e13c60ff156bb919d8abb Mon Sep 17 00:00:00 2001 From: Branislav Kontur <bkontur@gmail.com> Date: Thu, 27 Feb 2025 03:22:12 +0100 Subject: [PATCH] Refactor finished --- .../parachains/src/paras/benchmarking.rs | 21 +- polkadot/runtime/parachains/src/paras/mod.rs | 262 ++++++++++++----- .../runtime/parachains/src/paras/tests.rs | 263 +++++++++++------- .../polkadot_runtime_parachains_paras.rs | 4 +- .../polkadot_runtime_parachains_paras.rs | 4 +- prdoc/pr_7592.prdoc | 2 +- 6 files changed, 366 insertions(+), 190 deletions(-) diff --git a/polkadot/runtime/parachains/src/paras/benchmarking.rs b/polkadot/runtime/parachains/src/paras/benchmarking.rs index 8f5b48b0521..3d30c3efa35 100644 --- a/polkadot/runtime/parachains/src/paras/benchmarking.rs +++ b/polkadot/runtime/parachains/src/paras/benchmarking.rs @@ -250,25 +250,28 @@ mod benchmarks { } #[benchmark] - fn authorize_force_set_current_code_hash() { + fn authorize_code_hash() { let para_id = ParaId::from(1000); - let code_hash = [0; 32].into(); + let authorization = CodeHashAuthorization::ForceSetCurrentCode {para_id, code_hash: [0; 32].into() }; + let expire_at = frame_system::Pallet::<T>::block_number().saturating_add(BlockNumberFor::<T>::from(1_000_000_u32)); #[extrinsic_call] - _(RawOrigin::Root, para_id, code_hash); + _(RawOrigin::Root, authorization.clone(), expire_at); - assert_last_event::<T>(Event::CodeAuthorized { para_id, code_hash }.into()); + assert_last_event::<T>(Event::CodeAuthorized { authorization, expire_at }.into()); } #[benchmark] - fn apply_authorized_force_set_current_code(c: Linear<MIN_CODE_SIZE, MAX_CODE_SIZE>) { - let new_code = ValidationCode(vec![0; c as usize]); - let para_id = ParaId::from(c as u32); - AuthorizedCodeHash::<T>::insert(¶_id, new_code.hash()); + fn apply_authorized_code(c: Linear<MIN_CODE_SIZE, MAX_CODE_SIZE>) { + let code = ValidationCode(vec![0; c as usize]); + let para_id = ParaId::from(1000); + let authorization = CodeHashAuthorization::ForceSetCurrentCode {para_id, code_hash: code.hash() }; + let expire_at = frame_system::Pallet::<T>::block_number().saturating_add(BlockNumberFor::<T>::from(c)); + AuthorizedCodeHash::<T>::set(vec![(authorization.clone(), expire_at)]); generate_disordered_pruning::<T>(); #[extrinsic_call] - _(RawOrigin::Root, para_id, new_code); + _(RawOrigin::Root, authorization, code); assert_last_event::<T>(Event::CurrentCodeUpdated(para_id).into()); } diff --git a/polkadot/runtime/parachains/src/paras/mod.rs b/polkadot/runtime/parachains/src/paras/mod.rs index 7b64cc09027..59732a69fe7 100644 --- a/polkadot/runtime/parachains/src/paras/mod.rs +++ b/polkadot/runtime/parachains/src/paras/mod.rs @@ -550,6 +550,69 @@ impl AssignCoretime for () { } } +/// Represents different types of authorization for handling code hashes. +/// This enum defines various actions related to validation code management. +#[derive(PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, RuntimeDebug, TypeInfo)] +#[cfg_attr(test, derive(Ord, PartialOrd))] +pub enum CodeHashAuthorization<N> { + /// Forces the current validation code for a parachain to be set. + ForceSetCurrentCode { + /// The ID of the parachain. + para_id: ParaId, + /// The validation code hash. + code_hash: ValidationCodeHash, + }, + + /// Forces a scheduled code upgrade for a parachain. + ForceScheduleCodeUpgrade { + /// The ID of the parachain. + para_id: ParaId, + /// The validation code hash. + code_hash: ValidationCodeHash, + /// The relay parent block number at which the upgrade should be applied. + relay_parent_number: N, + }, + + /// Adds a trusted validation code hash to the system. + AddTrustedValidationCode { + /// The validation code hash + code_hash: ValidationCodeHash, + }, +} + +impl<N> CodeHashAuthorization<N> { + /// Determines if the current authorization should be replaced by another. + /// + /// # Returns + /// - `true` if `other` should replace `self` + /// - `false` otherwise + fn should_be_replaced_by(&self, other: &CodeHashAuthorization<N>) -> bool { + use CodeHashAuthorization::*; + + match (self, other) { + (ForceSetCurrentCode { para_id: a, .. }, ForceSetCurrentCode { para_id: b, .. }) + | (ForceScheduleCodeUpgrade { para_id: a, .. }, ForceScheduleCodeUpgrade { para_id: b, .. }) + if a == b => true, + + (AddTrustedValidationCode { code_hash: a }, AddTrustedValidationCode { code_hash: b }) + if a == b => true, + + _ => false, + } + } + + /// Compares the stored `code_hash` with the hash of the provided validation code. + fn code_matches(&self, code: &ValidationCode) -> bool { + let code_hash = match self { + CodeHashAuthorization::ForceSetCurrentCode { code_hash, .. } + | CodeHashAuthorization::ForceScheduleCodeUpgrade { code_hash, .. } + | CodeHashAuthorization::AddTrustedValidationCode { code_hash } => code_hash, + }; + + code_hash == &code.hash() + } +} + pub trait WeightInfo { fn force_set_current_code(c: u32) -> Weight; fn force_set_current_head(s: u32) -> Weight; @@ -565,8 +628,8 @@ pub trait WeightInfo { fn include_pvf_check_statement_finalize_onboarding_accept() -> Weight; fn include_pvf_check_statement_finalize_onboarding_reject() -> Weight; fn include_pvf_check_statement() -> Weight; - fn authorize_force_set_current_code_hash() -> Weight; - fn apply_authorized_force_set_current_code(c: u32) -> Weight; + fn authorize_code_hash() -> Weight; + fn apply_authorized_code(c: u32) -> Weight; } pub struct TestWeightInfo; @@ -612,10 +675,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 authorize_force_set_current_code_hash() -> Weight { + fn authorize_code_hash() -> Weight { Weight::MAX } - fn apply_authorized_force_set_current_code(_c: u32) -> Weight { + fn apply_authorized_code(_c: u32) -> Weight { Weight::MAX } } @@ -639,7 +702,7 @@ pub mod pallet { + shared::Config + frame_system::offchain::CreateInherent<Call<Self>> { - type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>; + type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; #[pallet::constant] type UnsignedPriority: Get<TransactionPriority>; @@ -668,11 +731,9 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { + pub enum Event<T: Config> { /// Current code has been updated for a Para. `para_id` CurrentCodeUpdated(ParaId), - /// New code hash has been authorized for a Para. - CodeAuthorized { code_hash: ValidationCodeHash, para_id: ParaId }, /// Current head has been updated for a Para. `para_id` CurrentHeadUpdated(ParaId), /// A code upgrade has been scheduled for a Para. `para_id` @@ -690,6 +751,13 @@ pub mod pallet { /// The given validation code was rejected by the PVF pre-checking vote. /// `code_hash` `para_id` PvfCheckRejected(ValidationCodeHash, ParaId), + /// New code hash has been authorized for a Para. + CodeAuthorized { + /// CodeHash authorization request. + authorization: CodeHashAuthorization<BlockNumberFor<T>>, + /// Block at which authorization expires and will be removed. + expire_at: BlockNumberFor<T>, + }, } #[pallet::error] @@ -724,6 +792,8 @@ pub mod pallet { NothingAuthorized, /// The submitted code is not authorized. Unauthorized, + /// Invalid block number. + InvalidBlockNumber, } /// All currently active PVF pre-checking votes. @@ -819,10 +889,10 @@ pub mod pallet { #[pallet::storage] pub type FutureCodeHash<T: Config> = StorageMap<_, Twox64Concat, ParaId, ValidationCodeHash>; - /// The code hash of a para which is authorized. + /// The code hash authorizations which will expire `expire_at` `BlockNumberFor<T>`. #[pallet::storage] pub(super) type AuthorizedCodeHash<T: Config> = - StorageMap<_, Twox64Concat, ParaId, ValidationCodeHash>; + StorageValue<_, Vec<(CodeHashAuthorization<BlockNumberFor<T>>, BlockNumberFor<T>)>, ValueQuery>; /// This is used by the relay-chain to communicate to a parachain a go-ahead with in the upgrade /// procedure. @@ -955,15 +1025,7 @@ pub mod pallet { relay_parent_number: BlockNumberFor<T>, ) -> DispatchResult { ensure_root(origin)?; - let config = configuration::ActiveConfig::<T>::get(); - Self::schedule_code_upgrade( - para, - new_code, - relay_parent_number, - &config, - UpgradeStrategy::ApplyAtExpectedBlock, - ); - Self::deposit_event(Event::CodeUpgradeScheduled(para)); + Self::do_force_schedule_code_upgrade(para, new_code, relay_parent_number); Ok(()) } @@ -1020,40 +1082,7 @@ pub mod pallet { validation_code: ValidationCode, ) -> DispatchResult { ensure_root(origin)?; - let code_hash = validation_code.hash(); - - if let Some(vote) = PvfActiveVoteMap::<T>::get(&code_hash) { - // Remove the existing vote. - PvfActiveVoteMap::<T>::remove(&code_hash); - PvfActiveVoteList::<T>::mutate(|l| { - if let Ok(i) = l.binary_search(&code_hash) { - l.remove(i); - } - }); - - let cfg = configuration::ActiveConfig::<T>::get(); - Self::enact_pvf_accepted( - frame_system::Pallet::<T>::block_number(), - &code_hash, - &vote.causes, - vote.age, - &cfg, - ); - return Ok(()) - } - - if CodeByHash::<T>::contains_key(&code_hash) { - // There is no vote, but the code exists. Nothing to do here. - return Ok(()) - } - - // At this point the code is unknown and there is no PVF pre-checking vote for it, so we - // can just add the code into the storage. - // - // NOTE That we do not use `increase_code_ref` here, because the code is not yet used - // by any parachain. - CodeByHash::<T>::insert(code_hash, &validation_code); - + Self::do_add_trusted_validation_code(validation_code); Ok(()) } @@ -1181,25 +1210,33 @@ pub mod pallet { } /// Sets the storage for the authorized current code hash of the parachain. + /// If not applied, it will be removed at the `expire_at` block. /// - /// This can be useful when we want to trigger `Paras::force_set_current_code(para, code)` + /// This can be useful, for example, when triggering `Paras::force_set_current_code(para, code)` /// from a different chain than the one where the `Paras` pallet is deployed. /// - /// The main reason is to avoid transferring the entire `new_code` wasm blob between chains. - /// Instead, we authorize `new_code_hash` with `root`, which can later be applied by - /// `Paras::apply_authorized_force_set_current_code(para, new_code)` by anyone. + /// The main purpose is to avoid transferring the entire `code` Wasm blob between chains. + /// Instead, we authorize `code_hash` with `root`, which can later be applied by + /// `Paras::apply_authorized_code(authorization, code)` by anyone. + /// + /// Authorizations are stored in an **overwriting manner**, + /// for exampe we won't store multiple `ForceSetCurrentCode` for one parachain. #[pallet::call_index(9)] - #[pallet::weight(<T as Config>::WeightInfo::authorize_force_set_current_code_hash())] - pub fn authorize_force_set_current_code_hash( + #[pallet::weight(<T as Config>::WeightInfo::authorize_code_hash())] + pub fn authorize_code_hash( origin: OriginFor<T>, - para: ParaId, - new_code_hash: ValidationCodeHash, + new_authorization: CodeHashAuthorization<BlockNumberFor<T>>, + expire_at: BlockNumberFor<T>, ) -> DispatchResult { ensure_root(origin)?; + ensure!(expire_at > frame_system::Pallet::<T>::block_number(), Error::<T>::InvalidBlockNumber); - // insert authorized code hash. - AuthorizedCodeHash::<T>::insert(¶, new_code_hash); - Self::deposit_event(Event::CodeAuthorized { para_id: para, code_hash: new_code_hash }); + // insert authorized code hash and make sure to overwrite existing variant `CodeHashAuthorization` for `para_id`. + AuthorizedCodeHash::<T>::mutate(|authorizations| { + authorizations.retain(|(authorization, _)| !authorization.should_be_replaced_by(&new_authorization)); + authorizations.push((new_authorization.clone(), expire_at)); + }); + Self::deposit_event(Event::CodeAuthorized { authorization: new_authorization, expire_at }); Ok(()) } @@ -1207,23 +1244,30 @@ pub mod pallet { /// Applies the already authorized current code for the parachain, /// triggering the same functionality as `force_set_current_code`. #[pallet::call_index(10)] - #[pallet::weight(<T as Config>::WeightInfo::apply_authorized_force_set_current_code(new_code.0.len() as u32))] - pub fn apply_authorized_force_set_current_code( + #[pallet::weight(<T as Config>::WeightInfo::apply_authorized_code(code.0.len() as u32))] + pub fn apply_authorized_code( _origin: OriginFor<T>, - para: ParaId, - new_code: ValidationCode, + authorization: CodeHashAuthorization<BlockNumberFor<T>>, + code: ValidationCode, ) -> DispatchResultWithPostInfo { // no need to ensure, anybody can do this // Ensure `new_code` is authorized - let authorized_code_hash = - AuthorizedCodeHash::<T>::take(¶).ok_or(Error::<T>::NothingAuthorized)?; - ensure!(authorized_code_hash == new_code.hash(), Error::<T>::Unauthorized); - - // TODO: FAIL-CI - more validations? - - // set current code - Self::do_force_set_current_code_update(para, new_code); + let authorized_code_hash = AuthorizedCodeHash::<T>::try_mutate(|authorizations| { + if let Some(idx) = authorizations.iter().position(|(a, _)| a == &authorization) { + Ok(authorizations.swap_remove(idx).0) + } else { + Err(Error::<T>::NothingAuthorized) + } + })?; + ensure!(authorized_code_hash.code_matches(&code), Error::<T>::Unauthorized); + + // apply/dispatch + match authorized_code_hash { + CodeHashAuthorization::ForceSetCurrentCode { para_id, .. } => Self::do_force_set_current_code_update(para_id, code), + CodeHashAuthorization::AddTrustedValidationCode { .. } => Self::do_add_trusted_validation_code(code), + CodeHashAuthorization::ForceScheduleCodeUpgrade { para_id, relay_parent_number, .. } => Self::do_force_schedule_code_upgrade(para_id, code, relay_parent_number), + } // if ok, then allows "for free" let post = PostDispatchInfo { @@ -1351,7 +1395,8 @@ impl<T: Config> Pallet<T> { pub(crate) fn initializer_initialize(now: BlockNumberFor<T>) -> Weight { Self::prune_old_code(now) + Self::process_scheduled_upgrade_changes(now) + - Self::process_future_code_upgrades_at(now) + Self::process_future_code_upgrades_at(now) + + Self::prune_expired_authorizations(now) } /// Called by the initializer to finalize the paras pallet. @@ -1565,6 +1610,16 @@ impl<T: Config> Pallet<T> { T::DbWeight::get().reads_writes(1 + pruning_tasks_done, 2 * pruning_tasks_done) } + /// This function removes authorizations that have expired, + /// meaning their `expire_at` block is less than or equal to the current block (`now`). + fn prune_expired_authorizations(now: BlockNumberFor<T>) -> Weight { + AuthorizedCodeHash::<T>::mutate(|authorizations: &mut Vec<(_, BlockNumberFor<T>)>| { + authorizations.retain(|(_, expire_at)| expire_at > &now); + }); + + T::DbWeight::get().reads_writes(1, 1) + } + /// Process the future code upgrades that should be applied directly. /// /// Upgrades that should not be applied directly are being processed in @@ -2252,6 +2307,59 @@ impl<T: Config> Pallet<T> { Self::deposit_event(Event::CurrentCodeUpdated(para)); } + /// Force schedule code upgrade for the given parachain. + fn do_force_schedule_code_upgrade( + para: ParaId, + new_code: ValidationCode, + relay_parent_number: BlockNumberFor<T>, + ) { + let config = configuration::ActiveConfig::<T>::get(); + Self::schedule_code_upgrade( + para, + new_code, + relay_parent_number, + &config, + UpgradeStrategy::ApplyAtExpectedBlock, + ); + Self::deposit_event(Event::CodeUpgradeScheduled(para)); + } + + fn do_add_trusted_validation_code(validation_code: ValidationCode) { + let code_hash = validation_code.hash(); + + if let Some(vote) = PvfActiveVoteMap::<T>::get(&code_hash) { + // Remove the existing vote. + PvfActiveVoteMap::<T>::remove(&code_hash); + PvfActiveVoteList::<T>::mutate(|l| { + if let Ok(i) = l.binary_search(&code_hash) { + l.remove(i); + } + }); + + let cfg = configuration::ActiveConfig::<T>::get(); + Self::enact_pvf_accepted( + frame_system::Pallet::<T>::block_number(), + &code_hash, + &vote.causes, + vote.age, + &cfg, + ); + return; + } + + if CodeByHash::<T>::contains_key(&code_hash) { + // There is no vote, but the code exists. Nothing to do here. + return; + } + + // At this point the code is unknown and there is no PVF pre-checking vote for it, so we + // can just add the code into the storage. + // + // NOTE That we do not use `increase_code_ref` here, because the code is not yet used + // by any parachain. + CodeByHash::<T>::insert(code_hash, &validation_code); + } + /// Returns the list of PVFs (aka validation code) that require casting a vote by a validator in /// the active validator set. pub(crate) fn pvfs_require_precheck() -> Vec<ValidationCodeHash> { diff --git a/polkadot/runtime/parachains/src/paras/tests.rs b/polkadot/runtime/parachains/src/paras/tests.rs index fbdefaac346..9a4ab59542c 100644 --- a/polkadot/runtime/parachains/src/paras/tests.rs +++ b/polkadot/runtime/parachains/src/paras/tests.rs @@ -2015,150 +2015,215 @@ fn parachains_cache_preserves_order() { } #[test] -fn authorize_and_apply_set_current_code_works() { +fn force_set_current_code_works() { new_test_ext(MockGenesisConfig::default()).execute_with(|| { let para_a = ParaId::from(111); let code_1 = ValidationCode(vec![1]); - let code_2 = ValidationCode(vec![2]); - let code_3 = ValidationCode(vec![3]); let code_1_hash = code_1.hash(); - let code_2_hash = code_2.hash(); - let code_3_hash = code_3.hash(); - let root = RuntimeOrigin::root(); - let non_root = RuntimeOrigin::signed(1); // check before - assert!(AuthorizedCodeHash::<Test>::get(para_a).is_none()); assert!(CurrentCodeHash::<Test>::get(para_a).is_none()); check_code_is_not_stored(&code_1); - check_code_is_not_stored(&code_2); - check_code_is_not_stored(&code_3); - // cannot apply non-authorized code + // non-root user cannot execute assert_err!( - Paras::apply_authorized_force_set_current_code( - non_root.clone(), - para_a, - code_1.clone() - ), - Error::<Test>::NothingAuthorized, + Paras::force_set_current_code(RuntimeOrigin::signed(1), para_a, code_1.clone()), + DispatchError::BadOrigin, ); + // root can execute + assert_ok!(Paras::force_set_current_code(RuntimeOrigin::root(), para_a, code_1.clone())); + + // check after + assert_eq!(CurrentCodeHash::<Test>::get(para_a), Some(code_1_hash)); + check_code_is_stored(&code_1); + }) +} + +#[test] +fn authorize_code_hash_works() { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let para_a = ParaId::from(111); + let para_b = ParaId::from(222); + let code_1 = ValidationCode(vec![1]); + let code_2 = ValidationCode(vec![2]); + let code_1_hash = code_1.hash(); + let code_2_hash = code_2.hash(); + let authorize_force_set_current_code_1_for_para_a = CodeHashAuthorization::ForceSetCurrentCode {para_id: para_a, code_hash: code_1_hash }; + let authorize_force_set_current_code_1_for_para_b = CodeHashAuthorization::ForceSetCurrentCode {para_id: para_b, code_hash: code_1_hash }; + let authorize_force_set_current_code_2_for_para_a = CodeHashAuthorization::ForceSetCurrentCode {para_id: para_a, code_hash: code_2_hash }; + let add_trusted_validation_code_1 = CodeHashAuthorization::AddTrustedValidationCode {code_hash: code_1_hash}; + let expire_at = 143; + + // check before + assert!(AuthorizedCodeHash::<Test>::get().is_empty()); // non-root user cannot authorize assert_err!( - Paras::authorize_force_set_current_code_hash(non_root.clone(), para_a, code_1_hash), + Paras::authorize_code_hash( + RuntimeOrigin::signed(1), + authorize_force_set_current_code_1_for_para_a.clone(), + expire_at + ), DispatchError::BadOrigin, ); - // root can authorize - assert_ok!(Paras::authorize_force_set_current_code_hash(root.clone(), para_a, code_1_hash)); - - // check authorized code hash stored - assert_eq!(AuthorizedCodeHash::<Test>::get(para_a), Some(code_1_hash)); - assert!(CurrentCodeHash::<Test>::get(para_a).is_none()); - check_code_is_not_stored(&code_1); - check_code_is_not_stored(&code_2); - check_code_is_not_stored(&code_3); - // non-root cannot apply unauthorized code + // cannot authorize when `expire_at` is in the past + System::set_block_number(expire_at + 1); assert_err!( - Paras::apply_authorized_force_set_current_code( - non_root.clone(), - para_a, - code_2.clone() - ), - Error::<Test>::Unauthorized, + Paras::authorize_code_hash(RuntimeOrigin::root(), authorize_force_set_current_code_1_for_para_a.clone(), expire_at), + Error::<Test>::InvalidBlockNumber, ); - assert_eq!(AuthorizedCodeHash::<Test>::get(para_a), Some(code_1_hash)); - assert!(CurrentCodeHash::<Test>::get(para_a).is_none()); - check_code_is_not_stored(&code_1); - check_code_is_not_stored(&code_2); - check_code_is_not_stored(&code_3); + let expire_at = expire_at + 2; - // non-root can apply authorized code - assert_ok!(Paras::apply_authorized_force_set_current_code( - non_root.clone(), - para_a, - code_1.clone() - )); + // root can authorize + assert_ok!(Paras::authorize_code_hash(RuntimeOrigin::root(), authorize_force_set_current_code_1_for_para_a.clone(), expire_at)); + assert_ok!(Paras::authorize_code_hash(RuntimeOrigin::root(), authorize_force_set_current_code_1_for_para_b.clone(), expire_at)); + assert_ok!(Paras::authorize_code_hash(RuntimeOrigin::root(), add_trusted_validation_code_1.clone(), expire_at)); + assert_eq!( + AuthorizedCodeHash::<Test>::get(), + vec![ + (authorize_force_set_current_code_1_for_para_a, expire_at), + (authorize_force_set_current_code_1_for_para_b.clone(), expire_at), + (add_trusted_validation_code_1.clone(), expire_at), + ] + ); - // check authorized code was applied - assert!(AuthorizedCodeHash::<Test>::get(para_a).is_none()); - assert_eq!(CurrentCodeHash::<Test>::get(para_a), Some(code_1_hash)); - check_code_is_stored(&code_1); - check_code_is_not_stored(&code_2); - check_code_is_not_stored(&code_3); + // the same authorization variant is overwritten + assert_ok!(Paras::authorize_code_hash(RuntimeOrigin::root(), authorize_force_set_current_code_2_for_para_a.clone(), expire_at)); + assert_ok!(Paras::authorize_code_hash(RuntimeOrigin::root(), add_trusted_validation_code_1.clone(), expire_at + 5)); + assert_eq!( + { + let mut sorted = AuthorizedCodeHash::<Test>::get(); + sorted.sort(); + sorted + }, + { + let mut sorted = vec![ + (authorize_force_set_current_code_2_for_para_a, expire_at), // changed code + (authorize_force_set_current_code_1_for_para_b, expire_at), // no change + (add_trusted_validation_code_1, expire_at + 5), // changed expire_at + ]; + sorted.sort(); + sorted + } + ); + }) +} - // authorize multiple without apply: - // authorize code_2_hash - assert_ok!(Paras::authorize_force_set_current_code_hash(root.clone(), para_a, code_2_hash)); - assert_eq!(AuthorizedCodeHash::<Test>::get(para_a), Some(code_2_hash)); - assert_eq!(CurrentCodeHash::<Test>::get(para_a), Some(code_1_hash)); - check_code_is_stored(&code_1); - check_code_is_not_stored(&code_2); - check_code_is_not_stored(&code_3); - // authorize code_3_hash - assert_ok!(Paras::authorize_force_set_current_code_hash(root, para_a, code_3_hash)); - assert_eq!(AuthorizedCodeHash::<Test>::get(para_a), Some(code_3_hash)); - assert_eq!(CurrentCodeHash::<Test>::get(para_a), Some(code_1_hash)); - check_code_is_stored(&code_1); - check_code_is_not_stored(&code_2); - check_code_is_not_stored(&code_3); +#[test] +fn apply_authorized_code_works() { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let para_a = ParaId::from(111); + let code_1 = ValidationCode(vec![1]); + let code_2 = ValidationCode(vec![2]); + let code_1_hash = code_1.hash(); + let code_2_hash = code_2.hash(); + let authorize_force_set_current_code_1_for_para_a = CodeHashAuthorization::ForceSetCurrentCode {para_id: para_a, code_hash: code_1_hash }; + let add_trusted_validation_code_2 = CodeHashAuthorization::AddTrustedValidationCode {code_hash: code_2_hash}; + let expire_at = 143; - // cannot apply older ones + // check before + assert!(AuthorizedCodeHash::<Test>::get().is_empty()); + + // cannot apply code when nothing authorized assert_err!( - Paras::apply_authorized_force_set_current_code( - non_root.clone(), - para_a, + Paras::apply_authorized_code( + RuntimeOrigin::signed(1), + authorize_force_set_current_code_1_for_para_a.clone(), code_1.clone() ), - Error::<Test>::Unauthorized, + Error::<Test>::NothingAuthorized, + ); + assert_err!( + Paras::apply_authorized_code( + RuntimeOrigin::signed(1), + add_trusted_validation_code_2.clone(), + code_2.clone() + ), + Error::<Test>::NothingAuthorized, ); + + // authorize + AuthorizedCodeHash::<Test>::set( + vec![ + (authorize_force_set_current_code_1_for_para_a.clone(), expire_at), + (add_trusted_validation_code_2.clone(), expire_at), + ] + ); + + // cannot apply unauthorized code_2 assert_err!( - Paras::apply_authorized_force_set_current_code( - non_root.clone(), - para_a, + Paras::apply_authorized_code( + RuntimeOrigin::signed(1), + authorize_force_set_current_code_1_for_para_a.clone(), code_2.clone() ), Error::<Test>::Unauthorized, ); - // apply just authorized - assert_ok!(Paras::apply_authorized_force_set_current_code( - non_root.clone(), - para_a, - code_3.clone() + // ok - can apply authorized code + assert_ok!(Paras::apply_authorized_code( + RuntimeOrigin::signed(1), + authorize_force_set_current_code_1_for_para_a.clone(), + code_1.clone() )); - assert!(AuthorizedCodeHash::<Test>::get(para_a).is_none()); - assert_eq!(CurrentCodeHash::<Test>::get(para_a), Some(code_3_hash)); - check_code_is_stored(&code_1); - check_code_is_not_stored(&code_2); - check_code_is_stored(&code_3); + assert_eq!( + AuthorizedCodeHash::<Test>::get(), + vec![ + (add_trusted_validation_code_2.clone(), expire_at), + ] + ); + + // cannot apply previously authorized code again + assert_err!( + Paras::apply_authorized_code( + RuntimeOrigin::signed(1), + authorize_force_set_current_code_1_for_para_a, + code_1, + ), + Error::<Test>::NothingAuthorized + ); }) } #[test] -fn force_set_current_code_works() { +fn prune_expired_authorizations_works() { new_test_ext(MockGenesisConfig::default()).execute_with(|| { let para_a = ParaId::from(111); let code_1 = ValidationCode(vec![1]); let code_1_hash = code_1.hash(); - let root = RuntimeOrigin::root(); - let non_root = RuntimeOrigin::signed(1); + let authorize_force_set_current_code_1_for_para_a = CodeHashAuthorization::ForceSetCurrentCode {para_id: para_a, code_hash: code_1_hash }; + let add_trusted_validation_code_1 = CodeHashAuthorization::AddTrustedValidationCode {code_hash: code_1_hash}; + + // add authorizations + AuthorizedCodeHash::<Test>::set( + vec![ + (authorize_force_set_current_code_1_for_para_a.clone(), 201), + (add_trusted_validation_code_1.clone(), 202), + ] + ); - // check before - assert!(CurrentCodeHash::<Test>::get(para_a).is_none()); - check_code_is_not_stored(&code_1); + // nothing + let _ = Paras::prune_expired_authorizations(200); + assert_eq!( + AuthorizedCodeHash::<Test>::get(), + vec![ + (authorize_force_set_current_code_1_for_para_a.clone(), 201), + (add_trusted_validation_code_1.clone(), 202), + ] + ); - // non-root user cannot execute - assert_err!( - Paras::force_set_current_code(non_root, para_a, code_1.clone()), - DispatchError::BadOrigin, + // pruned 201 + let _ = Paras::prune_expired_authorizations(201); + assert_eq!( + AuthorizedCodeHash::<Test>::get(), + vec![ + (add_trusted_validation_code_1.clone(), 202), + ] ); - // root can execute - assert_ok!(Paras::force_set_current_code(root, para_a, code_1.clone())); - // check after - assert_eq!(CurrentCodeHash::<Test>::get(para_a), Some(code_1_hash)); - check_code_is_stored(&code_1); + // pruned 202 + let _ = Paras::prune_expired_authorizations(203); + assert_eq!(AuthorizedCodeHash::<Test>::get(), vec![]); }) } diff --git a/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_paras.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_paras.rs index b8664c667de..ae2c4632aaa 100644 --- a/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_paras.rs +++ b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_paras.rs @@ -296,7 +296,7 @@ impl<T: frame_system::Config> polkadot_runtime_parachains::paras::WeightInfo for } /// Storage: `Paras::AuthorizedCodeHash` (r:1 w:1) /// Proof: `Paras::AuthorizedCodeHash` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn authorize_force_set_current_code_hash() -> Weight { + fn authorize_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `28` // Estimated: `3493` @@ -315,7 +315,7 @@ impl<T: frame_system::Config> polkadot_runtime_parachains::paras::WeightInfo for /// Storage: `Paras::CodeByHash` (r:0 w:1) /// Proof: `Paras::CodeByHash` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `c` is `[9, 3145728]`. - fn apply_authorized_force_set_current_code(c: u32, ) -> Weight { + fn apply_authorized_code(c: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `126` // Estimated: `3591` diff --git a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_paras.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_paras.rs index 7f0623c3a32..d855ab1eed9 100644 --- a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_paras.rs +++ b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_paras.rs @@ -294,7 +294,7 @@ impl<T: frame_system::Config> polkadot_runtime_parachains::paras::WeightInfo for } /// Storage: `Paras::AuthorizedCodeHash` (r:1 w:1) /// Proof: `Paras::AuthorizedCodeHash` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn authorize_force_set_current_code_hash() -> Weight { + fn authorize_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `28` // Estimated: `3493` @@ -313,7 +313,7 @@ impl<T: frame_system::Config> polkadot_runtime_parachains::paras::WeightInfo for /// Storage: `Paras::CodeByHash` (r:0 w:1) /// Proof: `Paras::CodeByHash` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `c` is `[9, 3145728]`. - fn apply_authorized_force_set_current_code(c: u32, ) -> Weight { + fn apply_authorized_code(c: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `126` // Estimated: `3591` diff --git a/prdoc/pr_7592.prdoc b/prdoc/pr_7592.prdoc index cc68b2b9bab..09d3ee18efc 100644 --- a/prdoc/pr_7592.prdoc +++ b/prdoc/pr_7592.prdoc @@ -5,7 +5,7 @@ doc: This feature can be useful when we want to trigger `Paras::force_set_current_code(para, code)` from a different chain than the one where the `Paras` pallet is deployed. The main reason is to avoid transferring the entire `new_code` wasm blob between chains. - Instead, we authorize `new_code_hash` with `root`, which can later be applied by `Paras::apply_authorized_force_set_current_code(para, new_code)` by anyone. + Instead, we authorize `new_code_hash` with `root`, which can later be applied by `Paras::apply_authorized_code(para, new_code)` by anyone. crates: - name: polkadot-runtime-parachains bump: major -- GitLab