diff --git a/polkadot/runtime/common/src/assigned_slots/mod.rs b/polkadot/runtime/common/src/assigned_slots/mod.rs index 6e96904a54b17..b0c445bb7a6e2 100644 --- a/polkadot/runtime/common/src/assigned_slots/mod.rs +++ b/polkadot/runtime/common/src/assigned_slots/mod.rs @@ -734,6 +734,7 @@ mod tests { type AssignCoretime = (); type Fungible = Balances; type CooldownRemovalMultiplier = ConstUint<1>; + type AuthorizeCurrentCodeOrigin = EnsureRoot; } 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 a477ef05f59ac..8b9f2c96c41b0 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -216,6 +216,7 @@ impl paras::Config for Test { type AssignCoretime = (); type Fungible = Balances; type CooldownRemovalMultiplier = ConstUint<1>; + type AuthorizeCurrentCodeOrigin = EnsureRoot; } parameter_types! { diff --git a/polkadot/runtime/common/src/paras_registrar/mock.rs b/polkadot/runtime/common/src/paras_registrar/mock.rs index 44b3a7b679e74..d29879e5285d0 100644 --- a/polkadot/runtime/common/src/paras_registrar/mock.rs +++ b/polkadot/runtime/common/src/paras_registrar/mock.rs @@ -131,6 +131,7 @@ impl paras::Config for Test { type AssignCoretime = (); type Fungible = Balances; type CooldownRemovalMultiplier = ConstUint<1>; + type AuthorizeCurrentCodeOrigin = frame_system::EnsureRoot; } impl configuration::Config for Test { diff --git a/polkadot/runtime/parachains/src/mock.rs b/polkadot/runtime/parachains/src/mock.rs index 60606242679bc..cba63ae7b1b04 100644 --- a/polkadot/runtime/parachains/src/mock.rs +++ b/polkadot/runtime/parachains/src/mock.rs @@ -40,7 +40,7 @@ use frame_support::{ PalletId, }; use frame_support_test::TestRandomness; -use frame_system::limits; +use frame_system::{limits, EnsureRoot}; use polkadot_primitives::{ AuthorityDiscoveryId, Balance, BlockNumber, CandidateHash, Moment, SessionIndex, UpwardMessage, ValidationCode, ValidatorIndex, @@ -249,6 +249,7 @@ impl crate::paras::Config for Test { type AssignCoretime = (); type Fungible = Balances; type CooldownRemovalMultiplier = ConstUint<1>; + type AuthorizeCurrentCodeOrigin = EnsureRoot; } impl crate::dmp::Config for Test {} diff --git a/polkadot/runtime/parachains/src/paras/benchmarking.rs b/polkadot/runtime/parachains/src/paras/benchmarking.rs index 9c9b46b86ffae..252d5eac21fb3 100644 --- a/polkadot/runtime/parachains/src/paras/benchmarking.rs +++ b/polkadot/runtime/parachains/src/paras/benchmarking.rs @@ -282,6 +282,44 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn authorize_force_set_current_code_hash() { + let para_id = ParaId::from(1000); + let code = ValidationCode(vec![0; 32]); + let new_code_hash = code.hash(); + let valid_period = BlockNumberFor::::from(1_000_000_u32); + + #[extrinsic_call] + _(RawOrigin::Root, para_id, new_code_hash, valid_period); + + assert_last_event::( + Event::CodeAuthorized { + para_id, + code_hash: new_code_hash, + expire_at: frame_system::Pallet::::block_number().saturating_add(valid_period), + } + .into(), + ); + } + + #[benchmark] + fn apply_authorized_force_set_current_code(c: Linear) { + let code = ValidationCode(vec![0; c as usize]); + let para_id = ParaId::from(1000); + let expire_at = + frame_system::Pallet::::block_number().saturating_add(BlockNumberFor::::from(c)); + AuthorizedCodeHash::::insert( + ¶_id, + AuthorizedCodeHashAndExpiry::from((code.hash(), expire_at)), + ); + generate_disordered_pruning::(); + + #[extrinsic_call] + _(RawOrigin::Root, para_id, code); + + assert_last_event::(Event::CurrentCodeUpdated(para_id).into()); + } + impl_benchmark_test_suite!( Pallet, crate::mock::new_test_ext(Default::default()), diff --git a/polkadot/runtime/parachains/src/paras/mod.rs b/polkadot/runtime/parachains/src/paras/mod.rs index c747d02d1d605..3caf5c8ab01ab 100644 --- a/polkadot/runtime/parachains/src/paras/mod.rs +++ b/polkadot/runtime/parachains/src/paras/mod.rs @@ -117,7 +117,12 @@ use alloc::{collections::btree_set::BTreeSet, vec::Vec}; use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; use codec::{Decode, Encode}; use core::{cmp, mem}; -use frame_support::{pallet_prelude::*, traits::EstimateNextSessionRotation, DefaultNoBound}; +use frame_support::{ + dispatch::PostDispatchInfo, + pallet_prelude::*, + traits::{EnsureOriginWithArg, EstimateNextSessionRotation}, + DefaultNoBound, +}; use frame_system::pallet_prelude::*; use polkadot_primitives::{ ConsensusLog, HeadData, Id as ParaId, PvfCheckStatement, SessionIndex, UpgradeGoAhead, @@ -547,6 +552,19 @@ impl AssignCoretime for () { } } +/// Holds an authorized validation code hash along with its expiry timestamp. +#[derive(Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo)] +#[cfg_attr(test, derive(PartialEq))] +pub struct AuthorizedCodeHashAndExpiry { + code_hash: ValidationCodeHash, + expire_at: T, +} +impl From<(ValidationCodeHash, T)> for AuthorizedCodeHashAndExpiry { + fn from(value: (ValidationCodeHash, T)) -> Self { + AuthorizedCodeHashAndExpiry { code_hash: value.0, expire_at: value.1 } + } +} + pub trait WeightInfo { fn force_set_current_code(c: u32) -> Weight; fn force_set_current_head(s: u32) -> Weight; @@ -563,6 +581,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; } pub struct TestWeightInfo; @@ -611,6 +631,12 @@ impl WeightInfo for TestWeightInfo { fn remove_upgrade_cooldown() -> Weight { Weight::MAX } + fn authorize_force_set_current_code_hash() -> Weight { + Weight::MAX + } + fn apply_authorized_force_set_current_code(_c: u32) -> Weight { + Weight::MAX + } } #[frame_support::pallet] @@ -639,7 +665,7 @@ pub mod pallet { + frame_system::offchain::CreateBare> { #[allow(deprecated)] - type RuntimeEvent: From + IsType<::RuntimeEvent>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; #[pallet::constant] type UnsignedPriority: Get; @@ -678,11 +704,14 @@ pub mod pallet { /// multiplier to determine the cost for removing the upgrade cooldown. Time left for the /// cooldown multiplied with this multiplier determines the cost. type CooldownRemovalMultiplier: Get>; + + /// The origin that can authorize `force_set_current_code_hash`. + type AuthorizeCurrentCodeOrigin: EnsureOriginWithArg; } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { + pub enum Event { /// Current code has been updated for a Para. `para_id` CurrentCodeUpdated(ParaId), /// Current head has been updated for a Para. `para_id` @@ -707,6 +736,15 @@ pub mod pallet { /// The parachain for which the cooldown got removed. para_id: ParaId, }, + /// A new code hash has been authorized for a Para. + CodeAuthorized { + /// Para + para_id: ParaId, + /// Authorized code hash. + code_hash: ValidationCodeHash, + /// Block at which authorization expires and will be removed. + expire_at: BlockNumberFor, + }, } #[pallet::error] @@ -737,6 +775,12 @@ pub mod pallet { CannotUpgradeCode, /// Invalid validation code size. InvalidCode, + /// No upgrade authorized. + NothingAuthorized, + /// The submitted code is not authorized. + Unauthorized, + /// Invalid block number. + InvalidBlockNumber, } /// All currently active PVF pre-checking votes. @@ -832,6 +876,11 @@ pub mod pallet { #[pallet::storage] pub type FutureCodeHash = StorageMap<_, Twox64Concat, ParaId, ValidationCodeHash>; + /// The code hash authorizations for a para which will expire `expire_at` `BlockNumberFor`. + #[pallet::storage] + pub type AuthorizedCodeHash = + StorageMap<_, Twox64Concat, ParaId, AuthorizedCodeHashAndExpiry>>; + /// This is used by the relay-chain to communicate to a parachain a go-ahead with in the upgrade /// procedure. /// @@ -936,10 +985,7 @@ pub mod pallet { new_code: ValidationCode, ) -> DispatchResult { ensure_root(origin)?; - 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)); + Self::do_force_set_current_code_update(para, new_code); Ok(()) } @@ -1228,6 +1274,72 @@ pub mod pallet { Ok(()) } + + /// Sets the storage for the authorized current code hash of the parachain. + /// If not applied, it will be removed at the `System::block_number() + valid_period` block. + /// + /// This can be useful, when triggering `Paras::force_set_current_code(para, code)` + /// from a different chain than the one where the `Paras` pallet is deployed. + /// + /// 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_force_set_current_code(para, code)` by anyone. + /// + /// Authorizations are stored in an **overwriting manner**. + #[pallet::call_index(10)] + #[pallet::weight(::WeightInfo::authorize_force_set_current_code_hash())] + pub fn authorize_force_set_current_code_hash( + origin: OriginFor, + para: ParaId, + new_code_hash: ValidationCodeHash, + valid_period: BlockNumberFor, + ) -> DispatchResult { + T::AuthorizeCurrentCodeOrigin::ensure_origin(origin, ¶)?; + + let now = frame_system::Pallet::::block_number(); + let expire_at = now.saturating_add(valid_period); + + // insert authorized code hash and make sure to overwrite existing one for a para. + AuthorizedCodeHash::::insert( + ¶, + AuthorizedCodeHashAndExpiry::from((new_code_hash, expire_at)), + ); + Self::deposit_event(Event::CodeAuthorized { + para_id: para, + code_hash: new_code_hash, + expire_at, + }); + + Ok(()) + } + + /// Applies the already authorized current code for the parachain, + /// triggering the same functionality as `force_set_current_code`. + #[pallet::call_index(11)] + #[pallet::weight(::WeightInfo::apply_authorized_force_set_current_code(new_code.0.len() as u32))] + pub fn apply_authorized_force_set_current_code( + _origin: OriginFor, + para: ParaId, + new_code: ValidationCode, + ) -> DispatchResultWithPostInfo { + // no need to ensure, anybody can do this + + // Ensure `new_code` is authorized + let _ = Self::validate_code_is_authorized(&new_code, ¶)?; + // Remove authorization + AuthorizedCodeHash::::remove(para); + + // apply/dispatch + Self::do_force_set_current_code_update(para, new_code); + + // if ok, then allows "for free" + Ok(PostDispatchInfo { + // consume the rest of the block to prevent further transactions + actual_weight: Some(T::BlockWeights::get().max_block), + // no fee for valid upgrade + pays_fee: Pays::No, + }) + } } impl Pallet { @@ -1258,52 +1370,71 @@ pub mod pallet { type Call = Call; fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { - let (stmt, signature) = match call { - Call::include_pvf_check_statement { stmt, signature } => (stmt, signature), - _ => return InvalidTransaction::Call.into(), - }; + match call { + Call::include_pvf_check_statement { stmt, signature } => { + let current_session = shared::CurrentSessionIndex::::get(); + if stmt.session_index < current_session { + return InvalidTransaction::Stale.into() + } else if stmt.session_index > current_session { + return InvalidTransaction::Future.into() + } - let current_session = shared::CurrentSessionIndex::::get(); - if stmt.session_index < current_session { - return InvalidTransaction::Stale.into() - } else if stmt.session_index > current_session { - return InvalidTransaction::Future.into() - } + let validator_index = stmt.validator_index.0 as usize; + let validators = shared::ActiveValidatorKeys::::get(); + let validator_public = match validators.get(validator_index) { + Some(pk) => pk, + None => + return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into(), + }; - let validator_index = stmt.validator_index.0 as usize; - let validators = shared::ActiveValidatorKeys::::get(); - let validator_public = match validators.get(validator_index) { - Some(pk) => pk, - None => return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into(), - }; + let signing_payload = stmt.signing_payload(); + if !signature.verify(&signing_payload[..], &validator_public) { + return InvalidTransaction::BadProof.into(); + } - let signing_payload = stmt.signing_payload(); - if !signature.verify(&signing_payload[..], &validator_public) { - return InvalidTransaction::BadProof.into() - } + let active_vote = match PvfActiveVoteMap::::get(&stmt.subject) { + Some(v) => v, + None => return InvalidTransaction::Custom(INVALID_TX_BAD_SUBJECT).into(), + }; - let active_vote = match PvfActiveVoteMap::::get(&stmt.subject) { - Some(v) => v, - None => return InvalidTransaction::Custom(INVALID_TX_BAD_SUBJECT).into(), - }; + match active_vote.has_vote(validator_index) { + Some(false) => (), + Some(true) => + return InvalidTransaction::Custom(INVALID_TX_DOUBLE_VOTE).into(), + None => + return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into(), + } - match active_vote.has_vote(validator_index) { - Some(false) => (), - Some(true) => return InvalidTransaction::Custom(INVALID_TX_DOUBLE_VOTE).into(), - None => return InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into(), + ValidTransaction::with_tag_prefix("PvfPreCheckingVote") + .priority(T::UnsignedPriority::get()) + .longevity( + TryInto::::try_into( + T::NextSessionRotation::average_session_length() / 2u32.into(), + ) + .unwrap_or(64_u64), + ) + .and_provides((stmt.session_index, stmt.validator_index, stmt.subject)) + .propagate(true) + .build() + }, + Call::apply_authorized_force_set_current_code { para, new_code } => + match Self::validate_code_is_authorized(new_code, para) { + Ok(authorized_code) => { + let now = frame_system::Pallet::::block_number(); + let longevity = authorized_code.expire_at.saturating_sub(now); + + ValidTransaction::with_tag_prefix("ApplyAuthorizedForceSetCurrentCode") + .priority(T::UnsignedPriority::get()) + .longevity(TryInto::::try_into(longevity).unwrap_or(64_u64)) + .and_provides((para, authorized_code.code_hash)) + .propagate(true) + .build() + }, + Err(_) => + return InvalidTransaction::Custom(INVALID_TX_UNAUTHORIZED_CODE).into(), + }, + _ => InvalidTransaction::Call.into(), } - - ValidTransaction::with_tag_prefix("PvfPreCheckingVote") - .priority(T::UnsignedPriority::get()) - .longevity( - TryInto::::try_into( - T::NextSessionRotation::average_session_length() / 2u32.into(), - ) - .unwrap_or(64_u64), - ) - .and_provides((stmt.session_index, stmt.validator_index, stmt.subject)) - .propagate(true) - .build() } fn pre_dispatch(_call: &Self::Call) -> Result<(), TransactionValidityError> { @@ -1323,6 +1454,7 @@ pub mod pallet { const INVALID_TX_BAD_VALIDATOR_IDX: u8 = 1; const INVALID_TX_BAD_SUBJECT: u8 = 2; const INVALID_TX_DOUBLE_VOTE: u8 = 3; +const INVALID_TX_UNAUTHORIZED_CODE: u8 = 4; /// This is intermediate "fix" for this issue: /// @@ -1368,7 +1500,8 @@ impl Pallet { pub(crate) fn initializer_initialize(now: BlockNumberFor) -> 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. @@ -1582,6 +1715,27 @@ impl Pallet { 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) -> Weight { + let mut weight = T::DbWeight::get().reads(1); + let to_remove = AuthorizedCodeHash::::iter().filter_map( + |(para, AuthorizedCodeHashAndExpiry { expire_at, .. })| { + if expire_at <= now { + Some(para) + } else { + None + } + }, + ); + for para in to_remove { + AuthorizedCodeHash::::remove(¶); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + } + + weight + } + /// Process the future code upgrades that should be applied directly. /// /// Upgrades that should not be applied directly are being processed in @@ -2261,6 +2415,14 @@ impl Pallet { weight + T::DbWeight::get().writes(1) } + /// Force set the current code for the given parachain. + fn do_force_set_current_code_update(para: ParaId, new_code: ValidationCode) { + 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)); + } + /// 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 { @@ -2428,6 +2590,21 @@ impl Pallet { ) -> Option>> { PvfActiveVoteMap::::get(code_hash) } + + /// This function checks whether the given `code.hash()` exists in the `AuthorizedCodeHash` map + /// of authorized code hashes for a para. If found, it verifies that the associated code + /// matches the provided `code`. If the validation is successful, it returns tuple as the + /// authorized `ValidationCodeHash` with `expire_at`. + pub(crate) fn validate_code_is_authorized( + code: &ValidationCode, + para: &ParaId, + ) -> Result>, Error> { + let authorized = AuthorizedCodeHash::::get(para).ok_or(Error::::NothingAuthorized)?; + let now = frame_system::Pallet::::block_number(); + ensure!(authorized.expire_at > now, Error::::InvalidBlockNumber); + ensure!(authorized.code_hash == code.hash(), Error::::Unauthorized); + Ok(authorized) + } } /// An overlay over the `Parachains` storage entry that provides a convenient interface for adding diff --git a/polkadot/runtime/parachains/src/paras/tests.rs b/polkadot/runtime/parachains/src/paras/tests.rs index 440c0795db84c..a1e51974f389e 100644 --- a/polkadot/runtime/parachains/src/paras/tests.rs +++ b/polkadot/runtime/parachains/src/paras/tests.rs @@ -2122,3 +2122,219 @@ fn remove_upgrade_cooldown_works() { } }); } + +#[test] +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_1_hash = code_1.hash(); + + // check before + assert!(CurrentCodeHash::::get(para_a).is_none()); + check_code_is_not_stored(&code_1); + + // non-root user cannot execute + assert_err!( + 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::::get(para_a), Some(code_1_hash)); + check_code_is_stored(&code_1); + }) +} + +#[test] +fn authorize_force_set_current_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 valid_period = 143; + + // check before + assert_eq!(AuthorizedCodeHash::::iter().count(), 0); + + // non-root user cannot authorize + assert_err!( + Paras::authorize_force_set_current_code_hash( + RuntimeOrigin::signed(1), + para_a, + code_1_hash, + valid_period, + ), + DispatchError::BadOrigin, + ); + + // root can authorize + System::set_block_number(1); + assert_ok!(Paras::authorize_force_set_current_code_hash( + RuntimeOrigin::root(), + para_a, + code_1_hash, + valid_period + )); + assert_eq!( + AuthorizedCodeHash::::get(¶_a), + Some((code_1_hash, 1 + valid_period).into()) + ); + System::set_block_number(5); + assert_ok!(Paras::authorize_force_set_current_code_hash( + RuntimeOrigin::root(), + para_b, + code_2_hash, + valid_period, + )); + assert_eq!( + AuthorizedCodeHash::::get(¶_b), + Some((code_2_hash, 5 + valid_period).into()) + ); + assert_eq!(AuthorizedCodeHash::::iter().count(), 2); + + // request for the same para is overwritten + assert_ok!(Paras::authorize_force_set_current_code_hash( + RuntimeOrigin::root(), + para_a, + code_1_hash, + valid_period + )); + assert_eq!( + AuthorizedCodeHash::::get(¶_a), + Some((code_1_hash, 5 + valid_period).into()) + ); + assert_ok!(Paras::authorize_force_set_current_code_hash( + RuntimeOrigin::root(), + para_a, + code_2_hash, + valid_period + )); + assert_eq!( + AuthorizedCodeHash::::get(¶_a), + Some((code_2_hash, 5 + valid_period).into()) + ); + }) +} + +#[test] +fn apply_authorized_force_set_current_code_works() { + let apply_code = |origin, + para: ParaId, + code: ValidationCode| + -> (Result<_, _>, DispatchResultWithPostInfo) { + let call = Call::apply_authorized_force_set_current_code { para, new_code: code.clone() }; + let validate_unsigned = + ::validate_unsigned(TransactionSource::InBlock, &call) + .map(|_| ()); + + let dispatch_result = Paras::apply_authorized_force_set_current_code(origin, para, code); + + (validate_unsigned, dispatch_result) + }; + + 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 valid_period = 143; + + // check before + assert_eq!(AuthorizedCodeHash::::iter().count(), 0); + + // cannot apply code when nothing authorized + assert_eq!( + apply_code(RuntimeOrigin::signed(1), para_a, code_1.clone()), + ( + Err(InvalidTransaction::Custom(INVALID_TX_UNAUTHORIZED_CODE).into()), + Err(Error::::NothingAuthorized.into()) + ), + ); + + // authorize + System::set_block_number(5); + AuthorizedCodeHash::::insert( + ¶_a, + AuthorizedCodeHashAndExpiry::from((code_1_hash, valid_period + 5)), + ); + + // cannot apply unauthorized code_2 + assert_eq!( + apply_code(RuntimeOrigin::signed(1), para_a, code_2.clone()), + ( + Err(InvalidTransaction::Custom(INVALID_TX_UNAUTHORIZED_CODE).into()), + Err(Error::::Unauthorized.into()) + ), + ); + + // cannot apply obsolete authorization + frame_system::Pallet::::set_block_number(valid_period + 5 + 10); + assert_eq!( + apply_code(RuntimeOrigin::signed(1), para_a, code_1.clone(),), + ( + Err(InvalidTransaction::Custom(INVALID_TX_UNAUTHORIZED_CODE).into()), + Err(Error::::InvalidBlockNumber.into()) + ), + ); + frame_system::Pallet::::set_block_number(5); + + // ok - can apply authorized code + let (validate_unsigned, dispatch_result) = + apply_code(RuntimeOrigin::signed(1), para_a, code_1.clone()); + assert_ok!(validate_unsigned); + assert_ok!(dispatch_result); + + // check for removed + assert!(AuthorizedCodeHash::::get(¶_a).is_none()); + + // cannot apply previously authorized code again + assert_eq!( + apply_code(RuntimeOrigin::signed(1), para_a, code_1,), + ( + Err(InvalidTransaction::Custom(INVALID_TX_UNAUTHORIZED_CODE).into()), + Err(Error::::NothingAuthorized.into()) + ), + ); + }) +} + +#[test] +fn prune_expired_authorizations_works() { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let para_a = ParaId::from(111); + let para_b = ParaId::from(123); + let code_1 = ValidationCode(vec![1]); + let code_1_hash = code_1.hash(); + + // add authorizations + AuthorizedCodeHash::::insert( + ¶_a, + AuthorizedCodeHashAndExpiry::from((code_1_hash, 201)), + ); + AuthorizedCodeHash::::insert( + ¶_b, + AuthorizedCodeHashAndExpiry::from((code_1_hash, 202)), + ); + + // nothing prunned at 200 + let _ = Paras::prune_expired_authorizations(200); + assert_eq!(AuthorizedCodeHash::::get(¶_a), Some((code_1_hash, 201).into())); + assert_eq!(AuthorizedCodeHash::::get(¶_b), Some((code_1_hash, 202).into())); + + // pruned at 201 + let _ = Paras::prune_expired_authorizations(201); + assert!(AuthorizedCodeHash::::get(¶_a).is_none()); + assert_eq!(AuthorizedCodeHash::::get(¶_b), Some((code_1_hash, 202).into())); + + // pruned at 203 + let _ = Paras::prune_expired_authorizations(203); + assert!(AuthorizedCodeHash::::get(¶_a).is_none()); + assert!(AuthorizedCodeHash::::get(¶_b).is_none()); + }) +} diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 3362be0c071ea..cccefb84e4289 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -1061,6 +1061,7 @@ impl parachains_paras::Config for Runtime { type Fungible = Balances; // Per day the cooldown is removed earlier, it should cost 1000. type CooldownRemovalMultiplier = ConstUint<{ 1000 * UNITS / DAYS as u128 }>; + type AuthorizeCurrentCodeOrigin = EnsureRoot; } parameter_types! { 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 24427acc6ace8..875635774b8fb 100644 --- a/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_paras.rs +++ b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_paras.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `polkadot_runtime_parachains::paras` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2025-02-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-03-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `d3a9aad6f7a3`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `299f3957073a`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: @@ -68,11 +68,11 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `8309` // Estimated: `11774` - // Minimum execution time: 34_621_000 picoseconds. - Weight::from_parts(35_098_000, 0) + // Minimum execution time: 34_344_000 picoseconds. + Weight::from_parts(35_086_000, 0) .saturating_add(Weight::from_parts(0, 11774)) - // Standard Error: 103 - .saturating_add(Weight::from_parts(10_826, 0).saturating_mul(c.into())) + // Standard Error: 101 + .saturating_add(Weight::from_parts(10_734, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(6)) } @@ -83,11 +83,11 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_045_000 picoseconds. - Weight::from_parts(7_396_000, 0) + // Minimum execution time: 7_032_000 picoseconds. + Weight::from_parts(7_232_000, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 31 - .saturating_add(Weight::from_parts(3_653, 0).saturating_mul(s.into())) + .saturating_add(Weight::from_parts(3_618, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Paras::MostRecentContext` (r:0 w:1) @@ -96,8 +96,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_548_000 picoseconds. - Weight::from_parts(3_776_000, 0) + // Minimum execution time: 3_545_000 picoseconds. + Weight::from_parts(3_822_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -124,11 +124,11 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `8452` // Estimated: `11917` - // Minimum execution time: 50_535_000 picoseconds. - Weight::from_parts(51_066_000, 0) + // Minimum execution time: 50_046_000 picoseconds. + Weight::from_parts(50_780_000, 0) .saturating_add(Weight::from_parts(0, 11917)) // Standard Error: 101 - .saturating_add(Weight::from_parts(10_834, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(10_685, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -147,11 +147,11 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `299` // Estimated: `3764` - // Minimum execution time: 18_562_000 picoseconds. - Weight::from_parts(19_372_000, 0) + // Minimum execution time: 18_737_000 picoseconds. + Weight::from_parts(18_995_000, 0) .saturating_add(Weight::from_parts(0, 3764)) // Standard Error: 31 - .saturating_add(Weight::from_parts(3_679, 0).saturating_mul(s.into())) + .saturating_add(Weight::from_parts(3_645, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -163,8 +163,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `4312` // Estimated: `7777` - // Minimum execution time: 20_925_000 picoseconds. - Weight::from_parts(21_803_000, 0) + // Minimum execution time: 21_058_000 picoseconds. + Weight::from_parts(21_657_000, 0) .saturating_add(Weight::from_parts(0, 7777)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -182,11 +182,11 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `683` // Estimated: `4148` - // Minimum execution time: 85_140_000 picoseconds. - Weight::from_parts(88_127_000, 0) + // Minimum execution time: 85_102_000 picoseconds. + Weight::from_parts(86_779_000, 0) .saturating_add(Weight::from_parts(0, 4148)) // Standard Error: 96 - .saturating_add(Weight::from_parts(10_273, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(10_226, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -198,8 +198,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `28` // Estimated: `3493` - // Minimum execution time: 6_962_000 picoseconds. - Weight::from_parts(7_283_000, 0) + // Minimum execution time: 7_005_000 picoseconds. + Weight::from_parts(7_242_000, 0) .saturating_add(Weight::from_parts(0, 3493)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -214,8 +214,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `26706` // Estimated: `30171` - // Minimum execution time: 108_616_000 picoseconds. - Weight::from_parts(113_278_000, 0) + // Minimum execution time: 114_183_000 picoseconds. + Weight::from_parts(117_660_000, 0) .saturating_add(Weight::from_parts(0, 30171)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -236,8 +236,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `27360` // Estimated: `30825` - // Minimum execution time: 738_629_000 picoseconds. - Weight::from_parts(768_833_000, 0) + // Minimum execution time: 701_780_000 picoseconds. + Weight::from_parts(717_725_000, 0) .saturating_add(Weight::from_parts(0, 30825)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(103)) @@ -252,8 +252,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `27338` // Estimated: `30803` - // Minimum execution time: 104_931_000 picoseconds. - Weight::from_parts(110_764_000, 0) + // Minimum execution time: 111_232_000 picoseconds. + Weight::from_parts(116_478_000, 0) .saturating_add(Weight::from_parts(0, 30803)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -272,8 +272,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `26728` // Estimated: `30193` - // Minimum execution time: 605_368_000 picoseconds. - Weight::from_parts(619_955_000, 0) + // Minimum execution time: 566_791_000 picoseconds. + Weight::from_parts(588_058_000, 0) .saturating_add(Weight::from_parts(0, 30193)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) @@ -288,8 +288,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `26706` // Estimated: `30171` - // Minimum execution time: 102_768_000 picoseconds. - Weight::from_parts(107_917_000, 0) + // Minimum execution time: 106_546_000 picoseconds. + Weight::from_parts(112_236_000, 0) .saturating_add(Weight::from_parts(0, 30171)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -302,4 +302,37 @@ impl polkadot_runtime_parachains::paras::WeightInfo for .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } + + /// Storage: `Paras::AuthorizedCodeHash` (r:0 w:1) + /// Proof: `Paras::AuthorizedCodeHash` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn authorize_force_set_current_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_217_000 picoseconds. + Weight::from_parts(8_415_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Paras::AuthorizedCodeHash` (r:1 w:1) + /// Proof: `Paras::AuthorizedCodeHash` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::CodeByHashRefs` (r:1 w:1) + /// Proof: `Paras::CodeByHashRefs` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::CurrentCodeHash` (r:1 w:1) + /// Proof: `Paras::CurrentCodeHash` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// 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 { + // Proof Size summary in bytes: + // Measured: `132` + // Estimated: `3597` + // Minimum execution time: 29_238_000 picoseconds. + Weight::from_parts(29_741_000, 0) + .saturating_add(Weight::from_parts(0, 3597)) + // Standard Error: 103 + .saturating_add(Weight::from_parts(12_157, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + } } diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 5aef006ee3d5b..c3071c48985fc 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -613,6 +613,7 @@ impl parachains_paras::Config for Runtime { type AssignCoretime = CoretimeAssignmentProvider; type Fungible = Balances; type CooldownRemovalMultiplier = ConstUint<1>; + type AuthorizeCurrentCodeOrigin = frame_system::EnsureRoot; } parameter_types! { diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 86be11d5335cc..0ee8e88d41bf7 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -36,10 +36,10 @@ use frame_support::{ pallet_prelude::PhantomData, parameter_types, traits::{ - fungible::HoldConsideration, tokens::UnityOrOuterConversion, ConstU32, Contains, EitherOf, - EitherOfDiverse, EnsureOriginWithArg, EverythingBut, FromContains, InstanceFilter, - KeyOwnerProofSystem, LinearStoragePrice, Nothing, ProcessMessage, ProcessMessageError, - VariantCountOf, WithdrawReasons, + fungible::HoldConsideration, tokens::UnityOrOuterConversion, AsEnsureOriginWithArg, + ConstU32, Contains, EitherOf, EitherOfDiverse, EnsureOriginWithArg, EverythingBut, + FromContains, InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, Nothing, + ProcessMessage, ProcessMessageError, VariantCountOf, WithdrawReasons, }, weights::{ConstantMultiplier, WeightMeter}, PalletId, @@ -129,6 +129,7 @@ pub use frame_system::Call as SystemCall; pub use pallet_balances::Call as BalancesCall; pub use pallet_election_provider_multi_phase::{Call as EPMCall, GeometricDepositBase}; pub use pallet_timestamp::Call as TimestampCall; +use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; use sp_runtime::traits::Get; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; @@ -1406,6 +1407,13 @@ impl parachains_paras::Config for Runtime { type Fungible = Balances; // Per day the cooldown is removed earlier, it should cost 1000. type CooldownRemovalMultiplier = ConstUint<{ 1000 * UNITS / DAYS as u128 }>; + type AuthorizeCurrentCodeOrigin = EitherOfDiverse< + EnsureRoot, + // Collectives DDay plurality mapping. + AsEnsureOriginWithArg< + EnsureXcm>, + >, + >; } parameter_types! { 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 605150141e483..91fb426933b7c 100644 --- a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_paras.rs +++ b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_paras.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `polkadot_runtime_parachains::paras` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-03-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `3a2e9ae8a8f5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `299f3957073a`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: @@ -68,11 +68,11 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `8309` // Estimated: `11774` - // Minimum execution time: 38_573_000 picoseconds. - Weight::from_parts(39_400_000, 0) + // Minimum execution time: 38_137_000 picoseconds. + Weight::from_parts(38_981_000, 0) .saturating_add(Weight::from_parts(0, 11774)) - // Standard Error: 128 - .saturating_add(Weight::from_parts(13_197, 0).saturating_mul(c.into())) + // Standard Error: 129 + .saturating_add(Weight::from_parts(13_542, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(6)) } @@ -83,11 +83,11 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_110_000 picoseconds. - Weight::from_parts(7_328_000, 0) + // Minimum execution time: 6_970_000 picoseconds. + Weight::from_parts(7_230_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 41 - .saturating_add(Weight::from_parts(4_535, 0).saturating_mul(s.into())) + // Standard Error: 42 + .saturating_add(Weight::from_parts(4_548, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Paras::MostRecentContext` (r:0 w:1) @@ -96,8 +96,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_564_000 picoseconds. - Weight::from_parts(3_677_000, 0) + // Minimum execution time: 3_539_000 picoseconds. + Weight::from_parts(3_836_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -124,11 +124,11 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `8452` // Estimated: `11917` - // Minimum execution time: 52_408_000 picoseconds. - Weight::from_parts(53_467_000, 0) + // Minimum execution time: 52_993_000 picoseconds. + Weight::from_parts(53_656_000, 0) .saturating_add(Weight::from_parts(0, 11917)) - // Standard Error: 129 - .saturating_add(Weight::from_parts(13_193, 0).saturating_mul(c.into())) + // Standard Error: 132 + .saturating_add(Weight::from_parts(13_454, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -145,11 +145,11 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `268` // Estimated: `3733` - // Minimum execution time: 14_751_000 picoseconds. - Weight::from_parts(15_033_000, 0) + // Minimum execution time: 14_784_000 picoseconds. + Weight::from_parts(15_216_000, 0) .saturating_add(Weight::from_parts(0, 3733)) - // Standard Error: 41 - .saturating_add(Weight::from_parts(4_536, 0).saturating_mul(s.into())) + // Standard Error: 42 + .saturating_add(Weight::from_parts(4_570, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -161,8 +161,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `4312` // Estimated: `7777` - // Minimum execution time: 24_624_000 picoseconds. - Weight::from_parts(25_772_000, 0) + // Minimum execution time: 24_300_000 picoseconds. + Weight::from_parts(25_154_000, 0) .saturating_add(Weight::from_parts(0, 7777)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -180,11 +180,11 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `683` // Estimated: `4148` - // Minimum execution time: 89_347_000 picoseconds. - Weight::from_parts(91_876_000, 0) + // Minimum execution time: 91_676_000 picoseconds. + Weight::from_parts(93_066_000, 0) .saturating_add(Weight::from_parts(0, 4148)) // Standard Error: 127 - .saturating_add(Weight::from_parts(12_741, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(12_834, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -196,8 +196,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `28` // Estimated: `3493` - // Minimum execution time: 6_736_000 picoseconds. - Weight::from_parts(7_018_000, 0) + // Minimum execution time: 6_886_000 picoseconds. + Weight::from_parts(7_123_000, 0) .saturating_add(Weight::from_parts(0, 3493)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -212,8 +212,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `26706` // Estimated: `30171` - // Minimum execution time: 112_868_000 picoseconds. - Weight::from_parts(115_588_000, 0) + // Minimum execution time: 114_157_000 picoseconds. + Weight::from_parts(120_289_000, 0) .saturating_add(Weight::from_parts(0, 30171)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -234,8 +234,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `27360` // Estimated: `30825` - // Minimum execution time: 773_659_000 picoseconds. - Weight::from_parts(794_204_000, 0) + // Minimum execution time: 727_795_000 picoseconds. + Weight::from_parts(754_623_000, 0) .saturating_add(Weight::from_parts(0, 30825)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(103)) @@ -250,8 +250,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `27338` // Estimated: `30803` - // Minimum execution time: 108_485_000 picoseconds. - Weight::from_parts(113_249_000, 0) + // Minimum execution time: 110_002_000 picoseconds. + Weight::from_parts(117_296_000, 0) .saturating_add(Weight::from_parts(0, 30803)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -270,8 +270,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `26728` // Estimated: `30193` - // Minimum execution time: 635_656_000 picoseconds. - Weight::from_parts(643_507_000, 0) + // Minimum execution time: 585_996_000 picoseconds. + Weight::from_parts(609_200_000, 0) .saturating_add(Weight::from_parts(0, 30193)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) @@ -286,8 +286,8 @@ impl polkadot_runtime_parachains::paras::WeightInfo for // Proof Size summary in bytes: // Measured: `26706` // Estimated: `30171` - // Minimum execution time: 103_358_000 picoseconds. - Weight::from_parts(107_759_000, 0) + // Minimum execution time: 106_306_000 picoseconds. + Weight::from_parts(113_807_000, 0) .saturating_add(Weight::from_parts(0, 30171)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -300,4 +300,37 @@ impl polkadot_runtime_parachains::paras::WeightInfo for .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } + + /// Storage: `Paras::AuthorizedCodeHash` (r:0 w:1) + /// Proof: `Paras::AuthorizedCodeHash` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn authorize_force_set_current_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_112_000 picoseconds. + Weight::from_parts(8_401_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Paras::AuthorizedCodeHash` (r:1 w:1) + /// Proof: `Paras::AuthorizedCodeHash` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::CodeByHashRefs` (r:1 w:1) + /// Proof: `Paras::CodeByHashRefs` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::CurrentCodeHash` (r:1 w:1) + /// Proof: `Paras::CurrentCodeHash` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// 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 { + // Proof Size summary in bytes: + // Measured: `132` + // Estimated: `3597` + // Minimum execution time: 32_733_000 picoseconds. + Weight::from_parts(33_172_000, 0) + .saturating_add(Weight::from_parts(0, 3597)) + // Standard Error: 133 + .saturating_add(Weight::from_parts(14_731, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + } } diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index e93da33b204f2..1b639433e0f5f 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -243,6 +243,8 @@ parameter_types! { pub const FellowshipAdminBodyId: BodyId = BodyId::Index(FELLOWSHIP_ADMIN_INDEX); // `Treasurer` pluralistic body. pub const TreasurerBodyId: BodyId = BodyId::Treasury; + // DDay pluralistic body. + pub const DDayBodyId: BodyId = BodyId::Moniker([b'd', b'd', b'a', b'y']); } /// Type to convert the `GeneralAdmin` origin to a Plurality `Location` value. diff --git a/prdoc/pr_7592.prdoc b/prdoc/pr_7592.prdoc new file mode 100644 index 0000000000000..bf916c86a59b0 --- /dev/null +++ b/prdoc/pr_7592.prdoc @@ -0,0 +1,17 @@ +title: Add `Paras` `authorize_code_hash` + `apply_authorized_code` feature +doc: +- audience: Runtime Dev + description: |- + This feature is useful when triggering a `Paras` pallet call from a different chain than the one where the `Paras` pallet is deployed. For example, we may want to send `Paras::force_set_current_code(para, code)` from the Collectives and/or AssetHub to the relay chain (because the relaychain governance will be migrated to the AssetHub as a part of AHM). + The primary reason for this approach is to avoid transferring the entire `new_code` Wasm blob between chains. Instead, we authorize the `code_hash` using `root` via `fn authorize_force_set_current_code_hash(new_authorization, expire_at)`. This authorization can later be applied by anyone using `Paras::apply_authorized_force_set_current_code(para, new_code)`. If `expire_at` is reached without the authorization being used, it is automatically removed. +crates: +- name: polkadot-runtime-parachains + bump: major +- name: polkadot-runtime-common + bump: patch +- name: rococo-runtime + bump: minor +- name: westend-runtime + bump: minor +- name: pallet-staking-async-rc-runtime + bump: minor diff --git a/substrate/frame/staking-async/runtimes/rc/src/lib.rs b/substrate/frame/staking-async/runtimes/rc/src/lib.rs index 5bf9cf1e0f954..b5acae3c0ba8a 100644 --- a/substrate/frame/staking-async/runtimes/rc/src/lib.rs +++ b/substrate/frame/staking-async/runtimes/rc/src/lib.rs @@ -1352,6 +1352,7 @@ impl parachains_paras::Config for Runtime { type AssignCoretime = CoretimeAssignmentProvider; type Fungible = Balances; type CooldownRemovalMultiplier = ConstUint<1>; + type AuthorizeCurrentCodeOrigin = EnsureRoot; } parameter_types! { diff --git a/substrate/frame/staking-async/runtimes/rc/src/weights/polkadot_runtime_parachains_paras.rs b/substrate/frame/staking-async/runtimes/rc/src/weights/polkadot_runtime_parachains_paras.rs index cc333d1b1ec73..03d4cae2f6405 100644 --- a/substrate/frame/staking-async/runtimes/rc/src/weights/polkadot_runtime_parachains_paras.rs +++ b/substrate/frame/staking-async/runtimes/rc/src/weights/polkadot_runtime_parachains_paras.rs @@ -303,4 +303,37 @@ impl polkadot_runtime_parachains::paras::WeightInfo for .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } + + /// Storage: `Paras::AuthorizedCodeHash` (r:0 w:1) + /// Proof: `Paras::AuthorizedCodeHash` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn authorize_force_set_current_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_112_000 picoseconds. + Weight::from_parts(8_401_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Paras::AuthorizedCodeHash` (r:1 w:1) + /// Proof: `Paras::AuthorizedCodeHash` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::CodeByHashRefs` (r:1 w:1) + /// Proof: `Paras::CodeByHashRefs` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::CurrentCodeHash` (r:1 w:1) + /// Proof: `Paras::CurrentCodeHash` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// 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 { + // Proof Size summary in bytes: + // Measured: `132` + // Estimated: `3597` + // Minimum execution time: 32_733_000 picoseconds. + Weight::from_parts(33_172_000, 0) + .saturating_add(Weight::from_parts(0, 3597)) + // Standard Error: 133 + .saturating_add(Weight::from_parts(14_731, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + } }