Skip to content
Snippets Groups Projects
tests.rs 67.4 KiB
Newer Older
// Copyright (C) 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 <http://www.gnu.org/licenses/>.

use super::*;
use frame_support::{assert_err, assert_ok, assert_storage_noop};
use polkadot_primitives::{BlockNumber, SchedulerParams, PARACHAIN_KEY_TYPE_ID};
use polkadot_primitives_test_helpers::{dummy_head_data, dummy_validation_code, validator_pubkeys};
use sc_keystore::LocalKeystore;
use sp_keyring::Sr25519Keyring;
use sp_keystore::{Keystore, KeystorePtr};
use std::sync::Arc;

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

static VALIDATORS: &[Sr25519Keyring] = &[
	Sr25519Keyring::Alice,
	Sr25519Keyring::Bob,
	Sr25519Keyring::Charlie,
	Sr25519Keyring::Dave,
	Sr25519Keyring::Ferdie,
];

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 submit_super_majority_pvf_votes(
	validation_code: &ValidationCode,
	session_index: SessionIndex,
	accept: bool,
) {
	[0, 1, 2, 3]
		.into_iter()
		.map(|i| PvfCheckStatement {
			accept,
			subject: validation_code.hash(),
			session_index,
			validator_index: i.into(),
		})
		.for_each(sign_and_include_pvf_check_statement);
}

fn test_validation_code_1() -> ValidationCode {
	let validation_code = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
	ValidationCode(validation_code)
}

fn test_validation_code_2() -> ValidationCode {
	let validation_code = vec![9, 8, 7, 6, 5, 4, 3, 2, 1];
	ValidationCode(validation_code)
}

fn run_to_block(to: BlockNumber, new_session: Option<Vec<BlockNumber>>) {
	let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory());
	for validator in VALIDATORS.iter() {
		Keystore::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 =
				shared::CurrentSessionIndex::<Test>::get() + 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<BlockNumber> {
	ReplacementTimes { expected_at, activated_at }
}

fn check_code_is_stored(validation_code: &ValidationCode) {
	assert!(CodeByHashRefs::<Test>::get(validation_code.hash()) != 0);
	assert!(CodeByHash::<Test>::contains_key(validation_code.hash()));
}

fn check_code_is_not_stored(validation_code: &ValidationCode) {
	assert!(!CodeByHashRefs::<Test>::contains_key(validation_code.hash()));
	assert!(!CodeByHash::<Test>::contains_key(validation_code.hash()));
/// An utility for checking that certain events were deposited.
struct EventValidator {
	events: Vec<
		frame_system::EventRecord<
			<Test as frame_system::Config>::RuntimeEvent,
			polkadot_primitives::Hash,
		>,
}

impl EventValidator {
	fn new() -> Self {
		Self { events: Vec::new() }
	}

	fn started(&mut self, code: &ValidationCode, id: ParaId) -> &mut Self {
		self.events.push(frame_system::EventRecord {
			phase: frame_system::Phase::Initialization,
			event: Event::PvfCheckStarted(code.hash(), id).into(),
			topics: vec![],
		});
		self
	}

	fn rejected(&mut self, code: &ValidationCode, id: ParaId) -> &mut Self {
		self.events.push(frame_system::EventRecord {
			phase: frame_system::Phase::Initialization,
			event: Event::PvfCheckRejected(code.hash(), id).into(),
			topics: vec![],
		});
		self
	}

	fn accepted(&mut self, code: &ValidationCode, id: ParaId) -> &mut Self {
		self.events.push(frame_system::EventRecord {
			phase: frame_system::Phase::Initialization,
			event: Event::PvfCheckAccepted(code.hash(), id).into(),
			topics: vec![],
		});
		self
	}

	fn check(&self) {
		assert_eq!(&frame_system::Pallet::<Test>::events(), &self.events);
	}
}

#[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::<Vec<_>>().is_empty());
	assert_eq!(old, past_code);

	assert_eq!(past_code.prune_up_to(10).collect::<Vec<_>>(), 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::<Vec<_>>().is_empty());

	assert_eq!(past_code.prune_up_to(26).collect::<Vec<_>>(), 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<_>>(), 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<_>>(), 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 {
					para_kind: ParaKind::Parathread,
					genesis_head: dummy_head_data(),
					validation_code: ValidationCode(vec![]),
				}
			),
			Error::<Test>::CannotOnboard,
		);

		assert_ok!(Paras::schedule_para_initialize(
			1000.into(),
			ParaGenesisArgs {
				para_kind: ParaKind::Parathread,
				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 {
				para_kind: ParaKind::Parachain,
				genesis_head: dummy_head_data(),
				validation_code: dummy_validation_code(),
			},
		),
		(
			1u32.into(),
			ParaGenesisArgs {
				para_kind: ParaKind::Parathread,
				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()
	};

	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 = test_validation_code_2();

		Paras::increase_code_ref(&validation_code.hash(), &validation_code);
		PastCodeHash::<Test>::insert(&(id, at_block), &validation_code.hash());
		PastCodePruning::<Test>::put(&vec![(id, included_block)]);
			let mut code_meta = paras::PastCodeMeta::<Test>::get(&id);
			code_meta.note_replacement(at_block, included_block);
			PastCodeMeta::<Test>::insert(&id, &code_meta);
		}

		let pruned_at: BlockNumber = included_block + code_retention_period + 1;
		assert_eq!(PastCodeHash::<Test>::get(&(id, at_block)), Some(validation_code.hash()));
		check_code_is_stored(&validation_code);

		run_to_block(pruned_at - 1, None);
		assert_eq!(PastCodeHash::<Test>::get(&(id, at_block)), Some(validation_code.hash()));
		assert_eq!(paras::PastCodeMeta::<Test>::get(&id).most_recent_change(), Some(at_block));
		check_code_is_stored(&validation_code);

		run_to_block(pruned_at, None);
		assert!(PastCodeHash::<Test>::get(&(id, at_block)).is_none());
		assert!(paras::PastCodeMeta::<Test>::get(&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 {
			para_kind: ParaKind::Parachain,
			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()
	};

	new_test_ext(genesis_config).execute_with(|| {
		let id_a = ParaId::from(0u32);

		assert_eq!(paras::Heads::<Test>::get(&id_a), Some(dummy_head_data()));

		Paras::note_new_head(id_a, vec![1, 2, 3].into(), 0);

		assert_eq!(paras::Heads::<Test>::get(&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 {
				para_kind: ParaKind::Parachain,
				genesis_head: dummy_head_data(),
				validation_code: dummy_validation_code(),
			},
		),
		(
			1u32.into(),
			ParaGenesisArgs {
				para_kind: ParaKind::Parathread,
				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()
	};

	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, test_validation_code_1().hash());
		Paras::note_past_code(id_b, 20, 23, test_validation_code_2().hash());
		assert_eq!(PastCodePruning::<Test>::get(), vec![(id_a, 12), (id_b, 23)]);
		assert_eq!(
			paras::PastCodeMeta::<Test>::get(&id_a),
			ParaPastCodeMeta { upgrade_times: vec![upgrade_at(10, 12)], last_pruned: None }
		);
		assert_eq!(
			paras::PastCodeMeta::<Test>::get(&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 = test_validation_code_1();
	let paras = vec![(
		0u32.into(),
		ParaGenesisArgs {
			para_kind: ParaKind::Parachain,
			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,
				..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 = test_validation_code_2();
		// Wait for at least one session change to set active validators.
		const EXPECTED_SESSION: SessionIndex = 1;
		run_to_block(2, Some(vec![1]));
		assert_eq!(Paras::current_code(&para_id), Some(original_code.clone()));

		let (expected_at, next_possible_upgrade_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;
				&configuration::ActiveConfig::<Test>::get(),
				UpgradeStrategy::SetGoAheadSignal,
			// Include votes for super-majority.
			submit_super_majority_pvf_votes(&new_code, EXPECTED_SESSION, true);

			Paras::note_new_head(para_id, Default::default(), 1);

			assert!(paras::PastCodeMeta::<Test>::get(&para_id).most_recent_change().is_none());
			assert_eq!(FutureCodeUpgrades::<Test>::get(&para_id), Some(expected_at));
			assert_eq!(FutureCodeHash::<Test>::get(&para_id), Some(new_code.hash()));
			assert_eq!(UpcomingUpgrades::<Test>::get(), vec![(para_id, expected_at)]);
			assert_eq!(UpgradeCooldowns::<Test>::get(), vec![(para_id, next_possible_upgrade_at)]);
			assert_eq!(Paras::current_code(&para_id), Some(original_code.clone()));
			check_code_is_stored(&original_code);
			check_code_is_stored(&new_code);

			(expected_at, next_possible_upgrade_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::PastCodeMeta::<Test>::get(&para_id).most_recent_change().is_none());
			assert_eq!(FutureCodeUpgrades::<Test>::get(&para_id), Some(expected_at));
			assert_eq!(FutureCodeHash::<Test>::get(&para_id), Some(new_code.hash()));
			assert_eq!(UpgradeGoAheadSignal::<Test>::get(&para_id), Some(UpgradeGoAhead::GoAhead));
			assert_eq!(Paras::current_code(&para_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::PastCodeMeta::<Test>::get(&para_id).most_recent_change(),
				Some(expected_at)
			);
			assert_eq!(
				PastCodeHash::<Test>::get(&(para_id, expected_at)),
				Some(original_code.hash()),
			);
			assert!(FutureCodeUpgrades::<Test>::get(&para_id).is_none());
			assert!(FutureCodeHash::<Test>::get(&para_id).is_none());
			assert!(UpgradeGoAheadSignal::<Test>::get(&para_id).is_none());
			assert_eq!(Paras::current_code(&para_id), Some(new_code.clone()));
			assert_eq!(
				UpgradeRestrictionSignal::<Test>::get(&para_id),
				Some(UpgradeRestriction::Present),
			);
			assert_eq!(UpgradeCooldowns::<Test>::get(), vec![(para_id, next_possible_upgrade_at)]);
			check_code_is_stored(&original_code);
			check_code_is_stored(&new_code);
		}

		run_to_block(next_possible_upgrade_at + 1, None);

		{
			assert!(UpgradeRestrictionSignal::<Test>::get(&para_id).is_none());
			assert!(UpgradeCooldowns::<Test>::get().is_empty());
		}
fn upgrade_strategy_apply_at_expected_block_works() {
	let code_retention_period = 10;
	let validation_upgrade_delay = 5;
	let validation_upgrade_cooldown = 10;

	let original_code = test_validation_code_1();
	let paras = vec![(
		0u32.into(),
		ParaGenesisArgs {
			para_kind: ParaKind::Parachain,
			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,
				..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 = test_validation_code_2();

		// Wait for at least one session change to set active validators.
		const EXPECTED_SESSION: SessionIndex = 1;
		run_to_block(2, Some(vec![1]));
		assert_eq!(Paras::current_code(&para_id), Some(original_code.clone()));

		// 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;
		// `set_go_ahead` parameter set to `false` which prevents signaling the parachain
		// with the `GoAhead` signal.
		Paras::schedule_code_upgrade(
			para_id,
			new_code.clone(),
			1,
			&configuration::ActiveConfig::<Test>::get(),
			UpgradeStrategy::ApplyAtExpectedBlock,
		);
		// Include votes for super-majority.
		submit_super_majority_pvf_votes(&new_code, EXPECTED_SESSION, true);
		assert!(FutureCodeUpgradesAt::<Test>::get().iter().any(|(id, _)| *id == para_id));
		// Going to the expected block triggers the upgrade directly.
		// Reporting a head doesn't change anything.
		Paras::note_new_head(para_id, Default::default(), expected_at - 1);
		assert_eq!(
			paras::PastCodeMeta::<Test>::get(&para_id).most_recent_change(),
			Some(expected_at)
		);
		assert_eq!(PastCodeHash::<Test>::get(&(para_id, expected_at)), Some(original_code.hash()));
		assert!(FutureCodeUpgrades::<Test>::get(&para_id).is_none());
		assert!(FutureCodeUpgradesAt::<Test>::get().iter().all(|(id, _)| *id != para_id));
		assert!(FutureCodeHash::<Test>::get(&para_id).is_none());
		assert!(UpgradeGoAheadSignal::<Test>::get(&para_id).is_none());
		assert_eq!(Paras::current_code(&para_id), Some(new_code.clone()));
		assert_eq!(
			UpgradeRestrictionSignal::<Test>::get(&para_id),
			Some(UpgradeRestriction::Present),
		);
		assert_eq!(UpgradeCooldowns::<Test>::get(), vec![(para_id, next_possible_upgrade_at)]);
		check_code_is_stored(&original_code);
		check_code_is_stored(&new_code);

		run_to_block(next_possible_upgrade_at + 1, None);

		{
			assert!(UpgradeRestrictionSignal::<Test>::get(&para_id).is_none());
			assert!(UpgradeCooldowns::<Test>::get().is_empty());
		}
	});
}

#[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 = test_validation_code_1();
	let paras = vec![(
		0u32.into(),
		ParaGenesisArgs {
			para_kind: ParaKind::Parachain,
			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,
				..Default::default()
			},
		},
		..Default::default()
	};

	new_test_ext(genesis_config).execute_with(|| {
		let para_id = ParaId::from(0);
		let new_code = test_validation_code_2();
		// Wait for at least one session change to set active validators.
		const EXPECTED_SESSION: SessionIndex = 1;
		run_to_block(2, Some(vec![1]));
		assert_eq!(Paras::current_code(&para_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;
				&configuration::ActiveConfig::<Test>::get(),
				UpgradeStrategy::SetGoAheadSignal,
			// Include votes for super-majority.
			submit_super_majority_pvf_votes(&new_code, EXPECTED_SESSION, true);

			Paras::note_new_head(para_id, Default::default(), 1);

			assert!(paras::PastCodeMeta::<Test>::get(&para_id).most_recent_change().is_none());
			assert_eq!(FutureCodeUpgrades::<Test>::get(&para_id), Some(expected_at));
			assert_eq!(FutureCodeHash::<Test>::get(&para_id), Some(new_code.hash()));
			assert_eq!(UpcomingUpgrades::<Test>::get(), vec![(para_id, expected_at)]);
			assert_eq!(UpgradeCooldowns::<Test>::get(), vec![(para_id, next_possible_upgrade_at)]);
			assert!(UpgradeGoAheadSignal::<Test>::get(&para_id).is_none());
			assert_eq!(Paras::current_code(&para_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::<Test>::get(&para_id), Some(UpgradeGoAhead::GoAhead));

			Paras::note_new_head(para_id, Default::default(), expected_at + 4);

			assert_eq!(
				paras::PastCodeMeta::<Test>::get(&para_id).most_recent_change(),
				Some(expected_at)
			);

			assert_eq!(
				PastCodeHash::<Test>::get(&(para_id, expected_at)),
				Some(original_code.hash()),
			);
			assert!(FutureCodeUpgrades::<Test>::get(&para_id).is_none());
			assert!(FutureCodeHash::<Test>::get(&para_id).is_none());
			assert!(UpgradeGoAheadSignal::<Test>::get(&para_id).is_none());
			assert_eq!(Paras::current_code(&para_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 {
			para_kind: ParaKind::Parachain,
			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,
				..Default::default()
			},
		},
		..Default::default()
	};

	new_test_ext(genesis_config).execute_with(|| {
		let para_id = ParaId::from(0);
		let new_code = test_validation_code_1();
		let newer_code = test_validation_code_2();
		// Wait for at least one session change to set active validators.
		const EXPECTED_SESSION: SessionIndex = 1;
		run_to_block(1, Some(vec![1]));

			&configuration::ActiveConfig::<Test>::get(),
			UpgradeStrategy::SetGoAheadSignal,
		// Include votes for super-majority.
		submit_super_majority_pvf_votes(&new_code, EXPECTED_SESSION, true);

		assert_eq!(FutureCodeUpgrades::<Test>::get(&para_id), Some(1 + validation_upgrade_delay));
		assert_eq!(FutureCodeHash::<Test>::get(&para_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));
			&configuration::ActiveConfig::<Test>::get(),
			UpgradeStrategy::SetGoAheadSignal,
		assert_eq!(
			FutureCodeUpgrades::<Test>::get(&para_id),
			Some(1 + validation_upgrade_delay), /* did not change since the same assertion from
			                                     * the last time. */
		assert_eq!(FutureCodeHash::<Test>::get(&para_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 accidentally changed.

	let code_retention_period = 10;
	let validation_upgrade_delay = 7;
	let validation_upgrade_cooldown = 30;

	let paras = vec![(
		0u32.into(),
		ParaGenesisArgs {
			para_kind: ParaKind::Parachain,
			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,
				..Default::default()
			},
		},
		..Default::default()
	};

	new_test_ext(genesis_config).execute_with(|| {
		let para_id = 0u32.into();
		let new_code = test_validation_code_1();
		let newer_code = test_validation_code_2();
		// Wait for at least one session change to set active validators.
		const EXPECTED_SESSION: SessionIndex = 1;
		run_to_block(1, Some(vec![1]));

			&configuration::ActiveConfig::<Test>::get(),
			UpgradeStrategy::SetGoAheadSignal,
		// Include votes for super-majority.
		submit_super_majority_pvf_votes(&new_code, EXPECTED_SESSION, true);

		Paras::note_new_head(para_id, dummy_head_data(), 0);
		assert_eq!(
			UpgradeRestrictionSignal::<Test>::get(&para_id),
			Some(UpgradeRestriction::Present),
		);
		assert_eq!(FutureCodeUpgrades::<Test>::get(&para_id), Some(0 + validation_upgrade_delay));
		assert!(!Paras::can_upgrade_validation_code(para_id));

		run_to_block(31, None);
		assert!(UpgradeRestrictionSignal::<Test>::get(&para_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.
			&configuration::ActiveConfig::<Test>::get(),
			UpgradeStrategy::SetGoAheadSignal,
		assert_eq!(FutureCodeUpgrades::<Test>::get(&para_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 = test_validation_code_1();
	let paras = vec![(
		0u32.into(),
		ParaGenesisArgs {
			para_kind: ParaKind::Parachain,
			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,
				minimum_validation_upgrade_delay: 2,
				// Those are not relevant to this test. However, HostConfiguration is still a
				// subject for the consistency check.
				scheduler_params: SchedulerParams {
					paras_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 = test_validation_code_2();
		// Wait for at least one session change to set active validators.
		const EXPECTED_SESSION: SessionIndex = 1;
		run_to_block(2, Some(vec![1]));

		assert_eq!(Paras::current_code(&para_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;
				&configuration::ActiveConfig::<Test>::get(),
				UpgradeStrategy::SetGoAheadSignal,
			// Include votes for super-majority.
			submit_super_majority_pvf_votes(&new_code, EXPECTED_SESSION, true);

			Paras::note_new_head(para_id, Default::default(), 1);

			assert!(paras::PastCodeMeta::<Test>::get(&para_id).most_recent_change().is_none());
			assert_eq!(FutureCodeUpgrades::<Test>::get(&para_id), Some(expected_at));
			assert_eq!(FutureCodeHash::<Test>::get(&para_id), Some(new_code.hash()));
			assert_eq!(Paras::current_code(&para_id), Some(original_code.clone()));
			check_code_is_stored(&original_code);
			check_code_is_stored(&new_code);

			expected_at
		};

		// 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!(frame_system::Pallet::<Test>::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::PastCodeMeta::<Test>::get(&para_id).most_recent_change(), Some(8));
		assert_eq!(PastCodeHash::<Test>::get(&(para_id, 8)), Some(new_code.hash()));
		assert_eq!(PastCodePruning::<Test>::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::<Test>::get(&para_id).is_none());
		assert!(FutureCodeHash::<Test>::get(&para_id).is_none());
		assert!(Paras::current_code(&para_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::PastCodeMeta::<Test>::get(&para_id), Default::default());
		assert!(PastCodeHash::<Test>::get(&(para_id, 7)).is_none());
		assert!(PastCodeHash::<Test>::get(&(para_id, 8)).is_none());
		assert!(PastCodePruning::<Test>::get().is_empty());
		check_code_is_not_stored(&original_code);
		check_code_is_not_stored(&new_code);
	});
}

#[test]
fn cannot_offboard_ongoing_pvf_check() {
	let para_id = ParaId::from(0);

	let existing_code = test_validation_code_1();
	let new_code = test_validation_code_2();

	let paras = vec![(
		para_id,
		ParaGenesisArgs {
			para_kind: ParaKind::Parachain,
			genesis_head: Default::default(),
			validation_code: existing_code,
		},
	)];

	let genesis_config = MockGenesisConfig {
		paras: GenesisConfig { paras, ..Default::default() },
		..Default::default()
	};