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(&para_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(&para, 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(&para).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