Skip to content

Commit

Permalink
paras: add governance control dispatchables (paritytech#4575)
Browse files Browse the repository at this point in the history
* paras: add governance control dispatchables

Adds a couple of functions for governance control for the paras module
in the anticipation of PVF pre-checking enabling.

Specifically, this commit adds a function for pre-registering a PVF that
governance trusts enough. This function will come in handy in case there
is a parachain that does not follow the GoAhead signal. That is, does
not include paritytech/cumulus#517.

This may be not an exhaustive list of the functions that may come in
handy. Any suggestions to add more are welcome.

* cargo run --quiet --release --features=runtime-benchmarks -- benchmark --chain=kusama-dev --steps=50 --repeat=20 --pallet=runtime_parachains::paras --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/kusama/src/weights/runtime_parachains_paras.rs

* cargo run --quiet --release --features=runtime-benchmarks -- benchmark --chain=polkadot-dev --steps=50 --repeat=20 --pallet=runtime_parachains::paras --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/polkadot/src/weights/runtime_parachains_paras.rs

* cargo run --quiet --release --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=runtime_parachains::paras --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/westend/src/weights/runtime_parachains_paras.rs

* cargo run --quiet --release --features runtime-benchmarks -- benchmark --chain=rococo-dev --steps=50 --repeat=20 --pallet=runtime_parachains::paras --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/rococo/src/weights/runtime_parachains_paras.rs

Co-authored-by: Parity Bot <admin@parity.io>
  • Loading branch information
2 people authored and Wizdave97 committed Feb 3, 2022
1 parent a0640dc commit 6ffb28b
Show file tree
Hide file tree
Showing 6 changed files with 373 additions and 47 deletions.
34 changes: 23 additions & 11 deletions runtime/kusama/src/weights/runtime_parachains_paras.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
//! Autogenerated weights for `runtime_parachains::paras`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2021-12-14, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2021-12-28, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("kusama-dev"), DB CACHE: 128

// Executed Command:
Expand Down Expand Up @@ -55,7 +55,7 @@ impl<T: frame_system::Config> runtime_parachains::paras::WeightInfo for WeightIn
}
// Storage: Paras Heads (r:0 w:1)
fn force_set_current_head(s: u32, ) -> Weight {
(11_818_000 as Weight)
(11_803_000 as Weight)
// Standard Error: 0
.saturating_add((1_000 as Weight).saturating_mul(s as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
Expand All @@ -78,24 +78,36 @@ impl<T: frame_system::Config> runtime_parachains::paras::WeightInfo for WeightIn
.saturating_add(T::DbWeight::get().reads(9 as Weight))
.saturating_add(T::DbWeight::get().writes(8 as Weight))
}
// Storage: Paras FutureCodeUpgrades (r:1 w:1)
// Storage: Paras FutureCodeHash (r:1 w:1)
// Storage: Paras CurrentCodeHash (r:1 w:1)
// Storage: System Digest (r:1 w:1)
// Storage: Paras FutureCodeUpgrades (r:1 w:0)
// Storage: Paras Heads (r:0 w:1)
// Storage: Paras UpgradeGoAheadSignal (r:0 w:1)
fn force_note_new_head(s: u32, ) -> Weight {
(37_343_000 as Weight)
(18_655_000 as Weight)
// Standard Error: 0
.saturating_add((1_000 as Weight).saturating_mul(s as Weight))
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(6 as Weight))
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
// Storage: ParasShared CurrentSessionIndex (r:1 w:0)
// Storage: Paras ActionsQueue (r:1 w:1)
fn force_queue_action() -> Weight {
(22_980_000 as Weight)
(23_208_000 as Weight)
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
// Storage: Paras PvfActiveVoteMap (r:1 w:0)
// Storage: Paras CodeByHash (r:1 w:1)
fn add_trusted_validation_code(c: u32, ) -> Weight {
(0 as Weight)
// Standard Error: 0
.saturating_add((3_000 as Weight).saturating_mul(c as Weight))
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
// Storage: Paras CodeByHashRefs (r:1 w:0)
// Storage: Paras CodeByHash (r:0 w:1)
fn poke_unused_validation_code() -> Weight {
(4_639_000 as Weight)
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
}
275 changes: 272 additions & 3 deletions runtime/parachains/src/paras.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,8 @@ pub trait WeightInfo {
fn force_schedule_code_upgrade(c: u32) -> Weight;
fn force_note_new_head(s: u32) -> Weight;
fn force_queue_action() -> Weight;
fn add_trusted_validation_code(c: u32) -> Weight;
fn poke_unused_validation_code() -> Weight;
}

pub struct TestWeightInfo;
Expand All @@ -411,6 +413,12 @@ impl WeightInfo for TestWeightInfo {
fn force_queue_action() -> Weight {
Weight::MAX
}
fn add_trusted_validation_code(_c: u32) -> Weight {
Weight::MAX
}
fn poke_unused_validation_code() -> Weight {
Weight::MAX
}
}

#[frame_support::pallet]
Expand Down Expand Up @@ -763,6 +771,79 @@ pub mod pallet {
Ok(())
}

/// Adds the validation code to the storage.
///
/// The code will not be added if it is already present. Additionally, if PVF pre-checking
/// is running for that code, it will be instantly accepted.
///
/// Otherwise, the code will be added into the storage. Note that the code will be added
/// into storage with reference count 0. This is to account the fact that there are no users
/// for this code yet. The caller will have to make sure that this code eventually gets
/// used by some parachain or removed from the storage to avoid storage leaks. For the latter
/// prefer to use the `poke_unused_validation_code` dispatchable to raw storage manipulation.
///
/// This function is mainly meant to be used for upgrading parachains that do not follow
/// the go-ahead signal while the PVF pre-checking feature is enabled.
#[pallet::weight(<T as Config>::WeightInfo::add_trusted_validation_code(validation_code.0.len() as u32))]
pub fn add_trusted_validation_code(
origin: OriginFor<T>,
validation_code: ValidationCode,
) -> DispatchResult {
ensure_root(origin)?;
let code_hash = validation_code.hash();

if let Some(vote) = <Self as Store>::PvfActiveVoteMap::get(&code_hash) {
// Remove the existing vote.
PvfActiveVoteMap::<T>::remove(&code_hash);
PvfActiveVoteList::<T>::mutate(|l| {
if let Ok(i) = l.binary_search(&code_hash) {
l.remove(i);
}
});

let cfg = configuration::Pallet::<T>::config();
Self::enact_pvf_accepted(
<frame_system::Pallet<T>>::block_number(),
&code_hash,
&vote.causes,
vote.age,
&cfg,
);
return Ok(())
}

if <Self as Store>::CodeByHash::contains_key(&code_hash) {
// There is no vote, but the code exists. Nothing to do here.
return Ok(())
}

// At this point the code is unknown and there is no PVF pre-checking vote for it, so we
// can just add the code into the storage.
//
// NOTE That we do not use `increase_code_ref` here, because the code is not yet used
// by any parachain.
<Self as Store>::CodeByHash::insert(code_hash, &validation_code);

Ok(())
}

/// Remove the validation code from the storage iff the reference count is 0.
///
/// This is better than removing the storage directly, because it will not remove the code
/// that was suddenly got used by some parachain while this dispatchable was pending
/// dispatching.
#[pallet::weight(<T as Config>::WeightInfo::poke_unused_validation_code())]
pub fn poke_unused_validation_code(
origin: OriginFor<T>,
validation_code_hash: ValidationCodeHash,
) -> DispatchResult {
ensure_root(origin)?;
if <Self as Store>::CodeByHashRefs::get(&validation_code_hash) == 0 {
<Self as Store>::CodeByHash::remove(&validation_code_hash);
}
Ok(())
}

/// Includes a statement for a PVF pre-checking vote. Potentially, finalizes the vote and
/// enacts the results if that was the last vote before achieving the supermajority.
#[pallet::weight(Weight::MAX)]
Expand Down Expand Up @@ -1609,6 +1690,8 @@ impl<T: Config> Pallet<T> {
weight += T::DbWeight::get().reads(1);
match PvfActiveVoteMap::<T>::get(&code_hash) {
None => {
// We deliberately are using `CodeByHash` here instead of the `CodeByHashRefs`. This
// is because the code may have been added by `add_trusted_validation_code`.
let known_code = CodeByHash::<T>::contains_key(&code_hash);
weight += T::DbWeight::get().reads(1);

Expand Down Expand Up @@ -1812,7 +1895,10 @@ impl<T: Config> Pallet<T> {
fn decrease_code_ref(code_hash: &ValidationCodeHash) -> Weight {
let mut weight = T::DbWeight::get().reads(1);
let refs = <Self as Store>::CodeByHashRefs::get(code_hash);
debug_assert!(refs != 0);
if refs == 0 {
log::error!(target: LOG_TARGET, "Code refs is already zero for {:?}", code_hash);
return weight
}
if refs <= 1 {
weight += T::DbWeight::get().writes(2);
<Self as Store>::CodeByHash::remove(code_hash);
Expand Down Expand Up @@ -1842,7 +1928,7 @@ impl<T: Config> Pallet<T> {
#[cfg(test)]
mod tests {
use super::*;
use frame_support::{assert_err, assert_ok};
use frame_support::{assert_err, assert_ok, assert_storage_noop};
use keyring::Sr25519Keyring;
use primitives::{
v0::PARACHAIN_KEY_TYPE_ID,
Expand All @@ -1855,7 +1941,10 @@ mod tests {

use crate::{
configuration::HostConfiguration,
mock::{new_test_ext, Configuration, MockGenesisConfig, Paras, ParasShared, System, Test},
mock::{
new_test_ext, Configuration, MockGenesisConfig, Origin, Paras, ParasShared, System,
Test,
},
};

static VALIDATORS: &[Sr25519Keyring] = &[
Expand Down Expand Up @@ -3066,6 +3155,186 @@ mod tests {
});
}

#[test]
fn add_trusted_validation_code_inserts_with_no_users() {
// This test is to ensure that trusted validation code is inserted into the storage
// with the reference count equal to 0.
let validation_code = ValidationCode(vec![1, 2, 3]);
new_test_ext(Default::default()).execute_with(|| {
assert_ok!(Paras::add_trusted_validation_code(Origin::root(), validation_code.clone()));
assert_eq!(<Paras as Store>::CodeByHashRefs::get(&validation_code.hash()), 0,);
});
}

#[test]
fn add_trusted_validation_code_idempotent() {
// This test makes sure that calling add_trusted_validation_code twice with the same
// parameters is a no-op.
let validation_code = ValidationCode(vec![1, 2, 3]);
new_test_ext(Default::default()).execute_with(|| {
assert_ok!(Paras::add_trusted_validation_code(Origin::root(), validation_code.clone()));
assert_storage_noop!({
assert_ok!(Paras::add_trusted_validation_code(
Origin::root(),
validation_code.clone()
));
});
});
}

#[test]
fn poke_unused_validation_code_removes_code_cleanly() {
// This test makes sure that calling poke_unused_validation_code with a code that is currently
// in the storage but has no users will remove it cleanly from the storage.
let validation_code = ValidationCode(vec![1, 2, 3]);
new_test_ext(Default::default()).execute_with(|| {
assert_ok!(Paras::add_trusted_validation_code(Origin::root(), validation_code.clone()));
assert_ok!(Paras::poke_unused_validation_code(Origin::root(), validation_code.hash()));

assert_eq!(<Paras as Store>::CodeByHashRefs::get(&validation_code.hash()), 0);
assert!(!<Paras as Store>::CodeByHash::contains_key(&validation_code.hash()));
});
}

#[test]
fn poke_unused_validation_code_doesnt_remove_code_with_users() {
let para_id = 100.into();
let validation_code = ValidationCode(vec![1, 2, 3]);
new_test_ext(Default::default()).execute_with(|| {
// First we add the code to the storage.
assert_ok!(Paras::add_trusted_validation_code(Origin::root(), validation_code.clone()));

// Then we add a user to the code, say by upgrading.
run_to_block(2, None);
Paras::schedule_code_upgrade(
para_id,
validation_code.clone(),
1,
&Configuration::config(),
);
Paras::note_new_head(para_id, HeadData::default(), 1);

// Finally we poke the code, which should not remove it from the storage.
assert_storage_noop!({
assert_ok!(Paras::poke_unused_validation_code(
Origin::root(),
validation_code.hash()
));
});
check_code_is_stored(&validation_code);
});
}

#[test]
fn increase_code_ref_doesnt_have_allergy_on_add_trusted_validation_code() {
// Verify that accidential calling of increase_code_ref or decrease_code_ref does not lead
// to a disaster.
// NOTE that this test is extra paranoid, as it is not really possible to hit
// `decrease_code_ref` without calling `increase_code_ref` first.
let code = ValidationCode(vec![1, 2, 3]);

new_test_ext(Default::default()).execute_with(|| {
assert_ok!(Paras::add_trusted_validation_code(Origin::root(), code.clone()));
Paras::increase_code_ref(&code.hash(), &code);
Paras::increase_code_ref(&code.hash(), &code);
assert!(<Paras as Store>::CodeByHash::contains_key(code.hash()));
assert_eq!(<Paras as Store>::CodeByHashRefs::get(code.hash()), 2);
});

new_test_ext(Default::default()).execute_with(|| {
assert_ok!(Paras::add_trusted_validation_code(Origin::root(), code.clone()));
Paras::decrease_code_ref(&code.hash());
assert!(<Paras as Store>::CodeByHash::contains_key(code.hash()));
assert_eq!(<Paras as Store>::CodeByHashRefs::get(code.hash()), 0);
});
}

#[test]
fn add_trusted_validation_code_insta_approval() {
// In particular, this tests that `kick_off_pvf_check` reacts to the `add_trusted_validation_code`
// and uses the `CodeByHash::contains_key` which is what `add_trusted_validation_code` uses.
let para_id = 100.into();
let validation_code = ValidationCode(vec![1, 2, 3]);
let validation_upgrade_delay = 25;
let minimum_validation_upgrade_delay = 2;
let genesis_config = MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
pvf_checking_enabled: true,
validation_upgrade_delay,
minimum_validation_upgrade_delay,
..Default::default()
},
..Default::default()
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
assert_ok!(Paras::add_trusted_validation_code(Origin::root(), validation_code.clone()));

// Then some parachain upgrades it's code with the relay-parent 1.
run_to_block(2, None);
Paras::schedule_code_upgrade(
para_id,
validation_code.clone(),
1,
&Configuration::config(),
);
Paras::note_new_head(para_id, HeadData::default(), 1);

// Verify that the code upgrade has `expected_at` set to `26`. This is the behavior
// equal to that of `pvf_checking_enabled: false`.
assert_eq!(
<Paras as Store>::FutureCodeUpgrades::get(&para_id),
Some(1 + validation_upgrade_delay)
);
});
}

#[test]
fn add_trusted_validation_code_enacts_existing_pvf_vote() {
// This test makes sure that calling `add_trusted_validation_code` with a code that is
// already going through PVF pre-checking voting will conclude the voting and enact the
// code upgrade.
let para_id = 100.into();
let validation_code = ValidationCode(vec![1, 2, 3]);
let validation_upgrade_delay = 25;
let minimum_validation_upgrade_delay = 2;
let genesis_config = MockGenesisConfig {
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
pvf_checking_enabled: true,
validation_upgrade_delay,
minimum_validation_upgrade_delay,
..Default::default()
},
..Default::default()
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
// First, some parachain upgrades it's code with the relay-parent 1.
run_to_block(2, None);
Paras::schedule_code_upgrade(
para_id,
validation_code.clone(),
1,
&Configuration::config(),
);
Paras::note_new_head(para_id, HeadData::default(), 1);

// No upgrade should be scheduled at this point. PVF pre-checking vote should run for
// that PVF.
assert!(<Paras as Store>::FutureCodeUpgrades::get(&para_id).is_none());
assert!(<Paras as Store>::PvfActiveVoteMap::contains_key(&validation_code.hash()));

// Then we add a trusted validation code. That should conclude the vote.
assert_ok!(Paras::add_trusted_validation_code(Origin::root(), validation_code.clone()));
assert!(<Paras as Store>::FutureCodeUpgrades::get(&para_id).is_some());
assert!(!<Paras as Store>::PvfActiveVoteMap::contains_key(&validation_code.hash()));
});
}

#[test]
fn verify_upgrade_go_ahead_signal_is_externally_accessible() {
use primitives::v1::well_known_keys;
Expand Down
Loading

0 comments on commit 6ffb28b

Please sign in to comment.