// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Cumulus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Cumulus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Cumulus. If not, see .
#![cfg_attr(not(feature = "std"), no_std)]
//! Enable Parachain validation function upgrades.
//!
//! Allow a user to determine when a parachain validation function upgrade
//! is legal, and perform the upgrade, triggering runtime events
//! for both storing and applying the new validation function.
//!
//! Depends on no external pallets or traits.
//!
//! This pallet depends on certain environmental conditions provided by
//! Cumulus. It will not work outside a Cumulus Parachain.
//!
//! Users must ensure that they register this pallet as an inherent provider.
use cumulus_primitives::{
inherents::{ValidationDataType, VALIDATION_DATA_IDENTIFIER as INHERENT_IDENTIFIER},
well_known_keys::{NEW_VALIDATION_CODE, VALIDATION_DATA}, AbridgedHostConfiguration,
OnValidationData, ValidationData, ParaId, relay_chain,
};
use frame_support::{
decl_error, decl_event, decl_module, decl_storage, ensure, storage,
weights::{DispatchClass, Weight}, dispatch::DispatchResult, traits::Get,
};
use frame_system::{ensure_none, ensure_root};
use parachain::primitives::RelayChainBlockNumber;
use sp_core::storage::well_known_keys;
use sp_inherents::{InherentData, InherentIdentifier, ProvideInherent};
use sp_std::vec::Vec;
mod relay_state_snapshot;
pub use relay_state_snapshot::MessagingStateSnapshot;
/// The pallet's configuration trait.
pub trait Config: frame_system::Config {
/// The overarching event type.
type Event: From + Into<::Event>;
/// Something which can be notified when the validation data is set.
type OnValidationData: OnValidationData;
/// Returns the parachain ID we are running with.
type SelfParaId: Get;
}
// This pallet's storage items.
decl_storage! {
trait Store for Module as ParachainUpgrade {
// we need to store the new validation function for the span between
// setting it and applying it.
PendingValidationFunction get(fn new_validation_function):
Option<(RelayChainBlockNumber, Vec)>;
/// Were the [`ValidationData`] updated in this block?
DidUpdateValidationData: bool;
/// Were the validation data set to notify the relay chain?
DidSetValidationCode: bool;
/// The last relay parent block number at which we signalled the code upgrade.
LastUpgrade: relay_chain::BlockNumber;
/// The snapshot of some state related to messaging relevant to the current parachain as per
/// the relay parent.
///
/// This field is meant to be updated each block with the validation data inherent. Therefore,
/// before processing of the inherent, e.g. in `on_initialize` this data may be stale.
///
/// This data is also absent from the genesis.
RelevantMessagingState get(fn relevant_messaging_state): Option;
/// The parachain host configuration that was obtained from the relay parent.
///
/// This field is meant to be updated each block with the validation data inherent. Therefore,
/// before processing of the inherent, e.g. in `on_initialize` this data may be stale.
///
/// This data is also absent from the genesis.
HostConfiguration get(fn host_configuration): Option;
}
}
// The pallet's dispatchable functions.
decl_module! {
pub struct Module for enum Call where origin: T::Origin {
// Initializing events
// this is needed only if you are using events in your pallet
fn deposit_event() = default;
// TODO: figure out a better weight than this
#[weight = (0, DispatchClass::Operational)]
pub fn schedule_upgrade(origin, validation_function: Vec) {
ensure_root(origin)?;
>::can_set_code(&validation_function)?;
Self::schedule_upgrade_impl(validation_function)?;
}
/// Schedule a validation function upgrade without further checks.
///
/// Same as [`Module::schedule_upgrade`], but without checking that the new `validation_function`
/// is correct. This makes it more flexible, but also opens the door to easily brick the chain.
#[weight = (0, DispatchClass::Operational)]
pub fn schedule_upgrade_without_checks(origin, validation_function: Vec) {
ensure_root(origin)?;
Self::schedule_upgrade_impl(validation_function)?;
}
/// Set the current validation data.
///
/// This should be invoked exactly once per block. It will panic at the finalization
/// phase if the call was not invoked.
///
/// The dispatch origin for this call must be `Inherent`
///
/// As a side effect, this function upgrades the current validation function
/// if the appropriate time has come.
#[weight = (0, DispatchClass::Mandatory)]
fn set_validation_data(origin, data: ValidationDataType) -> DispatchResult {
ensure_none(origin)?;
assert!(!DidUpdateValidationData::exists(), "ValidationData must be updated only once in a block");
let ValidationDataType {
validation_data: vfp,
relay_chain_state,
} = data;
// 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, validation_function)) = PendingValidationFunction::get() {
if vfp.persisted.block_number >= apply_block {
PendingValidationFunction::kill();
LastUpgrade::put(&apply_block);
Self::put_parachain_code(&validation_function);
Self::deposit_event(Event::ValidationFunctionApplied(vfp.persisted.block_number));
}
}
let (host_config, relevant_messaging_state) =
relay_state_snapshot::extract_from_proof(
T::SelfParaId::get(),
vfp.persisted.relay_storage_root,
relay_chain_state
)
.map_err(|err| {
frame_support::debug::print!("invalid relay chain merkle proof: {:?}", err);
Error::::InvalidRelayChainMerkleProof
})?;
storage::unhashed::put(VALIDATION_DATA, &vfp);
DidUpdateValidationData::put(true);
RelevantMessagingState::put(relevant_messaging_state);
HostConfiguration::put(host_config);
::on_validation_data(vfp);
Ok(())
}
fn on_finalize() {
assert!(DidUpdateValidationData::take(), "VFPs must be updated once per block");
DidSetValidationCode::take();
}
fn on_initialize(n: T::BlockNumber) -> Weight {
// To prevent removing `NEW_VALIDATION_CODE` that was set by another `on_initialize` like
// for example from scheduler, we only kill the storage entry if it was not yet updated
// in the current block.
if !DidSetValidationCode::get() {
storage::unhashed::kill(NEW_VALIDATION_CODE);
}
storage::unhashed::kill(VALIDATION_DATA);
0
}
}
}
impl Module {
/// Get validation data.
///
/// Returns `Some(_)` after the inherent set the data for the current block.
pub fn validation_data() -> Option {
storage::unhashed::get(VALIDATION_DATA)
}
/// Put a new validation function into a particular location where polkadot
/// monitors for updates. Calling this function notifies polkadot that a new
/// upgrade has been scheduled.
fn notify_polkadot_of_pending_upgrade(code: &[u8]) {
storage::unhashed::put_raw(NEW_VALIDATION_CODE, code);
DidSetValidationCode::put(true);
}
/// Put a new validation function into a particular location where this
/// parachain will execute it on subsequent blocks.
fn put_parachain_code(code: &[u8]) {
storage::unhashed::put_raw(well_known_keys::CODE, code);
}
/// The maximum code size permitted, in bytes.
///
/// Returns `None` if the relay chain parachain host configuration hasn't been submitted yet.
pub fn max_code_size() -> Option {
HostConfiguration::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: &ValidationData,
cfg: &AbridgedHostConfiguration,
) -> Option {
if PendingValidationFunction::get().is_some() {
// There is already upgrade scheduled. Upgrade is not allowed.
return None;
}
let relay_blocks_since_last_upgrade = vfp
.persisted
.block_number
.saturating_sub(LastUpgrade::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.persisted.block_number + cfg.validation_upgrade_delay)
}
/// The implementation of the runtime upgrade scheduling.
fn schedule_upgrade_impl(
validation_function: Vec,
) -> DispatchResult {
ensure!(
!PendingValidationFunction::exists(),
Error::::OverlappingUpgrades
);
let vfp = Self::validation_data().ok_or(Error::::ValidationDataNotAvailable)?;
let cfg = Self::host_configuration().ok_or(Error::::HostConfigurationNotAvailable)?;
ensure!(
validation_function.len() <= cfg.max_code_size as usize,
Error::::TooBig
);
let apply_block =
Self::code_upgrade_allowed(&vfp, &cfg).ok_or(Error::::ProhibitedByPolkadot)?;
// When a code upgrade is scheduled, it has to be applied in two
// places, synchronized: both polkadot and the individual parachain
// have to upgrade on the same relay chain block.
//
// `notify_polkadot_of_pending_upgrade` notifies polkadot; the `PendingValidationFunction`
// storage keeps track locally for the parachain upgrade, which will
// be applied later.
Self::notify_polkadot_of_pending_upgrade(&validation_function);
PendingValidationFunction::put((apply_block, validation_function));
Self::deposit_event(Event::ValidationFunctionStored(apply_block));
Ok(())
}
}
impl ProvideInherent for Module {
type Call = Call;
type Error = sp_inherents::MakeFatalError<()>;
const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
fn create_inherent(data: &InherentData) -> Option {
let data: ValidationDataType = data
.get_data(&INHERENT_IDENTIFIER)
.ok()
.flatten()
.expect("validation function params are always injected into inherent data; qed");
Some(Call::set_validation_data(data))
}
}
decl_event! {
pub enum Event {
// The validation function has been scheduled to apply as of the contained relay chain block number.
ValidationFunctionStored(RelayChainBlockNumber),
// The validation function was applied as of the contained relay chain block number.
ValidationFunctionApplied(RelayChainBlockNumber),
}
}
decl_error! {
pub enum Error for Module {
/// Attempt to upgrade validation function while existing upgrade pending
OverlappingUpgrades,
/// Polkadot currently prohibits this parachain from upgrading its validation function
ProhibitedByPolkadot,
/// The supplied validation function has compiled into a blob larger than Polkadot is willing to run
TooBig,
/// The inherent which supplies the validation data did not run this block
ValidationDataNotAvailable,
/// The inherent which supplies the host configuration did not run this block
HostConfigurationNotAvailable,
/// Invalid relay-chain storage merkle proof
InvalidRelayChainMerkleProof,
}
}
/// tests for this pallet
#[cfg(test)]
mod tests {
use super::*;
use codec::Encode;
use cumulus_primitives::{PersistedValidationData, TransientValidationData};
use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
use frame_support::{
assert_ok,
dispatch::UnfilteredDispatchable,
impl_outer_event, impl_outer_origin, parameter_types,
traits::{OnFinalize, OnInitialize},
};
use frame_system::{InitKind, RawOrigin};
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
};
use sp_version::RuntimeVersion;
impl_outer_origin! {
pub enum Origin for Test where system = frame_system {}
}
mod parachain_upgrade {
pub use crate::Event;
}
impl_outer_event! {
pub enum TestEvent for Test {
frame_system,
parachain_upgrade,
}
}
// For testing the pallet, we construct most of a mock runtime. This means
// first constructing a configuration type (`Test`) which `impl`s each of the
// configuration traits of modules we want to use.
#[derive(Clone, Eq, PartialEq)]
pub struct Test;
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub Version: RuntimeVersion = RuntimeVersion {
spec_name: sp_version::create_runtime_str!("test"),
impl_name: sp_version::create_runtime_str!("system-test"),
authoring_version: 1,
spec_version: 1,
impl_version: 1,
apis: sp_version::create_apis_vec!([]),
transaction_version: 1,
};
pub const ParachainId: ParaId = ParaId::new(200);
}
impl frame_system::Config for Test {
type Origin = Origin;
type Call = ();
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup;
type Header = Header;
type Event = TestEvent;
type BlockHashCount = BlockHashCount;
type BlockLength = ();
type BlockWeights = ();
type Version = Version;
type PalletInfo = ();
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type DbWeight = ();
type BaseCallFilter = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
}
impl Config for Test {
type Event = TestEvent;
type OnValidationData = ();
type SelfParaId = ParachainId;
}
type ParachainUpgrade = Module;
type System = frame_system::Module;
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
fn new_test_ext() -> sp_io::TestExternalities {
frame_system::GenesisConfig::default()
.build_storage::()
.unwrap()
.into()
}
struct CallInWasm(Vec);
impl sp_core::traits::CallInWasm for CallInWasm {
fn call_in_wasm(
&self,
_wasm_code: &[u8],
_code_hash: Option>,
_method: &str,
_call_data: &[u8],
_ext: &mut dyn sp_externalities::Externalities,
_missing_host_functions: sp_core::traits::MissingHostFunctions,
) -> Result, String> {
Ok(self.0.clone())
}
}
fn wasm_ext() -> sp_io::TestExternalities {
let version = RuntimeVersion {
spec_name: "test".into(),
spec_version: 2,
impl_version: 1,
..Default::default()
};
let call_in_wasm = CallInWasm(version.encode());
let mut ext = new_test_ext();
ext.register_extension(sp_core::traits::CallInWasmExt::new(call_in_wasm));
ext
}
struct BlockTest {
n: ::BlockNumber,
within_block: Box,
after_block: Option>,
}
/// BlockTests exist to test blocks with some setup: we have to assume that
/// `validate_block` will mutate and check storage in certain predictable
/// ways, for example, and we want to always ensure that tests are executed
/// in the context of some particular block number.
#[derive(Default)]
struct BlockTests {
tests: Vec,
pending_upgrade: Option,
ran: bool,
relay_sproof_builder_hook: Option<
Box
>,
}
impl BlockTests {
fn new() -> BlockTests {
Default::default()
}
fn add_raw(mut self, test: BlockTest) -> Self {
self.tests.push(test);
self
}
fn add(self, n: ::BlockNumber, within_block: F) -> Self
where
F: 'static + Fn(),
{
self.add_raw(BlockTest {
n,
within_block: Box::new(within_block),
after_block: None,
})
}
fn add_with_post_test(
self,
n: ::BlockNumber,
within_block: F1,
after_block: F2,
) -> Self
where
F1: 'static + Fn(),
F2: 'static + Fn(),
{
self.add_raw(BlockTest {
n,
within_block: Box::new(within_block),
after_block: Some(Box::new(after_block)),
})
}
fn with_relay_sproof_builder(mut self, f: F) -> Self
where
F: 'static + Fn(&BlockTests, RelayChainBlockNumber, &mut RelayStateSproofBuilder)
{
self.relay_sproof_builder_hook = Some(Box::new(f));
self
}
fn run(&mut self) {
self.ran = true;
wasm_ext().execute_with(|| {
for BlockTest {
n,
within_block,
after_block,
} in self.tests.iter()
{
// clear pending updates, as applicable
if let Some(upgrade_block) = self.pending_upgrade {
if n >= &upgrade_block.into() {
self.pending_upgrade = None;
}
}
// begin initialization
System::initialize(
&n,
&Default::default(),
&Default::default(),
InitKind::Full,
);
// now mess with the storage the way validate_block does
let mut sproof_builder = RelayStateSproofBuilder::default();
if let Some(ref hook) = self.relay_sproof_builder_hook {
hook(self, *n as RelayChainBlockNumber, &mut sproof_builder);
}
let (relay_storage_root, relay_chain_state) =
sproof_builder.into_state_root_and_proof();
let vfp = ValidationData {
persisted: PersistedValidationData {
block_number: *n as RelayChainBlockNumber,
relay_storage_root,
..Default::default()
},
transient: TransientValidationData::default(),
};
storage::unhashed::put(VALIDATION_DATA, &vfp);
storage::unhashed::kill(NEW_VALIDATION_CODE);
// It is insufficient to push the validation function params
// to storage; they must also be included in the inherent data.
let inherent_data = {
let mut inherent_data = InherentData::default();
inherent_data
.put_data(INHERENT_IDENTIFIER, &ValidationDataType {
validation_data: vfp.clone(),
relay_chain_state,
})
.expect("failed to put VFP inherent");
inherent_data
};
// execute the block
ParachainUpgrade::on_initialize(*n);
ParachainUpgrade::create_inherent(&inherent_data)
.expect("got an inherent")
.dispatch_bypass_filter(RawOrigin::None.into())
.expect("dispatch succeeded");
within_block();
ParachainUpgrade::on_finalize(*n);
// did block execution set new validation code?
if storage::unhashed::exists(NEW_VALIDATION_CODE) {
if self.pending_upgrade.is_some() {
panic!("attempted to set validation code while upgrade was pending");
}
self.pending_upgrade = vfp.transient.code_upgrade_allowed;
}
// clean up
System::finalize();
if let Some(after_block) = after_block {
after_block();
}
}
});
}
}
impl Drop for BlockTests {
fn drop(&mut self) {
if !self.ran {
self.run();
}
}
}
#[test]
#[should_panic]
fn block_tests_run_on_drop() {
BlockTests::new().add(123, || {
panic!("if this test passes, block tests run properly")
});
}
#[test]
fn requires_root() {
BlockTests::new().add(123, || {
assert_eq!(
ParachainUpgrade::schedule_upgrade(Origin::signed(1), Default::default()),
Err(sp_runtime::DispatchError::BadOrigin),
);
});
}
#[test]
fn requires_root_2() {
BlockTests::new().add(123, || {
assert_ok!(ParachainUpgrade::schedule_upgrade(
RawOrigin::Root.into(),
Default::default()
));
});
}
#[test]
fn events() {
BlockTests::new()
.with_relay_sproof_builder(|_, _, builder| {
builder.host_config.validation_upgrade_delay = 1000;
})
.add_with_post_test(
123,
|| {
assert_ok!(ParachainUpgrade::schedule_upgrade(
RawOrigin::Root.into(),
Default::default()
));
},
|| {
let events = System::events();
assert_eq!(
events[0].event,
TestEvent::parachain_upgrade(Event::ValidationFunctionStored(1123))
);
},
)
.add_with_post_test(
1234,
|| {},
|| {
let events = System::events();
assert_eq!(
events[0].event,
TestEvent::parachain_upgrade(Event::ValidationFunctionApplied(1234))
);
},
);
}
#[test]
fn non_overlapping() {
BlockTests::new()
.with_relay_sproof_builder(|_, _, builder| {
builder.host_config.validation_upgrade_delay = 1000;
})
.add(123, || {
assert_ok!(ParachainUpgrade::schedule_upgrade(
RawOrigin::Root.into(),
Default::default()
));
})
.add(234, || {
assert_eq!(
ParachainUpgrade::schedule_upgrade(RawOrigin::Root.into(), Default::default()),
Err(Error::::OverlappingUpgrades.into()),
)
});
}
#[test]
fn manipulates_storage() {
BlockTests::new()
.add(123, || {
assert!(
!PendingValidationFunction::exists(),
"validation function must not exist yet"
);
assert_ok!(ParachainUpgrade::schedule_upgrade(
RawOrigin::Root.into(),
Default::default()
));
assert!(
PendingValidationFunction::exists(),
"validation function must now exist"
);
})
.add_with_post_test(
1234,
|| {},
|| {
assert!(
!PendingValidationFunction::exists(),
"validation function must have been unset"
);
},
);
}
#[test]
fn checks_size() {
BlockTests::new()
.with_relay_sproof_builder(|_, _, builder| {
builder.host_config.max_code_size = 8;
})
.add(123, || {
assert_eq!(
ParachainUpgrade::schedule_upgrade(RawOrigin::Root.into(), vec![0; 64]),
Err(Error::::TooBig.into()),
);
});
}
}