From 09f602f8dedbdb47a8a4f198b5ec2f3dc8e5ad52 Mon Sep 17 00:00:00 2001
From: Robert Habermeier <rphmeier@gmail.com>
Date: Thu, 23 Jul 2020 15:02:24 -0400
Subject: [PATCH] Include a reference to the validation data in the candidate
 descriptor (#1442)

* rename GlobalValidationSchedule to GlobalValidationData

* guide: update candidate descriptor to contain validation data hash

* guide: add note in inclusion module about checking validation data hash

* primitives: update CandidateDescriptor to contain new hash

* fix payload computation

* add helpers for computing validation data to runtime modules

* guide: note routines

* inclusion: check validation data hash and fix local_validation_data bug

* add a case to candidate_checks and improve that test substantially

* bump versions

* address review comments

* add a test for including code upgrade

* bump kusama version

* bump westend & polkadot versions
---
 polkadot/collator/src/lib.rs                  |  10 +-
 polkadot/network/src/protocol/tests.rs        |   4 +-
 polkadot/node/core/backing/src/lib.rs         |  20 +-
 polkadot/node/primitives/src/lib.rs           |   4 +-
 .../adder/collator/src/main.rs                |   4 +-
 polkadot/primitives/src/v0.rs                 |   8 +-
 polkadot/primitives/src/v1.rs                 |  61 +++-
 .../src/runtime-api/README.md                 |   4 +-
 .../src/runtime/configuration.md              |   3 +
 .../src/runtime/inclusion.md                  |   1 +
 .../implementers-guide/src/runtime/paras.md   |   1 +
 .../src/types/availability.md                 |   4 +-
 .../implementers-guide/src/types/candidate.md |  19 +-
 .../src/types/overseer-protocol.md            |   2 +-
 polkadot/runtime/common/src/parachains.rs     |  12 +-
 polkadot/runtime/common/src/registrar.rs      |   2 +-
 polkadot/runtime/kusama/src/lib.rs            |   6 +-
 .../runtime/parachains/src/configuration.rs   |  13 +-
 polkadot/runtime/parachains/src/inclusion.rs  | 316 ++++++++++++++----
 polkadot/runtime/parachains/src/paras.rs      |  35 +-
 .../parachains/src/runtime_api_impl/v1.rs     |  50 +--
 polkadot/runtime/polkadot/src/lib.rs          |   6 +-
 polkadot/runtime/test-runtime/src/lib.rs      |   4 +-
 polkadot/runtime/westend/src/lib.rs           |   6 +-
 polkadot/validation/src/pipeline.rs           |  10 +-
 .../validation/src/validation_service/mod.rs  |   4 +-
 26 files changed, 434 insertions(+), 175 deletions(-)

diff --git a/polkadot/collator/src/lib.rs b/polkadot/collator/src/lib.rs
index 2e2b330b0c5..0b7419c29f5 100644
--- a/polkadot/collator/src/lib.rs
+++ b/polkadot/collator/src/lib.rs
@@ -58,7 +58,7 @@ use sp_core::Pair;
 use polkadot_primitives::v0::{
 	BlockId, Hash, Block, DownwardMessage,
 	BlockData, DutyRoster, HeadData, Id as ParaId,
-	PoVBlock, ValidatorId, CollatorPair, LocalValidationData, GlobalValidationSchedule,
+	PoVBlock, ValidatorId, CollatorPair, LocalValidationData, GlobalValidationData,
 	Collation, CollationInfo, collator_signature_payload,
 };
 use polkadot_cli::{
@@ -148,7 +148,7 @@ pub trait ParachainContext: Clone {
 	fn produce_candidate(
 		&mut self,
 		relay_parent: Hash,
-		global_validation: GlobalValidationSchedule,
+		global_validation: GlobalValidationData,
 		local_validation: LocalValidationData,
 		downward_messages: Vec<DownwardMessage>,
 	) -> Self::ProduceCandidate;
@@ -158,7 +158,7 @@ pub trait ParachainContext: Clone {
 pub async fn collate<P>(
 	relay_parent: Hash,
 	local_id: ParaId,
-	global_validation: GlobalValidationSchedule,
+	global_validation: GlobalValidationData,
 	local_validation_data: LocalValidationData,
 	downward_messages: Vec<DownwardMessage>,
 	mut para_context: P,
@@ -315,7 +315,7 @@ fn build_collator_service<P, C, R, Extrinsic>(
 
 			let work = future::lazy(move |_| {
 				let api = client.runtime_api();
-				let global_validation = try_fr!(api.global_validation_schedule(&id));
+				let global_validation = try_fr!(api.global_validation_data(&id));
 				let local_validation = match try_fr!(api.local_validation_data(&id, para_id)) {
 					Some(local_validation) => local_validation,
 					None => return future::Either::Left(future::ok(())),
@@ -477,7 +477,7 @@ mod tests {
 		fn produce_candidate(
 			&mut self,
 			_relay_parent: Hash,
-			_global: GlobalValidationSchedule,
+			_global: GlobalValidationData,
 			_local_validation: LocalValidationData,
 			_: Vec<DownwardMessage>,
 		) -> Self::ProduceCandidate {
diff --git a/polkadot/network/src/protocol/tests.rs b/polkadot/network/src/protocol/tests.rs
index 711906797be..4e7027963cb 100644
--- a/polkadot/network/src/protocol/tests.rs
+++ b/polkadot/network/src/protocol/tests.rs
@@ -21,7 +21,7 @@ use polkadot_primitives::v0::{
 	Block,
 	Id as ParaId, Chain, DutyRoster, ParachainHost, ValidatorId,
 	Retriable, CollatorId, AbridgedCandidateReceipt,
-	GlobalValidationSchedule, LocalValidationData, ErasureChunk, SigningContext,
+	GlobalValidationData, LocalValidationData, ErasureChunk, SigningContext,
 	PoVBlock, BlockData, ValidationCode,
 };
 use polkadot_validation::{SharedTable, TableRouter};
@@ -180,7 +180,7 @@ sp_api::mock_impl_runtime_apis! {
 			Some(ValidationCode(Vec::new()))
 		}
 
-		fn global_validation_schedule() -> GlobalValidationSchedule {
+		fn global_validation_data() -> GlobalValidationData {
 			Default::default()
 		}
 
diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs
index 3d5440968db..4647526fefa 100644
--- a/polkadot/node/core/backing/src/lib.rs
+++ b/polkadot/node/core/backing/src/lib.rs
@@ -606,7 +606,7 @@ impl CandidateBackingJob {
 		with_commitments: impl FnOnce(CandidateCommitments) -> Result<T, E>,
 	) -> Result<Result<T, E>, Error> {
 		let omitted_validation = OmittedValidationData {
-			global_validation: outputs.global_validation_schedule,
+			global_validation: outputs.global_validation_data,
 			local_validation: outputs.local_validation_data,
 		};
 
@@ -773,7 +773,7 @@ mod tests {
 	use futures::{executor, future, Future};
 	use polkadot_primitives::v1::{
 		AssignmentKind, BlockData, CandidateCommitments, CollatorId, CoreAssignment, CoreIndex,
-		LocalValidationData, GlobalValidationSchedule, GroupIndex, HeadData,
+		LocalValidationData, GlobalValidationData, GroupIndex, HeadData,
 		ValidatorPair, ValidityAttestation,
 	};
 	use polkadot_subsystem::{
@@ -792,7 +792,7 @@ mod tests {
 		keystore: KeyStorePtr,
 		validators: Vec<Sr25519Keyring>,
 		validator_public: Vec<ValidatorId>,
-		global_validation_schedule: GlobalValidationSchedule,
+		global_validation_data: GlobalValidationData,
 		local_validation_data: LocalValidationData,
 		roster: SchedulerRoster,
 		head_data: HashMap<ParaId, HeadData>,
@@ -877,7 +877,7 @@ mod tests {
 				validation_code_hash: Default::default(),
 			};
 
-			let global_validation_schedule = GlobalValidationSchedule {
+			let global_validation_data = GlobalValidationData {
 				max_code_size: 1000,
 				max_head_data_size: 1000,
 				block_number: Default::default(),
@@ -891,7 +891,7 @@ mod tests {
 				roster,
 				head_data,
 				local_validation_data,
-				global_validation_schedule,
+				global_validation_data,
 				signing_context,
 				relay_parent,
 			}
@@ -921,7 +921,7 @@ mod tests {
 
 	fn make_erasure_root(test: &TestState, pov: PoV) -> Hash {
 		let omitted_validation = OmittedValidationData {
-			global_validation: test.global_validation_schedule.clone(),
+			global_validation: test.global_validation_data.clone(),
 			local_validation: test.local_validation_data.clone(),
 		};
 
@@ -1048,7 +1048,7 @@ mod tests {
 				) if pov == pov && &c == candidate.descriptor() => {
 					tx.send(Ok(
 						ValidationResult::Valid(ValidationOutputs {
-							global_validation_schedule: test_state.global_validation_schedule,
+							global_validation_data: test_state.global_validation_data,
 							local_validation_data: test_state.local_validation_data,
 							head_data: expected_head_data.clone(),
 							upward_messages: Vec::new(),
@@ -1160,7 +1160,7 @@ mod tests {
 				) if pov == pov && &c == candidate_a.descriptor() => {
 					tx.send(Ok(
 						ValidationResult::Valid(ValidationOutputs {
-							global_validation_schedule: test_state.global_validation_schedule,
+							global_validation_data: test_state.global_validation_data,
 							local_validation_data: test_state.local_validation_data,
 							head_data: expected_head_data.clone(),
 							upward_messages: Vec::new(),
@@ -1281,7 +1281,7 @@ mod tests {
 				) if pov == pov && &c == candidate_a.descriptor() => {
 					tx.send(Ok(
 						ValidationResult::Valid(ValidationOutputs {
-							global_validation_schedule: test_state.global_validation_schedule,
+							global_validation_data: test_state.global_validation_data,
 							local_validation_data: test_state.local_validation_data,
 							head_data: expected_head_data.clone(),
 							upward_messages: Vec::new(),
@@ -1438,7 +1438,7 @@ mod tests {
 				) if pov == pov && &c == candidate_b.descriptor() => {
 					tx.send(Ok(
 						ValidationResult::Valid(ValidationOutputs {
-							global_validation_schedule: test_state.global_validation_schedule,
+							global_validation_data: test_state.global_validation_data,
 							local_validation_data: test_state.local_validation_data,
 							head_data: expected_head_data.clone(),
 							upward_messages: Vec::new(),
diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs
index 6630199ae50..5064a851588 100644
--- a/polkadot/node/primitives/src/lib.rs
+++ b/polkadot/node/primitives/src/lib.rs
@@ -24,7 +24,7 @@ use parity_scale_codec::{Decode, Encode};
 use polkadot_primitives::v1::{
 	Hash, CommittedCandidateReceipt, CandidateReceipt, CompactStatement,
 	EncodeAs, Signed, SigningContext, ValidatorIndex, ValidatorId,
-	UpwardMessage, Balance, ValidationCode, GlobalValidationSchedule, LocalValidationData,
+	UpwardMessage, Balance, ValidationCode, GlobalValidationData, LocalValidationData,
 	HeadData,
 };
 use polkadot_statement_table::{
@@ -118,7 +118,7 @@ pub struct ValidationOutputs {
 	/// The head-data produced by validation.
 	pub head_data: HeadData,
 	/// The global validation schedule.
-	pub global_validation_schedule: GlobalValidationSchedule,
+	pub global_validation_data: GlobalValidationData,
 	/// The local validation data.
 	pub local_validation_data: LocalValidationData,
 	/// Upward messages to the relay chain.
diff --git a/polkadot/parachain/test-parachains/adder/collator/src/main.rs b/polkadot/parachain/test-parachains/adder/collator/src/main.rs
index ec5b626883c..a5d3bb6dc14 100644
--- a/polkadot/parachain/test-parachains/adder/collator/src/main.rs
+++ b/polkadot/parachain/test-parachains/adder/collator/src/main.rs
@@ -24,7 +24,7 @@ use sp_core::Pair;
 use codec::{Encode, Decode};
 use primitives::v0::{
 	Hash, DownwardMessage,
-	HeadData, BlockData, Id as ParaId, LocalValidationData, GlobalValidationSchedule,
+	HeadData, BlockData, Id as ParaId, LocalValidationData, GlobalValidationData,
 };
 use collator::{ParachainContext, Network, BuildParachainContext, Cli, SubstrateCli};
 use parking_lot::Mutex;
@@ -58,7 +58,7 @@ impl ParachainContext for AdderContext {
 	fn produce_candidate(
 		&mut self,
 		_relay_parent: Hash,
-		_global_validation: GlobalValidationSchedule,
+		_global_validation: GlobalValidationData,
 		local_validation: LocalValidationData,
 		_: Vec<DownwardMessage>,
 	) -> Self::ProduceCandidate
diff --git a/polkadot/primitives/src/v0.rs b/polkadot/primitives/src/v0.rs
index e1ec3a55463..3fcb31193aa 100644
--- a/polkadot/primitives/src/v0.rs
+++ b/polkadot/primitives/src/v0.rs
@@ -179,7 +179,7 @@ pub struct DutyRoster {
 /// These are global parameters that apply to all parachain candidates in a block.
 #[derive(PartialEq, Eq, Clone, Encode, Decode)]
 #[cfg_attr(feature = "std", derive(Debug, Default))]
-pub struct GlobalValidationSchedule<N = BlockNumber> {
+pub struct GlobalValidationData<N = BlockNumber> {
 	/// The maximum code size permitted, in bytes.
 	pub max_code_size: u32,
 	/// The maximum head-data size permitted, in bytes.
@@ -278,7 +278,7 @@ pub struct CandidateReceipt<H = Hash, N = BlockNumber> {
 	/// The hash of the PoV-block.
 	pub pov_block_hash: H,
 	/// The global validation schedule.
-	pub global_validation: GlobalValidationSchedule<N>,
+	pub global_validation: GlobalValidationData<N>,
 	/// The local validation data.
 	pub local_validation: LocalValidationData<N>,
 	/// Commitments made as a result of validation.
@@ -352,7 +352,7 @@ impl Ord for CandidateReceipt {
 #[cfg_attr(feature = "std", derive(Debug, Default))]
 pub struct OmittedValidationData<N = BlockNumber> {
 	/// The global validation schedule.
-	pub global_validation: GlobalValidationSchedule<N>,
+	pub global_validation: GlobalValidationData<N>,
 	/// The local validation data.
 	pub local_validation: LocalValidationData<N>,
 }
@@ -762,7 +762,7 @@ sp_api::decl_runtime_apis! {
 		fn active_parachains() -> Vec<(Id, Option<(CollatorId, Retriable)>)>;
 		/// Get the global validation schedule that all parachains should
 		/// be validated under.
-		fn global_validation_schedule() -> GlobalValidationSchedule;
+		fn global_validation_data() -> GlobalValidationData;
 		/// Get the local validation data for a particular parachain.
 		fn local_validation_data(id: Id) -> Option<LocalValidationData>;
 		/// Get the given parachain's head code blob.
diff --git a/polkadot/primitives/src/v1.rs b/polkadot/primitives/src/v1.rs
index 796dd4fb315..5d523fd3007 100644
--- a/polkadot/primitives/src/v1.rs
+++ b/polkadot/primitives/src/v1.rs
@@ -60,14 +60,16 @@ pub const INCLUSION_INHERENT_IDENTIFIER: InherentIdentifier = *b"inclusn0";
 pub fn collator_signature_payload<H: AsRef<[u8]>>(
 	relay_parent: &H,
 	para_id: &Id,
+	validation_data_hash: &Hash,
 	pov_hash: &Hash,
-) -> [u8; 68] {
+) -> [u8; 100] {
 	// 32-byte hash length is protected in a test below.
-	let mut payload = [0u8; 68];
+	let mut payload = [0u8; 100];
 
 	payload[0..32].copy_from_slice(relay_parent.as_ref());
 	u32::from(*para_id).using_encoded(|s| payload[32..32 + s.len()].copy_from_slice(s));
-	payload[36..68].copy_from_slice(pov_hash.as_ref());
+	payload[36..68].copy_from_slice(validation_data_hash.as_ref());
+	payload[68..100].copy_from_slice(pov_hash.as_ref());
 
 	payload
 }
@@ -75,11 +77,18 @@ pub fn collator_signature_payload<H: AsRef<[u8]>>(
 fn check_collator_signature<H: AsRef<[u8]>>(
 	relay_parent: &H,
 	para_id: &Id,
+	validation_data_hash: &Hash,
 	pov_hash: &Hash,
 	collator: &CollatorId,
 	signature: &CollatorSignature,
 ) -> Result<(),()> {
-	let payload = collator_signature_payload(relay_parent, para_id, pov_hash);
+	let payload = collator_signature_payload(
+		relay_parent,
+		para_id,
+		validation_data_hash,
+		pov_hash,
+	);
+
 	if signature.verify(&payload[..], collator) {
 		Ok(())
 	} else {
@@ -87,6 +96,14 @@ fn check_collator_signature<H: AsRef<[u8]>>(
 	}
 }
 
+/// Compute the `validation_data_hash` from global & local validation data.
+pub fn validation_data_hash<N: Encode>(
+	global: &GlobalValidationData<N>,
+	local: &LocalValidationData<N>,
+) -> Hash {
+	BlakeTwo256::hash_of(&(global, local))
+}
+
 /// A unique descriptor of the candidate receipt.
 #[derive(PartialEq, Eq, Clone, Encode, Decode)]
 #[cfg_attr(feature = "std", derive(Debug, Default))]
@@ -97,11 +114,16 @@ pub struct CandidateDescriptor<H = Hash> {
 	pub relay_parent: H,
 	/// The collator's sr25519 public key.
 	pub collator: CollatorId,
-	/// Signature on blake2-256 of components of this receipt:
-	/// The parachain index, the relay parent, and the pov_hash.
-	pub signature: CollatorSignature,
+	/// The blake2-256 hash of the validation data. This is extra data derived from
+	/// relay-chain state which may vary based on bitfields included before the candidate.
+	/// Thus it cannot be derived entirely from the relay-parent.
+	pub validation_data_hash: Hash,
 	/// The blake2-256 hash of the pov.
 	pub pov_hash: Hash,
+	/// Signature on blake2-256 of components of this receipt:
+	/// The parachain index, the relay parent, the validation data hash, and the pov_hash.
+	pub signature: CollatorSignature,
+
 }
 
 impl<H: AsRef<[u8]>> CandidateDescriptor<H> {
@@ -110,6 +132,7 @@ impl<H: AsRef<[u8]>> CandidateDescriptor<H> {
 		check_collator_signature(
 			&self.relay_parent,
 			&self.para_id,
+			&self.validation_data_hash,
 			&self.pov_hash,
 			&self.collator,
 			&self.signature,
@@ -146,7 +169,7 @@ pub struct FullCandidateReceipt<H = Hash, N = BlockNumber> {
 	/// The inner candidate receipt.
 	pub inner: CandidateReceipt<H>,
 	/// The global validation schedule.
-	pub global_validation: GlobalValidationSchedule<N>,
+	pub global_validation: GlobalValidationData<N>,
 	/// The local validation data.
 	pub local_validation: LocalValidationData<N>,
 }
@@ -232,7 +255,7 @@ pub struct LocalValidationData<N = BlockNumber> {
 /// These are global parameters that apply to all candidates in a block.
 #[derive(PartialEq, Eq, Clone, Encode, Decode)]
 #[cfg_attr(feature = "std", derive(Debug, Default))]
-pub struct GlobalValidationSchedule<N = BlockNumber> {
+pub struct GlobalValidationData<N = BlockNumber> {
 	/// The maximum code size permitted, in bytes.
 	pub max_code_size: u32,
 	/// The maximum head-data size permitted, in bytes.
@@ -465,7 +488,7 @@ impl CoreAssignment {
 #[cfg_attr(feature = "std", derive(PartialEq, Debug))]
 pub struct OmittedValidationData {
 	/// The global validation schedule.
-	pub global_validation: GlobalValidationSchedule,
+	pub global_validation: GlobalValidationData,
 	/// The local validation data.
 	pub local_validation: LocalValidationData,
 }
@@ -636,9 +659,9 @@ sp_api::decl_runtime_apis! {
 		/// cores can have paras assigned to them.
 		fn availability_cores() -> Vec<CoreState<N>>;
 
-		/// Yields the GlobalValidationSchedule. This applies to all para candidates with the
+		/// Yields the GlobalValidationData. This applies to all para candidates with the
 		/// relay-parent equal to the block in which context this is invoked in.
-		fn global_validation_schedule() -> GlobalValidationSchedule<N>;
+		fn global_validation_data() -> GlobalValidationData<N>;
 
 		/// Yields the LocalValidationData for the given ParaId along with an assumption that
 		/// should be used if the para currently occupies a core.
@@ -696,4 +719,18 @@ mod tests {
 		assert_eq!(info.next_rotation_at(), 0);
 		assert_eq!(info.last_rotation_at(), 0);
 	}
+
+	#[test]
+	fn collator_signature_payload_is_valid() {
+		// if this fails, collator signature verification code has to be updated.
+		let h = Hash::default();
+		assert_eq!(h.as_ref().len(), 32);
+
+		let _payload = collator_signature_payload(
+			&Hash::from([1; 32]),
+			&5u32.into(),
+			&Hash::from([2; 32]),
+			&Hash::from([3; 32]),
+		);
+	}
 }
diff --git a/polkadot/roadmap/implementers-guide/src/runtime-api/README.md b/polkadot/roadmap/implementers-guide/src/runtime-api/README.md
index 4f36ac5d108..cb8998230d4 100644
--- a/polkadot/roadmap/implementers-guide/src/runtime-api/README.md
+++ b/polkadot/roadmap/implementers-guide/src/runtime-api/README.md
@@ -137,10 +137,10 @@ enum CoreState {
 
 ## Global Validation Schedule
 
-Yields the [`GlobalValidationSchedule`](../types/candidate.md#globalvalidationschedule) at the state of a given block. This applies to all para candidates with the relay-parent equal to that block.
+Yields the [`GlobalValidationData`](../types/candidate.md#globalvalidationschedule) at the state of a given block. This applies to all para candidates with the relay-parent equal to that block.
 
 ```rust
-fn global_validation_schedule(at: Block) -> GlobalValidationSchedule;
+fn global_validation_data(at: Block) -> GlobalValidationData;
 ```
 
 ## Local Validation Data
diff --git a/polkadot/roadmap/implementers-guide/src/runtime/configuration.md b/polkadot/roadmap/implementers-guide/src/runtime/configuration.md
index 37e5202429e..f1ed5eb5085 100644
--- a/polkadot/roadmap/implementers-guide/src/runtime/configuration.md
+++ b/polkadot/roadmap/implementers-guide/src/runtime/configuration.md
@@ -35,6 +35,9 @@ fn update_configuration(f: impl FnOnce(&mut HostConfiguration)) {
     *pending = Some(x);
   })
 }
+
+/// Get the GlobalValidationData, assuming the context is the parent block.
+fn global_validation_data() -> GlobalValidationData;
 ```
 
 ## Entry-points
diff --git a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md
index 2387a9244a5..e961b78bed7 100644
--- a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md
+++ b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md
@@ -62,6 +62,7 @@ All failed checks should lead to an unrecoverable error making the block invalid
   1. check that each candidate corresponds to a scheduled core and that they are ordered in the same order the cores appear in assignments in `scheduled`.
   1. check that `scheduled` is sorted ascending by `CoreIndex`, without duplicates.
   1. check that there is no candidate pending availability for any scheduled `ParaId`.
+  1. check that each candidate's `validation_data_hash` corresponds to a `(LocalValidationData, GlobalValidationData)` computed from the current state.
   1. If the core assignment includes a specific collator, ensure the backed candidate is issued by that collator.
   1. Ensure that any code upgrade scheduled by the candidate does not happen within `config.validation_upgrade_frequency` of `Paras::last_code_upgrade(para_id, true)`, if any, comparing against the value of `Paras::FutureCodeUpgrades` for the given para ID.
   1. Check the collator's signature on the candidate data.
diff --git a/polkadot/roadmap/implementers-guide/src/runtime/paras.md b/polkadot/roadmap/implementers-guide/src/runtime/paras.md
index e80c2d102d2..97c49ae1673 100644
--- a/polkadot/roadmap/implementers-guide/src/runtime/paras.md
+++ b/polkadot/roadmap/implementers-guide/src/runtime/paras.md
@@ -112,6 +112,7 @@ OutgoingParas: Vec<ParaId>;
 * `is_parathread(ParaId) -> bool`: Returns true if the para ID references any live parathread.
 
 * `last_code_upgrade(id: ParaId, include_future: bool) -> Option<BlockNumber>`: The block number of the last scheduled upgrade of the requested para. Includes future upgrades if the flag is set. This is the `expected_at` number, not the `activated_at` number.
+* `local_validation_data(id: ParaId) -> Option<LocalValidationData>`: Get the LocalValidationData of the given para, assuming the context is the parent block. Returns `None` if the para is not known.
 
 ## Finalization
 
diff --git a/polkadot/roadmap/implementers-guide/src/types/availability.md b/polkadot/roadmap/implementers-guide/src/types/availability.md
index be42c7a9278..3afa8d8df54 100644
--- a/polkadot/roadmap/implementers-guide/src/types/availability.md
+++ b/polkadot/roadmap/implementers-guide/src/types/availability.md
@@ -26,12 +26,12 @@ struct PoV(Vec<u8>);
 
 Validation data that is often omitted from types describing candidates as it can be derived from the relay-parent of the candidate. However, with the expectation of state pruning, these are best kept available elsewhere as well.
 
-This contains the [`GlobalValidationSchedule`](candidate.md#globalvalidationschedule) and [`LocalValidationData`](candidate.md#localvalidationdata)
+This contains the [`GlobalValidationData`](candidate.md#globalvalidationschedule) and [`LocalValidationData`](candidate.md#localvalidationdata)
 
 ```rust
 struct OmittedValidationData {
     /// The global validation schedule.
-    global_validation: GlobalValidationSchedule,
+    global_validation: GlobalValidationData,
     /// The local validation data.
     local_validation: LocalValidationData,
 }
diff --git a/polkadot/roadmap/implementers-guide/src/types/candidate.md b/polkadot/roadmap/implementers-guide/src/types/candidate.md
index 0002851cee1..dd69b233806 100644
--- a/polkadot/roadmap/implementers-guide/src/types/candidate.md
+++ b/polkadot/roadmap/implementers-guide/src/types/candidate.md
@@ -33,7 +33,7 @@ struct CandidateReceipt {
 
 ## Full Candidate Receipt
 
-This is the full receipt type. The `GlobalValidationSchedule` and the `LocalValidationData` are technically redundant with the `inner.relay_parent`, which uniquely describes the a block in the blockchain from whose state these values are derived. The [`CandidateReceipt`](#candidate-receipt) variant is often used instead for this reason.
+This is the full receipt type. The `GlobalValidationData` and the `LocalValidationData` are technically redundant with the `inner.relay_parent`, which uniquely describes the a block in the blockchain from whose state these values are derived. The [`CandidateReceipt`](#candidate-receipt) variant is often used instead for this reason.
 
 However, the Full Candidate Receipt type is useful as a means of avoiding the implicit dependency on availability of old blockchain state. In situations such as availability and approval, having the full description of the candidate within a self-contained struct is convenient.
 
@@ -42,7 +42,7 @@ However, the Full Candidate Receipt type is useful as a means of avoiding the im
 struct FullCandidateReceipt {
 	inner: CandidateReceipt,
 	/// The global validation schedule.
-	global_validation: GlobalValidationSchedule,
+	global_validation: GlobalValidationData,
 	/// The local validation data.
 	local_validation: LocalValidationData,
 }
@@ -77,16 +77,19 @@ struct CandidateDescriptor {
 	relay_parent: Hash,
 	/// The collator's sr25519 public key.
 	collator: CollatorId,
-	/// Signature on blake2-256 of components of this receipt:
-	/// The parachain index, the relay parent, and the pov_hash.
-	signature: CollatorSignature,
+	/// The blake2-256 hash of the validation data. These are extra parameters
+	/// derived from relay-chain state that influence the validity of the block.
+	validation_data_hash: Hash,
 	/// The blake2-256 hash of the pov-block.
 	pov_hash: Hash,
+	/// Signature on blake2-256 of components of this receipt:
+	/// The parachain index, the relay parent, the validation data hash, and the pov_hash.
+	signature: CollatorSignature,
 }
 ```
 
 
-## GlobalValidationSchedule
+## GlobalValidationData
 
 The global validation schedule comprises of information describing the global environment for para execution, as derived from a particular relay-parent. These are parameters that will apply to all parablocks executed in the context of this relay-parent.
 
@@ -95,7 +98,7 @@ The global validation schedule comprises of information describing the global en
 /// to fully validate the candidate.
 ///
 /// These are global parameters that apply to all candidates in a block.
-struct GlobalValidationSchedule {
+struct GlobalValidationData {
 	/// The maximum code size permitted, in bytes.
 	max_code_size: u32,
 	/// The maximum head-data size permitted, in bytes.
@@ -197,7 +200,7 @@ struct ValidationOutputs {
 	/// The head-data produced by validation.
 	head_data: HeadData,
 	/// The global validation schedule.
-	global_validation_schedule: GlobalValidationSchedule,
+	global_validation_data: GlobalValidationData,
 	/// The local validation data.
 	local_validation_data: LocalValidationData,
 	/// Upwards messages to the relay chain.
diff --git a/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md b/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md
index f6ff4213fd0..d777460176a 100644
--- a/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md
+++ b/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md
@@ -255,7 +255,7 @@ enum RuntimeApiRequest {
 	/// Get the validation code for a specific para, using the given occupied core assumption.
 	ValidationCode(ParaId, OccupiedCoreAssumption, ResponseChannel<Option<ValidationCode>>),
 	/// Get the global validation schedule at the state of a given block.
-	GlobalValidationSchedule(ResponseChannel<GlobalValidationSchedule>),
+	GlobalValidationData(ResponseChannel<GlobalValidationData>),
 	/// Get the local validation data for a specific para, with the given occupied core assumption.
 	LocalValidationData(
 		ParaId,
diff --git a/polkadot/runtime/common/src/parachains.rs b/polkadot/runtime/common/src/parachains.rs
index 2df3669ded4..a5d81989aa5 100644
--- a/polkadot/runtime/common/src/parachains.rs
+++ b/polkadot/runtime/common/src/parachains.rs
@@ -41,7 +41,7 @@ use primitives::v0::{
 	Balance, BlockNumber,
 	Id as ParaId, Chain, DutyRoster, AttestedCandidate, CompactStatement as Statement, ParachainDispatchOrigin,
 	UpwardMessage, ValidatorId, ActiveParas, CollatorId, Retriable, OmittedValidationData,
-	CandidateReceipt, GlobalValidationSchedule, AbridgedCandidateReceipt,
+	CandidateReceipt, GlobalValidationData, AbridgedCandidateReceipt,
 	LocalValidationData, Scheduling, ValidityAttestation, NEW_HEADS_IDENTIFIER, PARACHAIN_KEY_TYPE_ID,
 	ValidatorSignature, SigningContext, HeadData, ValidationCode,
 	Remark, DownwardMessage
@@ -601,7 +601,7 @@ decl_module! {
 
 			let mut proceeded = Vec::with_capacity(heads.len());
 
-			let schedule = Self::global_validation_schedule();
+			let schedule = Self::global_validation_data();
 
 			if !active_parachains.is_empty() {
 				// perform integrity checks before writing to storage.
@@ -1168,9 +1168,9 @@ impl<T: Trait> Module<T> {
 	}
 
 	/// Get the global validation schedule for all parachains.
-	pub fn global_validation_schedule() -> GlobalValidationSchedule {
+	pub fn global_validation_data() -> GlobalValidationData {
 		let now = <system::Module<T>>::block_number();
-		GlobalValidationSchedule {
+		GlobalValidationData {
 			max_code_size: T::MaxCodeSize::get(),
 			max_head_data_size: T::MaxHeadDataSize::get(),
 			block_number: T::BlockNumberConversion::convert(if now.is_zero() {
@@ -1322,7 +1322,7 @@ impl<T: Trait> Module<T> {
 	// check the attestations on these candidates. The candidates should have been checked
 	// that each candidates' chain ID is valid.
 	fn check_candidates(
-		schedule: &GlobalValidationSchedule,
+		schedule: &GlobalValidationData,
 		attested_candidates: &[AttestedCandidate],
 		active_parachains: &[(ParaId, Option<(CollatorId, Retriable)>)]
 	) -> sp_std::result::Result<IncludedBlocks<T>, sp_runtime::DispatchError> {
@@ -2157,7 +2157,7 @@ mod tests {
 			collator: Default::default(),
 			signature: Default::default(),
 			pov_block_hash: Default::default(),
-			global_validation: Parachains::global_validation_schedule(),
+			global_validation: Parachains::global_validation_data(),
 			local_validation: Parachains::current_local_validation_data(&para_id).unwrap(),
 			commitments: CandidateCommitments::default(),
 		}
diff --git a/polkadot/runtime/common/src/registrar.rs b/polkadot/runtime/common/src/registrar.rs
index e3b6d5ec12f..5493d20839f 100644
--- a/polkadot/runtime/common/src/registrar.rs
+++ b/polkadot/runtime/common/src/registrar.rs
@@ -1070,7 +1070,7 @@ mod tests {
 			collator: collator.public(),
 			signature: pov_block_hash.using_encoded(|d| collator.sign(d)),
 			pov_block_hash,
-			global_validation: Parachains::global_validation_schedule(),
+			global_validation: Parachains::global_validation_data(),
 			local_validation: Parachains::current_local_validation_data(&id).unwrap(),
 			commitments: CandidateCommitments {
 				fees: 0,
diff --git a/polkadot/runtime/kusama/src/lib.rs b/polkadot/runtime/kusama/src/lib.rs
index 7354feb6ddd..3e921b433b1 100644
--- a/polkadot/runtime/kusama/src/lib.rs
+++ b/polkadot/runtime/kusama/src/lib.rs
@@ -87,7 +87,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
 	spec_name: create_runtime_str!("kusama"),
 	impl_name: create_runtime_str!("parity-kusama"),
 	authoring_version: 2,
-	spec_version: 2019,
+	spec_version: 2020,
 	impl_version: 0,
 	#[cfg(not(feature = "disable-runtime-api"))]
 	apis: RUNTIME_API_VERSIONS,
@@ -1115,8 +1115,8 @@ sp_api::impl_runtime_apis! {
 		fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> {
 			Registrar::active_paras()
 		}
-		fn global_validation_schedule() -> parachain::GlobalValidationSchedule {
-			Parachains::global_validation_schedule()
+		fn global_validation_data() -> parachain::GlobalValidationData {
+			Parachains::global_validation_data()
 		}
 		fn local_validation_data(id: parachain::Id) -> Option<parachain::LocalValidationData> {
 			Parachains::current_local_validation_data(&id)
diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs
index b977bc119b1..6fc9dd4230b 100644
--- a/polkadot/runtime/parachains/src/configuration.rs
+++ b/polkadot/runtime/parachains/src/configuration.rs
@@ -19,12 +19,13 @@
 //! Configuration can change only at session boundaries and is buffered until then.
 
 use sp_std::prelude::*;
-use primitives::v1::ValidatorId;
+use primitives::v1::{ValidatorId, GlobalValidationData};
 use frame_support::{
 	decl_storage, decl_module, decl_error,
 	dispatch::DispatchResult,
 	weights::{DispatchClass, Weight},
 };
+use sp_runtime::traits::One;
 use codec::{Encode, Decode};
 use system::ensure_root;
 
@@ -219,6 +220,16 @@ impl<T: Trait> Module<T> {
 			<Self as Store>::PendingConfig::set(Some(prev));
 		}
 	}
+
+	/// Computes the global validation-data, assuming the context of the parent block.
+	pub(crate) fn global_validation_data() -> GlobalValidationData<T::BlockNumber> {
+		let config = Self::config();
+		GlobalValidationData {
+			max_code_size: config.max_code_size,
+			max_head_data_size: config.max_head_data_size,
+			block_number: <system::Module<T>>::block_number() - One::one(),
+		}
+	}
 }
 
 #[cfg(test)]
diff --git a/polkadot/runtime/parachains/src/inclusion.rs b/polkadot/runtime/parachains/src/inclusion.rs
index 435342cfb82..e6f5ff1a0a5 100644
--- a/polkadot/runtime/parachains/src/inclusion.rs
+++ b/polkadot/runtime/parachains/src/inclusion.rs
@@ -22,6 +22,7 @@
 
 use sp_std::prelude::*;
 use primitives::v1::{
+	validation_data_hash,
 	ValidatorId, CandidateCommitments, CandidateDescriptor, ValidatorIndex, Id as ParaId,
 	AvailabilityBitfield as AvailabilityBitfield, SignedAvailabilityBitfields, SigningContext,
 	BackedCandidate, CoreIndex, GroupIndex, CoreAssignment, CommittedCandidateReceipt,
@@ -145,6 +146,8 @@ decl_error! {
 		InvalidBacking,
 		/// Collator did not sign PoV.
 		NotCollatorSigned,
+		/// The validation data hash does not match expected.
+		ValidationDataHashMismatch,
 		/// Internal error only returned when compiled with debug assertions.
 		InternalError,
 	}
@@ -399,14 +402,21 @@ impl<T: Trait> Module<T> {
 					Error::<T>::CandidateNotInParentContext,
 				);
 
-				let code_upgrade_allowed = <paras::Module<T>>::last_code_upgrade(para_id, true)
-					.map_or(
-						true,
-						|last| last <= relay_parent_number &&
-							relay_parent_number.saturating_sub(last) >= config.validation_upgrade_frequency,
-					);
+				// if any, the code upgrade attempt is allowed.
+				let valid_upgrade_attempt =
+					candidate.candidate.commitments.new_validation_code.is_none() ||
+					<paras::Module<T>>::last_code_upgrade(para_id, true)
+						.map_or(
+							true,
+							|last| last <= relay_parent_number &&
+								relay_parent_number.saturating_sub(last)
+									>= config.validation_upgrade_frequency,
+						);
 
-				ensure!(code_upgrade_allowed, Error::<T>::PrematureCodeUpgrade);
+				ensure!(
+					valid_upgrade_attempt,
+					Error::<T>::PrematureCodeUpgrade,
+				);
 				ensure!(
 					candidate.descriptor().check_collator_signature().is_ok(),
 					Error::<T>::NotCollatorSigned,
@@ -423,6 +433,32 @@ impl<T: Trait> Module<T> {
 							);
 						}
 
+						{
+							// this should never fail because the para is registered
+							let (global_validation_data, local_validation_data) = (
+								<configuration::Module<T>>::global_validation_data(),
+								match <paras::Module<T>>::local_validation_data(para_id) {
+									Some(l) => l,
+									None => {
+										// We don't want to error out here because it will
+										// brick the relay-chain. So we return early without
+										// doing anything.
+										return Ok(Vec::new());
+									}
+								}
+							);
+
+							let expected = validation_data_hash(
+								&global_validation_data,
+								&local_validation_data,
+							);
+
+							ensure!(
+								expected == candidate.descriptor().validation_data_hash,
+								Error::<T>::ValidationDataHashMismatch,
+							);
+						}
+
 						ensure!(
 							<PendingAvailability<T>>::get(&para_id).is_none() &&
 							<PendingAvailabilityCommitments>::get(&para_id).is_none(),
@@ -686,6 +722,7 @@ mod tests {
 		let payload = primitives::v1::collator_signature_payload(
 			&candidate.descriptor.relay_parent,
 			&candidate.descriptor.para_id,
+			&candidate.descriptor.validation_data_hash,
 			&candidate.descriptor.pov_hash,
 		);
 
@@ -814,6 +851,7 @@ mod tests {
 		head_data: HeadData,
 		pov_hash: Hash,
 		relay_parent: Hash,
+		validation_data_hash: Hash,
 		new_validation_code: Option<ValidationCode>,
 	}
 
@@ -824,6 +862,7 @@ mod tests {
 					para_id: self.para_id,
 					pov_hash: self.pov_hash,
 					relay_parent: self.relay_parent,
+					validation_data_hash: self.validation_data_hash,
 					..Default::default()
 				},
 				commitments: CandidateCommitments {
@@ -835,6 +874,12 @@ mod tests {
 		}
 	}
 
+	fn make_vdata_hash(para_id: ParaId) -> Option<Hash> {
+		let global_validation_data = Configuration::global_validation_data();
+		let local_validation_data = Paras::local_validation_data(para_id)?;
+		Some(validation_data_hash(&global_validation_data, &local_validation_data))
+	}
+
 	#[test]
 	fn collect_pending_cleans_up_pending() {
 		let chain_a = ParaId::from(1);
@@ -1261,6 +1306,7 @@ mod tests {
 					para_id: chain_a,
 					relay_parent: System::parent_hash(),
 					pov_hash: Hash::from([1; 32]),
+					validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 				collator_sign_candidate(
@@ -1276,11 +1322,14 @@ mod tests {
 					BackingKind::Threshold,
 				);
 
-				assert!(Inclusion::process_candidates(
-					vec![backed],
-					vec![chain_b_assignment.clone()],
-					&group_validators,
-				).is_err());
+				assert_eq!(
+					Inclusion::process_candidates(
+						vec![backed],
+						vec![chain_b_assignment.clone()],
+						&group_validators,
+					),
+					Err(Error::<Test>::UnscheduledCandidate.into()),
+				);
 			}
 
 			// candidates out of order.
@@ -1289,12 +1338,14 @@ mod tests {
 					para_id: chain_a,
 					relay_parent: System::parent_hash(),
 					pov_hash: Hash::from([1; 32]),
+					validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 				let mut candidate_b = TestCandidateBuilder {
 					para_id: chain_b,
 					relay_parent: System::parent_hash(),
 					pov_hash: Hash::from([2; 32]),
+					validation_data_hash: make_vdata_hash(chain_b).unwrap(),
 					..Default::default()
 				}.build();
 
@@ -1324,11 +1375,15 @@ mod tests {
 					BackingKind::Threshold,
 				);
 
-				assert!(Inclusion::process_candidates(
-					vec![backed_b, backed_a],
-					vec![chain_a_assignment.clone(), chain_b_assignment.clone()],
-					&group_validators,
-				).is_err());
+				// out-of-order manifests as unscheduled.
+				assert_eq!(
+					Inclusion::process_candidates(
+						vec![backed_b, backed_a],
+						vec![chain_a_assignment.clone(), chain_b_assignment.clone()],
+						&group_validators,
+					),
+					Err(Error::<Test>::UnscheduledCandidate.into()),
+				);
 			}
 
 			// candidate not backed.
@@ -1337,6 +1392,7 @@ mod tests {
 					para_id: chain_a,
 					relay_parent: System::parent_hash(),
 					pov_hash: Hash::from([1; 32]),
+					validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 				collator_sign_candidate(
@@ -1352,11 +1408,14 @@ mod tests {
 					BackingKind::Lacking,
 				);
 
-				assert!(Inclusion::process_candidates(
-					vec![backed],
-					vec![chain_a_assignment.clone()],
-					&group_validators,
-				).is_err());
+				assert_eq!(
+					Inclusion::process_candidates(
+						vec![backed],
+						vec![chain_a_assignment.clone()],
+						&group_validators,
+					),
+					Err(Error::<Test>::InsufficientBacking.into()),
+				);
 			}
 
 			// candidate not in parent context.
@@ -1368,6 +1427,7 @@ mod tests {
 					para_id: chain_a,
 					relay_parent: wrong_parent_hash,
 					pov_hash: Hash::from([1; 32]),
+					validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 				collator_sign_candidate(
@@ -1383,11 +1443,14 @@ mod tests {
 					BackingKind::Threshold,
 				);
 
-				assert!(Inclusion::process_candidates(
-					vec![backed],
-					vec![chain_a_assignment.clone()],
-					&group_validators,
-				).is_err());
+				assert_eq!(
+					Inclusion::process_candidates(
+						vec![backed],
+						vec![chain_a_assignment.clone()],
+						&group_validators,
+					),
+					Err(Error::<Test>::CandidateNotInParentContext.into()),
+				);
 			}
 
 			// candidate has wrong collator.
@@ -1396,6 +1459,7 @@ mod tests {
 					para_id: thread_a,
 					relay_parent: System::parent_hash(),
 					pov_hash: Hash::from([1; 32]),
+					validation_data_hash: make_vdata_hash(thread_a).unwrap(),
 					..Default::default()
 				}.build();
 
@@ -1413,15 +1477,18 @@ mod tests {
 					BackingKind::Threshold,
 				);
 
-				assert!(Inclusion::process_candidates(
-					vec![backed],
-					vec![
-						chain_a_assignment.clone(),
-						chain_b_assignment.clone(),
-						thread_a_assignment.clone(),
-					],
-					&group_validators,
-				).is_err());
+				assert_eq!(
+					Inclusion::process_candidates(
+						vec![backed],
+						vec![
+							chain_a_assignment.clone(),
+							chain_b_assignment.clone(),
+							thread_a_assignment.clone(),
+						],
+						&group_validators,
+					),
+					Err(Error::<Test>::WrongCollator.into()),
+				);
 			}
 
 			// candidate not well-signed by collator.
@@ -1430,6 +1497,7 @@ mod tests {
 					para_id: thread_a,
 					relay_parent: System::parent_hash(),
 					pov_hash: Hash::from([1; 32]),
+					validation_data_hash: make_vdata_hash(thread_a).unwrap(),
 					..Default::default()
 				}.build();
 
@@ -1450,11 +1518,14 @@ mod tests {
 					BackingKind::Threshold,
 				);
 
-				assert!(Inclusion::process_candidates(
-					vec![backed],
-					vec![thread_a_assignment.clone()],
-					&group_validators,
-				).is_err());
+				assert_eq!(
+					Inclusion::process_candidates(
+						vec![backed],
+						vec![thread_a_assignment.clone()],
+						&group_validators,
+					),
+					Err(Error::<Test>::NotCollatorSigned.into()),
+				);
 			}
 
 			// para occupied - reject.
@@ -1463,6 +1534,7 @@ mod tests {
 					para_id: chain_a,
 					relay_parent: System::parent_hash(),
 					pov_hash: Hash::from([1; 32]),
+					validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 
@@ -1489,11 +1561,14 @@ mod tests {
 				});
 				<PendingAvailabilityCommitments>::insert(&chain_a, candidate.commitments);
 
-				assert!(Inclusion::process_candidates(
-					vec![backed],
-					vec![chain_a_assignment.clone()],
-					&group_validators,
-				).is_err());
+				assert_eq!(
+					Inclusion::process_candidates(
+						vec![backed],
+						vec![chain_a_assignment.clone()],
+						&group_validators,
+					),
+					Err(Error::<Test>::CandidateScheduledBeforeParaFree.into()),
+				);
 
 				<PendingAvailability<Test>>::remove(&chain_a);
 				<PendingAvailabilityCommitments>::remove(&chain_a);
@@ -1505,6 +1580,7 @@ mod tests {
 					para_id: chain_a,
 					relay_parent: System::parent_hash(),
 					pov_hash: Hash::from([1; 32]),
+					validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 
@@ -1524,11 +1600,14 @@ mod tests {
 					BackingKind::Threshold,
 				);
 
-				assert!(Inclusion::process_candidates(
-					vec![backed],
-					vec![chain_a_assignment.clone()],
-					&group_validators,
-				).is_err());
+				assert_eq!(
+					Inclusion::process_candidates(
+						vec![backed],
+						vec![chain_a_assignment.clone()],
+						&group_validators,
+					),
+					Err(Error::<Test>::CandidateScheduledBeforeParaFree.into()),
+				);
 
 				<PendingAvailabilityCommitments>::remove(&chain_a);
 			}
@@ -1540,6 +1619,7 @@ mod tests {
 					relay_parent: System::parent_hash(),
 					pov_hash: Hash::from([1; 32]),
 					new_validation_code: Some(vec![5, 6, 7, 8].into()),
+					validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 
@@ -1564,11 +1644,47 @@ mod tests {
 
 				assert_eq!(Paras::last_code_upgrade(chain_a, true), Some(10));
 
-				assert!(Inclusion::process_candidates(
-					vec![backed],
-					vec![thread_a_assignment.clone()],
-					&group_validators,
-				).is_err());
+				assert_eq!(
+					Inclusion::process_candidates(
+						vec![backed],
+						vec![chain_a_assignment.clone()],
+						&group_validators,
+					),
+					Err(Error::<Test>::PrematureCodeUpgrade.into()),
+				);
+			}
+
+			// Bad validation data hash - reject
+			{
+				let mut candidate = TestCandidateBuilder {
+					para_id: chain_a,
+					relay_parent: System::parent_hash(),
+					pov_hash: Hash::from([1; 32]),
+					validation_data_hash: [42u8; 32].into(),
+					..Default::default()
+				}.build();
+
+				collator_sign_candidate(
+					Sr25519Keyring::One,
+					&mut candidate,
+				);
+
+				let backed = back_candidate(
+					candidate,
+					&validators,
+					group_validators(GroupIndex::from(0)).unwrap().as_ref(),
+					&signing_context,
+					BackingKind::Threshold,
+				);
+
+				assert_eq!(
+					Inclusion::process_candidates(
+						vec![backed],
+						vec![chain_a_assignment.clone()],
+						&group_validators,
+					),
+					Err(Error::<Test>::ValidationDataHashMismatch.into()),
+				);
 			}
 		});
 	}
@@ -1634,6 +1750,7 @@ mod tests {
 				para_id: chain_a,
 				relay_parent: System::parent_hash(),
 				pov_hash: Hash::from([1; 32]),
+				validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 				..Default::default()
 			}.build();
 			collator_sign_candidate(
@@ -1645,6 +1762,7 @@ mod tests {
 				para_id: chain_b,
 				relay_parent: System::parent_hash(),
 				pov_hash: Hash::from([2; 32]),
+				validation_data_hash: make_vdata_hash(chain_b).unwrap(),
 				..Default::default()
 			}.build();
 			collator_sign_candidate(
@@ -1656,6 +1774,7 @@ mod tests {
 				para_id: thread_a,
 				relay_parent: System::parent_hash(),
 				pov_hash: Hash::from([3; 32]),
+				validation_data_hash: make_vdata_hash(thread_a).unwrap(),
 				..Default::default()
 			}.build();
 			collator_sign_candidate(
@@ -1746,6 +1865,91 @@ mod tests {
 		});
 	}
 
+	#[test]
+	fn can_include_candidate_with_ok_code_upgrade() {
+		let chain_a = ParaId::from(1);
+
+		let paras = vec![(chain_a, true)];
+		let validators = vec![
+			Sr25519Keyring::Alice,
+			Sr25519Keyring::Bob,
+			Sr25519Keyring::Charlie,
+			Sr25519Keyring::Dave,
+			Sr25519Keyring::Ferdie,
+		];
+		let validator_public = validator_pubkeys(&validators);
+
+		new_test_ext(genesis_config(paras)).execute_with(|| {
+			Validators::set(validator_public.clone());
+			CurrentSessionIndex::set(5);
+
+			run_to_block(5, |_| None);
+
+			let signing_context = SigningContext {
+				parent_hash: System::parent_hash(),
+				session_index: 5,
+			};
+
+			let group_validators = |group_index: GroupIndex| match group_index {
+				group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1, 2, 3, 4]),
+				_ => panic!("Group index out of bounds for 1 parachain"),
+			};
+
+			let chain_a_assignment = CoreAssignment {
+				core: CoreIndex::from(0),
+				para_id: chain_a,
+				kind: AssignmentKind::Parachain,
+				group_idx: GroupIndex::from(0),
+			};
+
+			let mut candidate_a = TestCandidateBuilder {
+				para_id: chain_a,
+				relay_parent: System::parent_hash(),
+				pov_hash: Hash::from([1; 32]),
+				validation_data_hash: make_vdata_hash(chain_a).unwrap(),
+				new_validation_code: Some(vec![1, 2, 3].into()),
+				..Default::default()
+			}.build();
+			collator_sign_candidate(
+				Sr25519Keyring::One,
+				&mut candidate_a,
+			);
+
+			let backed_a = back_candidate(
+				candidate_a.clone(),
+				&validators,
+				group_validators(GroupIndex::from(0)).unwrap().as_ref(),
+				&signing_context,
+				BackingKind::Threshold,
+			);
+
+			let occupied_cores = Inclusion::process_candidates(
+				vec![backed_a],
+				vec![
+					chain_a_assignment.clone(),
+				],
+				&group_validators,
+			).expect("candidates scheduled, in order, and backed");
+
+			assert_eq!(occupied_cores, vec![CoreIndex::from(0)]);
+
+			assert_eq!(
+				<PendingAvailability<Test>>::get(&chain_a),
+				Some(CandidatePendingAvailability {
+					core: CoreIndex::from(0),
+					descriptor: candidate_a.descriptor,
+					availability_votes: default_availability_votes(),
+					relay_parent_number: System::block_number() - 1,
+					backed_in_number: System::block_number(),
+				})
+			);
+			assert_eq!(
+				<PendingAvailabilityCommitments>::get(&chain_a),
+				Some(candidate_a.commitments),
+			);
+		});
+	}
+
 	#[test]
 	fn session_change_wipes_and_updates_session_info() {
 		let chain_a = ParaId::from(1);
diff --git a/polkadot/runtime/parachains/src/paras.rs b/polkadot/runtime/parachains/src/paras.rs
index 0117089f110..2eab9c05465 100644
--- a/polkadot/runtime/parachains/src/paras.rs
+++ b/polkadot/runtime/parachains/src/paras.rs
@@ -25,9 +25,9 @@
 
 use sp_std::prelude::*;
 use sp_std::marker::PhantomData;
-use sp_runtime::traits::One;
+use sp_runtime::traits::{One, BlakeTwo256, Hash as HashT, Saturating};
 use primitives::v1::{
-	Id as ParaId, ValidationCode, HeadData,
+	Id as ParaId, ValidationCode, HeadData, LocalValidationData,
 };
 use frame_support::{
 	decl_storage, decl_module, decl_error,
@@ -536,6 +536,37 @@ impl<T: Trait> Module<T> {
 
 		Self::past_code_meta(&id).most_recent_change()
 	}
+
+	/// Compute the local-validation data based on the head of the para. This assumes the
+	/// relay-parent is the parent of the current block.
+	pub(crate) fn local_validation_data(para_id: ParaId) -> Option<LocalValidationData<T::BlockNumber>> {
+		let relay_parent_number = <system::Module<T>>::block_number() - One::one();
+
+		let config = <configuration::Module<T>>::config();
+		let freq = config.validation_upgrade_frequency;
+		let delay = config.validation_upgrade_delay;
+
+		let last_code_upgrade = Self::last_code_upgrade(para_id, true);
+		let can_upgrade_code = last_code_upgrade.map_or(
+			true,
+			|l| { l <= relay_parent_number && relay_parent_number.saturating_sub(l) >= freq },
+		);
+
+		let code_upgrade_allowed = if can_upgrade_code {
+			Some(relay_parent_number + delay)
+		} else {
+			None
+		};
+
+		Some(LocalValidationData {
+			parent_head: Self::para_head(&para_id)?,
+			balance: 0,
+			validation_code_hash: BlakeTwo256::hash_of(
+				&Self::current_code(&para_id)?
+			),
+			code_upgrade_allowed,
+		})
+	}
 }
 
 #[cfg(test)]
diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs
index fa5026dbada..7f4a9093d42 100644
--- a/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs
+++ b/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs
@@ -18,12 +18,12 @@
 //! functions.
 
 use primitives::v1::{
-	ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, GlobalValidationSchedule,
+	ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, GlobalValidationData,
 	Id as ParaId, OccupiedCoreAssumption, LocalValidationData, SessionIndex, ValidationCode,
 	CommittedCandidateReceipt, ScheduledCore, OccupiedCore, CoreOccupied, CoreIndex,
 	GroupIndex, CandidateEvent,
 };
-use sp_runtime::traits::{One, BlakeTwo256, Hash as HashT, Saturating, Zero};
+use sp_runtime::traits::Zero;
 use frame_support::debug;
 use crate::{initializer, inclusion, scheduler, configuration, paras};
 
@@ -160,16 +160,11 @@ pub fn availability_cores<T: initializer::Trait>() -> Vec<CoreState<T::BlockNumb
 	core_states
 }
 
-/// Implementation for the `global_validation_schedule` function of the runtime API.
-pub fn global_validation_schedule<T: initializer::Trait>()
-	-> GlobalValidationSchedule<T::BlockNumber>
+/// Implementation for the `global_validation_data` function of the runtime API.
+pub fn global_validation_data<T: initializer::Trait>()
+	-> GlobalValidationData<T::BlockNumber>
 {
-	let config = <configuration::Module<T>>::config();
-	GlobalValidationSchedule {
-		max_code_size: config.max_code_size,
-		max_head_data_size: config.max_head_data_size,
-		block_number: <system::Module<T>>::block_number() - One::one(),
-	}
+	<configuration::Module<T>>::global_validation_data()
 }
 
 /// Implementation for the `local_validation_data` function of the runtime API.
@@ -177,46 +172,19 @@ pub fn local_validation_data<T: initializer::Trait>(
 	para_id: ParaId,
 	assumption: OccupiedCoreAssumption,
 ) -> Option<LocalValidationData<T::BlockNumber>> {
-	let construct = || {
-		let relay_parent_number = <system::Module<T>>::block_number() - One::one();
-
-		let config = <configuration::Module<T>>::config();
-		let freq = config.validation_upgrade_frequency;
-		let delay = config.validation_upgrade_delay;
-
-		let last_code_upgrade = <paras::Module<T>>::last_code_upgrade(para_id, true)?;
-		let can_upgrade_code = last_code_upgrade <= relay_parent_number
-			&& relay_parent_number.saturating_sub(last_code_upgrade) >= freq;
-
-		let code_upgrade_allowed = if can_upgrade_code {
-			Some(relay_parent_number + delay)
-		} else {
-			None
-		};
-
-		Some(LocalValidationData {
-			parent_head: <paras::Module<T>>::para_head(&para_id)?,
-			balance: 0,
-			validation_code_hash: BlakeTwo256::hash_of(
-				&<paras::Module<T>>::current_code(&para_id)?
-			),
-			code_upgrade_allowed,
-		})
-	};
-
 	match assumption {
 		OccupiedCoreAssumption::Included => {
 			<inclusion::Module<T>>::force_enact(para_id);
-			construct()
+			<paras::Module<T>>::local_validation_data(para_id)
 		}
 		OccupiedCoreAssumption::TimedOut => {
-			construct()
+			<paras::Module<T>>::local_validation_data(para_id)
 		}
 		OccupiedCoreAssumption::Free => {
 			if <inclusion::Module<T>>::pending_availability(para_id).is_some() {
 				None
 			} else {
-				construct()
+				<paras::Module<T>>::local_validation_data(para_id)
 			}
 		}
 	}
diff --git a/polkadot/runtime/polkadot/src/lib.rs b/polkadot/runtime/polkadot/src/lib.rs
index e7afcfe16b7..f8d8cea3ab1 100644
--- a/polkadot/runtime/polkadot/src/lib.rs
+++ b/polkadot/runtime/polkadot/src/lib.rs
@@ -86,7 +86,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
 	spec_name: create_runtime_str!("polkadot"),
 	impl_name: create_runtime_str!("parity-polkadot"),
 	authoring_version: 0,
-	spec_version: 19,
+	spec_version: 20,
 	impl_version: 0,
 	#[cfg(not(feature = "disable-runtime-api"))]
 	apis: RUNTIME_API_VERSIONS,
@@ -1256,8 +1256,8 @@ sp_api::impl_runtime_apis! {
 		fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> {
 			Registrar::active_paras()
 		}
-		fn global_validation_schedule() -> parachain::GlobalValidationSchedule {
-			Parachains::global_validation_schedule()
+		fn global_validation_data() -> parachain::GlobalValidationData {
+			Parachains::global_validation_data()
 		}
 		fn local_validation_data(id: parachain::Id) -> Option<parachain::LocalValidationData> {
 			Parachains::current_local_validation_data(&id)
diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs
index 032efaacfbe..65c92aae0c3 100644
--- a/polkadot/runtime/test-runtime/src/lib.rs
+++ b/polkadot/runtime/test-runtime/src/lib.rs
@@ -676,8 +676,8 @@ sp_api::impl_runtime_apis! {
 		fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> {
 			Registrar::active_paras()
 		}
-		fn global_validation_schedule() -> parachain::GlobalValidationSchedule {
-			Parachains::global_validation_schedule()
+		fn global_validation_data() -> parachain::GlobalValidationData {
+			Parachains::global_validation_data()
 		}
 		fn local_validation_data(id: parachain::Id) -> Option<parachain::LocalValidationData> {
 			Parachains::current_local_validation_data(&id)
diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs
index 6d7fdc79f00..f48ee40e53e 100644
--- a/polkadot/runtime/westend/src/lib.rs
+++ b/polkadot/runtime/westend/src/lib.rs
@@ -83,7 +83,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
 	spec_name: create_runtime_str!("westend"),
 	impl_name: create_runtime_str!("parity-westend"),
 	authoring_version: 2,
-	spec_version: 39,
+	spec_version: 40,
 	impl_version: 0,
 	#[cfg(not(feature = "disable-runtime-api"))]
 	apis: RUNTIME_API_VERSIONS,
@@ -897,8 +897,8 @@ sp_api::impl_runtime_apis! {
 		fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> {
 			Registrar::active_paras()
 		}
-		fn global_validation_schedule() -> parachain::GlobalValidationSchedule {
-			Parachains::global_validation_schedule()
+		fn global_validation_data() -> parachain::GlobalValidationData {
+			Parachains::global_validation_data()
 		}
 		fn local_validation_data(id: parachain::Id) -> Option<parachain::LocalValidationData> {
 			Parachains::current_local_validation_data(&id)
diff --git a/polkadot/validation/src/pipeline.rs b/polkadot/validation/src/pipeline.rs
index f2a705ba101..663321480c5 100644
--- a/polkadot/validation/src/pipeline.rs
+++ b/polkadot/validation/src/pipeline.rs
@@ -20,7 +20,7 @@
 use codec::Encode;
 use polkadot_erasure_coding as erasure;
 use polkadot_primitives::v0::{
-	CollationInfo, PoVBlock, LocalValidationData, GlobalValidationSchedule, OmittedValidationData,
+	CollationInfo, PoVBlock, LocalValidationData, GlobalValidationData, OmittedValidationData,
 	AvailableData, FeeSchedule, CandidateCommitments, ErasureChunk, ParachainHost,
 	Id as ParaId, AbridgedCandidateReceipt, ValidationCode,
 };
@@ -95,7 +95,7 @@ impl FullOutput {
 /// validation are needed, call `full_output`. Otherwise, safely drop this value.
 pub struct ValidatedCandidate<'a> {
 	pov_block: &'a PoVBlock,
-	global_validation: &'a GlobalValidationSchedule,
+	global_validation: &'a GlobalValidationData,
 	local_validation: &'a LocalValidationData,
 	upward_messages: Vec<UpwardMessage>,
 	fees: Balance,
@@ -189,7 +189,7 @@ pub fn validate<'a>(
 	collation: &'a CollationInfo,
 	pov_block: &'a PoVBlock,
 	local_validation: &'a LocalValidationData,
-	global_validation: &'a GlobalValidationSchedule,
+	global_validation: &'a GlobalValidationData,
 	validation_code: &ValidationCode,
 ) -> Result<ValidatedCandidate<'a>, Error> {
 	if collation.head_data.0.len() > global_validation.max_head_data_size as _ {
@@ -249,7 +249,7 @@ pub fn validate<'a>(
 
 /// Extracts validation parameters from a Polkadot runtime API for a specific parachain.
 pub fn validation_params<P>(api: &P, relay_parent: Hash, para_id: ParaId)
-	-> Result<(LocalValidationData, GlobalValidationSchedule, ValidationCode), Error>
+	-> Result<(LocalValidationData, GlobalValidationData, ValidationCode), Error>
 where
 	P: ProvideRuntimeApi<Block>,
 	P::Api: ParachainHost<Block, Error = sp_blockchain::Error>,
@@ -261,7 +261,7 @@ where
 	let local_validation = api.local_validation_data(&relay_parent, para_id)?
 		.ok_or_else(|| Error::InactiveParachain(para_id))?;
 
-	let global_validation = api.global_validation_schedule(&relay_parent)?;
+	let global_validation = api.global_validation_data(&relay_parent)?;
 	let validation_code = api.parachain_code(&relay_parent, para_id)?
 		.ok_or_else(|| Error::InactiveParachain(para_id))?;
 
diff --git a/polkadot/validation/src/validation_service/mod.rs b/polkadot/validation/src/validation_service/mod.rs
index 7f332f4c0d1..11116fcaffd 100644
--- a/polkadot/validation/src/validation_service/mod.rs
+++ b/polkadot/validation/src/validation_service/mod.rs
@@ -547,7 +547,7 @@ mod tests {
 	use availability_store::ErasureNetworking;
 	use polkadot_primitives::v0::{
 		PoVBlock, AbridgedCandidateReceipt, ErasureChunk, ValidatorIndex,
-		CollationInfo, DutyRoster, GlobalValidationSchedule, LocalValidationData,
+		CollationInfo, DutyRoster, GlobalValidationData, LocalValidationData,
 		Retriable, CollatorId, BlockData, Chain, AvailableData, SigningContext, ValidationCode,
 	};
 	use runtime_primitives::traits::Block as BlockT;
@@ -697,7 +697,7 @@ mod tests {
 			fn validators(&self) -> Vec<ValidatorId> { self.validators.clone() }
 			fn duty_roster(&self) -> DutyRoster { self.duty_roster.clone() }
 			fn active_parachains() -> Vec<(ParaId, Option<(CollatorId, Retriable)>)> { vec![(ParaId::from(1), None)] }
-			fn global_validation_schedule() -> GlobalValidationSchedule { Default::default() }
+			fn global_validation_data() -> GlobalValidationData { Default::default() }
 			fn local_validation_data(_: ParaId) -> Option<LocalValidationData> { None }
 			fn parachain_code(_: ParaId) -> Option<ValidationCode> { None }
 			fn get_heads(_: Vec<<Block as BlockT>::Extrinsic>) -> Option<Vec<AbridgedCandidateReceipt>> {
-- 
GitLab