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
+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