From 3a4d736c45dc0ad1258f2cc6a7b212ee886aa821 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Mon, 3 Jan 2022 03:03:34 +0100 Subject: [PATCH] paras: split tests (#4636) This is only a module structure change: the tests module was promoted to have its own file. --- .../parachains/src/{paras.rs => paras/mod.rs} | 1520 +---------------- runtime/parachains/src/paras/tests.rs | 1473 ++++++++++++++++ 2 files changed, 1476 insertions(+), 1517 deletions(-) rename runtime/parachains/src/{paras.rs => paras/mod.rs} (60%) create mode 100644 runtime/parachains/src/paras/tests.rs diff --git a/runtime/parachains/src/paras.rs b/runtime/parachains/src/paras/mod.rs similarity index 60% rename from runtime/parachains/src/paras.rs rename to runtime/parachains/src/paras/mod.rs index eb6b2bce3eaa..3620768857f8 100644 --- a/runtime/parachains/src/paras.rs +++ b/runtime/parachains/src/paras/mod.rs @@ -134,6 +134,9 @@ pub use crate::Origin as ParachainOrigin; #[cfg(feature = "runtime-benchmarks")] pub(crate) mod benchmarking; +#[cfg(test)] +pub(crate) mod tests; + pub use pallet::*; const LOG_TARGET: &str = "runtime::paras"; @@ -1947,1520 +1950,3 @@ impl Pallet { Heads::::insert(para_id, head_data); } } - -#[cfg(test)] -mod tests { - use super::*; - use frame_support::{assert_err, assert_ok, assert_storage_noop}; - use keyring::Sr25519Keyring; - use primitives::{ - v0::PARACHAIN_KEY_TYPE_ID, - v1::{BlockNumber, ValidatorId}, - }; - use sc_keystore::LocalKeystore; - use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; - use std::sync::Arc; - use test_helpers::{dummy_head_data, dummy_validation_code}; - - use crate::{ - configuration::HostConfiguration, - mock::{ - new_test_ext, Configuration, MockGenesisConfig, Origin, Paras, ParasShared, System, - Test, - }, - }; - - static VALIDATORS: &[Sr25519Keyring] = &[ - Sr25519Keyring::Alice, - Sr25519Keyring::Bob, - Sr25519Keyring::Charlie, - Sr25519Keyring::Dave, - Sr25519Keyring::Ferdie, - ]; - - fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec { - val_ids.iter().map(|v| v.public().into()).collect() - } - - fn sign_and_include_pvf_check_statement(stmt: PvfCheckStatement) { - let validators = &[ - Sr25519Keyring::Alice, - Sr25519Keyring::Bob, - Sr25519Keyring::Charlie, - Sr25519Keyring::Dave, - Sr25519Keyring::Ferdie, - ]; - let signature = validators[stmt.validator_index.0 as usize].sign(&stmt.signing_payload()); - Paras::include_pvf_check_statement(None.into(), stmt, signature.into()).unwrap(); - } - - fn run_to_block(to: BlockNumber, new_session: Option>) { - let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); - for validator in VALIDATORS.iter() { - SyncCryptoStore::sr25519_generate_new( - &*keystore, - PARACHAIN_KEY_TYPE_ID, - Some(&validator.to_seed()), - ) - .unwrap(); - } - let validator_pubkeys = validator_pubkeys(VALIDATORS); - - while System::block_number() < to { - let b = System::block_number(); - Paras::initializer_finalize(b); - ParasShared::initializer_finalize(); - if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) { - let mut session_change_notification = SessionChangeNotification::default(); - session_change_notification.session_index = ParasShared::session_index() + 1; - session_change_notification.validators = validator_pubkeys.clone(); - ParasShared::initializer_on_new_session( - session_change_notification.session_index, - session_change_notification.random_seed, - &session_change_notification.new_config, - session_change_notification.validators.clone(), - ); - ParasShared::set_active_validators_ascending(validator_pubkeys.clone()); - Paras::initializer_on_new_session(&session_change_notification); - } - System::on_finalize(b); - - System::on_initialize(b + 1); - System::set_block_number(b + 1); - - ParasShared::initializer_initialize(b + 1); - Paras::initializer_initialize(b + 1); - } - } - - fn upgrade_at( - expected_at: BlockNumber, - activated_at: BlockNumber, - ) -> ReplacementTimes { - ReplacementTimes { expected_at, activated_at } - } - - fn check_code_is_stored(validation_code: &ValidationCode) { - assert!(::CodeByHashRefs::get(validation_code.hash()) != 0); - assert!(::CodeByHash::contains_key(validation_code.hash())); - } - - fn check_code_is_not_stored(validation_code: &ValidationCode) { - assert!(!::CodeByHashRefs::contains_key(validation_code.hash())); - assert!(!::CodeByHash::contains_key(validation_code.hash())); - } - - #[test] - fn para_past_code_pruning_works_correctly() { - let mut past_code = ParaPastCodeMeta::default(); - past_code.note_replacement(10u32, 10); - past_code.note_replacement(20, 25); - past_code.note_replacement(30, 35); - - let old = past_code.clone(); - assert!(past_code.prune_up_to(9).collect::>().is_empty()); - assert_eq!(old, past_code); - - assert_eq!(past_code.prune_up_to(10).collect::>(), vec![10]); - assert_eq!( - past_code, - ParaPastCodeMeta { - upgrade_times: vec![upgrade_at(20, 25), upgrade_at(30, 35)], - last_pruned: Some(10), - } - ); - - assert!(past_code.prune_up_to(21).collect::>().is_empty()); - - assert_eq!(past_code.prune_up_to(26).collect::>(), vec![20]); - assert_eq!( - past_code, - ParaPastCodeMeta { upgrade_times: vec![upgrade_at(30, 35)], last_pruned: Some(25) } - ); - - past_code.note_replacement(40, 42); - past_code.note_replacement(50, 53); - past_code.note_replacement(60, 66); - - assert_eq!( - past_code, - ParaPastCodeMeta { - upgrade_times: vec![ - upgrade_at(30, 35), - upgrade_at(40, 42), - upgrade_at(50, 53), - upgrade_at(60, 66) - ], - last_pruned: Some(25), - } - ); - - assert_eq!(past_code.prune_up_to(60).collect::>(), vec![30, 40, 50]); - assert_eq!( - past_code, - ParaPastCodeMeta { upgrade_times: vec![upgrade_at(60, 66)], last_pruned: Some(53) } - ); - - assert_eq!(past_code.most_recent_change(), Some(60)); - assert_eq!(past_code.prune_up_to(66).collect::>(), vec![60]); - - assert_eq!( - past_code, - ParaPastCodeMeta { upgrade_times: Vec::new(), last_pruned: Some(66) } - ); - } - - #[test] - fn schedule_para_init_rejects_empty_code() { - new_test_ext(MockGenesisConfig::default()).execute_with(|| { - assert_err!( - Paras::schedule_para_initialize( - 1000.into(), - ParaGenesisArgs { - parachain: false, - genesis_head: dummy_head_data(), - validation_code: ValidationCode(vec![]), - } - ), - Error::::CannotOnboard, - ); - - assert_ok!(Paras::schedule_para_initialize( - 1000.into(), - ParaGenesisArgs { - parachain: false, - genesis_head: dummy_head_data(), - validation_code: ValidationCode(vec![1]), - } - )); - }); - } - - #[test] - fn para_past_code_pruning_in_initialize() { - let code_retention_period = 10; - let paras = vec![ - ( - 0u32.into(), - ParaGenesisArgs { - parachain: true, - genesis_head: dummy_head_data(), - validation_code: dummy_validation_code(), - }, - ), - ( - 1u32.into(), - ParaGenesisArgs { - parachain: false, - genesis_head: dummy_head_data(), - validation_code: dummy_validation_code(), - }, - ), - ]; - - let genesis_config = MockGenesisConfig { - paras: GenesisConfig { paras, ..Default::default() }, - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { code_retention_period, ..Default::default() }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - let id = ParaId::from(0u32); - let at_block: BlockNumber = 10; - let included_block: BlockNumber = 12; - let validation_code = ValidationCode(vec![4, 5, 6]); - - Paras::increase_code_ref(&validation_code.hash(), &validation_code); - ::PastCodeHash::insert(&(id, at_block), &validation_code.hash()); - ::PastCodePruning::put(&vec![(id, included_block)]); - - { - let mut code_meta = Paras::past_code_meta(&id); - code_meta.note_replacement(at_block, included_block); - ::PastCodeMeta::insert(&id, &code_meta); - } - - let pruned_at: BlockNumber = included_block + code_retention_period + 1; - assert_eq!( - ::PastCodeHash::get(&(id, at_block)), - Some(validation_code.hash()) - ); - check_code_is_stored(&validation_code); - - run_to_block(pruned_at - 1, None); - assert_eq!( - ::PastCodeHash::get(&(id, at_block)), - Some(validation_code.hash()) - ); - assert_eq!(Paras::past_code_meta(&id).most_recent_change(), Some(at_block)); - check_code_is_stored(&validation_code); - - run_to_block(pruned_at, None); - assert!(::PastCodeHash::get(&(id, at_block)).is_none()); - assert!(Paras::past_code_meta(&id).most_recent_change().is_none()); - check_code_is_not_stored(&validation_code); - }); - } - - #[test] - fn note_new_head_sets_head() { - let code_retention_period = 10; - let paras = vec![( - 0u32.into(), - ParaGenesisArgs { - parachain: true, - genesis_head: dummy_head_data(), - validation_code: dummy_validation_code(), - }, - )]; - - let genesis_config = MockGenesisConfig { - paras: GenesisConfig { paras, ..Default::default() }, - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { code_retention_period, ..Default::default() }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - let id_a = ParaId::from(0u32); - - assert_eq!(Paras::para_head(&id_a), Some(dummy_head_data())); - - Paras::note_new_head(id_a, vec![1, 2, 3].into(), 0); - - assert_eq!(Paras::para_head(&id_a), Some(vec![1, 2, 3].into())); - }); - } - - #[test] - fn note_past_code_sets_up_pruning_correctly() { - let code_retention_period = 10; - let paras = vec![ - ( - 0u32.into(), - ParaGenesisArgs { - parachain: true, - genesis_head: dummy_head_data(), - validation_code: dummy_validation_code(), - }, - ), - ( - 1u32.into(), - ParaGenesisArgs { - parachain: false, - genesis_head: dummy_head_data(), - validation_code: dummy_validation_code(), - }, - ), - ]; - - let genesis_config = MockGenesisConfig { - paras: GenesisConfig { paras, ..Default::default() }, - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { code_retention_period, ..Default::default() }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - let id_a = ParaId::from(0u32); - let id_b = ParaId::from(1u32); - - Paras::note_past_code(id_a, 10, 12, ValidationCode(vec![1, 2, 3]).hash()); - Paras::note_past_code(id_b, 20, 23, ValidationCode(vec![4, 5, 6]).hash()); - - assert_eq!(::PastCodePruning::get(), vec![(id_a, 12), (id_b, 23)]); - assert_eq!( - Paras::past_code_meta(&id_a), - ParaPastCodeMeta { upgrade_times: vec![upgrade_at(10, 12)], last_pruned: None } - ); - assert_eq!( - Paras::past_code_meta(&id_b), - ParaPastCodeMeta { upgrade_times: vec![upgrade_at(20, 23)], last_pruned: None } - ); - }); - } - - #[test] - fn code_upgrade_applied_after_delay() { - let code_retention_period = 10; - let validation_upgrade_delay = 5; - let validation_upgrade_cooldown = 10; - - let original_code = ValidationCode(vec![1, 2, 3]); - let paras = vec![( - 0u32.into(), - ParaGenesisArgs { - parachain: true, - genesis_head: dummy_head_data(), - validation_code: original_code.clone(), - }, - )]; - - let genesis_config = MockGenesisConfig { - paras: GenesisConfig { paras, ..Default::default() }, - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { - code_retention_period, - validation_upgrade_delay, - validation_upgrade_cooldown, - pvf_checking_enabled: false, - ..Default::default() - }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - check_code_is_stored(&original_code); - - let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); - - run_to_block(2, None); - assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); - - let expected_at = { - // this parablock is in the context of block 1. - let expected_at = 1 + validation_upgrade_delay; - let next_possible_upgrade_at = 1 + validation_upgrade_cooldown; - Paras::schedule_code_upgrade( - para_id, - new_code.clone(), - 1, - &Configuration::config(), - ); - Paras::note_new_head(para_id, Default::default(), 1); - - assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); - assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); - assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); - assert_eq!(::UpcomingUpgrades::get(), vec![(para_id, expected_at)]); - assert_eq!( - ::UpgradeCooldowns::get(), - vec![(para_id, next_possible_upgrade_at)] - ); - assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); - check_code_is_stored(&original_code); - check_code_is_stored(&new_code); - - expected_at - }; - - run_to_block(expected_at, None); - - // the candidate is in the context of the parent of `expected_at`, - // thus does not trigger the code upgrade. - { - Paras::note_new_head(para_id, Default::default(), expected_at - 1); - - assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); - assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); - assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); - assert_eq!( - ::UpgradeGoAheadSignal::get(¶_id), - Some(UpgradeGoAhead::GoAhead) - ); - assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); - check_code_is_stored(&original_code); - check_code_is_stored(&new_code); - } - - run_to_block(expected_at + 1, None); - - // the candidate is in the context of `expected_at`, and triggers - // the upgrade. - { - Paras::note_new_head(para_id, Default::default(), expected_at); - - assert_eq!(Paras::past_code_meta(¶_id).most_recent_change(), Some(expected_at)); - assert_eq!( - ::PastCodeHash::get(&(para_id, expected_at)), - Some(original_code.hash()), - ); - assert!(::FutureCodeUpgrades::get(¶_id).is_none()); - assert!(::FutureCodeHash::get(¶_id).is_none()); - assert!(::UpgradeGoAheadSignal::get(¶_id).is_none()); - assert_eq!(Paras::current_code(¶_id), Some(new_code.clone())); - check_code_is_stored(&original_code); - check_code_is_stored(&new_code); - } - }); - } - - #[test] - fn code_upgrade_applied_after_delay_even_when_late() { - let code_retention_period = 10; - let validation_upgrade_delay = 5; - let validation_upgrade_cooldown = 10; - - let original_code = ValidationCode(vec![1, 2, 3]); - let paras = vec![( - 0u32.into(), - ParaGenesisArgs { - parachain: true, - genesis_head: dummy_head_data(), - validation_code: original_code.clone(), - }, - )]; - - let genesis_config = MockGenesisConfig { - paras: GenesisConfig { paras, ..Default::default() }, - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { - code_retention_period, - validation_upgrade_delay, - validation_upgrade_cooldown, - pvf_checking_enabled: false, - ..Default::default() - }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); - - run_to_block(2, None); - assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); - - let expected_at = { - // this parablock is in the context of block 1. - let expected_at = 1 + validation_upgrade_delay; - let next_possible_upgrade_at = 1 + validation_upgrade_cooldown; - Paras::schedule_code_upgrade( - para_id, - new_code.clone(), - 1, - &Configuration::config(), - ); - Paras::note_new_head(para_id, Default::default(), 1); - - assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); - assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); - assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); - assert_eq!(::UpcomingUpgrades::get(), vec![(para_id, expected_at)]); - assert_eq!( - ::UpgradeCooldowns::get(), - vec![(para_id, next_possible_upgrade_at)] - ); - assert!(::UpgradeGoAheadSignal::get(¶_id).is_none()); - assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); - - expected_at - }; - - run_to_block(expected_at + 1 + 4, None); - - // the candidate is in the context of the first descendant of `expected_at`, and triggers - // the upgrade. - { - // The signal should be set to go-ahead until the new head is actually processed. - assert_eq!( - ::UpgradeGoAheadSignal::get(¶_id), - Some(UpgradeGoAhead::GoAhead), - ); - - Paras::note_new_head(para_id, Default::default(), expected_at + 4); - - assert_eq!(Paras::past_code_meta(¶_id).most_recent_change(), Some(expected_at)); - - assert_eq!( - ::PastCodeHash::get(&(para_id, expected_at)), - Some(original_code.hash()), - ); - assert!(::FutureCodeUpgrades::get(¶_id).is_none()); - assert!(::FutureCodeHash::get(¶_id).is_none()); - assert!(::UpgradeGoAheadSignal::get(¶_id).is_none()); - assert_eq!(Paras::current_code(¶_id), Some(new_code.clone())); - } - }); - } - - #[test] - fn submit_code_change_when_not_allowed_is_err() { - let code_retention_period = 10; - let validation_upgrade_delay = 7; - let validation_upgrade_cooldown = 100; - - let paras = vec![( - 0u32.into(), - ParaGenesisArgs { - parachain: true, - genesis_head: dummy_head_data(), - validation_code: vec![1, 2, 3].into(), - }, - )]; - - let genesis_config = MockGenesisConfig { - paras: GenesisConfig { paras, ..Default::default() }, - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { - code_retention_period, - validation_upgrade_delay, - validation_upgrade_cooldown, - pvf_checking_enabled: false, - ..Default::default() - }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); - let newer_code = ValidationCode(vec![4, 5, 6, 7]); - - run_to_block(1, None); - Paras::schedule_code_upgrade(para_id, new_code.clone(), 1, &Configuration::config()); - assert_eq!( - ::FutureCodeUpgrades::get(¶_id), - Some(1 + validation_upgrade_delay) - ); - assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); - check_code_is_stored(&new_code); - - // We expect that if an upgrade is signalled while there is already one pending we just - // ignore it. Note that this is only true from perspective of this module. - run_to_block(2, None); - assert!(!Paras::can_upgrade_validation_code(para_id)); - Paras::schedule_code_upgrade(para_id, newer_code.clone(), 2, &Configuration::config()); - assert_eq!( - ::FutureCodeUpgrades::get(¶_id), - Some(1 + validation_upgrade_delay), // did not change since the same assertion from the last time. - ); - assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); - check_code_is_not_stored(&newer_code); - }); - } - - #[test] - fn upgrade_restriction_elapsed_doesnt_mean_can_upgrade() { - // Situation: parachain scheduled upgrade but it doesn't produce any candidate after - // `expected_at`. When `validation_upgrade_cooldown` elapsed the parachain produces a - // candidate that tries to upgrade the code. - // - // In the current code this is not allowed: the upgrade should be consumed first. This is - // rather an artifact of the current implementation and not necessarily something we want - // to keep in the future. - // - // This test exists that this is not accidentially changed. - - let code_retention_period = 10; - let validation_upgrade_delay = 7; - let validation_upgrade_cooldown = 30; - - let paras = vec![( - 0u32.into(), - ParaGenesisArgs { - parachain: true, - genesis_head: dummy_head_data(), - validation_code: vec![1, 2, 3].into(), - }, - )]; - - let genesis_config = MockGenesisConfig { - paras: GenesisConfig { paras, ..Default::default() }, - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { - code_retention_period, - validation_upgrade_delay, - validation_upgrade_cooldown, - pvf_checking_enabled: false, - ..Default::default() - }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - let para_id = 0u32.into(); - let new_code = ValidationCode(vec![4, 5, 6]); - let newer_code = ValidationCode(vec![4, 5, 6, 7]); - - run_to_block(1, None); - Paras::schedule_code_upgrade(para_id, new_code.clone(), 0, &Configuration::config()); - Paras::note_new_head(para_id, dummy_head_data(), 0); - assert_eq!( - ::UpgradeRestrictionSignal::get(¶_id), - Some(UpgradeRestriction::Present), - ); - assert_eq!( - ::FutureCodeUpgrades::get(¶_id), - Some(0 + validation_upgrade_delay) - ); - assert!(!Paras::can_upgrade_validation_code(para_id)); - - run_to_block(31, None); - assert!(::UpgradeRestrictionSignal::get(¶_id).is_none()); - - // Note the para still cannot upgrade the validation code. - assert!(!Paras::can_upgrade_validation_code(para_id)); - - // And scheduling another upgrade does not do anything. `expected_at` is still the same. - Paras::schedule_code_upgrade(para_id, newer_code.clone(), 30, &Configuration::config()); - assert_eq!( - ::FutureCodeUpgrades::get(¶_id), - Some(0 + validation_upgrade_delay) - ); - }); - } - - #[test] - fn full_parachain_cleanup_storage() { - let code_retention_period = 20; - let validation_upgrade_delay = 1 + 5; - - let original_code = ValidationCode(vec![1, 2, 3]); - let paras = vec![( - 0u32.into(), - ParaGenesisArgs { - parachain: true, - genesis_head: dummy_head_data(), - validation_code: original_code.clone(), - }, - )]; - - let genesis_config = MockGenesisConfig { - paras: GenesisConfig { paras, ..Default::default() }, - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { - code_retention_period, - validation_upgrade_delay, - pvf_checking_enabled: false, - minimum_validation_upgrade_delay: 2, - // Those are not relevant to this test. However, HostConfiguration is still a - // subject for the consistency check. - chain_availability_period: 1, - thread_availability_period: 1, - ..Default::default() - }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - check_code_is_stored(&original_code); - - let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); - - run_to_block(2, None); - assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); - check_code_is_stored(&original_code); - - let expected_at = { - // this parablock is in the context of block 1. - let expected_at = 1 + validation_upgrade_delay; - Paras::schedule_code_upgrade( - para_id, - new_code.clone(), - 1, - &Configuration::config(), - ); - Paras::note_new_head(para_id, Default::default(), 1); - - assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); - assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); - assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); - assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); - check_code_is_stored(&original_code); - check_code_is_stored(&new_code); - - expected_at - }; - - // Cannot offboard while an upgrade is pending. - assert_err!(Paras::schedule_para_cleanup(para_id), Error::::CannotOffboard); - - // Enact the upgrade. - // - // For that run to block #7 and submit a new head. - assert_eq!(expected_at, 7); - run_to_block(7, None); - assert_eq!(>::block_number(), 7); - Paras::note_new_head(para_id, Default::default(), expected_at); - - assert_ok!(Paras::schedule_para_cleanup(para_id)); - - // run to block #10, with a 2 session changes at the end of the block 7 & 8 (so 8 and 9 - // observe the new sessions). - run_to_block(10, Some(vec![8, 9])); - - // cleaning up the parachain should place the current parachain code - // into the past code buffer & schedule cleanup. - // - // Why 7 and 8? See above, the clean up scheduled above was processed at the block 8. - // The initial upgrade was enacted at the block 7. - assert_eq!(Paras::past_code_meta(¶_id).most_recent_change(), Some(8)); - assert_eq!(::PastCodeHash::get(&(para_id, 8)), Some(new_code.hash())); - assert_eq!(::PastCodePruning::get(), vec![(para_id, 7), (para_id, 8)]); - check_code_is_stored(&original_code); - check_code_is_stored(&new_code); - - // any future upgrades haven't been used to validate yet, so those - // are cleaned up immediately. - assert!(::FutureCodeUpgrades::get(¶_id).is_none()); - assert!(::FutureCodeHash::get(¶_id).is_none()); - assert!(Paras::current_code(¶_id).is_none()); - - // run to do the final cleanup - let cleaned_up_at = 8 + code_retention_period + 1; - run_to_block(cleaned_up_at, None); - - // now the final cleanup: last past code cleaned up, and this triggers meta cleanup. - assert_eq!(Paras::past_code_meta(¶_id), Default::default()); - assert!(::PastCodeHash::get(&(para_id, 7)).is_none()); - assert!(::PastCodeHash::get(&(para_id, 8)).is_none()); - assert!(::PastCodePruning::get().is_empty()); - check_code_is_not_stored(&original_code); - check_code_is_not_stored(&new_code); - }); - } - - #[test] - fn para_incoming_at_session() { - let code_a = ValidationCode(vec![2]); - let code_b = ValidationCode(vec![1]); - let code_c = ValidationCode(vec![3]); - - let genesis_config = MockGenesisConfig { - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { pvf_checking_enabled: true, ..Default::default() }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - run_to_block(1, Some(vec![1])); - - let b = ParaId::from(525); - let a = ParaId::from(999); - let c = ParaId::from(333); - - assert_ok!(Paras::schedule_para_initialize( - b, - ParaGenesisArgs { - parachain: true, - genesis_head: vec![1].into(), - validation_code: code_b.clone(), - }, - )); - - assert_ok!(Paras::schedule_para_initialize( - a, - ParaGenesisArgs { - parachain: false, - genesis_head: vec![2].into(), - validation_code: code_a.clone(), - }, - )); - - assert_ok!(Paras::schedule_para_initialize( - c, - ParaGenesisArgs { - parachain: true, - genesis_head: vec![3].into(), - validation_code: code_c.clone(), - }, - )); - - IntoIterator::into_iter([0, 1, 2, 3]) - .map(|i| PvfCheckStatement { - accept: true, - subject: code_a.hash(), - session_index: 1, - validator_index: i.into(), - }) - .for_each(sign_and_include_pvf_check_statement); - - IntoIterator::into_iter([1, 2, 3, 4]) - .map(|i| PvfCheckStatement { - accept: true, - subject: code_b.hash(), - session_index: 1, - validator_index: i.into(), - }) - .for_each(sign_and_include_pvf_check_statement); - - IntoIterator::into_iter([0, 2, 3, 4]) - .map(|i| PvfCheckStatement { - accept: true, - subject: code_c.hash(), - session_index: 1, - validator_index: i.into(), - }) - .for_each(sign_and_include_pvf_check_statement); - - assert_eq!( - ::ActionsQueue::get(Paras::scheduled_session()), - vec![c, b, a], - ); - - // Lifecycle is tracked correctly - assert_eq!(::ParaLifecycles::get(&a), Some(ParaLifecycle::Onboarding)); - assert_eq!(::ParaLifecycles::get(&b), Some(ParaLifecycle::Onboarding)); - assert_eq!(::ParaLifecycles::get(&c), Some(ParaLifecycle::Onboarding)); - - // run to block without session change. - run_to_block(2, None); - - assert_eq!(Paras::parachains(), Vec::new()); - assert_eq!( - ::ActionsQueue::get(Paras::scheduled_session()), - vec![c, b, a], - ); - - // Lifecycle is tracked correctly - assert_eq!(::ParaLifecycles::get(&a), Some(ParaLifecycle::Onboarding)); - assert_eq!(::ParaLifecycles::get(&b), Some(ParaLifecycle::Onboarding)); - assert_eq!(::ParaLifecycles::get(&c), Some(ParaLifecycle::Onboarding)); - - // Two sessions pass, so action queue is triggered - run_to_block(4, Some(vec![3, 4])); - - assert_eq!(Paras::parachains(), vec![c, b]); - assert_eq!(::ActionsQueue::get(Paras::scheduled_session()), Vec::new()); - - // Lifecycle is tracked correctly - assert_eq!(::ParaLifecycles::get(&a), Some(ParaLifecycle::Parathread)); - assert_eq!(::ParaLifecycles::get(&b), Some(ParaLifecycle::Parachain)); - assert_eq!(::ParaLifecycles::get(&c), Some(ParaLifecycle::Parachain)); - - assert_eq!(Paras::current_code(&a), Some(vec![2].into())); - assert_eq!(Paras::current_code(&b), Some(vec![1].into())); - assert_eq!(Paras::current_code(&c), Some(vec![3].into())); - }) - } - - #[test] - fn code_hash_at_returns_up_to_end_of_code_retention_period() { - let code_retention_period = 10; - let validation_upgrade_delay = 2; - - let paras = vec![( - 0u32.into(), - ParaGenesisArgs { - parachain: true, - genesis_head: dummy_head_data(), - validation_code: vec![1, 2, 3].into(), - }, - )]; - - let genesis_config = MockGenesisConfig { - paras: GenesisConfig { paras, ..Default::default() }, - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { - code_retention_period, - validation_upgrade_delay, - pvf_checking_enabled: false, - ..Default::default() - }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - let para_id = ParaId::from(0); - let old_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![4, 5, 6].into(); - Paras::schedule_code_upgrade(para_id, new_code.clone(), 0, &Configuration::config()); - - // The new validation code can be applied but a new parablock hasn't gotten in yet, - // so the old code should still be current. - run_to_block(3, None); - assert_eq!(Paras::current_code(¶_id), Some(old_code.clone())); - - run_to_block(10, None); - Paras::note_new_head(para_id, Default::default(), 7); - - assert_eq!(Paras::past_code_meta(¶_id).upgrade_times, vec![upgrade_at(2, 10)]); - assert_eq!(Paras::current_code(¶_id), Some(new_code.clone())); - - // Make sure that the old code is available **before** the code retion period passes. - run_to_block(10 + code_retention_period, None); - assert_eq!(Paras::code_by_hash(&old_code.hash()), Some(old_code.clone())); - assert_eq!(Paras::code_by_hash(&new_code.hash()), Some(new_code.clone())); - - run_to_block(10 + code_retention_period + 1, None); - - // code entry should be pruned now. - - assert_eq!( - Paras::past_code_meta(¶_id), - ParaPastCodeMeta { upgrade_times: Vec::new(), last_pruned: Some(10) }, - ); - - assert_eq!(Paras::code_by_hash(&old_code.hash()), None); // pruned :( - assert_eq!(Paras::code_by_hash(&new_code.hash()), Some(new_code.clone())); - }); - } - - #[test] - fn code_ref_is_cleaned_correctly() { - new_test_ext(Default::default()).execute_with(|| { - let code: ValidationCode = vec![1, 2, 3].into(); - Paras::increase_code_ref(&code.hash(), &code); - Paras::increase_code_ref(&code.hash(), &code); - - assert!(::CodeByHash::contains_key(code.hash())); - assert_eq!(::CodeByHashRefs::get(code.hash()), 2); - - Paras::decrease_code_ref(&code.hash()); - - assert!(::CodeByHash::contains_key(code.hash())); - assert_eq!(::CodeByHashRefs::get(code.hash()), 1); - - Paras::decrease_code_ref(&code.hash()); - - assert!(!::CodeByHash::contains_key(code.hash())); - assert!(!::CodeByHashRefs::contains_key(code.hash())); - }); - } - - #[test] - fn pvf_check_coalescing_onboarding_and_upgrade() { - let validation_upgrade_delay = 5; - - let a = ParaId::from(111); - let b = ParaId::from(222); - let validation_code: ValidationCode = vec![3, 2, 1].into(); - - let paras = vec![( - a, - ParaGenesisArgs { - parachain: true, - genesis_head: Default::default(), - validation_code: ValidationCode(vec![]), // valid since in genesis - }, - )]; - - let genesis_config = MockGenesisConfig { - paras: GenesisConfig { paras, ..Default::default() }, - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { - pvf_checking_enabled: true, - validation_upgrade_delay, - ..Default::default() - }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - // At this point `a` is already onboarded. Run to block 1 performing session change at - // the end of block #0. - run_to_block(2, Some(vec![1])); - - // Expected current session index. - const EXPECTED_SESSION: SessionIndex = 1; - // Relay parent of the parablock that schedules the upgrade. - const RELAY_PARENT: BlockNumber = 1; - - // Now we register `b` with `validation_code` - assert_ok!(Paras::schedule_para_initialize( - b, - ParaGenesisArgs { - parachain: true, - genesis_head: vec![2].into(), - validation_code: validation_code.clone(), - }, - )); - - // And now at the same time upgrade `a` to `validation_code` - Paras::schedule_code_upgrade( - a, - validation_code.clone(), - RELAY_PARENT, - &Configuration::config(), - ); - assert!(!Paras::pvfs_require_precheck().is_empty()); - - // Supermajority of validators vote for `validation_code`. It should be approved. - IntoIterator::into_iter([0, 1, 2, 3]) - .map(|i| PvfCheckStatement { - accept: true, - subject: validation_code.hash(), - session_index: EXPECTED_SESSION, - validator_index: i.into(), - }) - .for_each(sign_and_include_pvf_check_statement); - - // Check that `b` actually onboards. - assert_eq!(::ActionsQueue::get(EXPECTED_SESSION + 2), vec![b]); - - // Check that the upgrade got scheduled. - assert_eq!( - ::FutureCodeUpgrades::get(&a), - Some(RELAY_PARENT + validation_upgrade_delay), - ); - }); - } - - #[test] - fn pvf_check_onboarding_reject_on_expiry() { - let pvf_voting_ttl = 2; - let a = ParaId::from(111); - let validation_code: ValidationCode = vec![3, 2, 1].into(); - - let genesis_config = MockGenesisConfig { - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { - pvf_checking_enabled: true, - pvf_voting_ttl, - ..Default::default() - }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - run_to_block(1, Some(vec![1])); - - assert_ok!(Paras::schedule_para_initialize( - a, - ParaGenesisArgs { - parachain: false, - genesis_head: vec![2].into(), - validation_code: validation_code.clone(), - }, - )); - - // Make sure that we kicked off the PVF vote for this validation code and that the - // validation code is stored. - assert!(::PvfActiveVoteMap::get(&validation_code.hash()).is_some()); - check_code_is_stored(&validation_code); - - // Skip 2 sessions (i.e. `pvf_voting_ttl`) verifying that the code is still stored in - // the intermediate session. - assert_eq!(pvf_voting_ttl, 2); - run_to_block(2, Some(vec![2])); - check_code_is_stored(&validation_code); - run_to_block(3, Some(vec![3])); - - // --- At this point the PVF vote for onboarding should be rejected. - - // Verify that the PVF is no longer stored and there is no active PVF vote. - check_code_is_not_stored(&validation_code); - assert!(::PvfActiveVoteMap::get(&validation_code.hash()).is_none()); - assert!(Paras::pvfs_require_precheck().is_empty()); - - // Verify that at this point we can again try to initialize the same para. - assert!(Paras::can_schedule_para_initialize(&a)); - }); - } - - #[test] - fn pvf_check_upgrade_reject() { - let a = ParaId::from(111); - let new_code: ValidationCode = vec![3, 2, 1].into(); - - let paras = vec![( - a, - ParaGenesisArgs { - parachain: false, - genesis_head: Default::default(), - validation_code: ValidationCode(vec![]), // valid since in genesis - }, - )]; - - let genesis_config = MockGenesisConfig { - paras: GenesisConfig { paras, ..Default::default() }, - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { pvf_checking_enabled: true, ..Default::default() }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - // At this point `a` is already onboarded. Run to block 1 performing session change at - // the end of block #0. - run_to_block(2, Some(vec![1])); - - // Relay parent of the block that schedules the upgrade. - const RELAY_PARENT: BlockNumber = 1; - // Expected current session index. - const EXPECTED_SESSION: SessionIndex = 1; - - Paras::schedule_code_upgrade( - a, - new_code.clone(), - RELAY_PARENT, - &Configuration::config(), - ); - check_code_is_stored(&new_code); - - // Supermajority of validators vote against `new_code`. PVF should be rejected. - IntoIterator::into_iter([0, 1, 2, 3]) - .map(|i| PvfCheckStatement { - accept: false, - subject: new_code.hash(), - session_index: EXPECTED_SESSION, - validator_index: i.into(), - }) - .for_each(sign_and_include_pvf_check_statement); - - // Verify that the new code is discarded. - check_code_is_not_stored(&new_code); - - assert!(::PvfActiveVoteMap::get(&new_code.hash()).is_none()); - assert!(Paras::pvfs_require_precheck().is_empty()); - assert!(::FutureCodeHash::get(&a).is_none()); - }); - } - - #[test] - fn pvf_check_submit_vote() { - let code_a: ValidationCode = vec![3, 2, 1].into(); - let code_b: ValidationCode = vec![1, 2, 3].into(); - - let check = |stmt: PvfCheckStatement| -> (Result<_, _>, Result<_, _>) { - let validators = &[ - Sr25519Keyring::Alice, - Sr25519Keyring::Bob, - Sr25519Keyring::Charlie, - Sr25519Keyring::Dave, - Sr25519Keyring::Ferdie, - Sr25519Keyring::Eve, // <- this validator is not in the set - ]; - let signature: ValidatorSignature = - validators[stmt.validator_index.0 as usize].sign(&stmt.signing_payload()).into(); - - let call = Call::include_pvf_check_statement { - stmt: stmt.clone(), - signature: signature.clone(), - }; - let validate_unsigned = - ::validate_unsigned(TransactionSource::InBlock, &call) - .map(|_| ()); - let dispatch_result = - Paras::include_pvf_check_statement(None.into(), stmt.clone(), signature.clone()); - - (validate_unsigned, dispatch_result) - }; - - let genesis_config = MockGenesisConfig { - configuration: crate::configuration::GenesisConfig { - config: HostConfiguration { pvf_checking_enabled: true, ..Default::default() }, - ..Default::default() - }, - ..Default::default() - }; - - new_test_ext(genesis_config).execute_with(|| { - // Important to run this to seed the validators. - run_to_block(1, Some(vec![1])); - - assert_ok!(Paras::schedule_para_initialize( - 1000.into(), - ParaGenesisArgs { - parachain: false, - genesis_head: vec![2].into(), - validation_code: code_a.clone(), - }, - )); - - assert_eq!( - check(PvfCheckStatement { - accept: false, - subject: code_a.hash(), - session_index: 1, - validator_index: 1.into(), - }), - (Ok(()), Ok(())), - ); - - // A vote in the same direction. - let (unsigned, dispatch) = check(PvfCheckStatement { - accept: false, - subject: code_a.hash(), - session_index: 1, - validator_index: 1.into(), - }); - assert_eq!(unsigned, Err(InvalidTransaction::Custom(INVALID_TX_DOUBLE_VOTE).into())); - assert_err!(dispatch, Error::::PvfCheckDoubleVote); - - // Equivocation - let (unsigned, dispatch) = check(PvfCheckStatement { - accept: true, - subject: code_a.hash(), - session_index: 1, - validator_index: 1.into(), - }); - assert_eq!(unsigned, Err(InvalidTransaction::Custom(INVALID_TX_DOUBLE_VOTE).into())); - assert_err!(dispatch, Error::::PvfCheckDoubleVote); - - // Vote for an earlier session. - let (unsigned, dispatch) = check(PvfCheckStatement { - accept: false, - subject: code_a.hash(), - session_index: 0, - validator_index: 1.into(), - }); - assert_eq!(unsigned, Err(InvalidTransaction::Stale.into())); - assert_err!(dispatch, Error::::PvfCheckStatementStale); - - // Vote for an later session. - let (unsigned, dispatch) = check(PvfCheckStatement { - accept: false, - subject: code_a.hash(), - session_index: 2, - validator_index: 1.into(), - }); - assert_eq!(unsigned, Err(InvalidTransaction::Future.into())); - assert_err!(dispatch, Error::::PvfCheckStatementFuture); - - // Validator not in the set. - let (unsigned, dispatch) = check(PvfCheckStatement { - accept: false, - subject: code_a.hash(), - session_index: 1, - validator_index: 5.into(), - }); - assert_eq!( - unsigned, - Err(InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into()) - ); - assert_err!(dispatch, Error::::PvfCheckValidatorIndexOutOfBounds); - - // Bad subject (code_b) - let (unsigned, dispatch) = check(PvfCheckStatement { - accept: false, - subject: code_b.hash(), - session_index: 1, - validator_index: 1.into(), - }); - assert_eq!(unsigned, Err(InvalidTransaction::Custom(INVALID_TX_BAD_SUBJECT).into())); - assert_err!(dispatch, Error::::PvfCheckSubjectInvalid); - }); - } - - #[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!(::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!(::CodeByHashRefs::get(&validation_code.hash()), 0); - assert!(!::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!(::CodeByHash::contains_key(code.hash())); - assert_eq!(::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!(::CodeByHash::contains_key(code.hash())); - assert_eq!(::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!( - ::FutureCodeUpgrades::get(¶_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!(::FutureCodeUpgrades::get(¶_id).is_none()); - assert!(::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!(::FutureCodeUpgrades::get(¶_id).is_some()); - assert!(!::PvfActiveVoteMap::contains_key(&validation_code.hash())); - }); - } - - #[test] - fn verify_upgrade_go_ahead_signal_is_externally_accessible() { - use primitives::v1::well_known_keys; - - let a = ParaId::from(2020); - - new_test_ext(Default::default()).execute_with(|| { - assert!(sp_io::storage::get(&well_known_keys::upgrade_go_ahead_signal(a)).is_none()); - ::UpgradeGoAheadSignal::insert(&a, UpgradeGoAhead::GoAhead); - assert_eq!( - sp_io::storage::get(&well_known_keys::upgrade_go_ahead_signal(a)).unwrap(), - vec![1u8], - ); - }); - } - - #[test] - fn verify_upgrade_restriction_signal_is_externally_accessible() { - use primitives::v1::well_known_keys; - - let a = ParaId::from(2020); - - new_test_ext(Default::default()).execute_with(|| { - assert!(sp_io::storage::get(&well_known_keys::upgrade_restriction_signal(a)).is_none()); - ::UpgradeRestrictionSignal::insert(&a, UpgradeRestriction::Present); - assert_eq!( - sp_io::storage::get(&well_known_keys::upgrade_restriction_signal(a)).unwrap(), - vec![0], - ); - }); - } -} diff --git a/runtime/parachains/src/paras/tests.rs b/runtime/parachains/src/paras/tests.rs new file mode 100644 index 000000000000..483bf63154c4 --- /dev/null +++ b/runtime/parachains/src/paras/tests.rs @@ -0,0 +1,1473 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use super::*; +use frame_support::{assert_err, assert_ok, assert_storage_noop}; +use keyring::Sr25519Keyring; +use primitives::{ + v0::PARACHAIN_KEY_TYPE_ID, + v1::{BlockNumber, ValidatorId}, +}; +use sc_keystore::LocalKeystore; +use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use std::sync::Arc; +use test_helpers::{dummy_head_data, dummy_validation_code}; + +use crate::{ + configuration::HostConfiguration, + mock::{ + new_test_ext, Configuration, MockGenesisConfig, Origin, Paras, ParasShared, System, Test, + }, +}; + +static VALIDATORS: &[Sr25519Keyring] = &[ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, +]; + +fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec { + val_ids.iter().map(|v| v.public().into()).collect() +} + +fn sign_and_include_pvf_check_statement(stmt: PvfCheckStatement) { + let validators = &[ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, + ]; + let signature = validators[stmt.validator_index.0 as usize].sign(&stmt.signing_payload()); + Paras::include_pvf_check_statement(None.into(), stmt, signature.into()).unwrap(); +} + +fn run_to_block(to: BlockNumber, new_session: Option>) { + let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); + for validator in VALIDATORS.iter() { + SyncCryptoStore::sr25519_generate_new( + &*keystore, + PARACHAIN_KEY_TYPE_ID, + Some(&validator.to_seed()), + ) + .unwrap(); + } + let validator_pubkeys = validator_pubkeys(VALIDATORS); + + while System::block_number() < to { + let b = System::block_number(); + Paras::initializer_finalize(b); + ParasShared::initializer_finalize(); + if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) { + let mut session_change_notification = SessionChangeNotification::default(); + session_change_notification.session_index = ParasShared::session_index() + 1; + session_change_notification.validators = validator_pubkeys.clone(); + ParasShared::initializer_on_new_session( + session_change_notification.session_index, + session_change_notification.random_seed, + &session_change_notification.new_config, + session_change_notification.validators.clone(), + ); + ParasShared::set_active_validators_ascending(validator_pubkeys.clone()); + Paras::initializer_on_new_session(&session_change_notification); + } + System::on_finalize(b); + + System::on_initialize(b + 1); + System::set_block_number(b + 1); + + ParasShared::initializer_initialize(b + 1); + Paras::initializer_initialize(b + 1); + } +} + +fn upgrade_at( + expected_at: BlockNumber, + activated_at: BlockNumber, +) -> ReplacementTimes { + ReplacementTimes { expected_at, activated_at } +} + +fn check_code_is_stored(validation_code: &ValidationCode) { + assert!(::CodeByHashRefs::get(validation_code.hash()) != 0); + assert!(::CodeByHash::contains_key(validation_code.hash())); +} + +fn check_code_is_not_stored(validation_code: &ValidationCode) { + assert!(!::CodeByHashRefs::contains_key(validation_code.hash())); + assert!(!::CodeByHash::contains_key(validation_code.hash())); +} + +#[test] +fn para_past_code_pruning_works_correctly() { + let mut past_code = ParaPastCodeMeta::default(); + past_code.note_replacement(10u32, 10); + past_code.note_replacement(20, 25); + past_code.note_replacement(30, 35); + + let old = past_code.clone(); + assert!(past_code.prune_up_to(9).collect::>().is_empty()); + assert_eq!(old, past_code); + + assert_eq!(past_code.prune_up_to(10).collect::>(), vec![10]); + assert_eq!( + past_code, + ParaPastCodeMeta { + upgrade_times: vec![upgrade_at(20, 25), upgrade_at(30, 35)], + last_pruned: Some(10), + } + ); + + assert!(past_code.prune_up_to(21).collect::>().is_empty()); + + assert_eq!(past_code.prune_up_to(26).collect::>(), vec![20]); + assert_eq!( + past_code, + ParaPastCodeMeta { upgrade_times: vec![upgrade_at(30, 35)], last_pruned: Some(25) } + ); + + past_code.note_replacement(40, 42); + past_code.note_replacement(50, 53); + past_code.note_replacement(60, 66); + + assert_eq!( + past_code, + ParaPastCodeMeta { + upgrade_times: vec![ + upgrade_at(30, 35), + upgrade_at(40, 42), + upgrade_at(50, 53), + upgrade_at(60, 66) + ], + last_pruned: Some(25), + } + ); + + assert_eq!(past_code.prune_up_to(60).collect::>(), vec![30, 40, 50]); + assert_eq!( + past_code, + ParaPastCodeMeta { upgrade_times: vec![upgrade_at(60, 66)], last_pruned: Some(53) } + ); + + assert_eq!(past_code.most_recent_change(), Some(60)); + assert_eq!(past_code.prune_up_to(66).collect::>(), vec![60]); + + assert_eq!(past_code, ParaPastCodeMeta { upgrade_times: Vec::new(), last_pruned: Some(66) }); +} + +#[test] +fn schedule_para_init_rejects_empty_code() { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + assert_err!( + Paras::schedule_para_initialize( + 1000.into(), + ParaGenesisArgs { + parachain: false, + genesis_head: dummy_head_data(), + validation_code: ValidationCode(vec![]), + } + ), + Error::::CannotOnboard, + ); + + assert_ok!(Paras::schedule_para_initialize( + 1000.into(), + ParaGenesisArgs { + parachain: false, + genesis_head: dummy_head_data(), + validation_code: ValidationCode(vec![1]), + } + )); + }); +} + +#[test] +fn para_past_code_pruning_in_initialize() { + let code_retention_period = 10; + let paras = vec![ + ( + 0u32.into(), + ParaGenesisArgs { + parachain: true, + genesis_head: dummy_head_data(), + validation_code: dummy_validation_code(), + }, + ), + ( + 1u32.into(), + ParaGenesisArgs { + parachain: false, + genesis_head: dummy_head_data(), + validation_code: dummy_validation_code(), + }, + ), + ]; + + let genesis_config = MockGenesisConfig { + paras: GenesisConfig { paras, ..Default::default() }, + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { code_retention_period, ..Default::default() }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + let id = ParaId::from(0u32); + let at_block: BlockNumber = 10; + let included_block: BlockNumber = 12; + let validation_code = ValidationCode(vec![4, 5, 6]); + + Paras::increase_code_ref(&validation_code.hash(), &validation_code); + ::PastCodeHash::insert(&(id, at_block), &validation_code.hash()); + ::PastCodePruning::put(&vec![(id, included_block)]); + + { + let mut code_meta = Paras::past_code_meta(&id); + code_meta.note_replacement(at_block, included_block); + ::PastCodeMeta::insert(&id, &code_meta); + } + + let pruned_at: BlockNumber = included_block + code_retention_period + 1; + assert_eq!( + ::PastCodeHash::get(&(id, at_block)), + Some(validation_code.hash()) + ); + check_code_is_stored(&validation_code); + + run_to_block(pruned_at - 1, None); + assert_eq!( + ::PastCodeHash::get(&(id, at_block)), + Some(validation_code.hash()) + ); + assert_eq!(Paras::past_code_meta(&id).most_recent_change(), Some(at_block)); + check_code_is_stored(&validation_code); + + run_to_block(pruned_at, None); + assert!(::PastCodeHash::get(&(id, at_block)).is_none()); + assert!(Paras::past_code_meta(&id).most_recent_change().is_none()); + check_code_is_not_stored(&validation_code); + }); +} + +#[test] +fn note_new_head_sets_head() { + let code_retention_period = 10; + let paras = vec![( + 0u32.into(), + ParaGenesisArgs { + parachain: true, + genesis_head: dummy_head_data(), + validation_code: dummy_validation_code(), + }, + )]; + + let genesis_config = MockGenesisConfig { + paras: GenesisConfig { paras, ..Default::default() }, + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { code_retention_period, ..Default::default() }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + let id_a = ParaId::from(0u32); + + assert_eq!(Paras::para_head(&id_a), Some(dummy_head_data())); + + Paras::note_new_head(id_a, vec![1, 2, 3].into(), 0); + + assert_eq!(Paras::para_head(&id_a), Some(vec![1, 2, 3].into())); + }); +} + +#[test] +fn note_past_code_sets_up_pruning_correctly() { + let code_retention_period = 10; + let paras = vec![ + ( + 0u32.into(), + ParaGenesisArgs { + parachain: true, + genesis_head: dummy_head_data(), + validation_code: dummy_validation_code(), + }, + ), + ( + 1u32.into(), + ParaGenesisArgs { + parachain: false, + genesis_head: dummy_head_data(), + validation_code: dummy_validation_code(), + }, + ), + ]; + + let genesis_config = MockGenesisConfig { + paras: GenesisConfig { paras, ..Default::default() }, + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { code_retention_period, ..Default::default() }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + let id_a = ParaId::from(0u32); + let id_b = ParaId::from(1u32); + + Paras::note_past_code(id_a, 10, 12, ValidationCode(vec![1, 2, 3]).hash()); + Paras::note_past_code(id_b, 20, 23, ValidationCode(vec![4, 5, 6]).hash()); + + assert_eq!(::PastCodePruning::get(), vec![(id_a, 12), (id_b, 23)]); + assert_eq!( + Paras::past_code_meta(&id_a), + ParaPastCodeMeta { upgrade_times: vec![upgrade_at(10, 12)], last_pruned: None } + ); + assert_eq!( + Paras::past_code_meta(&id_b), + ParaPastCodeMeta { upgrade_times: vec![upgrade_at(20, 23)], last_pruned: None } + ); + }); +} + +#[test] +fn code_upgrade_applied_after_delay() { + let code_retention_period = 10; + let validation_upgrade_delay = 5; + let validation_upgrade_cooldown = 10; + + let original_code = ValidationCode(vec![1, 2, 3]); + let paras = vec![( + 0u32.into(), + ParaGenesisArgs { + parachain: true, + genesis_head: dummy_head_data(), + validation_code: original_code.clone(), + }, + )]; + + let genesis_config = MockGenesisConfig { + paras: GenesisConfig { paras, ..Default::default() }, + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { + code_retention_period, + validation_upgrade_delay, + validation_upgrade_cooldown, + pvf_checking_enabled: false, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + check_code_is_stored(&original_code); + + let para_id = ParaId::from(0); + let new_code = ValidationCode(vec![4, 5, 6]); + + run_to_block(2, None); + assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); + + let expected_at = { + // this parablock is in the context of block 1. + let expected_at = 1 + validation_upgrade_delay; + let next_possible_upgrade_at = 1 + validation_upgrade_cooldown; + Paras::schedule_code_upgrade(para_id, new_code.clone(), 1, &Configuration::config()); + Paras::note_new_head(para_id, Default::default(), 1); + + assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); + assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); + assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); + assert_eq!(::UpcomingUpgrades::get(), vec![(para_id, expected_at)]); + assert_eq!( + ::UpgradeCooldowns::get(), + vec![(para_id, next_possible_upgrade_at)] + ); + assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); + check_code_is_stored(&original_code); + check_code_is_stored(&new_code); + + expected_at + }; + + run_to_block(expected_at, None); + + // the candidate is in the context of the parent of `expected_at`, + // thus does not trigger the code upgrade. + { + Paras::note_new_head(para_id, Default::default(), expected_at - 1); + + assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); + assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); + assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); + assert_eq!( + ::UpgradeGoAheadSignal::get(¶_id), + Some(UpgradeGoAhead::GoAhead) + ); + assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); + check_code_is_stored(&original_code); + check_code_is_stored(&new_code); + } + + run_to_block(expected_at + 1, None); + + // the candidate is in the context of `expected_at`, and triggers + // the upgrade. + { + Paras::note_new_head(para_id, Default::default(), expected_at); + + assert_eq!(Paras::past_code_meta(¶_id).most_recent_change(), Some(expected_at)); + assert_eq!( + ::PastCodeHash::get(&(para_id, expected_at)), + Some(original_code.hash()), + ); + assert!(::FutureCodeUpgrades::get(¶_id).is_none()); + assert!(::FutureCodeHash::get(¶_id).is_none()); + assert!(::UpgradeGoAheadSignal::get(¶_id).is_none()); + assert_eq!(Paras::current_code(¶_id), Some(new_code.clone())); + check_code_is_stored(&original_code); + check_code_is_stored(&new_code); + } + }); +} + +#[test] +fn code_upgrade_applied_after_delay_even_when_late() { + let code_retention_period = 10; + let validation_upgrade_delay = 5; + let validation_upgrade_cooldown = 10; + + let original_code = ValidationCode(vec![1, 2, 3]); + let paras = vec![( + 0u32.into(), + ParaGenesisArgs { + parachain: true, + genesis_head: dummy_head_data(), + validation_code: original_code.clone(), + }, + )]; + + let genesis_config = MockGenesisConfig { + paras: GenesisConfig { paras, ..Default::default() }, + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { + code_retention_period, + validation_upgrade_delay, + validation_upgrade_cooldown, + pvf_checking_enabled: false, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + let para_id = ParaId::from(0); + let new_code = ValidationCode(vec![4, 5, 6]); + + run_to_block(2, None); + assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); + + let expected_at = { + // this parablock is in the context of block 1. + let expected_at = 1 + validation_upgrade_delay; + let next_possible_upgrade_at = 1 + validation_upgrade_cooldown; + Paras::schedule_code_upgrade(para_id, new_code.clone(), 1, &Configuration::config()); + Paras::note_new_head(para_id, Default::default(), 1); + + assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); + assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); + assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); + assert_eq!(::UpcomingUpgrades::get(), vec![(para_id, expected_at)]); + assert_eq!( + ::UpgradeCooldowns::get(), + vec![(para_id, next_possible_upgrade_at)] + ); + assert!(::UpgradeGoAheadSignal::get(¶_id).is_none()); + assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); + + expected_at + }; + + run_to_block(expected_at + 1 + 4, None); + + // the candidate is in the context of the first descendant of `expected_at`, and triggers + // the upgrade. + { + // The signal should be set to go-ahead until the new head is actually processed. + assert_eq!( + ::UpgradeGoAheadSignal::get(¶_id), + Some(UpgradeGoAhead::GoAhead), + ); + + Paras::note_new_head(para_id, Default::default(), expected_at + 4); + + assert_eq!(Paras::past_code_meta(¶_id).most_recent_change(), Some(expected_at)); + + assert_eq!( + ::PastCodeHash::get(&(para_id, expected_at)), + Some(original_code.hash()), + ); + assert!(::FutureCodeUpgrades::get(¶_id).is_none()); + assert!(::FutureCodeHash::get(¶_id).is_none()); + assert!(::UpgradeGoAheadSignal::get(¶_id).is_none()); + assert_eq!(Paras::current_code(¶_id), Some(new_code.clone())); + } + }); +} + +#[test] +fn submit_code_change_when_not_allowed_is_err() { + let code_retention_period = 10; + let validation_upgrade_delay = 7; + let validation_upgrade_cooldown = 100; + + let paras = vec![( + 0u32.into(), + ParaGenesisArgs { + parachain: true, + genesis_head: dummy_head_data(), + validation_code: vec![1, 2, 3].into(), + }, + )]; + + let genesis_config = MockGenesisConfig { + paras: GenesisConfig { paras, ..Default::default() }, + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { + code_retention_period, + validation_upgrade_delay, + validation_upgrade_cooldown, + pvf_checking_enabled: false, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + let para_id = ParaId::from(0); + let new_code = ValidationCode(vec![4, 5, 6]); + let newer_code = ValidationCode(vec![4, 5, 6, 7]); + + run_to_block(1, None); + Paras::schedule_code_upgrade(para_id, new_code.clone(), 1, &Configuration::config()); + assert_eq!( + ::FutureCodeUpgrades::get(¶_id), + Some(1 + validation_upgrade_delay) + ); + assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); + check_code_is_stored(&new_code); + + // We expect that if an upgrade is signalled while there is already one pending we just + // ignore it. Note that this is only true from perspective of this module. + run_to_block(2, None); + assert!(!Paras::can_upgrade_validation_code(para_id)); + Paras::schedule_code_upgrade(para_id, newer_code.clone(), 2, &Configuration::config()); + assert_eq!( + ::FutureCodeUpgrades::get(¶_id), + Some(1 + validation_upgrade_delay), // did not change since the same assertion from the last time. + ); + assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); + check_code_is_not_stored(&newer_code); + }); +} + +#[test] +fn upgrade_restriction_elapsed_doesnt_mean_can_upgrade() { + // Situation: parachain scheduled upgrade but it doesn't produce any candidate after + // `expected_at`. When `validation_upgrade_cooldown` elapsed the parachain produces a + // candidate that tries to upgrade the code. + // + // In the current code this is not allowed: the upgrade should be consumed first. This is + // rather an artifact of the current implementation and not necessarily something we want + // to keep in the future. + // + // This test exists that this is not accidentially changed. + + let code_retention_period = 10; + let validation_upgrade_delay = 7; + let validation_upgrade_cooldown = 30; + + let paras = vec![( + 0u32.into(), + ParaGenesisArgs { + parachain: true, + genesis_head: dummy_head_data(), + validation_code: vec![1, 2, 3].into(), + }, + )]; + + let genesis_config = MockGenesisConfig { + paras: GenesisConfig { paras, ..Default::default() }, + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { + code_retention_period, + validation_upgrade_delay, + validation_upgrade_cooldown, + pvf_checking_enabled: false, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + let para_id = 0u32.into(); + let new_code = ValidationCode(vec![4, 5, 6]); + let newer_code = ValidationCode(vec![4, 5, 6, 7]); + + run_to_block(1, None); + Paras::schedule_code_upgrade(para_id, new_code.clone(), 0, &Configuration::config()); + Paras::note_new_head(para_id, dummy_head_data(), 0); + assert_eq!( + ::UpgradeRestrictionSignal::get(¶_id), + Some(UpgradeRestriction::Present), + ); + assert_eq!( + ::FutureCodeUpgrades::get(¶_id), + Some(0 + validation_upgrade_delay) + ); + assert!(!Paras::can_upgrade_validation_code(para_id)); + + run_to_block(31, None); + assert!(::UpgradeRestrictionSignal::get(¶_id).is_none()); + + // Note the para still cannot upgrade the validation code. + assert!(!Paras::can_upgrade_validation_code(para_id)); + + // And scheduling another upgrade does not do anything. `expected_at` is still the same. + Paras::schedule_code_upgrade(para_id, newer_code.clone(), 30, &Configuration::config()); + assert_eq!( + ::FutureCodeUpgrades::get(¶_id), + Some(0 + validation_upgrade_delay) + ); + }); +} + +#[test] +fn full_parachain_cleanup_storage() { + let code_retention_period = 20; + let validation_upgrade_delay = 1 + 5; + + let original_code = ValidationCode(vec![1, 2, 3]); + let paras = vec![( + 0u32.into(), + ParaGenesisArgs { + parachain: true, + genesis_head: dummy_head_data(), + validation_code: original_code.clone(), + }, + )]; + + let genesis_config = MockGenesisConfig { + paras: GenesisConfig { paras, ..Default::default() }, + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { + code_retention_period, + validation_upgrade_delay, + pvf_checking_enabled: false, + minimum_validation_upgrade_delay: 2, + // Those are not relevant to this test. However, HostConfiguration is still a + // subject for the consistency check. + chain_availability_period: 1, + thread_availability_period: 1, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + check_code_is_stored(&original_code); + + let para_id = ParaId::from(0); + let new_code = ValidationCode(vec![4, 5, 6]); + + run_to_block(2, None); + assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); + check_code_is_stored(&original_code); + + let expected_at = { + // this parablock is in the context of block 1. + let expected_at = 1 + validation_upgrade_delay; + Paras::schedule_code_upgrade(para_id, new_code.clone(), 1, &Configuration::config()); + Paras::note_new_head(para_id, Default::default(), 1); + + assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); + assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); + assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); + assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); + check_code_is_stored(&original_code); + check_code_is_stored(&new_code); + + expected_at + }; + + // Cannot offboard while an upgrade is pending. + assert_err!(Paras::schedule_para_cleanup(para_id), Error::::CannotOffboard); + + // Enact the upgrade. + // + // For that run to block #7 and submit a new head. + assert_eq!(expected_at, 7); + run_to_block(7, None); + assert_eq!(>::block_number(), 7); + Paras::note_new_head(para_id, Default::default(), expected_at); + + assert_ok!(Paras::schedule_para_cleanup(para_id)); + + // run to block #10, with a 2 session changes at the end of the block 7 & 8 (so 8 and 9 + // observe the new sessions). + run_to_block(10, Some(vec![8, 9])); + + // cleaning up the parachain should place the current parachain code + // into the past code buffer & schedule cleanup. + // + // Why 7 and 8? See above, the clean up scheduled above was processed at the block 8. + // The initial upgrade was enacted at the block 7. + assert_eq!(Paras::past_code_meta(¶_id).most_recent_change(), Some(8)); + assert_eq!(::PastCodeHash::get(&(para_id, 8)), Some(new_code.hash())); + assert_eq!(::PastCodePruning::get(), vec![(para_id, 7), (para_id, 8)]); + check_code_is_stored(&original_code); + check_code_is_stored(&new_code); + + // any future upgrades haven't been used to validate yet, so those + // are cleaned up immediately. + assert!(::FutureCodeUpgrades::get(¶_id).is_none()); + assert!(::FutureCodeHash::get(¶_id).is_none()); + assert!(Paras::current_code(¶_id).is_none()); + + // run to do the final cleanup + let cleaned_up_at = 8 + code_retention_period + 1; + run_to_block(cleaned_up_at, None); + + // now the final cleanup: last past code cleaned up, and this triggers meta cleanup. + assert_eq!(Paras::past_code_meta(¶_id), Default::default()); + assert!(::PastCodeHash::get(&(para_id, 7)).is_none()); + assert!(::PastCodeHash::get(&(para_id, 8)).is_none()); + assert!(::PastCodePruning::get().is_empty()); + check_code_is_not_stored(&original_code); + check_code_is_not_stored(&new_code); + }); +} + +#[test] +fn para_incoming_at_session() { + let code_a = ValidationCode(vec![2]); + let code_b = ValidationCode(vec![1]); + let code_c = ValidationCode(vec![3]); + + let genesis_config = MockGenesisConfig { + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { pvf_checking_enabled: true, ..Default::default() }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + run_to_block(1, Some(vec![1])); + + let b = ParaId::from(525); + let a = ParaId::from(999); + let c = ParaId::from(333); + + assert_ok!(Paras::schedule_para_initialize( + b, + ParaGenesisArgs { + parachain: true, + genesis_head: vec![1].into(), + validation_code: code_b.clone(), + }, + )); + + assert_ok!(Paras::schedule_para_initialize( + a, + ParaGenesisArgs { + parachain: false, + genesis_head: vec![2].into(), + validation_code: code_a.clone(), + }, + )); + + assert_ok!(Paras::schedule_para_initialize( + c, + ParaGenesisArgs { + parachain: true, + genesis_head: vec![3].into(), + validation_code: code_c.clone(), + }, + )); + + IntoIterator::into_iter([0, 1, 2, 3]) + .map(|i| PvfCheckStatement { + accept: true, + subject: code_a.hash(), + session_index: 1, + validator_index: i.into(), + }) + .for_each(sign_and_include_pvf_check_statement); + + IntoIterator::into_iter([1, 2, 3, 4]) + .map(|i| PvfCheckStatement { + accept: true, + subject: code_b.hash(), + session_index: 1, + validator_index: i.into(), + }) + .for_each(sign_and_include_pvf_check_statement); + + IntoIterator::into_iter([0, 2, 3, 4]) + .map(|i| PvfCheckStatement { + accept: true, + subject: code_c.hash(), + session_index: 1, + validator_index: i.into(), + }) + .for_each(sign_and_include_pvf_check_statement); + + assert_eq!(::ActionsQueue::get(Paras::scheduled_session()), vec![c, b, a],); + + // Lifecycle is tracked correctly + assert_eq!(::ParaLifecycles::get(&a), Some(ParaLifecycle::Onboarding)); + assert_eq!(::ParaLifecycles::get(&b), Some(ParaLifecycle::Onboarding)); + assert_eq!(::ParaLifecycles::get(&c), Some(ParaLifecycle::Onboarding)); + + // run to block without session change. + run_to_block(2, None); + + assert_eq!(Paras::parachains(), Vec::new()); + assert_eq!(::ActionsQueue::get(Paras::scheduled_session()), vec![c, b, a],); + + // Lifecycle is tracked correctly + assert_eq!(::ParaLifecycles::get(&a), Some(ParaLifecycle::Onboarding)); + assert_eq!(::ParaLifecycles::get(&b), Some(ParaLifecycle::Onboarding)); + assert_eq!(::ParaLifecycles::get(&c), Some(ParaLifecycle::Onboarding)); + + // Two sessions pass, so action queue is triggered + run_to_block(4, Some(vec![3, 4])); + + assert_eq!(Paras::parachains(), vec![c, b]); + assert_eq!(::ActionsQueue::get(Paras::scheduled_session()), Vec::new()); + + // Lifecycle is tracked correctly + assert_eq!(::ParaLifecycles::get(&a), Some(ParaLifecycle::Parathread)); + assert_eq!(::ParaLifecycles::get(&b), Some(ParaLifecycle::Parachain)); + assert_eq!(::ParaLifecycles::get(&c), Some(ParaLifecycle::Parachain)); + + assert_eq!(Paras::current_code(&a), Some(vec![2].into())); + assert_eq!(Paras::current_code(&b), Some(vec![1].into())); + assert_eq!(Paras::current_code(&c), Some(vec![3].into())); + }) +} + +#[test] +fn code_hash_at_returns_up_to_end_of_code_retention_period() { + let code_retention_period = 10; + let validation_upgrade_delay = 2; + + let paras = vec![( + 0u32.into(), + ParaGenesisArgs { + parachain: true, + genesis_head: dummy_head_data(), + validation_code: vec![1, 2, 3].into(), + }, + )]; + + let genesis_config = MockGenesisConfig { + paras: GenesisConfig { paras, ..Default::default() }, + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { + code_retention_period, + validation_upgrade_delay, + pvf_checking_enabled: false, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + let para_id = ParaId::from(0); + let old_code: ValidationCode = vec![1, 2, 3].into(); + let new_code: ValidationCode = vec![4, 5, 6].into(); + Paras::schedule_code_upgrade(para_id, new_code.clone(), 0, &Configuration::config()); + + // The new validation code can be applied but a new parablock hasn't gotten in yet, + // so the old code should still be current. + run_to_block(3, None); + assert_eq!(Paras::current_code(¶_id), Some(old_code.clone())); + + run_to_block(10, None); + Paras::note_new_head(para_id, Default::default(), 7); + + assert_eq!(Paras::past_code_meta(¶_id).upgrade_times, vec![upgrade_at(2, 10)]); + assert_eq!(Paras::current_code(¶_id), Some(new_code.clone())); + + // Make sure that the old code is available **before** the code retion period passes. + run_to_block(10 + code_retention_period, None); + assert_eq!(Paras::code_by_hash(&old_code.hash()), Some(old_code.clone())); + assert_eq!(Paras::code_by_hash(&new_code.hash()), Some(new_code.clone())); + + run_to_block(10 + code_retention_period + 1, None); + + // code entry should be pruned now. + + assert_eq!( + Paras::past_code_meta(¶_id), + ParaPastCodeMeta { upgrade_times: Vec::new(), last_pruned: Some(10) }, + ); + + assert_eq!(Paras::code_by_hash(&old_code.hash()), None); // pruned :( + assert_eq!(Paras::code_by_hash(&new_code.hash()), Some(new_code.clone())); + }); +} + +#[test] +fn code_ref_is_cleaned_correctly() { + new_test_ext(Default::default()).execute_with(|| { + let code: ValidationCode = vec![1, 2, 3].into(); + Paras::increase_code_ref(&code.hash(), &code); + Paras::increase_code_ref(&code.hash(), &code); + + assert!(::CodeByHash::contains_key(code.hash())); + assert_eq!(::CodeByHashRefs::get(code.hash()), 2); + + Paras::decrease_code_ref(&code.hash()); + + assert!(::CodeByHash::contains_key(code.hash())); + assert_eq!(::CodeByHashRefs::get(code.hash()), 1); + + Paras::decrease_code_ref(&code.hash()); + + assert!(!::CodeByHash::contains_key(code.hash())); + assert!(!::CodeByHashRefs::contains_key(code.hash())); + }); +} + +#[test] +fn pvf_check_coalescing_onboarding_and_upgrade() { + let validation_upgrade_delay = 5; + + let a = ParaId::from(111); + let b = ParaId::from(222); + let validation_code: ValidationCode = vec![3, 2, 1].into(); + + let paras = vec![( + a, + ParaGenesisArgs { + parachain: true, + genesis_head: Default::default(), + validation_code: ValidationCode(vec![]), // valid since in genesis + }, + )]; + + let genesis_config = MockGenesisConfig { + paras: GenesisConfig { paras, ..Default::default() }, + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { + pvf_checking_enabled: true, + validation_upgrade_delay, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + // At this point `a` is already onboarded. Run to block 1 performing session change at + // the end of block #0. + run_to_block(2, Some(vec![1])); + + // Expected current session index. + const EXPECTED_SESSION: SessionIndex = 1; + // Relay parent of the parablock that schedules the upgrade. + const RELAY_PARENT: BlockNumber = 1; + + // Now we register `b` with `validation_code` + assert_ok!(Paras::schedule_para_initialize( + b, + ParaGenesisArgs { + parachain: true, + genesis_head: vec![2].into(), + validation_code: validation_code.clone(), + }, + )); + + // And now at the same time upgrade `a` to `validation_code` + Paras::schedule_code_upgrade( + a, + validation_code.clone(), + RELAY_PARENT, + &Configuration::config(), + ); + assert!(!Paras::pvfs_require_precheck().is_empty()); + + // Supermajority of validators vote for `validation_code`. It should be approved. + IntoIterator::into_iter([0, 1, 2, 3]) + .map(|i| PvfCheckStatement { + accept: true, + subject: validation_code.hash(), + session_index: EXPECTED_SESSION, + validator_index: i.into(), + }) + .for_each(sign_and_include_pvf_check_statement); + + // Check that `b` actually onboards. + assert_eq!(::ActionsQueue::get(EXPECTED_SESSION + 2), vec![b]); + + // Check that the upgrade got scheduled. + assert_eq!( + ::FutureCodeUpgrades::get(&a), + Some(RELAY_PARENT + validation_upgrade_delay), + ); + }); +} + +#[test] +fn pvf_check_onboarding_reject_on_expiry() { + let pvf_voting_ttl = 2; + let a = ParaId::from(111); + let validation_code: ValidationCode = vec![3, 2, 1].into(); + + let genesis_config = MockGenesisConfig { + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { + pvf_checking_enabled: true, + pvf_voting_ttl, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + run_to_block(1, Some(vec![1])); + + assert_ok!(Paras::schedule_para_initialize( + a, + ParaGenesisArgs { + parachain: false, + genesis_head: vec![2].into(), + validation_code: validation_code.clone(), + }, + )); + + // Make sure that we kicked off the PVF vote for this validation code and that the + // validation code is stored. + assert!(::PvfActiveVoteMap::get(&validation_code.hash()).is_some()); + check_code_is_stored(&validation_code); + + // Skip 2 sessions (i.e. `pvf_voting_ttl`) verifying that the code is still stored in + // the intermediate session. + assert_eq!(pvf_voting_ttl, 2); + run_to_block(2, Some(vec![2])); + check_code_is_stored(&validation_code); + run_to_block(3, Some(vec![3])); + + // --- At this point the PVF vote for onboarding should be rejected. + + // Verify that the PVF is no longer stored and there is no active PVF vote. + check_code_is_not_stored(&validation_code); + assert!(::PvfActiveVoteMap::get(&validation_code.hash()).is_none()); + assert!(Paras::pvfs_require_precheck().is_empty()); + + // Verify that at this point we can again try to initialize the same para. + assert!(Paras::can_schedule_para_initialize(&a)); + }); +} + +#[test] +fn pvf_check_upgrade_reject() { + let a = ParaId::from(111); + let new_code: ValidationCode = vec![3, 2, 1].into(); + + let paras = vec![( + a, + ParaGenesisArgs { + parachain: false, + genesis_head: Default::default(), + validation_code: ValidationCode(vec![]), // valid since in genesis + }, + )]; + + let genesis_config = MockGenesisConfig { + paras: GenesisConfig { paras, ..Default::default() }, + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { pvf_checking_enabled: true, ..Default::default() }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + // At this point `a` is already onboarded. Run to block 1 performing session change at + // the end of block #0. + run_to_block(2, Some(vec![1])); + + // Relay parent of the block that schedules the upgrade. + const RELAY_PARENT: BlockNumber = 1; + // Expected current session index. + const EXPECTED_SESSION: SessionIndex = 1; + + Paras::schedule_code_upgrade(a, new_code.clone(), RELAY_PARENT, &Configuration::config()); + check_code_is_stored(&new_code); + + // Supermajority of validators vote against `new_code`. PVF should be rejected. + IntoIterator::into_iter([0, 1, 2, 3]) + .map(|i| PvfCheckStatement { + accept: false, + subject: new_code.hash(), + session_index: EXPECTED_SESSION, + validator_index: i.into(), + }) + .for_each(sign_and_include_pvf_check_statement); + + // Verify that the new code is discarded. + check_code_is_not_stored(&new_code); + + assert!(::PvfActiveVoteMap::get(&new_code.hash()).is_none()); + assert!(Paras::pvfs_require_precheck().is_empty()); + assert!(::FutureCodeHash::get(&a).is_none()); + }); +} + +#[test] +fn pvf_check_submit_vote() { + let code_a: ValidationCode = vec![3, 2, 1].into(); + let code_b: ValidationCode = vec![1, 2, 3].into(); + + let check = |stmt: PvfCheckStatement| -> (Result<_, _>, Result<_, _>) { + let validators = &[ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, + Sr25519Keyring::Eve, // <- this validator is not in the set + ]; + let signature: ValidatorSignature = + validators[stmt.validator_index.0 as usize].sign(&stmt.signing_payload()).into(); + + let call = + Call::include_pvf_check_statement { stmt: stmt.clone(), signature: signature.clone() }; + let validate_unsigned = + ::validate_unsigned(TransactionSource::InBlock, &call) + .map(|_| ()); + let dispatch_result = + Paras::include_pvf_check_statement(None.into(), stmt.clone(), signature.clone()); + + (validate_unsigned, dispatch_result) + }; + + let genesis_config = MockGenesisConfig { + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { pvf_checking_enabled: true, ..Default::default() }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + // Important to run this to seed the validators. + run_to_block(1, Some(vec![1])); + + assert_ok!(Paras::schedule_para_initialize( + 1000.into(), + ParaGenesisArgs { + parachain: false, + genesis_head: vec![2].into(), + validation_code: code_a.clone(), + }, + )); + + assert_eq!( + check(PvfCheckStatement { + accept: false, + subject: code_a.hash(), + session_index: 1, + validator_index: 1.into(), + }), + (Ok(()), Ok(())), + ); + + // A vote in the same direction. + let (unsigned, dispatch) = check(PvfCheckStatement { + accept: false, + subject: code_a.hash(), + session_index: 1, + validator_index: 1.into(), + }); + assert_eq!(unsigned, Err(InvalidTransaction::Custom(INVALID_TX_DOUBLE_VOTE).into())); + assert_err!(dispatch, Error::::PvfCheckDoubleVote); + + // Equivocation + let (unsigned, dispatch) = check(PvfCheckStatement { + accept: true, + subject: code_a.hash(), + session_index: 1, + validator_index: 1.into(), + }); + assert_eq!(unsigned, Err(InvalidTransaction::Custom(INVALID_TX_DOUBLE_VOTE).into())); + assert_err!(dispatch, Error::::PvfCheckDoubleVote); + + // Vote for an earlier session. + let (unsigned, dispatch) = check(PvfCheckStatement { + accept: false, + subject: code_a.hash(), + session_index: 0, + validator_index: 1.into(), + }); + assert_eq!(unsigned, Err(InvalidTransaction::Stale.into())); + assert_err!(dispatch, Error::::PvfCheckStatementStale); + + // Vote for an later session. + let (unsigned, dispatch) = check(PvfCheckStatement { + accept: false, + subject: code_a.hash(), + session_index: 2, + validator_index: 1.into(), + }); + assert_eq!(unsigned, Err(InvalidTransaction::Future.into())); + assert_err!(dispatch, Error::::PvfCheckStatementFuture); + + // Validator not in the set. + let (unsigned, dispatch) = check(PvfCheckStatement { + accept: false, + subject: code_a.hash(), + session_index: 1, + validator_index: 5.into(), + }); + assert_eq!(unsigned, Err(InvalidTransaction::Custom(INVALID_TX_BAD_VALIDATOR_IDX).into())); + assert_err!(dispatch, Error::::PvfCheckValidatorIndexOutOfBounds); + + // Bad subject (code_b) + let (unsigned, dispatch) = check(PvfCheckStatement { + accept: false, + subject: code_b.hash(), + session_index: 1, + validator_index: 1.into(), + }); + assert_eq!(unsigned, Err(InvalidTransaction::Custom(INVALID_TX_BAD_SUBJECT).into())); + assert_err!(dispatch, Error::::PvfCheckSubjectInvalid); + }); +} + +#[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!(::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!(::CodeByHashRefs::get(&validation_code.hash()), 0); + assert!(!::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!(::CodeByHash::contains_key(code.hash())); + assert_eq!(::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!(::CodeByHash::contains_key(code.hash())); + assert_eq!(::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!( + ::FutureCodeUpgrades::get(¶_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!(::FutureCodeUpgrades::get(¶_id).is_none()); + assert!(::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!(::FutureCodeUpgrades::get(¶_id).is_some()); + assert!(!::PvfActiveVoteMap::contains_key(&validation_code.hash())); + }); +} + +#[test] +fn verify_upgrade_go_ahead_signal_is_externally_accessible() { + use primitives::v1::well_known_keys; + + let a = ParaId::from(2020); + + new_test_ext(Default::default()).execute_with(|| { + assert!(sp_io::storage::get(&well_known_keys::upgrade_go_ahead_signal(a)).is_none()); + ::UpgradeGoAheadSignal::insert(&a, UpgradeGoAhead::GoAhead); + assert_eq!( + sp_io::storage::get(&well_known_keys::upgrade_go_ahead_signal(a)).unwrap(), + vec![1u8], + ); + }); +} + +#[test] +fn verify_upgrade_restriction_signal_is_externally_accessible() { + use primitives::v1::well_known_keys; + + let a = ParaId::from(2020); + + new_test_ext(Default::default()).execute_with(|| { + assert!(sp_io::storage::get(&well_known_keys::upgrade_restriction_signal(a)).is_none()); + ::UpgradeRestrictionSignal::insert(&a, UpgradeRestriction::Present); + assert_eq!( + sp_io::storage::get(&well_known_keys::upgrade_restriction_signal(a)).unwrap(), + vec![0], + ); + }); +}