Look at the upgrade go-ahead and restriction signals (#517)

* Look at the upgrade go-ahead and restriction signals

* Update Cargo.toml

* Drop old docs for validation code

* Update tests

* Fix typo

* Add doc-comments for read_optional_entry

* Add a note about ValidationData

* Introduce migration for removing unused storage entry

* Fix indentation

* Use intra-doc link syntax

* Double-check that GoAhead signal is not spurious

* fmt

* Drop commented code

* Fix typos

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>

* Add a weight for StorageVersion write

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

Co-authored-by: Chris Sosnin <chris125_@live.com>
Co-authored-by: Chris Sosnin <48099298+slumber@users.noreply.github.com>
Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Sergei Shulepov
2021-10-12 18:08:23 +02:00
committed by GitHub
parent 9fc6015ac0
commit 3b3f9dca1d
6 changed files with 245 additions and 88 deletions
+72 -84
View File
@@ -54,6 +54,7 @@ use sp_runtime::{
};
use sp_std::{cmp, collections::btree_map::BTreeMap, prelude::*};
mod migration;
mod relay_state_snapshot;
#[macro_use]
pub mod validate_block;
@@ -94,6 +95,7 @@ pub mod pallet {
use frame_system::pallet_prelude::*;
#[pallet::pallet]
#[pallet::storage_version(migration::STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::config]
@@ -129,8 +131,13 @@ pub mod pallet {
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_runtime_upgrade() -> Weight {
migration::on_runtime_upgrade::<T>()
}
fn on_finalize(_: T::BlockNumber) {
<DidSetValidationCode<T>>::kill();
<UpgradeRestrictionSignal<T>>::kill();
assert!(
<ValidationData<T>>::exists(),
@@ -268,26 +275,6 @@ pub mod pallet {
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Force an already scheduled validation function upgrade to happen on a particular block.
///
/// Note that coordinating this block for the upgrade has to happen independently on the
/// relay chain and this parachain. Synchronizing the block for the upgrade is sensitive,
/// and this bypasses all checks and and normal protocols. Very easy to brick your chain
/// if done wrong.
#[pallet::weight((0, DispatchClass::Operational))]
pub fn set_upgrade_block(
origin: OriginFor<T>,
relay_chain_block: RelayChainBlockNumber,
) -> DispatchResult {
ensure_root(origin)?;
if <PendingRelayChainBlockNumber<T>>::get().is_some() {
<PendingRelayChainBlockNumber<T>>::put(relay_chain_block);
Ok(())
} else {
Err(Error::<T>::NotScheduled.into())
}
}
/// Set the current validation data.
///
/// This should be invoked exactly once per block. It will panic at the finalization
@@ -318,19 +305,6 @@ pub mod pallet {
Self::validate_validation_data(&vfp);
// initialization logic: we know that this runs exactly once every block,
// which means we can put the initialization logic here to remove the
// sequencing problem.
if let Some(apply_block) = <PendingRelayChainBlockNumber<T>>::get() {
if vfp.relay_parent_number >= apply_block {
<PendingRelayChainBlockNumber<T>>::kill();
let validation_code = <PendingValidationCode<T>>::take();
<LastUpgrade<T>>::put(&apply_block);
Self::put_parachain_code(&validation_code);
Self::deposit_event(Event::ValidationFunctionApplied(vfp.relay_parent_number));
}
}
let relay_state_proof = RelayChainStateProof::new(
T::SelfParaId::get(),
vfp.relay_parent_storage_root,
@@ -338,6 +312,35 @@ pub mod pallet {
)
.expect("Invalid relay chain state proof");
// initialization logic: we know that this runs exactly once every block,
// which means we can put the initialization logic here to remove the
// sequencing problem.
let upgrade_go_ahead_signal = relay_state_proof
.read_upgrade_go_ahead_signal()
.expect("Invalid upgrade go ahead signal");
match upgrade_go_ahead_signal {
Some(relay_chain::v1::UpgradeGoAhead::GoAhead) => {
assert!(
<PendingValidationCode<T>>::exists(),
"No new validation function found in storage, GoAhead signal is not expected",
);
let validation_code = <PendingValidationCode<T>>::take();
Self::put_parachain_code(&validation_code);
Self::deposit_event(Event::ValidationFunctionApplied(vfp.relay_parent_number));
},
Some(relay_chain::v1::UpgradeGoAhead::Abort) => {
<PendingValidationCode<T>>::kill();
Self::deposit_event(Event::ValidationFunctionDiscarded);
},
None => {},
}
<UpgradeRestrictionSignal<T>>::put(
relay_state_proof
.read_upgrade_restriction_signal()
.expect("Invalid upgrade restriction signal"),
);
let host_config = relay_state_proof
.read_abridged_host_configuration()
.expect("Invalid host configuration in relay chain state proof");
@@ -401,11 +404,12 @@ pub mod pallet {
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// The validation function has been scheduled to apply as of the contained relay chain
/// block number.
ValidationFunctionStored(RelayChainBlockNumber),
/// The validation function has been scheduled to apply.
ValidationFunctionStored,
/// The validation function was applied as of the contained relay chain block number.
ValidationFunctionApplied(RelayChainBlockNumber),
/// The relay-chain aborted the upgrade process.
ValidationFunctionDiscarded,
/// An upgrade has been authorized.
UpgradeAuthorized(T::Hash),
/// Some downward messages have been received and will be processed.
@@ -437,22 +441,27 @@ pub mod pallet {
Unauthorized,
}
/// We need to store the new validation function for the span between
/// setting it and applying it. If it has a
/// value, then [`PendingValidationCode`] must have a real value, and
/// together will coordinate the block number where the upgrade will happen.
#[pallet::storage]
pub(super) type PendingRelayChainBlockNumber<T: Config> =
StorageValue<_, RelayChainBlockNumber>;
/// The new validation function we will upgrade to when the relay chain
/// reaches [`PendingRelayChainBlockNumber`]. A real validation function must
/// exist here as long as [`PendingRelayChainBlockNumber`] is set.
/// In case of a scheduled upgrade, this storage field contains the validation code to be applied.
///
/// As soon as the relay chain gives us the go-ahead signal, we will overwrite the [`:code`][well_known_keys::CODE]
/// which will result the next block process with the new validation code. This concludes the upgrade process.
///
/// [well_known_keys::CODE]: sp_core::storage::well_known_keys::CODE
#[pallet::storage]
#[pallet::getter(fn new_validation_function)]
pub(super) type PendingValidationCode<T: Config> = StorageValue<_, Vec<u8>, ValueQuery>;
/// Validation code that is set by the parachain and is to be communicated to collator and
/// consequently the relay-chain.
///
/// This will be cleared in `on_initialize` of each new block if no other pallet already set
/// the value.
#[pallet::storage]
pub(super) type NewValidationCode<T: Config> = StorageValue<_, Vec<u8>, OptionQuery>;
/// The [`PersistedValidationData`] set for this block.
/// This value is expected to be set only once per block and it's never stored
/// in the trie.
#[pallet::storage]
#[pallet::getter(fn validation_data)]
pub(super) type ValidationData<T: Config> = StorageValue<_, PersistedValidationData>;
@@ -461,9 +470,16 @@ pub mod pallet {
#[pallet::storage]
pub(super) type DidSetValidationCode<T: Config> = StorageValue<_, bool, ValueQuery>;
/// The last relay parent block number at which we signalled the code upgrade.
/// An option which indicates if the relay-chain restricts signalling a validation code upgrade.
/// In other words, if this is `Some` and [`NewValidationCode`] is `Some` then the produced
/// candidate will be invalid.
///
/// This storage item is a mirror of the corresponding value for the current parachain from the
/// relay-chain. This value is ephemeral which means it doesn't hit the storage. This value is
/// set after the inherent.
#[pallet::storage]
pub(super) type LastUpgrade<T: Config> = StorageValue<_, relay_chain::BlockNumber, ValueQuery>;
pub(super) type UpgradeRestrictionSignal<T: Config> =
StorageValue<_, Option<relay_chain::v1::UpgradeRestriction>, ValueQuery>;
/// The snapshot of some state related to messaging relevant to the current parachain as per
/// the relay parent.
@@ -507,13 +523,6 @@ pub mod pallet {
#[pallet::storage]
pub(super) type ProcessedDownwardMessages<T: Config> = StorageValue<_, u32, ValueQuery>;
/// New validation code that was set in a block.
///
/// This will be cleared in `on_initialize` of each new block if no other pallet already set
/// the value.
#[pallet::storage]
pub(super) type NewValidationCode<T: Config> = StorageValue<_, Vec<u8>, OptionQuery>;
/// HRMP watermark that was set in a block.
///
/// This will be cleared in `on_initialize` of each new block.
@@ -860,36 +869,16 @@ impl<T: Config> Pallet<T> {
<HostConfiguration<T>>::get().map(|cfg| cfg.max_code_size)
}
/// Returns if a PVF/runtime upgrade could be signalled at the current block, and if so
/// when the new code will take the effect.
fn code_upgrade_allowed(
vfp: &PersistedValidationData,
cfg: &AbridgedHostConfiguration,
) -> Option<relay_chain::BlockNumber> {
if <PendingRelayChainBlockNumber<T>>::get().is_some() {
// There is already upgrade scheduled. Upgrade is not allowed.
return None
}
let relay_blocks_since_last_upgrade =
vfp.relay_parent_number.saturating_sub(<LastUpgrade<T>>::get());
if relay_blocks_since_last_upgrade <= cfg.validation_upgrade_frequency {
// The cooldown after the last upgrade hasn't elapsed yet. Upgrade is not allowed.
return None
}
Some(vfp.relay_parent_number + cfg.validation_upgrade_delay)
}
/// The implementation of the runtime upgrade functionality for parachains.
fn set_code_impl(validation_function: Vec<u8>) -> DispatchResult {
// Ensure that `ValidationData` exists. We do not care about the validation data per se,
// but we do care about the [`UpgradeRestrictionSignal`] which arrives with the same inherent.
ensure!(<ValidationData<T>>::exists(), Error::<T>::ValidationDataNotAvailable,);
ensure!(<UpgradeRestrictionSignal<T>>::get().is_none(), Error::<T>::ProhibitedByPolkadot);
ensure!(!<PendingValidationCode<T>>::exists(), Error::<T>::OverlappingUpgrades);
let vfp = Self::validation_data().ok_or(Error::<T>::ValidationDataNotAvailable)?;
let cfg = Self::host_configuration().ok_or(Error::<T>::HostConfigurationNotAvailable)?;
ensure!(validation_function.len() <= cfg.max_code_size as usize, Error::<T>::TooBig);
let apply_block =
Self::code_upgrade_allowed(&vfp, &cfg).ok_or(Error::<T>::ProhibitedByPolkadot)?;
// When a code upgrade is scheduled, it has to be applied in two
// places, synchronized: both polkadot and the individual parachain
@@ -897,11 +886,10 @@ impl<T: Config> Pallet<T> {
//
// `notify_polkadot_of_pending_upgrade` notifies polkadot; the `PendingValidationCode`
// storage keeps track locally for the parachain upgrade, which will
// be applied later.
// be applied later: when the relay-chain communicates go-ahead signal to us.
Self::notify_polkadot_of_pending_upgrade(&validation_function);
<PendingRelayChainBlockNumber<T>>::put(apply_block);
<PendingValidationCode<T>>::put(validation_function);
Self::deposit_event(Event::ValidationFunctionStored(apply_block));
Self::deposit_event(Event::ValidationFunctionStored);
Ok(())
}