Add Authorize Upgrade Pattern to Frame System (#2682)

Adds the `authorize_upgrade` -> `enact_authorized_upgrade` pattern to
`frame-system`. This will be useful for upgrading bridged chains that
are under the governance of Polkadot without passing entire runtime Wasm
blobs over a bridge.

Notes:

- Changed `enact_authorized_upgrade` to `apply_authorized_upgrade`.
Personal opinion, "apply" more accurately expresses what it's doing. Can
change back if outvoted.
- Remove `check_version` in favor of two extrinsics, so as to make
_checked_ the default.
- Left calls in `parachain-system` and marked as deprecated to prevent
breaking the API. They just call into the `frame-system` functions.
- Updated `frame-system` benchmarks to v2 syntax.

---------

Co-authored-by: command-bot <>
This commit is contained in:
joe petrowski
2023-12-20 16:12:21 +01:00
committed by GitHub
parent b51904da56
commit 280aa0b573
17 changed files with 654 additions and 113 deletions
+20 -51
View File
@@ -27,7 +27,7 @@
//!
//! Users must ensure that they register this pallet as an inherent provider.
use codec::{Decode, Encode, MaxEncodedLen};
use codec::{Decode, Encode};
use cumulus_primitives_core::{
relay_chain, AbridgedHostConfiguration, ChannelInfo, ChannelStatus, CollationInfo,
GetChannelInfo, InboundDownwardMessage, InboundHrmpMessage, MessageSendError,
@@ -50,10 +50,9 @@ use scale_info::TypeInfo;
use sp_runtime::{
traits::{Block as BlockT, BlockNumberProvider, Hash},
transaction_validity::{
InvalidTransaction, TransactionLongevity, TransactionSource, TransactionValidity,
ValidTransaction,
InvalidTransaction, TransactionSource, TransactionValidity, ValidTransaction,
},
BoundedSlice, DispatchError, FixedU128, RuntimeDebug, Saturating,
BoundedSlice, FixedU128, RuntimeDebug, Saturating,
};
use sp_std::{cmp, collections::btree_map::BTreeMap, prelude::*};
use xcm::latest::XcmHash;
@@ -169,20 +168,6 @@ impl CheckAssociatedRelayNumber for RelayNumberMonotonicallyIncreases {
}
}
/// Information needed when a new runtime binary is submitted and needs to be authorized before
/// replacing the current runtime.
#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
#[scale_info(skip_type_params(T))]
struct CodeUpgradeAuthorization<T>
where
T: Config,
{
/// Hash of the new runtime binary.
code_hash: T::Hash,
/// Whether or not to carry out version checks.
check_version: bool,
}
/// The max length of a DMP message.
pub type MaxDmpMessageLenOf<T> = <<T as Config>::DmpQueue as HandleMessage>::MaxMessageLen;
@@ -204,7 +189,7 @@ pub mod ump_constants {
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use frame_system::{pallet_prelude::*, WeightInfo as SystemWeightInfo};
#[pallet::pallet]
#[pallet::storage_version(migration::STORAGE_VERSION)]
@@ -677,16 +662,18 @@ pub mod pallet {
///
/// This call requires Root origin.
#[pallet::call_index(2)]
#[pallet::weight((1_000_000, DispatchClass::Operational))]
#[pallet::weight(<T as frame_system::Config>::SystemWeightInfo::authorize_upgrade())]
#[allow(deprecated)]
#[deprecated(
note = "To be removed after June 2024. Migrate to `frame_system::authorize_upgrade`."
)]
pub fn authorize_upgrade(
origin: OriginFor<T>,
code_hash: T::Hash,
check_version: bool,
) -> DispatchResult {
ensure_root(origin)?;
AuthorizedUpgrade::<T>::put(CodeUpgradeAuthorization { code_hash, check_version });
Self::deposit_event(Event::UpgradeAuthorized { code_hash });
frame_system::Pallet::<T>::do_authorize_upgrade(code_hash, check_version);
Ok(())
}
@@ -700,15 +687,17 @@ pub mod pallet {
///
/// All origins are allowed.
#[pallet::call_index(3)]
#[pallet::weight({1_000_000})]
#[pallet::weight(<T as frame_system::Config>::SystemWeightInfo::apply_authorized_upgrade())]
#[allow(deprecated)]
#[deprecated(
note = "To be removed after June 2024. Migrate to `frame_system::apply_authorized_upgrade`."
)]
pub fn enact_authorized_upgrade(
_: OriginFor<T>,
code: Vec<u8>,
) -> DispatchResultWithPostInfo {
Self::validate_authorized_upgrade(&code[..])?;
Self::schedule_code_upgrade(code)?;
AuthorizedUpgrade::<T>::kill();
Ok(Pays::No.into())
let post = frame_system::Pallet::<T>::do_apply_authorize_upgrade(code)?;
Ok(post)
}
}
@@ -721,8 +710,6 @@ pub mod pallet {
ValidationFunctionApplied { relay_chain_block_num: RelayChainBlockNumber },
/// The relay-chain aborted the upgrade process.
ValidationFunctionDiscarded,
/// An upgrade has been authorized.
UpgradeAuthorized { code_hash: T::Hash },
/// Some downward messages have been received and will be processed.
DownwardMessagesReceived { count: u32 },
/// Downward messages were processed using the given weight.
@@ -928,10 +915,6 @@ pub mod pallet {
#[pallet::storage]
pub(super) type ReservedDmpWeightOverride<T: Config> = StorageValue<_, Weight>;
/// The next authorized upgrade, if there is one.
#[pallet::storage]
pub(super) type AuthorizedUpgrade<T: Config> = StorageValue<_, CodeUpgradeAuthorization<T>>;
/// A custom head data that should be returned as result of `validate_block`.
///
/// See `Pallet::set_custom_validation_head_data` for more information.
@@ -982,7 +965,8 @@ pub mod pallet {
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
if let Call::enact_authorized_upgrade { ref code } = call {
if let Ok(hash) = Self::validate_authorized_upgrade(code) {
if let Ok(hash) = frame_system::Pallet::<T>::validate_authorized_upgrade(&code[..])
{
return Ok(ValidTransaction {
priority: 100,
requires: Vec::new(),
@@ -1001,21 +985,6 @@ pub mod pallet {
}
impl<T: Config> Pallet<T> {
fn validate_authorized_upgrade(code: &[u8]) -> Result<T::Hash, DispatchError> {
let authorization = AuthorizedUpgrade::<T>::get().ok_or(Error::<T>::NothingAuthorized)?;
// ensure that the actual hash matches the authorized hash
let actual_hash = T::Hashing::hash(code);
ensure!(actual_hash == authorization.code_hash, Error::<T>::Unauthorized);
// check versions if required as part of the authorization
if authorization.check_version {
frame_system::Pallet::<T>::can_set_code(code)?;
}
Ok(actual_hash)
}
/// Get the unincluded segment size after the given hash.
///
/// If the unincluded segment doesn't contain the given hash, this returns the
@@ -1563,8 +1532,8 @@ impl<T: Config> Pallet<T> {
}
}
/// Type that implements `SetCode`.
pub struct ParachainSetCode<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> frame_system::SetCode<T> for ParachainSetCode<T> {
fn set_code(code: Vec<u8>) -> DispatchResult {
Pallet::<T>::schedule_code_upgrade(code)
@@ -1127,8 +1127,9 @@ fn upgrade_version_checks_should_work() {
let new_code = vec![1, 2, 3, 4];
let new_code_hash = H256(sp_core::blake2_256(&new_code));
let _authorize =
ParachainSystem::authorize_upgrade(RawOrigin::Root.into(), new_code_hash, true);
#[allow(deprecated)]
let _authorize = ParachainSystem::authorize_upgrade(RawOrigin::Root.into(), new_code_hash, true);
#[allow(deprecated)]
let res = ParachainSystem::enact_authorized_upgrade(RawOrigin::None.into(), new_code);
assert_eq!(expected.map_err(DispatchErrorWithPostInfo::from), res);
@@ -152,4 +152,31 @@ impl<T: frame_system::Config> frame_system::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into())))
.saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into()))
}
/// Storage: `System::AuthorizedUpgrade` (r:0 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
fn authorize_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 33_027_000 picoseconds.
Weight::from_parts(33_027_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `System::AuthorizedUpgrade` (r:1 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
/// Storage: `System::Digest` (r:1 w:1)
/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
/// Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
fn apply_authorized_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `22`
// Estimated: `1518`
// Minimum execution time: 118_101_992_000 picoseconds.
Weight::from_parts(118_101_992_000, 0)
.saturating_add(Weight::from_parts(0, 1518))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
}
@@ -151,4 +151,31 @@ impl<T: frame_system::Config> frame_system::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into())))
.saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into()))
}
/// Storage: `System::AuthorizedUpgrade` (r:0 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
fn authorize_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 33_027_000 picoseconds.
Weight::from_parts(33_027_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `System::AuthorizedUpgrade` (r:1 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
/// Storage: `System::Digest` (r:1 w:1)
/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
/// Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
fn apply_authorized_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `22`
// Estimated: `1518`
// Minimum execution time: 118_101_992_000 picoseconds.
Weight::from_parts(118_101_992_000, 0)
.saturating_add(Weight::from_parts(0, 1518))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
}
@@ -151,4 +151,31 @@ impl<T: frame_system::Config> frame_system::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into())))
.saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into()))
}
/// Storage: `System::AuthorizedUpgrade` (r:0 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
fn authorize_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 33_027_000 picoseconds.
Weight::from_parts(33_027_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `System::AuthorizedUpgrade` (r:1 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
/// Storage: `System::Digest` (r:1 w:1)
/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
/// Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
fn apply_authorized_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `22`
// Estimated: `1518`
// Minimum execution time: 118_101_992_000 picoseconds.
Weight::from_parts(118_101_992_000, 0)
.saturating_add(Weight::from_parts(0, 1518))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
}
@@ -152,4 +152,31 @@ impl<T: frame_system::Config> frame_system::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into())))
.saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into()))
}
/// Storage: `System::AuthorizedUpgrade` (r:0 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
fn authorize_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 33_027_000 picoseconds.
Weight::from_parts(33_027_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `System::AuthorizedUpgrade` (r:1 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
/// Storage: `System::Digest` (r:1 w:1)
/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
/// Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
fn apply_authorized_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `22`
// Estimated: `1518`
// Minimum execution time: 118_101_992_000 picoseconds.
Weight::from_parts(118_101_992_000, 0)
.saturating_add(Weight::from_parts(0, 1518))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
}
@@ -151,4 +151,31 @@ impl<T: frame_system::Config> frame_system::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into())))
.saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into()))
}
/// Storage: `System::AuthorizedUpgrade` (r:0 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
fn authorize_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 33_027_000 picoseconds.
Weight::from_parts(33_027_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `System::AuthorizedUpgrade` (r:1 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
/// Storage: `System::Digest` (r:1 w:1)
/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
/// Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
fn apply_authorized_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `22`
// Estimated: `1518`
// Minimum execution time: 118_101_992_000 picoseconds.
Weight::from_parts(118_101_992_000, 0)
.saturating_add(Weight::from_parts(0, 1518))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
}
@@ -150,4 +150,31 @@ impl<T: frame_system::Config> frame_system::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into())))
.saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into()))
}
/// Storage: `System::AuthorizedUpgrade` (r:0 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
fn authorize_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 33_027_000 picoseconds.
Weight::from_parts(33_027_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `System::AuthorizedUpgrade` (r:1 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
/// Storage: `System::Digest` (r:1 w:1)
/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
/// Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
fn apply_authorized_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `22`
// Estimated: `1518`
// Minimum execution time: 118_101_992_000 picoseconds.
Weight::from_parts(118_101_992_000, 0)
.saturating_add(Weight::from_parts(0, 1518))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
}
@@ -141,4 +141,31 @@ impl<T: frame_system::Config> frame_system::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into())))
.saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into()))
}
/// Storage: `System::AuthorizedUpgrade` (r:0 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
fn authorize_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 33_027_000 picoseconds.
Weight::from_parts(33_027_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `System::AuthorizedUpgrade` (r:1 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
/// Storage: `System::Digest` (r:1 w:1)
/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
/// Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
fn apply_authorized_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `22`
// Estimated: `1518`
// Minimum execution time: 118_101_992_000 picoseconds.
Weight::from_parts(118_101_992_000, 0)
.saturating_add(Weight::from_parts(0, 1518))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
}
@@ -144,4 +144,31 @@ impl<T: frame_system::Config> frame_system::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into())))
.saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into()))
}
/// Storage: `System::AuthorizedUpgrade` (r:0 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
fn authorize_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 33_027_000 picoseconds.
Weight::from_parts(33_027_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `System::AuthorizedUpgrade` (r:1 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
/// Storage: `System::Digest` (r:1 w:1)
/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
/// Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
fn apply_authorized_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `22`
// Estimated: `1518`
// Minimum execution time: 118_101_992_000 picoseconds.
Weight::from_parts(118_101_992_000, 0)
.saturating_add(Weight::from_parts(0, 1518))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
}
+21
View File
@@ -0,0 +1,21 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
title: "Add Authorize Upgrade Pattern to Frame System"
doc:
- audience: Runtime User
description: |
Adds the `authorize_upgrade` -> `enact_authorized_upgrade` pattern to `frame-system`. This
will be useful for upgrading bridged chains that are under the governance of Polkadot without
passing entire runtime Wasm blobs over a bridge.
Notes:
- Changed `enact_authorized_upgrade` to `apply_authorized_upgrade`.
- Left calls in `parachain-system` and marked as deprecated to prevent breaking the API. They
just call into the `frame-system` functions.
- Deprecated calls will be removed no earlier than June 2024.
- Updated `frame-system` benchmarks to v2 syntax.
crates: [ ]
@@ -94,6 +94,8 @@ fn module_error_outer_enum_expand_explicit() {
frame_system::Error::InvalidTask => (),
#[cfg(feature = "experimental")]
frame_system::Error::FailedTask => (),
frame_system::Error::NothingAuthorized => (),
frame_system::Error::Unauthorized => (),
frame_system::Error::__Ignore(_, _) => (),
},
@@ -94,6 +94,8 @@ fn module_error_outer_enum_expand_implicit() {
frame_system::Error::InvalidTask => (),
#[cfg(feature = "experimental")]
frame_system::Error::FailedTask => (),
frame_system::Error::NothingAuthorized => (),
frame_system::Error::Unauthorized => (),
frame_system::Error::__Ignore(_, _) => (),
},
+114 -48
View File
@@ -21,10 +21,7 @@
#![cfg(feature = "runtime-benchmarks")]
use codec::Encode;
use frame_benchmarking::{
v1::{benchmarks, whitelisted_caller},
BenchmarkError,
};
use frame_benchmarking::{impl_benchmark_test_suite, v2::*};
use frame_support::{dispatch::DispatchClass, storage, traits::Get};
use frame_system::{Call, Pallet as System, RawOrigin};
use sp_core::storage::well_known_keys;
@@ -55,69 +52,104 @@ pub trait Config: frame_system::Config {
}
}
benchmarks! {
remark {
let b in 0 .. *T::BlockLength::get().max.get(DispatchClass::Normal) as u32;
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn remark(
b: Linear<0, { *T::BlockLength::get().max.get(DispatchClass::Normal) as u32 }>,
) -> Result<(), BenchmarkError> {
let remark_message = vec![1; b as usize];
let caller = whitelisted_caller();
}: _(RawOrigin::Signed(caller), remark_message)
remark_with_event {
let b in 0 .. *T::BlockLength::get().max.get(DispatchClass::Normal) as u32;
let remark_message = vec![1; b as usize];
let caller = whitelisted_caller();
}: _(RawOrigin::Signed(caller), remark_message)
#[extrinsic_call]
remark(RawOrigin::Signed(caller), remark_message);
set_heap_pages {
}: _(RawOrigin::Root, Default::default())
set_code {
let runtime_blob = T::prepare_set_code_data();
T::setup_set_code_requirements(&runtime_blob)?;
}: _(RawOrigin::Root, runtime_blob)
verify {
T::verify_set_code()
Ok(())
}
#[extra]
set_code_without_checks {
#[benchmark]
fn remark_with_event(
b: Linear<0, { *T::BlockLength::get().max.get(DispatchClass::Normal) as u32 }>,
) -> Result<(), BenchmarkError> {
let remark_message = vec![1; b as usize];
let caller: T::AccountId = whitelisted_caller();
let hash = T::Hashing::hash(&remark_message[..]);
#[extrinsic_call]
remark_with_event(RawOrigin::Signed(caller.clone()), remark_message);
System::<T>::assert_last_event(
frame_system::Event::<T>::Remarked { sender: caller, hash }.into(),
);
Ok(())
}
#[benchmark]
fn set_heap_pages() -> Result<(), BenchmarkError> {
#[extrinsic_call]
set_heap_pages(RawOrigin::Root, Default::default());
Ok(())
}
#[benchmark]
fn set_code() -> Result<(), BenchmarkError> {
let runtime_blob = T::prepare_set_code_data();
T::setup_set_code_requirements(&runtime_blob)?;
#[extrinsic_call]
set_code(RawOrigin::Root, runtime_blob);
T::verify_set_code();
Ok(())
}
#[benchmark(extra)]
fn set_code_without_checks() -> Result<(), BenchmarkError> {
// Assume Wasm ~4MB
let code = vec![1; 4_000_000 as usize];
T::setup_set_code_requirements(&code)?;
}: _(RawOrigin::Root, code)
verify {
let current_code = storage::unhashed::get_raw(well_known_keys::CODE).ok_or("Code not stored.")?;
#[block]
{
System::<T>::set_code_without_checks(RawOrigin::Root.into(), code)?;
}
let current_code =
storage::unhashed::get_raw(well_known_keys::CODE).ok_or("Code not stored.")?;
assert_eq!(current_code.len(), 4_000_000 as usize);
Ok(())
}
#[skip_meta]
set_storage {
let i in 0 .. 1000;
#[benchmark(skip_meta)]
fn set_storage(i: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> {
// Set up i items to add
let mut items = Vec::new();
for j in 0 .. i {
for j in 0..i {
let hash = (i, j).using_encoded(T::Hashing::hash).as_ref().to_vec();
items.push((hash.clone(), hash.clone()));
}
let items_to_verify = items.clone();
}: _(RawOrigin::Root, items)
verify {
#[extrinsic_call]
set_storage(RawOrigin::Root, items);
// Verify that they're actually in the storage.
for (item, _) in items_to_verify {
let value = storage::unhashed::get_raw(&item).ok_or("No value stored")?;
assert_eq!(value, *item);
}
Ok(())
}
#[skip_meta]
kill_storage {
let i in 0 .. 1000;
#[benchmark(skip_meta)]
fn kill_storage(i: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> {
// Add i items to storage
let mut items = Vec::with_capacity(i as usize);
for j in 0 .. i {
for j in 0..i {
let hash = (i, j).using_encoded(T::Hashing::hash).as_ref().to_vec();
storage::unhashed::put_raw(&hash, &hash);
items.push(hash);
@@ -130,22 +162,23 @@ benchmarks! {
}
let items_to_verify = items.clone();
}: _(RawOrigin::Root, items)
verify {
#[extrinsic_call]
kill_storage(RawOrigin::Root, items);
// Verify that they're not in the storage anymore.
for item in items_to_verify {
assert!(storage::unhashed::get_raw(&item).is_none());
}
Ok(())
}
#[skip_meta]
kill_prefix {
let p in 0 .. 1000;
#[benchmark(skip_meta)]
fn kill_prefix(p: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> {
let prefix = p.using_encoded(T::Hashing::hash).as_ref().to_vec();
let mut items = Vec::with_capacity(p as usize);
// add p items that share a prefix
for i in 0 .. p {
for i in 0..p {
let hash = (p, i).using_encoded(T::Hashing::hash).as_ref().to_vec();
let key = [&prefix[..], &hash[..]].concat();
storage::unhashed::put_raw(&key, &key);
@@ -157,12 +190,45 @@ benchmarks! {
let value = storage::unhashed::get_raw(item).ok_or("No value stored")?;
assert_eq!(value, *item);
}
}: _(RawOrigin::Root, prefix, p)
verify {
#[extrinsic_call]
kill_prefix(RawOrigin::Root, prefix, p);
// Verify that they're not in the storage anymore.
for item in items {
assert!(storage::unhashed::get_raw(&item).is_none());
}
Ok(())
}
#[benchmark]
fn authorize_upgrade() -> Result<(), BenchmarkError> {
let runtime_blob = T::prepare_set_code_data();
T::setup_set_code_requirements(&runtime_blob)?;
let hash = T::Hashing::hash(&runtime_blob);
#[extrinsic_call]
authorize_upgrade(RawOrigin::Root, hash);
assert!(System::<T>::authorized_upgrade().is_some());
Ok(())
}
#[benchmark]
fn apply_authorized_upgrade() -> Result<(), BenchmarkError> {
let runtime_blob = T::prepare_set_code_data();
T::setup_set_code_requirements(&runtime_blob)?;
let hash = T::Hashing::hash(&runtime_blob);
// Will be heavier when it needs to do verification (i.e. don't use `...without_checks`).
System::<T>::authorize_upgrade(RawOrigin::Root.into(), hash)?;
#[extrinsic_call]
apply_authorized_upgrade(RawOrigin::Root, runtime_blob);
// Can't check for `CodeUpdated` in parachain upgrades. Just check that the authorization is
// gone.
assert!(System::<T>::authorized_upgrade().is_none());
Ok(())
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
+180 -12
View File
@@ -17,27 +17,60 @@
//! # System Pallet
//!
//! The System pallet provides low-level access to core types and cross-cutting utilities.
//! It acts as the base layer for other pallets to interact with the Substrate framework components.
//! The System pallet provides low-level access to core types and cross-cutting utilities. It acts
//! as the base layer for other pallets to interact with the Substrate framework components.
//!
//! - [`Config`]
//!
//! ## Overview
//!
//! The System pallet defines the core data types used in a Substrate runtime.
//! It also provides several utility functions (see [`Pallet`]) for other FRAME pallets.
//! The System pallet defines the core data types used in a Substrate runtime. It also provides
//! several utility functions (see [`Pallet`]) for other FRAME pallets.
//!
//! In addition, it manages the storage items for extrinsics data, indexes, event records, and
//! digest items, among other things that support the execution of the current block.
//! In addition, it manages the storage items for extrinsic data, indices, event records, and digest
//! items, among other things that support the execution of the current block.
//!
//! It also handles low-level tasks like depositing logs, basic set up and take down of
//! temporary storage entries, and access to previous block hashes.
//! It also handles low-level tasks like depositing logs, basic set up and take down of temporary
//! storage entries, and access to previous block hashes.
//!
//! ## Interface
//!
//! ### Dispatchable Functions
//!
//! The System pallet does not implement any dispatchable functions.
//! The System pallet provides dispatchable functions that, with the exception of `remark`, manage
//! low-level or privileged functionality of a Substrate-based runtime.
//!
//! - `remark`: Make some on-chain remark.
//! - `set_heap_pages`: Set the number of pages in the WebAssembly environment's heap.
//! - `set_code`: Set the new runtime code.
//! - `set_code_without_checks`: Set the new runtime code without any checks.
//! - `set_storage`: Set some items of storage.
//! - `kill_storage`: Kill some items from storage.
//! - `kill_prefix`: Kill all storage items with a key that starts with the given prefix.
//! - `remark_with_event`: Make some on-chain remark and emit an event.
//! - `do_task`: Do some specified task.
//! - `authorize_upgrade`: Authorize new runtime code.
//! - `authorize_upgrade_without_checks`: Authorize new runtime code and an upgrade sans
//! verification.
//! - `apply_authorized_upgrade`: Provide new, already-authorized runtime code.
//!
//! #### A Note on Upgrades
//!
//! The pallet provides two primary means of upgrading the runtime, a single-phase means using
//! `set_code` and a two-phase means using `authorize_upgrade` followed by
//! `apply_authorized_upgrade`. The first will directly attempt to apply the provided `code`
//! (application may have to be scheduled, depending on the context and implementation of the
//! `OnSetCode` trait).
//!
//! The `authorize_upgrade` route allows the authorization of a runtime's code hash. Once
//! authorized, anyone may upload the correct runtime to apply the code. This pattern is useful when
//! providing the runtime ahead of time may be unwieldy, for example when a large preimage (the
//! code) would need to be stored on-chain or sent over a message transport protocol such as a
//! bridge.
//!
//! The `*_without_checks` variants do not perform any version checks, so using them runs the risk
//! of applying a downgrade or entirely other chain specification. They will still validate that the
//! `code` meets the authorized hash.
//!
//! ### Public Functions
//!
@@ -59,7 +92,7 @@
//! - [`CheckTxVersion`]: Checks that the transaction version is the same as the one used to sign
//! the transaction.
//!
//! Lookup the runtime aggregator file (e.g. `node/runtime`) to see the full list of signed
//! Look up the runtime aggregator file (e.g. `node/runtime`) to see the full list of signed
//! extensions included in a chain.
#![cfg_attr(not(feature = "std"), no_std)]
@@ -77,6 +110,10 @@ use sp_runtime::{
Hash, Header, Lookup, LookupError, MaybeDisplay, MaybeSerializeDeserialize, Member, One,
Saturating, SimpleBitOps, StaticLookup, Zero,
},
transaction_validity::{
InvalidTransaction, TransactionLongevity, TransactionSource, TransactionValidity,
ValidTransaction,
},
DispatchError, RuntimeDebug,
};
#[cfg(any(feature = "std", test))]
@@ -90,9 +127,10 @@ use frame_support::traits::BuildGenesisConfig;
use frame_support::{
dispatch::{
extract_actual_pays_fee, extract_actual_weight, DispatchClass, DispatchInfo,
DispatchResult, DispatchResultWithPostInfo, PerDispatchClass,
DispatchResult, DispatchResultWithPostInfo, PerDispatchClass, PostDispatchInfo,
},
impl_ensure_origin_with_arg_ignoring_arg,
ensure, impl_ensure_origin_with_arg_ignoring_arg,
pallet_prelude::Pays,
storage::{self, StorageStreamIter},
traits::{
ConstU32, Contains, EnsureOrigin, EnsureOriginWithArg, Get, HandleLifetime,
@@ -198,6 +236,20 @@ impl<MaxNormal: Get<u32>, MaxOverflow: Get<u32>> ConsumerLimits for (MaxNormal,
}
}
/// Information needed when a new runtime binary is submitted and needs to be authorized before
/// replacing the current runtime.
#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct CodeUpgradeAuthorization<T>
where
T: Config,
{
/// Hash of the new runtime binary.
code_hash: T::Hash,
/// Whether or not to carry out version checks.
check_version: bool,
}
#[frame_support::pallet]
pub mod pallet {
use crate::{self as frame_system, pallet_prelude::*, *};
@@ -661,6 +713,56 @@ pub mod pallet {
// Return success.
Ok(().into())
}
/// Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be supplied
/// later.
///
/// This call requires Root origin.
#[pallet::call_index(9)]
#[pallet::weight((T::SystemWeightInfo::authorize_upgrade(), DispatchClass::Operational))]
pub fn authorize_upgrade(origin: OriginFor<T>, code_hash: T::Hash) -> DispatchResult {
ensure_root(origin)?;
Self::do_authorize_upgrade(code_hash, true);
Ok(())
}
/// Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be supplied
/// later.
///
/// WARNING: This authorizes an upgrade that will take place without any safety checks, for
/// example that the spec name remains the same and that the version number increases. Not
/// recommended for normal use. Use `authorize_upgrade` instead.
///
/// This call requires Root origin.
#[pallet::call_index(10)]
#[pallet::weight((T::SystemWeightInfo::authorize_upgrade(), DispatchClass::Operational))]
pub fn authorize_upgrade_without_checks(
origin: OriginFor<T>,
code_hash: T::Hash,
) -> DispatchResult {
ensure_root(origin)?;
Self::do_authorize_upgrade(code_hash, false);
Ok(())
}
/// Provide the preimage (runtime binary) `code` for an upgrade that has been authorized.
///
/// If the authorization required a version check, this call will ensure the spec name
/// remains unchanged and that the spec version has increased.
///
/// Depending on the runtime's `OnSetCode` configuration, this function may directly apply
/// the new `code` in the same block or attempt to schedule the upgrade.
///
/// All origins are allowed.
#[pallet::call_index(11)]
#[pallet::weight((T::SystemWeightInfo::apply_authorized_upgrade(), DispatchClass::Operational))]
pub fn apply_authorized_upgrade(
_: OriginFor<T>,
code: Vec<u8>,
) -> DispatchResultWithPostInfo {
let post = Self::do_apply_authorize_upgrade(code)?;
Ok(post)
}
}
/// Event for the System pallet.
@@ -687,6 +789,8 @@ pub mod pallet {
#[cfg(feature = "experimental")]
/// A [`Task`] failed during execution.
TaskFailed { task: T::RuntimeTask, err: DispatchError },
/// An upgrade was authorized.
UpgradeAuthorized { code_hash: T::Hash, check_version: bool },
}
/// Error for the System pallet
@@ -714,6 +818,10 @@ pub mod pallet {
#[cfg(feature = "experimental")]
/// The specified [`Task`] failed during execution.
FailedTask,
/// No upgrade authorized.
NothingAuthorized,
/// The submitted code is not authorized.
Unauthorized,
}
/// Exposed trait-generic origin type.
@@ -829,6 +937,12 @@ pub mod pallet {
#[pallet::whitelist_storage]
pub(super) type ExecutionPhase<T: Config> = StorageValue<_, Phase>;
/// `Some` if a code upgrade has been authorized.
#[pallet::storage]
#[pallet::getter(fn authorized_upgrade)]
pub(super) type AuthorizedUpgrade<T: Config> =
StorageValue<_, CodeUpgradeAuthorization<T>, OptionQuery>;
#[derive(frame_support::DefaultNoBound)]
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
@@ -848,6 +962,25 @@ pub mod pallet {
sp_io::storage::set(well_known_keys::EXTRINSIC_INDEX, &0u32.encode());
}
}
#[pallet::validate_unsigned]
impl<T: Config> sp_runtime::traits::ValidateUnsigned for Pallet<T> {
type Call = Call<T>;
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
if let Call::apply_authorized_upgrade { ref code } = call {
if let Ok(hash) = Self::validate_authorized_upgrade(&code[..]) {
return Ok(ValidTransaction {
priority: 100,
requires: Vec::new(),
provides: vec![hash.as_ref().to_vec()],
longevity: TransactionLongevity::max_value(),
propagate: true,
})
}
}
Err(InvalidTransaction::Call.into())
}
}
}
pub type Key = Vec<u8>;
@@ -1872,6 +2005,41 @@ impl<T: Config> Pallet<T> {
}
}
}
/// To be called after any origin/privilege checks. Put the code upgrade authorization into
/// storage and emit an event. Infallible.
pub fn do_authorize_upgrade(code_hash: T::Hash, check_version: bool) {
AuthorizedUpgrade::<T>::put(CodeUpgradeAuthorization { code_hash, check_version });
Self::deposit_event(Event::UpgradeAuthorized { code_hash, check_version });
}
/// Apply an authorized upgrade, performing any validation checks, and remove the authorization.
/// Whether or not the code is set directly depends on the `OnSetCode` configuration of the
/// runtime.
pub fn do_apply_authorize_upgrade(code: Vec<u8>) -> Result<PostDispatchInfo, DispatchError> {
Self::validate_authorized_upgrade(&code[..])?;
T::OnSetCode::set_code(code)?;
AuthorizedUpgrade::<T>::kill();
let post = 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,
};
Ok(post)
}
/// Check that provided `code` can be upgraded to. Namely, check that its hash matches an
/// existing authorization and that it meets the specification requirements of `can_set_code`.
pub fn validate_authorized_upgrade(code: &[u8]) -> Result<T::Hash, DispatchError> {
let authorization = AuthorizedUpgrade::<T>::get().ok_or(Error::<T>::NothingAuthorized)?;
let actual_hash = T::Hashing::hash(code);
ensure!(actual_hash == authorization.code_hash, Error::<T>::Unauthorized);
if authorization.check_version {
Self::can_set_code(code)?
}
Ok(actual_hash)
}
}
/// Returns a 32 byte datum which is guaranteed to be universally unique. `entropy` is provided
+40
View File
@@ -675,6 +675,46 @@ fn set_code_with_real_wasm_blob() {
});
}
#[test]
fn set_code_via_authorization_works() {
let executor = substrate_test_runtime_client::new_native_or_wasm_executor();
let mut ext = new_test_ext();
ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(executor));
ext.execute_with(|| {
System::set_block_number(1);
assert!(System::authorized_upgrade().is_none());
let runtime = substrate_test_runtime_client::runtime::wasm_binary_unwrap().to_vec();
let hash = <mock::Test as pallet::Config>::Hashing::hash(&runtime);
// Can't apply before authorization
assert_noop!(
System::apply_authorized_upgrade(RawOrigin::None.into(), runtime.clone()),
Error::<Test>::NothingAuthorized,
);
// Can authorize
assert_ok!(System::authorize_upgrade(RawOrigin::Root.into(), hash));
System::assert_has_event(
SysEvent::UpgradeAuthorized { code_hash: hash, check_version: true }.into(),
);
assert!(System::authorized_upgrade().is_some());
// Can't be sneaky
let mut bad_runtime = substrate_test_runtime_client::runtime::wasm_binary_unwrap().to_vec();
bad_runtime.extend(b"sneaky");
assert_noop!(
System::apply_authorized_upgrade(RawOrigin::None.into(), bad_runtime),
Error::<Test>::Unauthorized,
);
// Can apply correct runtime
assert_ok!(System::apply_authorized_upgrade(RawOrigin::None.into(), runtime));
System::assert_has_event(SysEvent::CodeUpdated.into());
assert!(System::authorized_upgrade().is_none());
});
}
#[test]
fn runtime_upgraded_with_set_storage() {
let executor = substrate_test_runtime_client::new_native_or_wasm_executor();
+56
View File
@@ -57,6 +57,8 @@ pub trait WeightInfo {
fn set_storage(i: u32, ) -> Weight;
fn kill_storage(i: u32, ) -> Weight;
fn kill_prefix(p: u32, ) -> Weight;
fn authorize_upgrade() -> Weight;
fn apply_authorized_upgrade() -> Weight;
}
/// Weights for frame_system using the Substrate node and recommended hardware.
@@ -149,6 +151,33 @@ impl<T: crate::Config> WeightInfo for SubstrateWeight<T> {
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into())))
.saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into()))
}
/// Storage: `System::AuthorizedUpgrade` (r:0 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
fn authorize_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 33_027_000 picoseconds.
Weight::from_parts(33_027_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `System::AuthorizedUpgrade` (r:1 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
/// Storage: `System::Digest` (r:1 w:1)
/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
/// Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
fn apply_authorized_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `22`
// Estimated: `1518`
// Minimum execution time: 118_101_992_000 picoseconds.
Weight::from_parts(118_101_992_000, 0)
.saturating_add(Weight::from_parts(0, 1518))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(3))
}
}
// For backwards compatibility and tests
@@ -240,4 +269,31 @@ impl WeightInfo for () {
.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(p.into())))
.saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into()))
}
/// Storage: `System::AuthorizedUpgrade` (r:0 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
fn authorize_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 33_027_000 picoseconds.
Weight::from_parts(33_027_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: `System::AuthorizedUpgrade` (r:1 w:1)
/// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`)
/// Storage: `System::Digest` (r:1 w:1)
/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
/// Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1)
fn apply_authorized_upgrade() -> Weight {
// Proof Size summary in bytes:
// Measured: `22`
// Estimated: `1518`
// Minimum execution time: 118_101_992_000 picoseconds.
Weight::from_parts(118_101_992_000, 0)
.saturating_add(Weight::from_parts(0, 1518))
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(3))
}
}