diff --git a/polkadot/Cargo.lock b/polkadot/Cargo.lock
index 7c998fc0a54d51d6432ffa3a3c29d30335dda889..287b58f8af77cbbaebcf7d167bcb34d328dfec92 100644
--- a/polkadot/Cargo.lock
+++ b/polkadot/Cargo.lock
@@ -8694,19 +8694,6 @@ dependencies = [
  "test-parachain-adder",
 ]
 
-[[package]]
-name = "test-parachain-code-upgrader"
-version = "0.7.22"
-dependencies = [
- "dlmalloc",
- "parity-scale-codec",
- "polkadot-parachain",
- "sp-io",
- "sp-std",
- "substrate-wasm-builder-runner 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "tiny-keccak 1.5.0",
-]
-
 [[package]]
 name = "test-parachain-halt"
 version = "0.8.22"
@@ -8722,7 +8709,6 @@ dependencies = [
  "polkadot-parachain",
  "sp-core",
  "test-parachain-adder",
- "test-parachain-code-upgrader",
  "test-parachain-halt",
  "tiny-keccak 1.5.0",
 ]
diff --git a/polkadot/Cargo.toml b/polkadot/Cargo.toml
index 1c98ca8d0b85715ce85444b180ef8e4f63e9aedd..817851211fbdf7f16288ec8344e589ad2dd31fe9 100644
--- a/polkadot/Cargo.toml
+++ b/polkadot/Cargo.toml
@@ -70,7 +70,6 @@ members = [
 	"parachain/test-parachains",
 	"parachain/test-parachains/adder",
 	"parachain/test-parachains/adder/collator",
-	"parachain/test-parachains/code-upgrader",
 ]
 
 [badges]
diff --git a/polkadot/node/collation-generation/src/lib.rs b/polkadot/node/collation-generation/src/lib.rs
index 793616d907187074c7a1eafda2ede842186caeb7..c2f6f9bc2ceb74ce21de9648be0ebf2768f63992 100644
--- a/polkadot/node/collation-generation/src/lib.rs
+++ b/polkadot/node/collation-generation/src/lib.rs
@@ -34,13 +34,13 @@ use polkadot_node_subsystem::{
 	metrics::{self, prometheus},
 };
 use polkadot_node_subsystem_util::{
-	self as util, request_availability_cores_ctx, request_global_validation_data_ctx,
-	request_local_validation_data_ctx, request_validators_ctx,
+	self as util, request_availability_cores_ctx, request_full_validation_data_ctx,
+	request_validators_ctx,
 };
 use polkadot_primitives::v1::{
-	collator_signature_payload, validation_data_hash, AvailableData, CandidateCommitments,
-	CandidateDescriptor, CandidateReceipt, CoreState, GlobalValidationData, Hash,
-	LocalValidationData, OccupiedCoreAssumption, PoV,
+	collator_signature_payload, AvailableData, CandidateCommitments,
+	CandidateDescriptor, CandidateReceipt, CoreState, Hash, OccupiedCoreAssumption,
+	PersistedValidationData, PoV,
 };
 use sp_core::crypto::Pair;
 use std::sync::Arc;
@@ -198,13 +198,11 @@ async fn handle_new_activations<Context: SubsystemContext>(
 	for relay_parent in activated.iter().copied() {
 		// double-future magic happens here: the first layer of requests takes a mutable borrow of the context, and
 		// returns a receiver. The second layer of requests actually polls those receivers to completion.
-		let (global_validation_data, availability_cores, validators) = join!(
-			request_global_validation_data_ctx(relay_parent, ctx).await?,
+		let (availability_cores, validators) = join!(
 			request_availability_cores_ctx(relay_parent, ctx).await?,
 			request_validators_ctx(relay_parent, ctx).await?,
 		);
 
-		let global_validation_data = global_validation_data??;
 		let availability_cores = availability_cores??;
 		let n_validators = validators??.len();
 
@@ -224,9 +222,10 @@ async fn handle_new_activations<Context: SubsystemContext>(
 				continue;
 			}
 
-			// we get local validation data synchronously for each core instead of within the subtask loop,
-			// because we have only a single mutable handle to the context, so the work can't really be distributed
-			let local_validation_data = match request_local_validation_data_ctx(
+			// we get validation data synchronously for each core instead of
+			// within the subtask loop, because we have only a single mutable handle to the
+			// context, so the work can't really be distributed
+			let validation_data = match request_full_validation_data_ctx(
 				relay_parent,
 				scheduled_core.para_id,
 				assumption,
@@ -235,30 +234,32 @@ async fn handle_new_activations<Context: SubsystemContext>(
 			.await?
 			.await??
 			{
-				Some(local_validation_data) => local_validation_data,
+				Some(v) => v,
 				None => continue,
 			};
 
-			let task_global_validation_data = global_validation_data.clone();
 			let task_config = config.clone();
 			let mut task_sender = sender.clone();
 			let metrics = metrics.clone();
 			ctx.spawn("collation generation collation builder", Box::pin(async move {
-				let validation_data_hash =
-					validation_data_hash(&task_global_validation_data, &local_validation_data);
+				let persisted_validation_data_hash = validation_data.persisted.hash();
 
-				let collation = (task_config.collator)(&task_global_validation_data, &local_validation_data).await;
+				let collation = (task_config.collator)(&validation_data).await;
 
 				let pov_hash = collation.proof_of_validity.hash();
 
 				let signature_payload = collator_signature_payload(
 					&relay_parent,
 					&scheduled_core.para_id,
-					&validation_data_hash,
+					&persisted_validation_data_hash,
 					&pov_hash,
 				);
 
-				let erasure_root = match erasure_root(n_validators, local_validation_data, task_global_validation_data, collation.proof_of_validity.clone()) {
+				let erasure_root = match erasure_root(
+					n_validators,
+					validation_data.persisted,
+					collation.proof_of_validity.clone(),
+				) {
 					Ok(erasure_root) => erasure_root,
 					Err(err) => {
 						log::error!(target: "collation_generation", "failed to calculate erasure root for para_id {}: {:?}", scheduled_core.para_id, err);
@@ -281,7 +282,7 @@ async fn handle_new_activations<Context: SubsystemContext>(
 						para_id: scheduled_core.para_id,
 						relay_parent,
 						collator: task_config.key.public(),
-						validation_data_hash,
+						persisted_validation_data_hash,
 						pov_hash,
 					},
 				};
@@ -302,17 +303,11 @@ async fn handle_new_activations<Context: SubsystemContext>(
 
 fn erasure_root(
 	n_validators: usize,
-	local_validation_data: LocalValidationData,
-	global_validation_data: GlobalValidationData,
+	persisted_validation: PersistedValidationData,
 	pov: PoV,
 ) -> Result<Hash> {
-	let omitted_validation = polkadot_primitives::v1::OmittedValidationData {
-		global_validation: global_validation_data,
-		local_validation: local_validation_data,
-	};
-
 	let available_data = AvailableData {
-		omitted_validation,
+		validation_data: persisted_validation,
 		pov,
 	};
 
@@ -369,8 +364,8 @@ mod tests {
 			subsystem_test_harness, TestSubsystemContextHandle,
 		};
 		use polkadot_primitives::v1::{
-			BlockData, BlockNumber, CollatorPair, GlobalValidationData, Id as ParaId,
-			LocalValidationData, PoV, ScheduledCore,
+			BlockData, BlockNumber, CollatorPair, Id as ParaId,
+			PersistedValidationData, PoV, ScheduledCore, ValidationData,
 		};
 		use std::pin::Pin;
 
@@ -402,7 +397,7 @@ mod tests {
 		fn test_config<Id: Into<ParaId>>(para_id: Id) -> Arc<CollationGenerationConfig> {
 			Arc::new(CollationGenerationConfig {
 				key: CollatorPair::generate().0,
-				collator: Box::new(|_gvd: &GlobalValidationData, _lvd: &LocalValidationData| {
+				collator: Box::new(|_vd: &ValidationData| {
 					Box::new(TestCollator)
 				}),
 				para_id: para_id.into(),
@@ -417,7 +412,7 @@ mod tests {
 		}
 
 		#[test]
-		fn requests_validation_and_availability_per_relay_parent() {
+		fn requests_availability_per_relay_parent() {
 			let activated_hashes: Vec<Hash> = vec![
 				[1; 32].into(),
 				[4; 32].into(),
@@ -425,19 +420,13 @@ mod tests {
 				[16; 32].into(),
 			];
 
-			let requested_validation_data = Arc::new(Mutex::new(Vec::new()));
 			let requested_availability_cores = Arc::new(Mutex::new(Vec::new()));
 
-			let overseer_requested_validation_data = requested_validation_data.clone();
 			let overseer_requested_availability_cores = requested_availability_cores.clone();
 			let overseer = |mut handle: TestSubsystemContextHandle<CollationGenerationMessage>| async move {
 				loop {
 					match handle.try_recv().await {
 						None => break,
-						Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::GlobalValidationData(tx)))) => {
-							overseer_requested_validation_data.lock().await.push(hash);
-							tx.send(Ok(Default::default())).unwrap();
-						}
 						Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, RuntimeApiRequest::AvailabilityCores(tx)))) => {
 							overseer_requested_availability_cores.lock().await.push(hash);
 							tx.send(Ok(vec![])).unwrap();
@@ -455,7 +444,7 @@ mod tests {
 			let subsystem_activated_hashes = activated_hashes.clone();
 			subsystem_test_harness(overseer, |mut ctx| async move {
 				handle_new_activations(
-					test_config(123),
+					test_config(123u32),
 					&subsystem_activated_hashes,
 					&mut ctx,
 					Metrics(None),
@@ -465,21 +454,16 @@ mod tests {
 				.unwrap();
 			});
 
-			let mut requested_validation_data = Arc::try_unwrap(requested_validation_data)
-				.expect("overseer should have shut down by now")
-				.into_inner();
-			requested_validation_data.sort();
 			let mut requested_availability_cores = Arc::try_unwrap(requested_availability_cores)
 				.expect("overseer should have shut down by now")
 				.into_inner();
 			requested_availability_cores.sort();
 
-			assert_eq!(requested_validation_data, activated_hashes);
 			assert_eq!(requested_availability_cores, activated_hashes);
 		}
 
 		#[test]
-		fn requests_local_validation_for_scheduled_matches() {
+		fn requests_validation_data_for_scheduled_matches() {
 			let activated_hashes: Vec<Hash> = vec![
 				Hash::repeat_byte(1),
 				Hash::repeat_byte(4),
@@ -487,19 +471,13 @@ mod tests {
 				Hash::repeat_byte(16),
 			];
 
-			let requested_local_validation_data = Arc::new(Mutex::new(Vec::new()));
+			let requested_full_validation_data = Arc::new(Mutex::new(Vec::new()));
 
-			let overseer_requested_local_validation_data = requested_local_validation_data.clone();
+			let overseer_requested_full_validation_data = requested_full_validation_data.clone();
 			let overseer = |mut handle: TestSubsystemContextHandle<CollationGenerationMessage>| async move {
 				loop {
 					match handle.try_recv().await {
 						None => break,
-						Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
-							_hash,
-							RuntimeApiRequest::GlobalValidationData(tx),
-						))) => {
-							tx.send(Ok(Default::default())).unwrap();
-						}
 						Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
 							hash,
 							RuntimeApiRequest::AvailabilityCores(tx),
@@ -518,13 +496,13 @@ mod tests {
 						}
 						Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
 							hash,
-							RuntimeApiRequest::LocalValidationData(
+							RuntimeApiRequest::FullValidationData(
 								_para_id,
 								_occupied_core_assumption,
 								tx,
 							),
 						))) => {
-							overseer_requested_local_validation_data
+							overseer_requested_full_validation_data
 								.lock()
 								.await
 								.push(hash);
@@ -551,7 +529,7 @@ mod tests {
 					.unwrap();
 			});
 
-			let requested_local_validation_data = Arc::try_unwrap(requested_local_validation_data)
+			let requested_full_validation_data = Arc::try_unwrap(requested_full_validation_data)
 				.expect("overseer should have shut down by now")
 				.into_inner();
 
@@ -559,7 +537,7 @@ mod tests {
 			// each activated hash generates two scheduled cores: one with its value * 4, one with its value * 5
 			// given that the test configuration has a para_id of 16, there's only one way to get that value: with the 4
 			// hash.
-			assert_eq!(requested_local_validation_data, vec![[4; 32].into()]);
+			assert_eq!(requested_full_validation_data, vec![[4; 32].into()]);
 		}
 
 		#[test]
@@ -575,12 +553,6 @@ mod tests {
 				loop {
 					match handle.try_recv().await {
 						None => break,
-						Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
-							_hash,
-							RuntimeApiRequest::GlobalValidationData(tx),
-						))) => {
-							tx.send(Ok(Default::default())).unwrap();
-						}
 						Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
 							hash,
 							RuntimeApiRequest::AvailabilityCores(tx),
@@ -599,7 +571,7 @@ mod tests {
 						}
 						Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
 							_hash,
-							RuntimeApiRequest::LocalValidationData(
+							RuntimeApiRequest::FullValidationData(
 								_para_id,
 								_occupied_core_assumption,
 								tx,
@@ -647,8 +619,8 @@ mod tests {
 			// we don't care too much about the commitments_hash right now, but let's ensure that we've calculated the
 			// correct descriptor
 			let expect_pov_hash = test_collation().proof_of_validity.hash();
-			let expect_validation_data_hash =
-				validation_data_hash::<BlockNumber>(&Default::default(), &Default::default());
+			let expect_validation_data_hash
+				= PersistedValidationData::<BlockNumber>::default().hash();
 			let expect_relay_parent = Hash::repeat_byte(4);
 			let expect_payload = collator_signature_payload(
 				&expect_relay_parent,
@@ -661,7 +633,7 @@ mod tests {
 				para_id: config.para_id,
 				relay_parent: expect_relay_parent,
 				collator: config.key.public(),
-				validation_data_hash: expect_validation_data_hash,
+				persisted_validation_data_hash: expect_validation_data_hash,
 				pov_hash: expect_pov_hash,
 			};
 
@@ -680,7 +652,7 @@ mod tests {
 						&collator_signature_payload(
 							&descriptor.relay_parent,
 							&descriptor.para_id,
-							&descriptor.validation_data_hash,
+							&descriptor.persisted_validation_data_hash,
 							&descriptor.pov_hash,
 						)
 						.as_ref(),
diff --git a/polkadot/node/core/av-store/src/lib.rs b/polkadot/node/core/av-store/src/lib.rs
index 84381e438e608b40830d110f34b1212d1654e494..5837377dd5f975bb8e6e75fec1115fe78c1dbd68 100644
--- a/polkadot/node/core/av-store/src/lib.rs
+++ b/polkadot/node/core/av-store/src/lib.rs
@@ -356,8 +356,7 @@ mod tests {
 	};
 	use std::cell::RefCell;
 	use polkadot_primitives::v1::{
-		AvailableData, BlockData, HeadData, GlobalValidationData, LocalValidationData, PoV,
-		OmittedValidationData,
+		AvailableData, BlockData, HeadData, PersistedValidationData, PoV,
 	};
 	use polkadot_node_subsystem_test_helpers as test_helpers;
 
@@ -370,29 +369,19 @@ mod tests {
 	}
 
 	struct TestState {
-		global_validation_schedule: GlobalValidationData,
-		local_validation_data: LocalValidationData,
+		persisted_validation_data: PersistedValidationData,
 	}
 
 	impl Default for TestState {
 		fn default() -> Self {
 
-			let local_validation_data = LocalValidationData {
+			let persisted_validation_data = PersistedValidationData {
 				parent_head: HeadData(vec![7, 8, 9]),
-				balance: Default::default(),
-				code_upgrade_allowed: None,
-				validation_code_hash: Default::default(),
-			};
-
-			let global_validation_schedule = GlobalValidationData {
-				max_code_size: 1000,
-				max_head_data_size: 1000,
 				block_number: Default::default(),
+				hrmp_mqc_heads: Vec::new(),
 			};
-
 			Self {
-				local_validation_data,
-				global_validation_schedule,
+				persisted_validation_data,
 			}
 		}
 	}
@@ -470,17 +459,9 @@ mod tests {
 				block_data: BlockData(vec![4, 5, 6]),
 			};
 
-			let global_validation = test_state.global_validation_schedule;
-			let local_validation = test_state.local_validation_data;
-
-			let omitted_validation = OmittedValidationData {
-				global_validation,
-				local_validation,
-			};
-
 			let available_data = AvailableData {
 				pov,
-				omitted_validation,
+				validation_data: test_state.persisted_validation_data,
 			};
 
 
@@ -531,17 +512,9 @@ mod tests {
 				block_data: BlockData(vec![4, 5, 6]),
 			};
 
-			let global_validation = test_state.global_validation_schedule;
-			let local_validation = test_state.local_validation_data;
-
-			let omitted_validation = OmittedValidationData {
-				global_validation,
-				local_validation,
-			};
-
 			let available_data = AvailableData {
 				pov,
-				omitted_validation,
+				validation_data: test_state.persisted_validation_data,
 			};
 
 			let no_metrics = Metrics(None);
diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs
index 30a95965eb21cd066f89ef230d841bf552acbf62..6ca3b43d14fd229840f8ffe615827d7aa0bfb209 100644
--- a/polkadot/node/core/backing/src/lib.rs
+++ b/polkadot/node/core/backing/src/lib.rs
@@ -30,7 +30,7 @@ use futures::{
 use keystore::KeyStorePtr;
 use polkadot_primitives::v1::{
 	CommittedCandidateReceipt, BackedCandidate, Id as ParaId, ValidatorId,
-	ValidatorIndex, SigningContext, PoV, OmittedValidationData,
+	ValidatorIndex, SigningContext, PoV,
 	CandidateDescriptor, AvailableData, ValidatorSignature, Hash, CandidateReceipt,
 	CandidateCommitments, CoreState, CoreIndex,
 };
@@ -623,14 +623,9 @@ impl CandidateBackingJob {
 		outputs: ValidationOutputs,
 		with_commitments: impl FnOnce(CandidateCommitments) -> Result<T, E>,
 	) -> Result<Result<T, E>, Error> {
-		let omitted_validation = OmittedValidationData {
-			global_validation: outputs.global_validation_data,
-			local_validation: outputs.local_validation_data,
-		};
-
 		let available_data = AvailableData {
 			pov,
-			omitted_validation,
+			validation_data: outputs.validation_data,
 		};
 
 		let chunks = erasure_coding::obtain_chunks_v1(
@@ -835,7 +830,7 @@ mod tests {
 	use futures::{executor, future, Future};
 	use polkadot_primitives::v1::{
 		ScheduledCore, BlockData, CandidateCommitments, CollatorId,
-		LocalValidationData, GlobalValidationData, HeadData,
+		PersistedValidationData, ValidationData, TransientValidationData, HeadData,
 		ValidatorPair, ValidityAttestation, GroupRotationInfo,
 	};
 	use polkadot_subsystem::{
@@ -855,8 +850,7 @@ mod tests {
 		keystore: KeyStorePtr,
 		validators: Vec<Sr25519Keyring>,
 		validator_public: Vec<ValidatorId>,
-		global_validation_data: GlobalValidationData,
-		local_validation_data: LocalValidationData,
+		validation_data: ValidationData,
 		validator_groups: (Vec<Vec<ValidatorIndex>>, GroupRotationInfo),
 		availability_cores: Vec<CoreState>,
 		head_data: HashMap<ParaId, HeadData>,
@@ -920,17 +914,18 @@ mod tests {
 				parent_hash: relay_parent,
 			};
 
-			let local_validation_data = LocalValidationData {
-				parent_head: HeadData(vec![7, 8, 9]),
-				balance: Default::default(),
-				code_upgrade_allowed: None,
-				validation_code_hash: Default::default(),
-			};
-
-			let global_validation_data = GlobalValidationData {
-				max_code_size: 1000,
-				max_head_data_size: 1000,
-				block_number: Default::default(),
+			let validation_data = ValidationData {
+				persisted: PersistedValidationData {
+					parent_head: HeadData(vec![7, 8, 9]),
+					block_number: Default::default(),
+					hrmp_mqc_heads: Vec::new(),
+				},
+				transient: TransientValidationData {
+					max_code_size: 1000,
+					max_head_data_size: 1000,
+					balance: Default::default(),
+					code_upgrade_allowed: None,
+				},
 			};
 
 			Self {
@@ -941,8 +936,7 @@ mod tests {
 				validator_groups: (validator_groups, group_rotation_info),
 				availability_cores,
 				head_data,
-				local_validation_data,
-				global_validation_data,
+				validation_data,
 				signing_context,
 				relay_parent,
 			}
@@ -971,13 +965,8 @@ mod tests {
 	}
 
 	fn make_erasure_root(test: &TestState, pov: PoV) -> Hash {
-		let omitted_validation = OmittedValidationData {
-			global_validation: test.global_validation_data.clone(),
-			local_validation: test.local_validation_data.clone(),
-		};
-
 		let available_data = AvailableData {
-			omitted_validation,
+			validation_data: test.validation_data.persisted.clone(),
 			pov,
 		};
 
@@ -1109,8 +1098,7 @@ mod tests {
 				) if pov == pov && &c == candidate.descriptor() => {
 					tx.send(Ok(
 						ValidationResult::Valid(ValidationOutputs {
-							global_validation_data: test_state.global_validation_data,
-							local_validation_data: test_state.local_validation_data,
+							validation_data: test_state.validation_data.persisted,
 							head_data: expected_head_data.clone(),
 							upward_messages: Vec::new(),
 							fees: Default::default(),
@@ -1221,8 +1209,7 @@ mod tests {
 				) if pov == pov && &c == candidate_a.descriptor() => {
 					tx.send(Ok(
 						ValidationResult::Valid(ValidationOutputs {
-							global_validation_data: test_state.global_validation_data,
-							local_validation_data: test_state.local_validation_data,
+							validation_data: test_state.validation_data.persisted,
 							head_data: expected_head_data.clone(),
 							upward_messages: Vec::new(),
 							fees: Default::default(),
@@ -1351,8 +1338,7 @@ mod tests {
 				) if pov == pov && &c == candidate_a.descriptor() => {
 					tx.send(Ok(
 						ValidationResult::Valid(ValidationOutputs {
-							global_validation_data: test_state.global_validation_data,
-							local_validation_data: test_state.local_validation_data,
+							validation_data: test_state.validation_data.persisted,
 							head_data: expected_head_data.clone(),
 							upward_messages: Vec::new(),
 							fees: Default::default(),
@@ -1508,8 +1494,7 @@ mod tests {
 				) if pov == pov && &c == candidate_b.descriptor() => {
 					tx.send(Ok(
 						ValidationResult::Valid(ValidationOutputs {
-							global_validation_data: test_state.global_validation_data,
-							local_validation_data: test_state.local_validation_data,
+							validation_data: test_state.validation_data.persisted,
 							head_data: expected_head_data.clone(),
 							upward_messages: Vec::new(),
 							fees: Default::default(),
diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs
index bb832ee19554cd4e2bca134de67d7fdc6e13db14..600dde2c4e72a14bf06dd7243e28f79e88d34f8d 100644
--- a/polkadot/node/core/candidate-validation/src/lib.rs
+++ b/polkadot/node/core/candidate-validation/src/lib.rs
@@ -32,8 +32,8 @@ use polkadot_subsystem::{
 use polkadot_subsystem::errors::RuntimeApiError;
 use polkadot_node_primitives::{ValidationResult, ValidationOutputs, InvalidCandidate};
 use polkadot_primitives::v1::{
-	ValidationCode, OmittedValidationData, PoV, CandidateDescriptor, LocalValidationData,
-	GlobalValidationData, OccupiedCoreAssumption, Hash, validation_data_hash,
+	ValidationCode, PoV, CandidateDescriptor, ValidationData, PersistedValidationData,
+	TransientValidationData, OccupiedCoreAssumption, Hash,
 };
 use polkadot_parachain::wasm_executor::{self, ValidationPool, ExecutionMode, ValidationError,
 	InvalidCandidate as WasmInvalidCandidate};
@@ -158,7 +158,8 @@ async fn run(
 					}
 				}
 				CandidateValidationMessage::ValidateFromExhaustive(
-					omitted_validation,
+					persisted_validation_data,
+					transient_validation_data,
 					validation_code,
 					descriptor,
 					pov,
@@ -167,7 +168,8 @@ async fn run(
 					let res = spawn_validate_exhaustive(
 						&mut ctx,
 						Some(pool.clone()),
-						omitted_validation,
+						persisted_validation_data,
+						transient_validation_data,
 						validation_code,
 						descriptor,
 						pov,
@@ -210,7 +212,7 @@ async fn runtime_api_request<T>(
 
 #[derive(Debug)]
 enum AssumptionCheckOutcome {
-	Matches(OmittedValidationData, ValidationCode),
+	Matches(ValidationData, ValidationCode),
 	DoesNotMatch,
 	BadRequest,
 }
@@ -218,15 +220,14 @@ enum AssumptionCheckOutcome {
 async fn check_assumption_validation_data(
 	ctx: &mut impl SubsystemContext<Message = CandidateValidationMessage>,
 	descriptor: &CandidateDescriptor,
-	global_validation_data: &GlobalValidationData,
 	assumption: OccupiedCoreAssumption,
 ) -> SubsystemResult<AssumptionCheckOutcome> {
-	let local_validation_data = {
+	let validation_data = {
 		let (tx, rx) = oneshot::channel();
 		let d = runtime_api_request(
 			ctx,
 			descriptor.relay_parent,
-			RuntimeApiRequest::LocalValidationData(
+			RuntimeApiRequest::FullValidationData(
 				descriptor.para_id,
 				assumption,
 				tx,
@@ -242,17 +243,9 @@ async fn check_assumption_validation_data(
 		}
 	};
 
-	let validation_data_hash = validation_data_hash(
-		&global_validation_data,
-		&local_validation_data,
-	);
-
-	SubsystemResult::Ok(if descriptor.validation_data_hash == validation_data_hash {
-		let omitted_validation = OmittedValidationData {
-			global_validation: global_validation_data.clone(),
-			local_validation: local_validation_data,
-		};
+	let persisted_validation_data_hash = validation_data.persisted.hash();
 
+	SubsystemResult::Ok(if descriptor.persisted_validation_data_hash == persisted_validation_data_hash {
 		let (code_tx, code_rx) = oneshot::channel();
 		let validation_code = runtime_api_request(
 			ctx,
@@ -267,7 +260,7 @@ async fn check_assumption_validation_data(
 
 		match validation_code {
 			Ok(None) | Err(_) => AssumptionCheckOutcome::BadRequest,
-			Ok(Some(v)) => AssumptionCheckOutcome::Matches(omitted_validation, v),
+			Ok(Some(v)) => AssumptionCheckOutcome::Matches(validation_data, v),
 		}
 	} else {
 		AssumptionCheckOutcome::DoesNotMatch
@@ -281,44 +274,21 @@ async fn spawn_validate_from_chain_state(
 	pov: Arc<PoV>,
 	spawn: impl SpawnNamed + 'static,
 ) -> SubsystemResult<Result<ValidationResult, ValidationFailed>> {
-	// The candidate descriptor has a `validation_data_hash` which corresponds to
+	// The candidate descriptor has a `persisted_validation_data_hash` which corresponds to
 	// one of up to two possible values that we can derive from the state of the
-	// relay-parent. We can fetch these values by getting the `global_validation_data`,
-	// and both `local_validation_data` based on the different `OccupiedCoreAssumption`s.
-	let global_validation_data = {
-		let (tx, rx) = oneshot::channel();
-		let res = runtime_api_request(
-			ctx,
-			descriptor.relay_parent,
-			RuntimeApiRequest::GlobalValidationData(tx),
-			rx,
-		).await?;
-
-		match res {
-			Ok(g) => g,
-			Err(e) => {
-				log::warn!(
-					target: LOG_TARGET,
-					"Error making runtime API request: {:?}",
-					e,
-				);
-
-				return Ok(Err(ValidationFailed("Error making API request".into())));
-			}
-		}
-	};
-
+	// relay-parent. We can fetch these values by getting the persisted validation data
+	// based on the different `OccupiedCoreAssumption`s.
 	match check_assumption_validation_data(
 		ctx,
 		&descriptor,
-		&global_validation_data,
 		OccupiedCoreAssumption::Included,
 	).await? {
-		AssumptionCheckOutcome::Matches(omitted_validation, validation_code) => {
+		AssumptionCheckOutcome::Matches(validation_data, validation_code) => {
 			return spawn_validate_exhaustive(
 				ctx,
 				validation_pool,
-				omitted_validation,
+				validation_data.persisted,
+				Some(validation_data.transient),
 				validation_code,
 				descriptor,
 				pov,
@@ -332,14 +302,14 @@ async fn spawn_validate_from_chain_state(
 	match check_assumption_validation_data(
 		ctx,
 		&descriptor,
-		&global_validation_data,
 		OccupiedCoreAssumption::TimedOut,
 	).await? {
-		AssumptionCheckOutcome::Matches(omitted_validation, validation_code) => {
+		AssumptionCheckOutcome::Matches(validation_data, validation_code) => {
 			return spawn_validate_exhaustive(
 				ctx,
 				validation_pool,
-				omitted_validation,
+				validation_data.persisted,
+				Some(validation_data.transient),
 				validation_code,
 				descriptor,
 				pov,
@@ -351,7 +321,7 @@ async fn spawn_validate_from_chain_state(
 	}
 
 	// If neither the assumption of the occupied core having the para included or the assumption
-	// of the occupied core timing out are valid, then the validation_data_hash in the descriptor
+	// of the occupied core timing out are valid, then the persisted_validation_data_hash in the descriptor
 	// is not based on the relay parent and is thus invalid.
 	Ok(Ok(ValidationResult::Invalid(InvalidCandidate::BadParent)))
 }
@@ -359,7 +329,8 @@ async fn spawn_validate_from_chain_state(
 async fn spawn_validate_exhaustive(
 	ctx: &mut impl SubsystemContext<Message = CandidateValidationMessage>,
 	validation_pool: Option<ValidationPool>,
-	omitted_validation: OmittedValidationData,
+	persisted_validation_data: PersistedValidationData,
+	transient_validation_data: Option<TransientValidationData>,
 	validation_code: ValidationCode,
 	descriptor: CandidateDescriptor,
 	pov: Arc<PoV>,
@@ -369,7 +340,8 @@ async fn spawn_validate_exhaustive(
 	let fut = async move {
 		let res = validate_candidate_exhaustive::<RealValidationBackend, _>(
 			validation_pool,
-			omitted_validation,
+			persisted_validation_data,
+			transient_validation_data,
 			validation_code,
 			descriptor,
 			pov,
@@ -414,16 +386,19 @@ fn perform_basic_checks(
 ///
 /// Returns `Ok(())` if checks pass, error otherwise.
 fn check_wasm_result_against_constraints(
-	global_validation_data: &GlobalValidationData,
-	_local_validation_data: &LocalValidationData,
+	transient_params: &TransientValidationData,
 	result: &WasmValidationResult,
 ) -> Result<(), InvalidCandidate> {
-	if result.head_data.0.len() > global_validation_data.max_head_data_size as _ {
+	if result.head_data.0.len() > transient_params.max_head_data_size as _ {
 		return Err(InvalidCandidate::HeadDataTooLarge(result.head_data.0.len() as u64))
 	}
 
 	if let Some(ref code) = result.new_validation_code {
-		if code.0.len() > global_validation_data.max_code_size as _ {
+		if transient_params.code_upgrade_allowed.is_none() {
+			return Err(InvalidCandidate::CodeUpgradeNotAllowed)
+		}
+
+		if code.0.len() > transient_params.max_code_size as _ {
 			return Err(InvalidCandidate::NewCodeTooLarge(code.0.len() as u64))
 		}
 	}
@@ -471,7 +446,8 @@ impl ValidationBackend for RealValidationBackend {
 /// Sends the result of validation on the channel once complete.
 fn validate_candidate_exhaustive<B: ValidationBackend, S: SpawnNamed + 'static>(
 	backend_arg: B::Arg,
-	omitted_validation: OmittedValidationData,
+	persisted_validation_data: PersistedValidationData,
+	transient_validation_data: Option<TransientValidationData>,
 	validation_code: ValidationCode,
 	descriptor: CandidateDescriptor,
 	pov: Arc<PoV>,
@@ -481,15 +457,11 @@ fn validate_candidate_exhaustive<B: ValidationBackend, S: SpawnNamed + 'static>(
 		return Ok(ValidationResult::Invalid(e))
 	}
 
-	let OmittedValidationData { global_validation, local_validation } = omitted_validation;
-
 	let params = ValidationParams {
-		parent_head: local_validation.parent_head.clone(),
+		parent_head: persisted_validation_data.parent_head.clone(),
 		block_data: pov.block_data.clone(),
-		max_code_size: global_validation.max_code_size,
-		max_head_data_size: global_validation.max_head_data_size,
-		relay_chain_height: global_validation.block_number,
-		code_upgrade_allowed: local_validation.code_upgrade_allowed,
+		relay_chain_height: persisted_validation_data.block_number,
+		hrmp_mqc_heads: persisted_validation_data.hrmp_mqc_heads.clone(),
 	};
 
 	match B::validate(backend_arg, &validation_code, params, spawn) {
@@ -507,17 +479,19 @@ fn validate_candidate_exhaustive<B: ValidationBackend, S: SpawnNamed + 'static>(
 			Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(e.to_string()))),
 		Err(ValidationError::Internal(e)) => Err(ValidationFailed(e.to_string())),
 		Ok(res) => {
-			let post_check_result = check_wasm_result_against_constraints(
-				&global_validation,
-				&local_validation,
-				&res,
-			);
+			let post_check_result = if let Some(transient) = transient_validation_data {
+				check_wasm_result_against_constraints(
+					&transient,
+					&res,
+				)
+			} else {
+				Ok(())
+			};
 
 			Ok(match post_check_result {
 				Ok(()) => ValidationResult::Valid(ValidationOutputs {
 					head_data: res.head_data,
-					global_validation_data: global_validation,
-					local_validation_data: local_validation,
+					validation_data: persisted_validation_data,
 					upward_messages: res.upward_messages,
 					fees: 0,
 					new_validation_code: res.new_validation_code,
@@ -562,7 +536,7 @@ mod tests {
 		let payload = polkadot_primitives::v1::collator_signature_payload(
 			&descriptor.relay_parent,
 			&descriptor.para_id,
-			&descriptor.validation_data_hash,
+			&descriptor.persisted_validation_data_hash,
 			&descriptor.pov_hash,
 		);
 
@@ -572,17 +546,16 @@ mod tests {
 
 	#[test]
 	fn correctly_checks_included_assumption() {
-		let local_validation_data = LocalValidationData::default();
-		let global_validation_data = GlobalValidationData::default();
+		let validation_data: ValidationData = Default::default();
 		let validation_code: ValidationCode = vec![1, 2, 3].into();
 
-		let validation_data_hash = validation_data_hash(&global_validation_data, &local_validation_data);
+		let persisted_validation_data_hash = validation_data.persisted.hash();
 		let relay_parent = [2; 32].into();
 		let para_id = 5.into();
 
 		let mut candidate = CandidateDescriptor::default();
 		candidate.relay_parent = relay_parent;
-		candidate.validation_data_hash = validation_data_hash;
+		candidate.persisted_validation_data_hash = persisted_validation_data_hash;
 		candidate.para_id = para_id;
 
 		let pool = TaskExecutor::new();
@@ -591,22 +564,20 @@ mod tests {
 		let (check_fut, check_result) = check_assumption_validation_data(
 			&mut ctx,
 			&candidate,
-			&global_validation_data,
 			OccupiedCoreAssumption::Included,
 		).remote_handle();
 
-		let global_validation_data = global_validation_data.clone();
 		let test_fut = async move {
 			assert_matches!(
 				ctx_handle.recv().await,
 				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
 					rp,
-					RuntimeApiRequest::LocalValidationData(p, OccupiedCoreAssumption::Included, tx)
+					RuntimeApiRequest::FullValidationData(p, OccupiedCoreAssumption::Included, tx)
 				)) => {
 					assert_eq!(rp, relay_parent);
 					assert_eq!(p, para_id);
 
-					let _ = tx.send(Ok(Some(local_validation_data.clone())));
+					let _ = tx.send(Ok(Some(validation_data.clone())));
 				}
 			);
 
@@ -624,11 +595,7 @@ mod tests {
 			);
 
 			assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::Matches(o, v) => {
-				assert_eq!(o, OmittedValidationData {
-					local_validation: local_validation_data,
-					global_validation: global_validation_data,
-				});
-
+				assert_eq!(o, validation_data);
 				assert_eq!(v, validation_code);
 			});
 		};
@@ -639,17 +606,16 @@ mod tests {
 
 	#[test]
 	fn correctly_checks_timed_out_assumption() {
-		let local_validation_data = LocalValidationData::default();
-		let global_validation_data = GlobalValidationData::default();
+		let validation_data: ValidationData = Default::default();
 		let validation_code: ValidationCode = vec![1, 2, 3].into();
 
-		let validation_data_hash = validation_data_hash(&global_validation_data, &local_validation_data);
+		let persisted_validation_data_hash = validation_data.persisted.hash();
 		let relay_parent = [2; 32].into();
 		let para_id = 5.into();
 
 		let mut candidate = CandidateDescriptor::default();
 		candidate.relay_parent = relay_parent;
-		candidate.validation_data_hash = validation_data_hash;
+		candidate.persisted_validation_data_hash = persisted_validation_data_hash;
 		candidate.para_id = para_id;
 
 		let pool = TaskExecutor::new();
@@ -658,22 +624,20 @@ mod tests {
 		let (check_fut, check_result) = check_assumption_validation_data(
 			&mut ctx,
 			&candidate,
-			&global_validation_data,
 			OccupiedCoreAssumption::TimedOut,
 		).remote_handle();
 
-		let global_validation_data = global_validation_data.clone();
 		let test_fut = async move {
 			assert_matches!(
 				ctx_handle.recv().await,
 				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
 					rp,
-					RuntimeApiRequest::LocalValidationData(p, OccupiedCoreAssumption::TimedOut, tx)
+					RuntimeApiRequest::FullValidationData(p, OccupiedCoreAssumption::TimedOut, tx)
 				)) => {
 					assert_eq!(rp, relay_parent);
 					assert_eq!(p, para_id);
 
-					let _ = tx.send(Ok(Some(local_validation_data.clone())));
+					let _ = tx.send(Ok(Some(validation_data.clone())));
 				}
 			);
 
@@ -691,11 +655,7 @@ mod tests {
 			);
 
 			assert_matches!(check_result.await.unwrap(), AssumptionCheckOutcome::Matches(o, v) => {
-				assert_eq!(o, OmittedValidationData {
-					local_validation: local_validation_data,
-					global_validation: global_validation_data,
-				});
-
+				assert_eq!(o, validation_data);
 				assert_eq!(v, validation_code);
 			});
 		};
@@ -706,16 +666,14 @@ mod tests {
 
 	#[test]
 	fn check_is_bad_request_if_no_validation_data() {
-		let local_validation_data = LocalValidationData::default();
-		let global_validation_data = GlobalValidationData::default();
-
-		let validation_data_hash = validation_data_hash(&global_validation_data, &local_validation_data);
+		let validation_data: ValidationData = Default::default();
+		let persisted_validation_data_hash = validation_data.persisted.hash();
 		let relay_parent = [2; 32].into();
 		let para_id = 5.into();
 
 		let mut candidate = CandidateDescriptor::default();
 		candidate.relay_parent = relay_parent;
-		candidate.validation_data_hash = validation_data_hash;
+		candidate.persisted_validation_data_hash = persisted_validation_data_hash;
 		candidate.para_id = para_id;
 
 		let pool = TaskExecutor::new();
@@ -724,7 +682,6 @@ mod tests {
 		let (check_fut, check_result) = check_assumption_validation_data(
 			&mut ctx,
 			&candidate,
-			&global_validation_data,
 			OccupiedCoreAssumption::Included,
 		).remote_handle();
 
@@ -733,7 +690,7 @@ mod tests {
 				ctx_handle.recv().await,
 				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
 					rp,
-					RuntimeApiRequest::LocalValidationData(p, OccupiedCoreAssumption::Included, tx)
+					RuntimeApiRequest::FullValidationData(p, OccupiedCoreAssumption::Included, tx)
 				)) => {
 					assert_eq!(rp, relay_parent);
 					assert_eq!(p, para_id);
@@ -751,16 +708,14 @@ mod tests {
 
 	#[test]
 	fn check_is_bad_request_if_no_validation_code() {
-		let local_validation_data = LocalValidationData::default();
-		let global_validation_data = GlobalValidationData::default();
-
-		let validation_data_hash = validation_data_hash(&global_validation_data, &local_validation_data);
+		let validation_data: ValidationData = Default::default();
+		let persisted_validation_data_hash = validation_data.persisted.hash();
 		let relay_parent = [2; 32].into();
 		let para_id = 5.into();
 
 		let mut candidate = CandidateDescriptor::default();
 		candidate.relay_parent = relay_parent;
-		candidate.validation_data_hash = validation_data_hash;
+		candidate.persisted_validation_data_hash = persisted_validation_data_hash;
 		candidate.para_id = para_id;
 
 		let pool = TaskExecutor::new();
@@ -769,7 +724,6 @@ mod tests {
 		let (check_fut, check_result) = check_assumption_validation_data(
 			&mut ctx,
 			&candidate,
-			&global_validation_data,
 			OccupiedCoreAssumption::TimedOut,
 		).remote_handle();
 
@@ -778,12 +732,12 @@ mod tests {
 				ctx_handle.recv().await,
 				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
 					rp,
-					RuntimeApiRequest::LocalValidationData(p, OccupiedCoreAssumption::TimedOut, tx)
+					RuntimeApiRequest::FullValidationData(p, OccupiedCoreAssumption::TimedOut, tx)
 				)) => {
 					assert_eq!(rp, relay_parent);
 					assert_eq!(p, para_id);
 
-					let _ = tx.send(Ok(Some(local_validation_data.clone())));
+					let _ = tx.send(Ok(Some(validation_data.clone())));
 				}
 			);
 
@@ -809,15 +763,13 @@ mod tests {
 
 	#[test]
 	fn check_does_not_match() {
-		let local_validation_data = LocalValidationData::default();
-		let global_validation_data = GlobalValidationData::default();
-
+		let validation_data: ValidationData = Default::default();
 		let relay_parent = [2; 32].into();
 		let para_id = 5.into();
 
 		let mut candidate = CandidateDescriptor::default();
 		candidate.relay_parent = relay_parent;
-		candidate.validation_data_hash = [3; 32].into();
+		candidate.persisted_validation_data_hash = [3; 32].into();
 		candidate.para_id = para_id;
 
 		let pool = TaskExecutor::new();
@@ -826,7 +778,6 @@ mod tests {
 		let (check_fut, check_result) = check_assumption_validation_data(
 			&mut ctx,
 			&candidate,
-			&global_validation_data,
 			OccupiedCoreAssumption::Included,
 		).remote_handle();
 
@@ -835,12 +786,12 @@ mod tests {
 				ctx_handle.recv().await,
 				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
 					rp,
-					RuntimeApiRequest::LocalValidationData(p, OccupiedCoreAssumption::Included, tx)
+					RuntimeApiRequest::FullValidationData(p, OccupiedCoreAssumption::Included, tx)
 				)) => {
 					assert_eq!(rp, relay_parent);
 					assert_eq!(p, para_id);
 
-					let _ = tx.send(Ok(Some(local_validation_data.clone())));
+					let _ = tx.send(Ok(Some(validation_data.clone())));
 				}
 			);
 
@@ -853,13 +804,10 @@ mod tests {
 
 	#[test]
 	fn candidate_validation_ok_is_ok() {
-		let mut omitted_validation = OmittedValidationData {
-			local_validation: Default::default(),
-			global_validation: Default::default(),
-		};
-
-		omitted_validation.global_validation.max_head_data_size = 1024;
-		omitted_validation.global_validation.max_code_size = 1024;
+		let mut validation_data: ValidationData = Default::default();
+		validation_data.transient.max_head_data_size = 1024;
+		validation_data.transient.max_code_size = 1024;
+		validation_data.transient.code_upgrade_allowed = Some(20);
 
 		let pov = PoV { block_data: BlockData(vec![1; 32]) };
 
@@ -877,14 +825,14 @@ mod tests {
 		};
 
 		assert!(check_wasm_result_against_constraints(
-			&omitted_validation.global_validation,
-			&omitted_validation.local_validation,
+			&validation_data.transient,
 			&validation_result,
 		).is_ok());
 
 		let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
 			MockValidationArg { result: Ok(validation_result) },
-			omitted_validation.clone(),
+			validation_data.persisted.clone(),
+			Some(validation_data.transient),
 			vec![1, 2, 3].into(),
 			descriptor,
 			Arc::new(pov),
@@ -893,8 +841,7 @@ mod tests {
 
 		assert_matches!(v, ValidationResult::Valid(outputs) => {
 			assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1]));
-			assert_eq!(outputs.global_validation_data, omitted_validation.global_validation);
-			assert_eq!(outputs.local_validation_data, omitted_validation.local_validation);
+			assert_eq!(outputs.validation_data, validation_data.persisted);
 			assert_eq!(outputs.upward_messages, Vec::new());
 			assert_eq!(outputs.fees, 0);
 			assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into()));
@@ -903,13 +850,11 @@ mod tests {
 
 	#[test]
 	fn candidate_validation_bad_return_is_invalid() {
-		let mut omitted_validation = OmittedValidationData {
-			local_validation: Default::default(),
-			global_validation: Default::default(),
-		};
+		let mut validation_data: ValidationData = Default::default();
 
-		omitted_validation.global_validation.max_head_data_size = 1024;
-		omitted_validation.global_validation.max_code_size = 1024;
+		validation_data.transient.max_head_data_size = 1024;
+		validation_data.transient.max_code_size = 1024;
+		validation_data.transient.code_upgrade_allowed = Some(20);
 
 		let pov = PoV { block_data: BlockData(vec![1; 32]) };
 
@@ -927,8 +872,7 @@ mod tests {
 		};
 
 		assert!(check_wasm_result_against_constraints(
-			&omitted_validation.global_validation,
-			&omitted_validation.local_validation,
+			&validation_data.transient,
 			&validation_result,
 		).is_ok());
 
@@ -938,7 +882,8 @@ mod tests {
 					WasmInvalidCandidate::BadReturn
 				))
 			},
-			omitted_validation.clone(),
+			validation_data.persisted,
+			Some(validation_data.transient),
 			vec![1, 2, 3].into(),
 			descriptor,
 			Arc::new(pov),
@@ -951,13 +896,11 @@ mod tests {
 
 	#[test]
 	fn candidate_validation_timeout_is_internal_error() {
-		let mut omitted_validation = OmittedValidationData {
-			local_validation: Default::default(),
-			global_validation: Default::default(),
-		};
+		let mut validation_data: ValidationData = Default::default();
 
-		omitted_validation.global_validation.max_head_data_size = 1024;
-		omitted_validation.global_validation.max_code_size = 1024;
+		validation_data.transient.max_head_data_size = 1024;
+		validation_data.transient.max_code_size = 1024;
+		validation_data.transient.code_upgrade_allowed = Some(20);
 
 		let pov = PoV { block_data: BlockData(vec![1; 32]) };
 
@@ -975,8 +918,7 @@ mod tests {
 		};
 
 		assert!(check_wasm_result_against_constraints(
-			&omitted_validation.global_validation,
-			&omitted_validation.local_validation,
+			&validation_data.transient,
 			&validation_result,
 		).is_ok());
 
@@ -986,7 +928,8 @@ mod tests {
 					WasmInvalidCandidate::Timeout
 				))
 			},
-			omitted_validation.clone(),
+			validation_data.persisted,
+			Some(validation_data.transient),
 			vec![1, 2, 3].into(),
 			descriptor,
 			Arc::new(pov),
@@ -995,4 +938,49 @@ mod tests {
 
 		assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::Timeout)));
 	}
+
+	#[test]
+	fn candidate_validation_ok_does_not_validate_outputs_if_no_transient() {
+		let mut validation_data: ValidationData = Default::default();
+		validation_data.transient.max_head_data_size = 1;
+		validation_data.transient.max_code_size = 1;
+
+		let pov = PoV { block_data: BlockData(vec![1; 32]) };
+
+		let mut descriptor = CandidateDescriptor::default();
+		descriptor.pov_hash = pov.hash();
+		collator_sign(&mut descriptor, Sr25519Keyring::Alice);
+
+		assert!(perform_basic_checks(&descriptor, Some(1024), &pov).is_ok());
+
+		let validation_result = WasmValidationResult {
+			head_data: HeadData(vec![1, 1, 1]),
+			new_validation_code: Some(vec![2, 2, 2].into()),
+			upward_messages: Vec::new(),
+			processed_downward_messages: 0,
+		};
+
+		assert!(check_wasm_result_against_constraints(
+			&validation_data.transient,
+			&validation_result,
+		).is_err());
+
+		let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
+			MockValidationArg { result: Ok(validation_result) },
+			validation_data.persisted.clone(),
+			None,
+			vec![1, 2, 3].into(),
+			descriptor,
+			Arc::new(pov),
+			TaskExecutor::new(),
+		).unwrap();
+
+		assert_matches!(v, ValidationResult::Valid(outputs) => {
+			assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1]));
+			assert_eq!(outputs.validation_data, validation_data.persisted);
+			assert_eq!(outputs.upward_messages, Vec::new());
+			assert_eq!(outputs.fees, 0);
+			assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into()));
+		});
+	}
 }
diff --git a/polkadot/node/core/provisioner/src/lib.rs b/polkadot/node/core/provisioner/src/lib.rs
index ef93d15ab8324550e3712d3b5c2c0b667b3119e2..24254734289bd9c6f00f6d44158bb56298f5d8c1 100644
--- a/polkadot/node/core/provisioner/src/lib.rs
+++ b/polkadot/node/core/provisioner/src/lib.rs
@@ -35,11 +35,10 @@ use polkadot_node_subsystem::{
 use polkadot_node_subsystem_util::{
 	self as util,
 	delegated_subsystem,
-	request_availability_cores, request_global_validation_data,
-	request_local_validation_data, JobTrait, ToJobTrait,
+	request_availability_cores, request_persisted_validation_data, JobTrait, ToJobTrait,
 };
 use polkadot_primitives::v1::{
-	validation_data_hash, BackedCandidate, BlockNumber, CoreState, Hash, OccupiedCoreAssumption,
+	BackedCandidate, BlockNumber, CoreState, Hash, OccupiedCoreAssumption,
 	SignedAvailabilityBitfield,
 };
 use std::{collections::HashMap, convert::TryFrom, pin::Pin};
@@ -355,10 +354,6 @@ async fn select_candidates(
 ) -> Result<Vec<BackedCandidate>, Error> {
 	let block_number = get_block_number_under_construction(relay_parent, sender).await?;
 
-	let global_validation_data = request_global_validation_data(relay_parent, sender)
-		.await?
-		.await??;
-
 	let mut selected_candidates =
 		Vec::with_capacity(candidates.len().min(availability_cores.len()));
 
@@ -387,7 +382,7 @@ async fn select_candidates(
 			_ => continue,
 		};
 
-		let local_validation_data = match request_local_validation_data(
+		let validation_data = match request_persisted_validation_data(
 			relay_parent,
 			scheduled_core.para_id,
 			assumption,
@@ -396,18 +391,17 @@ async fn select_candidates(
 		.await?
 		.await??
 		{
-			Some(local_validation_data) => local_validation_data,
+			Some(v) => v,
 			None => continue,
 		};
 
-		let computed_validation_data_hash =
-			validation_data_hash(&global_validation_data, &local_validation_data);
+		let computed_validation_data_hash = validation_data.hash();
 
 		// we arbitrarily pick the first of the backed candidates which match the appropriate selection criteria
 		if let Some(candidate) = candidates.iter().find(|backed_candidate| {
 			let descriptor = &backed_candidate.candidate.descriptor;
 			descriptor.para_id == scheduled_core.para_id
-				&& descriptor.validation_data_hash == computed_validation_data_hash
+				&& descriptor.persisted_validation_data_hash == computed_validation_data_hash
 		}) {
 			selected_candidates.push(candidate.clone());
 		}
@@ -657,10 +651,10 @@ mod tests {
 		use super::super::*;
 		use super::{build_occupied_core, default_bitvec, occupied_core, scheduled_core};
 		use polkadot_node_subsystem::messages::RuntimeApiRequest::{
-			AvailabilityCores, GlobalValidationData, LocalValidationData,
+			AvailabilityCores, PersistedValidationData as PersistedValidationDataReq,
 		};
 		use polkadot_primitives::v1::{
-			BlockNumber, CandidateDescriptor, CommittedCandidateReceipt,
+			BlockNumber, CandidateDescriptor, CommittedCandidateReceipt, PersistedValidationData,
 		};
 		use FromJob::{ChainApi, Runtime};
 
@@ -771,12 +765,9 @@ mod tests {
 					ChainApi(BlockNumber(_relay_parent, tx)) => {
 						tx.send(Ok(Some(BLOCK_UNDER_PRODUCTION - 1))).unwrap()
 					}
-					Runtime(Request(_parent_hash, GlobalValidationData(tx))) => {
-						tx.send(Ok(Default::default())).unwrap()
-					}
 					Runtime(Request(
 						_parent_hash,
-						LocalValidationData(_para_id, _assumption, tx),
+						PersistedValidationDataReq(_para_id, _assumption, tx),
 					)) => tx.send(Ok(Some(Default::default()))).unwrap(),
 					Runtime(Request(_parent_hash, AvailabilityCores(tx))) => {
 						tx.send(Ok(mock_availability_cores())).unwrap()
@@ -823,14 +814,12 @@ mod tests {
 		fn selects_correct_candidates() {
 			let mock_cores = mock_availability_cores();
 
-			let empty_hash =
-				validation_data_hash::<BlockNumber>(&Default::default(), &Default::default());
-			dbg!(empty_hash);
+			let empty_hash = PersistedValidationData::<BlockNumber>::default().hash();
 
 			let candidate_template = BackedCandidate {
 				candidate: CommittedCandidateReceipt {
 					descriptor: CandidateDescriptor {
-						validation_data_hash: empty_hash,
+						persisted_validation_data_hash: empty_hash,
 						..Default::default()
 					},
 					..Default::default()
@@ -855,7 +844,8 @@ mod tests {
 						candidate
 					} else if idx < mock_cores.len() * 2 {
 						// for the second repetition of the candidates, give them the wrong hash
-						candidate.candidate.descriptor.validation_data_hash = Default::default();
+						candidate.candidate.descriptor.persisted_validation_data_hash
+							= Default::default();
 						candidate
 					} else {
 						// third go-around: right hash, wrong para_id
diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs
index dd5f247eb0dad6b9d4a26016829d95b227c8f8a3..3c05b71dd7000405b8e711c1879f90feed2f2bc3 100644
--- a/polkadot/node/core/runtime-api/src/lib.rs
+++ b/polkadot/node/core/runtime-api/src/lib.rs
@@ -110,9 +110,10 @@ fn make_runtime_api_request<Client>(
 		Request::Validators(sender) => query!(validators(), sender),
 		Request::ValidatorGroups(sender) => query!(validator_groups(), sender),
 		Request::AvailabilityCores(sender) => query!(availability_cores(), sender),
-		Request::GlobalValidationData(sender) => query!(global_validation_data(), sender),
-		Request::LocalValidationData(para, assumption, sender) =>
-			query!(local_validation_data(para, assumption), sender),
+		Request::PersistedValidationData(para, assumption, sender) =>
+			query!(persisted_validation_data(para, assumption), sender),
+		Request::FullValidationData(para, assumption, sender) =>
+			query!(full_validation_data(para, assumption), sender),
 		Request::SessionIndexForChild(sender) => query!(session_index_for_child(), sender),
 		Request::ValidationCode(para, assumption, sender) =>
 			query!(validation_code(para, assumption), sender),
@@ -166,8 +167,8 @@ mod tests {
 	use super::*;
 
 	use polkadot_primitives::v1::{
-		ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, GlobalValidationData,
-		Id as ParaId, OccupiedCoreAssumption, LocalValidationData, SessionIndex, ValidationCode,
+		ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, PersistedValidationData,
+		Id as ParaId, OccupiedCoreAssumption, ValidationData, SessionIndex, ValidationCode,
 		CommittedCandidateReceipt, CandidateEvent,
 	};
 	use polkadot_node_subsystem_test_helpers as test_helpers;
@@ -181,8 +182,7 @@ mod tests {
 		validators: Vec<ValidatorId>,
 		validator_groups: Vec<Vec<ValidatorIndex>>,
 		availability_cores: Vec<CoreState>,
-		global_validation_data: GlobalValidationData,
-		local_validation_data: HashMap<ParaId, LocalValidationData>,
+		validation_data: HashMap<ParaId, ValidationData>,
 		session_index_for_child: SessionIndex,
 		validation_code: HashMap<ParaId, ValidationCode>,
 		candidate_pending_availability: HashMap<ParaId, CommittedCandidateReceipt>,
@@ -220,16 +220,20 @@ mod tests {
 				self.availability_cores.clone()
 			}
 
-			fn global_validation_data(&self) -> GlobalValidationData {
-				self.global_validation_data.clone()
+			fn persisted_validation_data(
+				&self,
+				para: ParaId,
+				_assumption: OccupiedCoreAssumption,
+			) -> Option<PersistedValidationData> {
+				self.validation_data.get(&para).map(|l| l.persisted.clone())
 			}
 
-			fn local_validation_data(
+			fn full_validation_data(
 				&self,
 				para: ParaId,
 				_assumption: OccupiedCoreAssumption,
-			) -> Option<LocalValidationData> {
-				self.local_validation_data.get(&para).map(|l| l.clone())
+			) -> Option<ValidationData> {
+				self.validation_data.get(&para).map(|l| l.clone())
 			}
 
 			fn session_index_for_child(&self) -> SessionIndex {
@@ -327,10 +331,14 @@ mod tests {
 	}
 
 	#[test]
-	fn requests_global_validation_data() {
+	fn requests_persisted_validation_data() {
 		let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new());
-		let runtime_api = MockRuntimeApi::default();
+		let mut runtime_api = MockRuntimeApi::default();
 		let relay_parent = [1; 32].into();
+		let para_a = 5.into();
+		let para_b = 6.into();
+
+		runtime_api.validation_data.insert(para_a, Default::default());
 
 		let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None));
 		let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
@@ -338,10 +346,23 @@ mod tests {
 			let (tx, rx) = oneshot::channel();
 
 			ctx_handle.send(FromOverseer::Communication {
-				msg: RuntimeApiMessage::Request(relay_parent, Request::GlobalValidationData(tx))
+				msg: RuntimeApiMessage::Request(
+					relay_parent,
+					Request::PersistedValidationData(para_a, OccupiedCoreAssumption::Included, tx)
+				),
 			}).await;
 
-			assert_eq!(rx.await.unwrap().unwrap(), runtime_api.global_validation_data);
+			assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default()));
+
+			let (tx, rx) = oneshot::channel();
+			ctx_handle.send(FromOverseer::Communication {
+				msg: RuntimeApiMessage::Request(
+					relay_parent,
+					Request::PersistedValidationData(para_b, OccupiedCoreAssumption::Included, tx)
+				),
+			}).await;
+
+			assert_eq!(rx.await.unwrap().unwrap(), None);
 
 			ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
 		};
@@ -350,14 +371,14 @@ mod tests {
 	}
 
 	#[test]
-	fn requests_local_validation_data() {
+	fn requests_full_validation_data() {
 		let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new());
 		let mut runtime_api = MockRuntimeApi::default();
 		let relay_parent = [1; 32].into();
 		let para_a = 5.into();
 		let para_b = 6.into();
 
-		runtime_api.local_validation_data.insert(para_a, Default::default());
+		runtime_api.validation_data.insert(para_a, Default::default());
 
 		let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None));
 		let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap());
@@ -367,7 +388,7 @@ mod tests {
 			ctx_handle.send(FromOverseer::Communication {
 				msg: RuntimeApiMessage::Request(
 					relay_parent,
-					Request::LocalValidationData(para_a, OccupiedCoreAssumption::Included, tx)
+					Request::FullValidationData(para_a, OccupiedCoreAssumption::Included, tx)
 				),
 			}).await;
 
@@ -377,7 +398,7 @@ mod tests {
 			ctx_handle.send(FromOverseer::Communication {
 				msg: RuntimeApiMessage::Request(
 					relay_parent,
-					Request::LocalValidationData(para_b, OccupiedCoreAssumption::Included, tx)
+					Request::FullValidationData(para_b, OccupiedCoreAssumption::Included, tx)
 				),
 			}).await;
 
diff --git a/polkadot/node/network/availability-distribution/src/tests.rs b/polkadot/node/network/availability-distribution/src/tests.rs
index f4988fd8b77ed91b00a1ea8f67784276749a4f44..143fd8b05b7bb842cbef4f8e4b907d63eef1c11b 100644
--- a/polkadot/node/network/availability-distribution/src/tests.rs
+++ b/polkadot/node/network/availability-distribution/src/tests.rs
@@ -18,9 +18,9 @@ use super::*;
 use assert_matches::assert_matches;
 use polkadot_erasure_coding::{branches, obtain_chunks_v1 as obtain_chunks};
 use polkadot_primitives::v1::{
-	AvailableData, BlockData, CandidateCommitments, CandidateDescriptor, GlobalValidationData,
-	GroupIndex, GroupRotationInfo, HeadData, LocalValidationData, OccupiedCore,
-	OmittedValidationData, PoV, ScheduledCore, ValidatorPair,
+	AvailableData, BlockData, CandidateCommitments, CandidateDescriptor, GroupIndex,
+	GroupRotationInfo, HeadData, PersistedValidationData, OccupiedCore,
+	PoV, ScheduledCore, ValidatorPair,
 };
 use polkadot_subsystem_testhelpers as test_helpers;
 use polkadot_node_network_protocol::ObservedRole;
@@ -148,8 +148,7 @@ struct TestState {
 	relay_parent: Hash,
 	ancestors: Vec<Hash>,
 	availability_cores: Vec<CoreState>,
-	global_validation_data: GlobalValidationData,
-	local_validation_data: LocalValidationData,
+	persisted_validation_data: PersistedValidationData,
 }
 
 fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec<ValidatorId> {
@@ -210,17 +209,10 @@ impl Default for TestState {
 		];
 		let relay_parent = Hash::repeat_byte(0x05);
 
-		let local_validation_data = LocalValidationData {
+		let persisted_validation_data = PersistedValidationData {
 			parent_head: HeadData(vec![7, 8, 9]),
-			balance: Default::default(),
-			code_upgrade_allowed: None,
-			validation_code_hash: Default::default(),
-		};
-
-		let global_validation_data = GlobalValidationData {
-			max_code_size: 1000,
-			max_head_data_size: 1000,
 			block_number: Default::default(),
+			hrmp_mqc_heads: Vec::new(),
 		};
 
 		let validator_index = Some((validators.len() - 1) as ValidatorIndex);
@@ -233,8 +225,7 @@ impl Default for TestState {
 			validator_groups,
 			availability_cores,
 			head_data,
-			local_validation_data,
-			global_validation_data,
+			persisted_validation_data,
 			relay_parent,
 			ancestors,
 			validator_index,
@@ -243,13 +234,8 @@ impl Default for TestState {
 }
 
 fn make_available_data(test: &TestState, pov: PoV) -> AvailableData {
-	let omitted_validation = OmittedValidationData {
-		global_validation: test.global_validation_data.clone(),
-		local_validation: test.local_validation_data.clone(),
-	};
-
 	AvailableData {
-		omitted_validation,
+		validation_data: test.persisted_validation_data.clone(),
 		pov,
 	}
 }
@@ -434,8 +420,7 @@ fn reputation_verification() {
 			validator_groups,
 			availability_cores,
 			head_data: _,
-			local_validation_data: _,
-			global_validation_data: _,
+			persisted_validation_data: _,
 			relay_parent: current,
 			ancestors,
 			validator_index: _,
diff --git a/polkadot/node/overseer/src/lib.rs b/polkadot/node/overseer/src/lib.rs
index 321a20f568f5923a227bef90bd2937d07fb85b23..1d4c7bcaf493a95d61d6898500d65bed46750e49 100644
--- a/polkadot/node/overseer/src/lib.rs
+++ b/polkadot/node/overseer/src/lib.rs
@@ -1800,7 +1800,7 @@ mod tests {
 	fn test_collator_generation_msg() -> CollationGenerationMessage {
 		CollationGenerationMessage::Initialize(CollationGenerationConfig {
 			key: CollatorPair::generate().0,
-			collator: Box::new(|_, _| Box::new(TestCollator)),
+			collator: Box::new(|_| Box::new(TestCollator)),
 			para_id: Default::default(),
 		})
 	}
diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs
index 2ff704c2dd1628f0d372b338108267cd0554abbb..df8bc22551da479cd8a285b9e7c8c9db0a0fb7a5 100644
--- a/polkadot/node/primitives/src/lib.rs
+++ b/polkadot/node/primitives/src/lib.rs
@@ -25,7 +25,7 @@ use parity_scale_codec::{Decode, Encode};
 use polkadot_primitives::v1::{
 	Hash, CommittedCandidateReceipt, CandidateReceipt, CompactStatement,
 	EncodeAs, Signed, SigningContext, ValidatorIndex, ValidatorId,
-	UpwardMessage, Balance, ValidationCode, GlobalValidationData, LocalValidationData,
+	UpwardMessage, Balance, ValidationCode, PersistedValidationData, ValidationData,
 	HeadData, PoV, CollatorPair, Id as ParaId,
 };
 use polkadot_statement_table::{
@@ -118,10 +118,8 @@ pub struct FromTableMisbehavior {
 pub struct ValidationOutputs {
 	/// The head-data produced by validation.
 	pub head_data: HeadData,
-	/// The global validation schedule.
-	pub global_validation_data: GlobalValidationData,
-	/// The local validation data.
-	pub local_validation_data: LocalValidationData,
+	/// The persisted validation data.
+	pub validation_data: PersistedValidationData,
 	/// Upward messages to the relay chain.
 	pub upward_messages: Vec<UpwardMessage>,
 	/// Fees paid to the validators of the relay-chain.
@@ -153,6 +151,8 @@ pub enum InvalidCandidate {
 	NewCodeTooLarge(u64),
 	/// Head-data is over the limit.
 	HeadDataTooLarge(u64),
+	/// Code upgrade triggered but not allowed.
+	CodeUpgradeNotAllowed,
 }
 
 /// Result of the validation of the candidate.
@@ -285,7 +285,7 @@ pub struct CollationGenerationConfig {
 	/// Collator's authentication key, so it can sign things.
 	pub key: CollatorPair,
 	/// Collation function.
-	pub collator: Box<dyn Fn(&GlobalValidationData, &LocalValidationData) -> Box<dyn Future<Output = Collation> + Unpin + Send> + Send + Sync>,
+	pub collator: Box<dyn Fn(&ValidationData) -> Box<dyn Future<Output = Collation> + Unpin + Send> + Send + Sync>,
 	/// The parachain that this collator collates for
 	pub para_id: ParaId,
 }
diff --git a/polkadot/node/subsystem-util/src/lib.rs b/polkadot/node/subsystem-util/src/lib.rs
index 57f1efed3bb05cae3789e3a633e324427129eecd..ab7742715333a27ccf920b2acab65508c7c09fdb 100644
--- a/polkadot/node/subsystem-util/src/lib.rs
+++ b/polkadot/node/subsystem-util/src/lib.rs
@@ -39,8 +39,8 @@ use keystore::KeyStorePtr;
 use parity_scale_codec::Encode;
 use pin_project::{pin_project, pinned_drop};
 use polkadot_primitives::v1::{
-	CandidateEvent, CommittedCandidateReceipt, CoreState, EncodeAs, GlobalValidationData,
-	GroupRotationInfo, Hash, Id as ParaId, LocalValidationData, OccupiedCoreAssumption,
+	CandidateEvent, CommittedCandidateReceipt, CoreState, EncodeAs, PersistedValidationData,
+	GroupRotationInfo, Hash, Id as ParaId, ValidationData, OccupiedCoreAssumption,
 	SessionIndex, Signed, SigningContext, ValidationCode, ValidatorId, ValidatorIndex,
 	ValidatorPair,
 };
@@ -184,8 +184,8 @@ specialize_requests! {
 	fn request_validators() -> Vec<ValidatorId>; Validators;
 	fn request_validator_groups() -> (Vec<Vec<ValidatorIndex>>, GroupRotationInfo); ValidatorGroups;
 	fn request_availability_cores() -> Vec<CoreState>; AvailabilityCores;
-	fn request_global_validation_data() -> GlobalValidationData; GlobalValidationData;
-	fn request_local_validation_data(para_id: ParaId, assumption: OccupiedCoreAssumption) -> Option<LocalValidationData>; LocalValidationData;
+	fn request_full_validation_data(para_id: ParaId, assumption: OccupiedCoreAssumption) -> Option<ValidationData>; FullValidationData;
+	fn request_persisted_validation_data(para_id: ParaId, assumption: OccupiedCoreAssumption) -> Option<PersistedValidationData>; PersistedValidationData;
 	fn request_session_index_for_child() -> SessionIndex; SessionIndexForChild;
 	fn request_validation_code(para_id: ParaId, assumption: OccupiedCoreAssumption) -> Option<ValidationCode>; ValidationCode;
 	fn request_candidate_pending_availability(para_id: ParaId) -> Option<CommittedCandidateReceipt>; CandidatePendingAvailability;
@@ -267,8 +267,8 @@ specialize_requests_ctx! {
 	fn request_validators_ctx() -> Vec<ValidatorId>; Validators;
 	fn request_validator_groups_ctx() -> (Vec<Vec<ValidatorIndex>>, GroupRotationInfo); ValidatorGroups;
 	fn request_availability_cores_ctx() -> Vec<CoreState>; AvailabilityCores;
-	fn request_global_validation_data_ctx() -> GlobalValidationData; GlobalValidationData;
-	fn request_local_validation_data_ctx(para_id: ParaId, assumption: OccupiedCoreAssumption) -> Option<LocalValidationData>; LocalValidationData;
+	fn request_full_validation_data_ctx(para_id: ParaId, assumption: OccupiedCoreAssumption) -> Option<ValidationData>; FullValidationData;
+	fn request_persisted_validation_data_ctx(para_id: ParaId, assumption: OccupiedCoreAssumption) -> Option<PersistedValidationData>; PersistedValidationData;
 	fn request_session_index_for_child_ctx() -> SessionIndex; SessionIndexForChild;
 	fn request_validation_code_ctx(para_id: ParaId, assumption: OccupiedCoreAssumption) -> Option<ValidationCode>; ValidationCode;
 	fn request_candidate_pending_availability_ctx(para_id: ParaId) -> Option<CommittedCandidateReceipt>; CandidatePendingAvailability;
diff --git a/polkadot/node/subsystem/src/messages.rs b/polkadot/node/subsystem/src/messages.rs
index 9891692a062a4db30d5d65235fa7a3e154385af4..39f3dc923f777294f4529f98be8bb2081c24e1f0 100644
--- a/polkadot/node/subsystem/src/messages.rs
+++ b/polkadot/node/subsystem/src/messages.rs
@@ -33,9 +33,9 @@ use polkadot_node_primitives::{
 use polkadot_primitives::v1::{
 	AvailableData, BackedCandidate, BlockNumber, CandidateDescriptor, CandidateEvent,
 	CandidateReceipt, CollatorId, CommittedCandidateReceipt,
-	CoreState, ErasureChunk, GlobalValidationData, GroupRotationInfo,
-	Hash, Id as ParaId, LocalValidationData, OccupiedCoreAssumption, OmittedValidationData, PoV,
-	SessionIndex, SignedAvailabilityBitfield, ValidationCode, ValidatorId, ValidatorIndex,
+	CoreState, ErasureChunk, GroupRotationInfo, Hash, Id as ParaId,
+	OccupiedCoreAssumption, PersistedValidationData, PoV, SessionIndex, SignedAvailabilityBitfield,
+	TransientValidationData, ValidationCode, ValidatorId, ValidationData, ValidatorIndex,
 	ValidatorSignature,
 };
 use std::sync::Arc;
@@ -107,7 +107,7 @@ pub struct ValidationFailed(pub String);
 pub enum CandidateValidationMessage {
 	/// Validate a candidate with provided parameters using relay-chain state.
 	///
-	/// This will implicitly attempt to gather the `OmittedValidationData` and `ValidationCode`
+	/// This will implicitly attempt to gather the `PersistedValidationData` and `ValidationCode`
 	/// from the runtime API of the chain, based on the `relay_parent`
 	/// of the `CandidateDescriptor`.
 	///
@@ -120,10 +120,12 @@ pub enum CandidateValidationMessage {
 	),
 	/// Validate a candidate with provided, exhaustive parameters for validation.
 	///
-	/// Explicitly provide the `OmittedValidationData` and `ValidationCode` so this can do full
-	/// validation without needing to access the state of the relay-chain.
+	/// Explicitly provide the `PersistedValidationData` and `ValidationCode` so this can do full
+	/// validation without needing to access the state of the relay-chain. Optionally provide the
+	/// `TransientValidationData` for further checks on the outputs.
 	ValidateFromExhaustive(
-		OmittedValidationData,
+		PersistedValidationData,
+		Option<TransientValidationData>,
 		ValidationCode,
 		CandidateDescriptor,
 		Arc<PoV>,
@@ -136,7 +138,7 @@ impl CandidateValidationMessage {
 	pub fn relay_parent(&self) -> Option<Hash> {
 		match self {
 			Self::ValidateFromChainState(_, _, _) => None,
-			Self::ValidateFromExhaustive(_, _, _, _, _) => None,
+			Self::ValidateFromExhaustive(_, _, _, _, _, _) => None,
 		}
 	}
 }
@@ -357,15 +359,21 @@ pub enum RuntimeApiRequest {
 	ValidatorGroups(RuntimeApiSender<(Vec<Vec<ValidatorIndex>>, GroupRotationInfo)>),
 	/// Get information on all availability cores.
 	AvailabilityCores(RuntimeApiSender<Vec<CoreState>>),
-	/// Get the global validation data.
-	GlobalValidationData(RuntimeApiSender<GlobalValidationData>),
-	/// Get the local validation data for a particular para, taking the given
+	/// Get the persisted validation data for a particular para, taking the given
 	/// `OccupiedCoreAssumption`, which will inform on how the validation data should be computed
 	/// if the para currently occupies a core.
-	LocalValidationData(
+	PersistedValidationData(
 		ParaId,
 		OccupiedCoreAssumption,
-		RuntimeApiSender<Option<LocalValidationData>>,
+		RuntimeApiSender<Option<PersistedValidationData>>,
+	),
+	/// Get the full validation data for a particular para, taking the given
+	/// `OccupiedCoreAssumption`, which will inform on how the validation data should be computed
+	/// if the para currently occupies a core.
+	FullValidationData(
+		ParaId,
+		OccupiedCoreAssumption,
+		RuntimeApiSender<Option<ValidationData>>,
 	),
 	/// Get the session index that a child of the block will have.
 	SessionIndexForChild(RuntimeApiSender<SessionIndex>),
diff --git a/polkadot/parachain/src/primitives.rs b/polkadot/parachain/src/primitives.rs
index 955842e30fb91c860f8b77b62f80f7a99a522414..3a1ee81bcdc31451b897d140b590d2994c91c15c 100644
--- a/polkadot/parachain/src/primitives.rs
+++ b/polkadot/parachain/src/primitives.rs
@@ -28,6 +28,8 @@ use serde::{Serialize, Deserialize};
 #[cfg(feature = "std")]
 use sp_core::bytes;
 
+use polkadot_core_primitives::Hash;
+
 /// Block number type used by the relay chain.
 pub use polkadot_core_primitives::BlockNumber as RelayChainBlockNumber;
 
@@ -228,22 +230,16 @@ pub struct UpwardMessage {
 #[derive(PartialEq, Eq, Decode)]
 #[cfg_attr(feature = "std", derive(Debug, Encode))]
 pub struct ValidationParams {
-	/// The collation body.
-	pub block_data: BlockData,
 	/// Previous head-data.
 	pub parent_head: HeadData,
-	/// The maximum code size permitted, in bytes.
-	pub max_code_size: u32,
-	/// The maximum head-data size permitted, in bytes.
-	pub max_head_data_size: u32,
+	/// The collation body.
+	pub block_data: BlockData,
 	/// The current relay-chain block number.
-	pub relay_chain_height: polkadot_core_primitives::BlockNumber,
-	/// Whether a code upgrade is allowed or not, and at which height the upgrade
-	/// would be applied after, if so. The parachain logic should apply any upgrade
-	/// issued in this block after the first block
-	/// with `relay_chain_height` at least this value, if `Some`. if `None`, issue
-	/// no upgrade.
-	pub code_upgrade_allowed: Option<polkadot_core_primitives::BlockNumber>,
+	pub relay_chain_height: RelayChainBlockNumber,
+	/// The list of MQC heads for the inbound HRMP channels paired with the sender para ids. This
+	/// vector is sorted ascending by the para id and doesn't contain multiple entries with the same
+	/// sender.
+	pub hrmp_mqc_heads: Vec<(Id, Hash)>,
 }
 
 /// The result of parachain validation.
diff --git a/polkadot/parachain/test-parachains/Cargo.toml b/polkadot/parachain/test-parachains/Cargo.toml
index 044115119d97d745cfd0a8aafa30645965b5198d..d391c1b2314dc70e256b796ad127adb4235cf376 100644
--- a/polkadot/parachain/test-parachains/Cargo.toml
+++ b/polkadot/parachain/test-parachains/Cargo.toml
@@ -12,7 +12,6 @@ codec = { package = "parity-scale-codec", version = "1.3.4", default-features =
 parachain = { package = "polkadot-parachain", path = ".." }
 adder = { package = "test-parachain-adder", path = "adder" }
 halt = { package = "test-parachain-halt", path = "halt" }
-code-upgrader = { package = "test-parachain-code-upgrader", path = "code-upgrader" }
 
 [dev-dependencies]
 sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
@@ -22,5 +21,4 @@ default = [ "std" ]
 std = [
 	"adder/std",
 	"halt/std",
-	"code-upgrader/std",
 ]
diff --git a/polkadot/parachain/test-parachains/code-upgrader/Cargo.toml b/polkadot/parachain/test-parachains/code-upgrader/Cargo.toml
deleted file mode 100644
index 6f673adbf82a4d5f1ad6527a40a55326ebe36ad9..0000000000000000000000000000000000000000
--- a/polkadot/parachain/test-parachains/code-upgrader/Cargo.toml
+++ /dev/null
@@ -1,27 +0,0 @@
-[package]
-name = "test-parachain-code-upgrader"
-version = "0.7.22"
-authors = ["Parity Technologies <admin@parity.io>"]
-description = "Test parachain which can upgrade code"
-edition = "2018"
-build = "build.rs"
-
-[dependencies]
-parachain = { package = "polkadot-parachain", path = "../../", default-features = false, features = [ "wasm-api" ] }
-codec = { package = "parity-scale-codec", version = "1.1.0", default-features = false, features = ["derive"] }
-sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
-tiny-keccak = "1.5.0"
-dlmalloc = { version = "0.1.3", features = [ "global" ] }
-
-# We need to make sure the global allocator is disabled until we have support of full substrate externalities
-runtime-io = { package = "sp-io", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, features = [ "disable_allocator" ] }
-
-[build-dependencies]
-wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "1.0.6" }
-
-[features]
-default = [ "std" ]
-std = [
-	"parachain/std",
-	"sp-std/std",
-]
diff --git a/polkadot/parachain/test-parachains/code-upgrader/build.rs b/polkadot/parachain/test-parachains/code-upgrader/build.rs
deleted file mode 100644
index 2e407bbef3876e3ff3cc1183533545fb74952acf..0000000000000000000000000000000000000000
--- a/polkadot/parachain/test-parachains/code-upgrader/build.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2019-2020 Parity Technologies (UK) Ltd.
-// This file is part of Polkadot.
-
-// Substrate 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.
-
-// Substrate 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 Substrate.  If not, see <http://www.gnu.org/licenses/>.
-
-use wasm_builder_runner::WasmBuilder;
-
-fn main() {
-	WasmBuilder::new()
-		.with_current_project()
-		.with_wasm_builder_from_crates("2.0.0")
-		.export_heap_base()
-		.build()
-}
diff --git a/polkadot/parachain/test-parachains/code-upgrader/src/lib.rs b/polkadot/parachain/test-parachains/code-upgrader/src/lib.rs
deleted file mode 100644
index c0219e0229a8459acf813b6a2c9fd8bcf5c691fa..0000000000000000000000000000000000000000
--- a/polkadot/parachain/test-parachains/code-upgrader/src/lib.rs
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright 2020 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/>.
-
-//! Test parachain WASM which implements code ugprades.
-
-#![no_std]
-
-#![cfg_attr(not(feature = "std"), feature(core_intrinsics, lang_items, core_panic_info, alloc_error_handler))]
-
-use codec::{Encode, Decode};
-use parachain::primitives::{RelayChainBlockNumber, ValidationCode};
-
-#[cfg(not(feature = "std"))]
-mod wasm_validation;
-
-#[cfg(not(feature = "std"))]
-#[global_allocator]
-static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc;
-
-// Make the WASM binary available.
-#[cfg(feature = "std")]
-include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
-
-#[cfg(feature = "std")]
-/// Wasm binary unwrapped. If built with `BUILD_DUMMY_WASM_BINARY`, the function panics.
-pub fn wasm_binary_unwrap() -> &'static [u8] {
-	WASM_BINARY.expect("Development wasm binary is not available. Testing is only \
-						supported with the flag disabled.")
-}
-
-#[derive(Encode, Decode, Clone, Default)]
-pub struct State {
-	/// The current code that is "active" in this chain.
-	pub code: ValidationCode,
-	/// Code upgrade that is pending.
-	pub pending_code: Option<(ValidationCode, RelayChainBlockNumber)>,
-}
-
-/// Head data for this parachain.
-#[derive(Default, Clone, Hash, Eq, PartialEq, Encode, Decode)]
-pub struct HeadData {
-	/// Block number
-	pub number: u64,
-	/// parent block keccak256
-	pub parent_hash: [u8; 32],
-	/// hash of post-execution state.
-	pub post_state: [u8; 32],
-}
-
-impl HeadData {
-	pub fn hash(&self) -> [u8; 32] {
-		tiny_keccak::keccak256(&self.encode())
-	}
-}
-
-/// Block data for this parachain.
-#[derive(Default, Clone, Encode, Decode)]
-pub struct BlockData {
-	/// State to begin from.
-	pub state: State,
-	/// Code to upgrade to.
-	pub new_validation_code: Option<ValidationCode>,
-}
-
-pub fn hash_state(state: &State) -> [u8; 32] {
-	tiny_keccak::keccak256(state.encode().as_slice())
-}
-
-#[derive(Debug)]
-pub enum Error {
-	/// Start state mismatched with parent header's state hash.
-	StateMismatch,
-	/// New validation code too large.
-	NewCodeTooLarge,
-	/// Code upgrades not allowed at this time.
-	CodeUpgradeDisallowed,
-}
-
-pub struct ValidationResult {
-	/// The new head data.
-	pub head_data: HeadData,
-	/// The new validation code.
-	pub new_validation_code: Option<ValidationCode>,
-}
-
-pub struct RelayChainParams {
-	/// Whether a code upgrade is allowed and at what relay-chain block number
-	/// to process it after.
-	pub code_upgrade_allowed: Option<RelayChainBlockNumber>,
-	/// The maximum code size allowed for an upgrade.
-	pub max_code_size: u32,
-	/// The relay-chain block number.
-	pub relay_chain_block_number: RelayChainBlockNumber,
-}
-
-/// Execute a block body on top of given parent head, producing new parent head
-/// if valid.
-pub fn execute(
-	parent_hash: [u8; 32],
-	parent_head: HeadData,
-	block_data: BlockData,
-	relay_params: &RelayChainParams,
-) -> Result<ValidationResult, Error> {
-	debug_assert_eq!(parent_hash, parent_head.hash());
-
-	if hash_state(&block_data.state) != parent_head.post_state {
-		return Err(Error::StateMismatch);
-	}
-
-	let mut new_state = block_data.state;
-
-	if let Some((pending_code, after)) = new_state.pending_code.take() {
-		if after <= relay_params.relay_chain_block_number {
-			// code applied.
-			new_state.code = pending_code;
-		} else {
-			// reinstate.
-			new_state.pending_code = Some((pending_code, after));
-		}
-	}
-
-	let new_validation_code = if let Some(ref new_validation_code) = block_data.new_validation_code {
-		if new_validation_code.0.len() as u32 > relay_params.max_code_size {
-			return Err(Error::NewCodeTooLarge);
-		}
-
-		// replace the code if allowed and we don't have an upgrade pending.
-		match (new_state.pending_code.is_some(), relay_params.code_upgrade_allowed) {
-			(_, None) => return Err(Error::CodeUpgradeDisallowed),
-			(false, Some(after)) => {
-				new_state.pending_code = Some((new_validation_code.clone(), after));
-				Some(new_validation_code.clone())
-			}
-			(true, Some(_)) => None,
-		}
-	} else {
-		None
-	};
-
-	let head_data = HeadData {
-		number: parent_head.number + 1,
-		parent_hash,
-		post_state: hash_state(&new_state),
-	};
-
-	Ok(ValidationResult {
-		head_data,
-		new_validation_code: new_validation_code,
-	})
-}
diff --git a/polkadot/parachain/test-parachains/code-upgrader/src/wasm_validation.rs b/polkadot/parachain/test-parachains/code-upgrader/src/wasm_validation.rs
deleted file mode 100644
index 3f4d0c44f8f4cfd853182d6f4804ec6281e0749b..0000000000000000000000000000000000000000
--- a/polkadot/parachain/test-parachains/code-upgrader/src/wasm_validation.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2019-2020 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/>.
-
-//! WASM validation for adder parachain.
-
-use crate::{HeadData, BlockData, RelayChainParams};
-use core::{intrinsics, panic};
-use parachain::primitives::{ValidationResult, HeadData as GenericHeadData};
-use codec::{Encode, Decode};
-
-#[no_mangle]
-pub extern fn validate_block(params: *const u8, len: usize) -> u64 {
-	let params = unsafe { parachain::load_params(params, len) };
-	let parent_head = HeadData::decode(&mut &params.parent_head.0[..])
-		.expect("invalid parent head format.");
-
-	let block_data = BlockData::decode(&mut &params.block_data.0[..])
-		.expect("invalid block data format.");
-
-	let parent_hash = tiny_keccak::keccak256(&params.parent_head.0[..]);
-
-	let res = crate::execute(
-		parent_hash,
-		parent_head,
-		block_data,
-		&RelayChainParams {
-			code_upgrade_allowed: params.code_upgrade_allowed,
-			max_code_size: params.max_code_size,
-			relay_chain_block_number: params.relay_chain_height,
-		},
-	);
-
-	match res {
-		Ok(output) => parachain::write_result(
-			&ValidationResult {
-				head_data: GenericHeadData(output.head_data.encode()),
-				new_validation_code: output.new_validation_code,
-				upward_messages: sp_std::vec::Vec::new(),
-				processed_downward_messages: 0,
-			}
-		),
-		Err(_) => panic!("execution failure"),
-	}
-}
diff --git a/polkadot/parachain/test-parachains/code-upgrader/wasm/Cargo.toml b/polkadot/parachain/test-parachains/code-upgrader/wasm/Cargo.toml
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/polkadot/parachain/test-parachains/tests/adder/mod.rs b/polkadot/parachain/test-parachains/tests/adder/mod.rs
index ecbac12ac5abee87cf22a8faf5e70a0e6d12fa4d..76924551ba9a33e35ad90516bd604bfee7c7b5d6 100644
--- a/polkadot/parachain/test-parachains/tests/adder/mod.rs
+++ b/polkadot/parachain/test-parachains/tests/adder/mod.rs
@@ -72,10 +72,8 @@ pub fn execute_good_on_parent() {
 		ValidationParams {
 			parent_head: GenericHeadData(parent_head.encode()),
 			block_data: GenericBlockData(block_data.encode()),
-			max_code_size: 1024,
-			max_head_data_size: 1024,
 			relay_chain_height: 1,
-			code_upgrade_allowed: None,
+			hrmp_mqc_heads: Vec::new(),
 		},
 		parachain::wasm_executor::ExecutionMode::RemoteTest(&pool),
 		sp_core::testing::TaskExecutor::new(),
@@ -112,10 +110,8 @@ fn execute_good_chain_on_parent() {
 			ValidationParams {
 				parent_head: GenericHeadData(parent_head.encode()),
 				block_data: GenericBlockData(block_data.encode()),
-				max_code_size: 1024,
-				max_head_data_size: 1024,
 				relay_chain_height: number as RelayChainBlockNumber + 1,
-				code_upgrade_allowed: None,
+				hrmp_mqc_heads: Vec::new(),
 			},
 			parachain::wasm_executor::ExecutionMode::RemoteTest(&pool),
 			sp_core::testing::TaskExecutor::new(),
@@ -153,10 +149,8 @@ fn execute_bad_on_parent() {
 		ValidationParams {
 			parent_head: GenericHeadData(parent_head.encode()),
 			block_data: GenericBlockData(block_data.encode()),
-			max_code_size: 1024,
-			max_head_data_size: 1024,
 			relay_chain_height: 1,
-			code_upgrade_allowed: None,
+			hrmp_mqc_heads: Vec::new(),
 		},
 		parachain::wasm_executor::ExecutionMode::RemoteTest(&pool),
 		sp_core::testing::TaskExecutor::new(),
diff --git a/polkadot/parachain/test-parachains/tests/code_upgrader/mod.rs b/polkadot/parachain/test-parachains/tests/code_upgrader/mod.rs
deleted file mode 100644
index b99a6e9dbf45b9d721e9dbde5d6f9e48f75c3fcd..0000000000000000000000000000000000000000
--- a/polkadot/parachain/test-parachains/tests/code_upgrader/mod.rs
+++ /dev/null
@@ -1,217 +0,0 @@
-// Copyright 2017-2020 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/>.
-
-//! Basic parachain that adds a number as part of its state.
-
-use parachain::primitives::{
-	BlockData as GenericBlockData,
-	HeadData as GenericHeadData,
-	ValidationParams, ValidationCode,
-};
-use codec::{Decode, Encode};
-use code_upgrader::{hash_state, HeadData, BlockData, State};
-
-#[test]
-pub fn execute_good_no_upgrade() {
-	let pool = parachain::wasm_executor::ValidationPool::new();
-
-	let parent_head = HeadData {
-		number: 0,
-		parent_hash: [0; 32],
-		post_state: hash_state(&State::default()),
-	};
-
-	let block_data = BlockData {
-		state: State::default(),
-		new_validation_code: None,
-	};
-
-	let ret = parachain::wasm_executor::validate_candidate(
-		code_upgrader::wasm_binary_unwrap(),
-		ValidationParams {
-			parent_head: GenericHeadData(parent_head.encode()),
-			block_data: GenericBlockData(block_data.encode()),
-			max_code_size: 1024,
-			max_head_data_size: 1024,
-			relay_chain_height: 1,
-			code_upgrade_allowed: None,
-		},
-		parachain::wasm_executor::ExecutionMode::RemoteTest(&pool),
-		sp_core::testing::TaskExecutor::new(),
-	).unwrap();
-
-	let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap();
-
-	assert!(ret.new_validation_code.is_none());
-	assert_eq!(new_head.number, 1);
-	assert_eq!(new_head.parent_hash, parent_head.hash());
-	assert_eq!(new_head.post_state, hash_state(&State::default()));
-}
-
-#[test]
-pub fn execute_good_with_upgrade() {
-	let pool = parachain::wasm_executor::ValidationPool::new();
-
-	let parent_head = HeadData {
-		number: 0,
-		parent_hash: [0; 32],
-		post_state: hash_state(&State::default()),
-	};
-
-	let block_data = BlockData {
-		state: State::default(),
-		new_validation_code: Some(ValidationCode(vec![1, 2, 3])),
-	};
-
-	let ret = parachain::wasm_executor::validate_candidate(
-		code_upgrader::wasm_binary_unwrap(),
-		ValidationParams {
-			parent_head: GenericHeadData(parent_head.encode()),
-			block_data: GenericBlockData(block_data.encode()),
-			max_code_size: 1024,
-			max_head_data_size: 1024,
-			relay_chain_height: 1,
-			code_upgrade_allowed: Some(20),
-		},
-		parachain::wasm_executor::ExecutionMode::RemoteTest(&pool),
-		sp_core::testing::TaskExecutor::new(),
-	).unwrap();
-
-	let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap();
-
-	assert_eq!(ret.new_validation_code.unwrap(), ValidationCode(vec![1, 2, 3]));
-	assert_eq!(new_head.number, 1);
-	assert_eq!(new_head.parent_hash, parent_head.hash());
-	assert_eq!(
-		new_head.post_state,
-		hash_state(&State {
-			code: ValidationCode::default(),
-			pending_code: Some((ValidationCode(vec![1, 2, 3]), 20)),
-		}),
-	);
-}
-
-#[test]
-#[should_panic]
-pub fn code_upgrade_not_allowed() {
-	let pool = parachain::wasm_executor::ValidationPool::new();
-
-	let parent_head = HeadData {
-		number: 0,
-		parent_hash: [0; 32],
-		post_state: hash_state(&State::default()),
-	};
-
-	let block_data = BlockData {
-		state: State::default(),
-		new_validation_code: Some(ValidationCode(vec![1, 2, 3])),
-	};
-
-	parachain::wasm_executor::validate_candidate(
-		code_upgrader::wasm_binary_unwrap(),
-		ValidationParams {
-			parent_head: GenericHeadData(parent_head.encode()),
-			block_data: GenericBlockData(block_data.encode()),
-			max_code_size: 1024,
-			max_head_data_size: 1024,
-			relay_chain_height: 1,
-			code_upgrade_allowed: None,
-		},
-		parachain::wasm_executor::ExecutionMode::RemoteTest(&pool),
-		sp_core::testing::TaskExecutor::new(),
-	).unwrap();
-}
-
-#[test]
-pub fn applies_code_upgrade_after_delay() {
-	let pool = parachain::wasm_executor::ValidationPool::new();
-
-	let (new_head, state) = {
-		let parent_head = HeadData {
-			number: 0,
-			parent_hash: [0; 32],
-			post_state: hash_state(&State::default()),
-		};
-
-		let block_data = BlockData {
-			state: State::default(),
-			new_validation_code: Some(ValidationCode(vec![1, 2, 3])),
-		};
-
-		let ret = parachain::wasm_executor::validate_candidate(
-			code_upgrader::wasm_binary_unwrap(),
-			ValidationParams {
-				parent_head: GenericHeadData(parent_head.encode()),
-				block_data: GenericBlockData(block_data.encode()),
-				max_code_size: 1024,
-				max_head_data_size: 1024,
-				relay_chain_height: 1,
-				code_upgrade_allowed: Some(2),
-			},
-			parachain::wasm_executor::ExecutionMode::RemoteTest(&pool),
-			sp_core::testing::TaskExecutor::new(),
-		).unwrap();
-
-		let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap();
-
-		let parent_hash = parent_head.hash();
-		let state = State {
-			code: ValidationCode::default(),
-			pending_code: Some((ValidationCode(vec![1, 2, 3]), 2)),
-		};
-		assert_eq!(ret.new_validation_code.unwrap(), ValidationCode(vec![1, 2, 3]));
-		assert_eq!(new_head.number, 1);
-		assert_eq!(new_head.parent_hash, parent_hash);
-		assert_eq!(new_head.post_state, hash_state(&state));
-
-		(new_head, state)
-	};
-
-	{
-		let parent_head = new_head;
-		let block_data = BlockData {
-			state,
-			new_validation_code: None,
-		};
-
-		let ret = parachain::wasm_executor::validate_candidate(
-			code_upgrader::wasm_binary_unwrap(),
-			ValidationParams {
-				parent_head: GenericHeadData(parent_head.encode()),
-				block_data: GenericBlockData(block_data.encode()),
-				max_code_size: 1024,
-				max_head_data_size: 1024,
-				relay_chain_height: 2,
-				code_upgrade_allowed: None,
-			},
-			parachain::wasm_executor::ExecutionMode::RemoteTest(&pool),
-			sp_core::testing::TaskExecutor::new(),
-		).unwrap();
-
-		let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap();
-
-		assert!(ret.new_validation_code.is_none());
-		assert_eq!(new_head.number, 2);
-		assert_eq!(new_head.parent_hash, parent_head.hash());
-		assert_eq!(
-			new_head.post_state,
-			hash_state(&State {
-				code: ValidationCode(vec![1, 2, 3]),
-				pending_code: None,
-			}),
-		);
-	}
-}
diff --git a/polkadot/parachain/test-parachains/tests/lib.rs b/polkadot/parachain/test-parachains/tests/lib.rs
index 692c04c10ca5fd66eba3de5229c1302d1d9252ff..3ad021a16e7035b16702af59cfbd40ed44329be7 100644
--- a/polkadot/parachain/test-parachains/tests/lib.rs
+++ b/polkadot/parachain/test-parachains/tests/lib.rs
@@ -15,7 +15,6 @@
 // along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
 
 mod adder;
-mod code_upgrader;
 mod wasm_executor;
 
 use parachain::wasm_executor::run_worker;
diff --git a/polkadot/parachain/test-parachains/tests/wasm_executor/mod.rs b/polkadot/parachain/test-parachains/tests/wasm_executor/mod.rs
index 769ad737ce9f4c34574ffe0a33ea2aae24339599..b4f2211baa56d8175b4840beb63b4d8dd4b56c78 100644
--- a/polkadot/parachain/test-parachains/tests/wasm_executor/mod.rs
+++ b/polkadot/parachain/test-parachains/tests/wasm_executor/mod.rs
@@ -31,10 +31,8 @@ fn terminates_on_timeout() {
 		ValidationParams {
 			block_data: BlockData(Vec::new()),
 			parent_head: Default::default(),
-			max_code_size: 1024,
-			max_head_data_size: 1024,
 			relay_chain_height: 1,
-			code_upgrade_allowed: None,
+			hrmp_mqc_heads: Vec::new(),
 		},
 		parachain::wasm_executor::ExecutionMode::RemoteTest(&pool),
 		sp_core::testing::TaskExecutor::new(),
@@ -61,10 +59,8 @@ fn parallel_execution() {
 		ValidationParams {
 			block_data: BlockData(Vec::new()),
 			parent_head: Default::default(),
-			max_code_size: 1024,
-			max_head_data_size: 1024,
 			relay_chain_height: 1,
-			code_upgrade_allowed: None,
+			hrmp_mqc_heads: Vec::new(),
 		},
 		parachain::wasm_executor::ExecutionMode::RemoteTest(&pool2),
 		sp_core::testing::TaskExecutor::new(),
@@ -74,10 +70,8 @@ fn parallel_execution() {
 		ValidationParams {
 			block_data: BlockData(Vec::new()),
 			parent_head: Default::default(),
-			max_code_size: 1024,
-			max_head_data_size: 1024,
 			relay_chain_height: 1,
-			code_upgrade_allowed: None,
+			hrmp_mqc_heads: Vec::new(),
 		},
 		parachain::wasm_executor::ExecutionMode::RemoteTest(&pool),
 		sp_core::testing::TaskExecutor::new(),
diff --git a/polkadot/primitives/src/v1.rs b/polkadot/primitives/src/v1.rs
index 086eface31672d1886cce5947396ff626aa68f56..c2a8ec43d5a7131950248e377cb26efc882c0d31 100644
--- a/polkadot/primitives/src/v1.rs
+++ b/polkadot/primitives/src/v1.rs
@@ -60,7 +60,7 @@ 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,
+	persisted_validation_data_hash: &Hash,
 	pov_hash: &Hash,
 ) -> [u8; 100] {
 	// 32-byte hash length is protected in a test below.
@@ -68,7 +68,7 @@ pub fn collator_signature_payload<H: AsRef<[u8]>>(
 
 	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(validation_data_hash.as_ref());
+	payload[36..68].copy_from_slice(persisted_validation_data_hash.as_ref());
 	payload[68..100].copy_from_slice(pov_hash.as_ref());
 
 	payload
@@ -77,7 +77,7 @@ 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,
+	persisted_validation_data_hash: &Hash,
 	pov_hash: &Hash,
 	collator: &CollatorId,
 	signature: &CollatorSignature,
@@ -85,7 +85,7 @@ fn check_collator_signature<H: AsRef<[u8]>>(
 	let payload = collator_signature_payload(
 		relay_parent,
 		para_id,
-		validation_data_hash,
+		persisted_validation_data_hash,
 		pov_hash,
 	);
 
@@ -96,14 +96,6 @@ 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, Hash))]
@@ -114,16 +106,15 @@ pub struct CandidateDescriptor<H = Hash> {
 	pub relay_parent: H,
 	/// The collator's sr25519 public key.
 	pub collator: CollatorId,
-	/// The blake2-256 hash of the validation data. This is extra data derived from
+	/// The blake2-256 hash of the persisted 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,
+	pub persisted_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> {
@@ -132,7 +123,7 @@ impl<H: AsRef<[u8]>> CandidateDescriptor<H> {
 		check_collator_signature(
 			&self.relay_parent,
 			&self.para_id,
-			&self.validation_data_hash,
+			&self.persisted_validation_data_hash,
 			&self.pov_hash,
 			&self.collator,
 			&self.signature,
@@ -168,10 +159,11 @@ impl<H> CandidateReceipt<H> {
 pub struct FullCandidateReceipt<H = Hash, N = BlockNumber> {
 	/// The inner candidate receipt.
 	pub inner: CandidateReceipt<H>,
-	/// The global validation schedule.
-	pub global_validation: GlobalValidationData<N>,
-	/// The local validation data.
-	pub local_validation: LocalValidationData<N>,
+	/// The validation data derived from the relay-chain state at that
+	/// point. The hash of the persisted validation data should
+	/// match the `persisted_validation_data_hash` in the descriptor
+	/// of the receipt.
+	pub validation_data: ValidationData<N>,
 }
 
 /// A candidate-receipt with commitments directly included.
@@ -224,17 +216,78 @@ impl Ord for CommittedCandidateReceipt {
 	}
 }
 
-/// Extra data that is needed along with the other fields in a `CandidateReceipt`
-/// to fully validate the candidate. These fields are parachain-specific.
+/// The validation data provide information about how to validate both the inputs and
+/// outputs of a candidate.
+///
+/// There are two types of validation data: persisted and transient.
+/// Their respective sections of the guide elaborate on their functionality in more detail.
+///
+/// This information is derived from the chain state and will vary from para to para,
+/// although some of the fields may be the same for every para.
+///
+/// Persisted validation data are generally derived from some relay-chain state to form inputs
+/// to the validation function, and as such need to be persisted by the availability system to
+/// avoid dependence on availability of the relay-chain state. The backing phase of the
+/// inclusion pipeline ensures that everything that is included in a valid fork of the
+/// relay-chain already adheres to the transient constraints.
+///
+/// The validation data also serve the purpose of giving collators a means of ensuring that
+/// their produced candidate and the commitments submitted to the relay-chain alongside it
+/// will pass the checks done by the relay-chain when backing, and give validators
+/// the same understanding when determining whether to second or attest to a candidate.
+///
+/// Since the commitments of the validation function are checked by the
+/// relay-chain, secondary checkers can rely on the invariant that the relay-chain
+/// only includes para-blocks for which these checks have already been done. As such,
+/// there is no need for the validation data used to inform validators and collators about
+/// the checks the relay-chain will perform to be persisted by the availability system.
+/// Nevertheless, we expose it so the backing validators can validate the outputs of a
+/// candidate before voting to submit it to the relay-chain and so collators can
+/// collate candidates that satisfy the criteria implied these transient validation data.
+#[derive(PartialEq, Eq, Clone, Encode, Decode)]
+#[cfg_attr(feature = "std", derive(Debug, Default))]
+pub struct ValidationData<N = BlockNumber> {
+	/// The persisted validation data.
+	pub persisted: PersistedValidationData<N>,
+	/// The transient validation data.
+	pub transient: TransientValidationData<N>,
+}
+
+/// Validation data that needs to be persisted for secondary checkers.
 #[derive(PartialEq, Eq, Clone, Encode, Decode)]
 #[cfg_attr(feature = "std", derive(Debug, Default))]
-pub struct LocalValidationData<N = BlockNumber> {
+pub struct PersistedValidationData<N = BlockNumber> {
 	/// The parent head-data.
 	pub parent_head: HeadData,
+	/// The relay-chain block number this is in the context of.
+	pub block_number: N,
+	/// The list of MQC heads for the inbound channels paired with the sender para ids. This
+	/// vector is sorted ascending by the para id and doesn't contain multiple entries with the same
+	/// sender.
+	pub hrmp_mqc_heads: Vec<(Id, Hash)>,
+}
+
+impl<N: Encode> PersistedValidationData<N> {
+	/// Compute the blake2-256 hash of the persisted validation data.
+	pub fn hash(&self) -> Hash {
+		BlakeTwo256::hash_of(self)
+	}
+}
+
+/// Validation data for checking outputs of the validation-function.
+/// As such, they also inform the collator about how to construct the candidate.
+///
+/// These are transient because they are not necessary beyond the point where the
+/// candidate is backed.
+#[derive(PartialEq, Eq, Clone, Encode, Decode)]
+#[cfg_attr(feature = "std", derive(Debug, Default))]
+pub struct TransientValidationData<N = BlockNumber> {
+	/// The maximum code size permitted, in bytes.
+	pub max_code_size: u32,
+	/// The maximum head-data size permitted, in bytes.
+	pub max_head_data_size: u32,
 	/// The balance of the parachain at the moment of validation.
 	pub balance: Balance,
-	/// The blake2-256 hash of the validation code used to execute the candidate.
-	pub validation_code_hash: Hash,
 	/// Whether the parachain is allowed to upgrade its validation code.
 	///
 	/// This is `Some` if so, and contains the number of the minimum relay-chain
@@ -249,21 +302,6 @@ pub struct LocalValidationData<N = BlockNumber> {
 	pub code_upgrade_allowed: Option<N>,
 }
 
-/// Extra data that is needed along with the other fields in a `CandidateReceipt`
-/// to fully validate the candidate.
-///
-/// 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 GlobalValidationData<N = BlockNumber> {
-	/// The maximum code size permitted, in bytes.
-	pub max_code_size: u32,
-	/// The maximum head-data size permitted, in bytes.
-	pub max_head_data_size: u32,
-	/// The relay-chain block number this is in the context of.
-	pub block_number: N,
-}
-
 /// Commitments made in a `CandidateReceipt`. Many of these are outputs of validation.
 #[derive(PartialEq, Eq, Clone, Encode, Decode)]
 #[cfg_attr(feature = "std", derive(Debug, Default, Hash))]
@@ -434,25 +472,14 @@ pub enum CoreOccupied {
 	Parachain,
 }
 
-/// Validation data omitted from most candidate descriptor structs, as it can be derived from the
-/// relay-parent.
-#[derive(Clone, Encode, Decode)]
-#[cfg_attr(feature = "std", derive(PartialEq, Debug, Default))]
-pub struct OmittedValidationData {
-	/// The global validation schedule.
-	pub global_validation: GlobalValidationData,
-	/// The local validation data.
-	pub local_validation: LocalValidationData,
-}
-
 /// This is the data we keep available for each candidate included in the relay chain.
 #[derive(Clone, Encode, Decode)]
 #[cfg_attr(feature = "std", derive(PartialEq, Debug))]
 pub struct AvailableData {
 	/// The Proof-of-Validation of the candidate.
 	pub pov: PoV,
-	/// The omitted validation data.
-	pub omitted_validation: OmittedValidationData,
+	/// The persisted validation data needed for secondary checks.
+	pub validation_data: PersistedValidationData,
 }
 
 /// A helper data-type for tracking validator-group rotations.
@@ -622,17 +649,21 @@ sp_api::decl_runtime_apis! {
 		/// cores can have paras assigned to them.
 		fn availability_cores() -> Vec<CoreState<N>>;
 
-		/// 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_data() -> GlobalValidationData<N>;
+		/// Yields the full validation data for the given ParaId along with an assumption that
+		/// should be used if the para currently occupieds a core.
+		///
+		/// Returns `None` if either the para is not registered or the assumption is `Freed`
+		/// and the para already occupies a core.
+		fn full_validation_data(para_id: Id, assumption: OccupiedCoreAssumption)
+			-> Option<ValidationData<N>>;
 
-		/// Yields the LocalValidationData for the given ParaId along with an assumption that
+		/// Yields the persisted validation data for the given ParaId along with an assumption that
 		/// should be used if the para currently occupies a core.
 		///
 		/// Returns `None` if either the para is not registered or the assumption is `Freed`
 		/// and the para already occupies a core.
-		fn local_validation_data(para_id: Id, assumption: OccupiedCoreAssumption)
-			-> Option<LocalValidationData<N>>;
+		fn persisted_validation_data(para_id: Id, assumption: OccupiedCoreAssumption)
+			-> Option<PersistedValidationData<N>>;
 
 		/// Returns the session index expected at a child of the block.
 		///
diff --git a/polkadot/roadmap/implementers-guide/src/node/utility/candidate-validation.md b/polkadot/roadmap/implementers-guide/src/node/utility/candidate-validation.md
index cd4a040fbfe7c1b3adf8bfcaa62ee688f5f1461a..19108bd78e811f48bb42d714595b1e06de4ec58b 100644
--- a/polkadot/roadmap/implementers-guide/src/node/utility/candidate-validation.md
+++ b/polkadot/roadmap/implementers-guide/src/node/utility/candidate-validation.md
@@ -24,7 +24,7 @@ Upon receiving a validation request, the first thing the candidate validation su
 
 ### Determining Parameters
 
-For a [`CandidateValidationMessage`][CVM]`::ValidateFromExhaustive`, these parameters are exhaustively provided. The [`OmittedValidationData`](../../types/availability.md#omittedvalidationdata) can be deconstructed into the validation data.
+For a [`CandidateValidationMessage`][CVM]`::ValidateFromExhaustive`, these parameters are exhaustively provided. The [`TransientValidationData`](../../types/candidate.md#transientvalidationdata) is optional, and is used to perform further checks on the outputs of validation.
 
 For a [`CandidateValidationMessage`][CVM]`::ValidateFromChainState`, some more work needs to be done. Due to the uncertainty of Availability Cores (implemented in the [`Scheduler`](../../runtime/scheduler.md) module of the runtime), a candidate at a particular relay-parent and for a particular para may have two different valid validation-data to be executed under depending on what is assumed to happen if the para is occupying a core at the onset of the new block. This is encoded as an `OccupiedCoreAssumption` in the runtime API.
 
@@ -40,7 +40,7 @@ Once we have all parameters, we can spin up a background task to perform the val
   * The collator signature is valid
   * The PoV provided matches the `pov_hash` field of the descriptor
 
-After that, we can invoke the validation function. Lastly, we do some final checks on the output:
+After that, we can invoke the validation function. Lastly, if available, we do some final checks on the output using the `TransientValidationData`:
   * The produced head-data is no larger than the maximum allowed.
   * The produced code upgrade, if any, is no larger than the maximum allowed, and a code upgrade was allowed to be signaled.
   * The amount and size of produced upward messages is not too large.
diff --git a/polkadot/roadmap/implementers-guide/src/types/candidate.md b/polkadot/roadmap/implementers-guide/src/types/candidate.md
index 02ece10806ac0a4060fc502e5c65dbdb76d0ae9d..70191af973a8aa5b2eea043cea5099667ff323f7 100644
--- a/polkadot/roadmap/implementers-guide/src/types/candidate.md
+++ b/polkadot/roadmap/implementers-guide/src/types/candidate.md
@@ -77,7 +77,7 @@ struct CandidateDescriptor {
 	/// The blake2-256 hash of the persisted validation data. These are extra parameters
 	/// derived from relay-chain state that influence the validity of the block which
 	/// must also be kept available for secondary checkers.
-	validation_data_hash: Hash,
+	persisted_validation_data_hash: Hash,
 	/// The blake2-256 hash of the pov-block.
 	pov_hash: Hash,
 	/// Signature on blake2-256 of components of this receipt:
@@ -119,23 +119,13 @@ Validation data that needs to be persisted for secondary checkers. See the secti
 struct PersistedValidationData {
 	/// The parent head-data.
 	parent_head: HeadData,
-	/// Whether the parachain is allowed to upgrade its validation code.
-	///
-	/// This is `Some` if so, and contains the number of the minimum relay-chain
-	/// height at which the upgrade will be applied, if an upgrade is signaled
-	/// now.
-	///
-	/// A parachain should enact its side of the upgrade at the end of the first
-	/// parablock executing in the context of a relay-chain block with at least this
-	/// height. This may be equal to the current perceived relay-chain block height, in
-	/// which case the code upgrade should be applied at the end of the signaling
-	/// block.
-	///
-	/// This informs a relay-chain backing check and the parachain logic.
-	code_upgrade_allowed: Option<BlockNumber>,
-
 	/// The relay-chain block number this is in the context of. This informs the collator.
 	block_number: BlockNumber,
+	/// The relay-chain 
+	/// The list of MQC heads for the inbound channels paired with the sender para ids. This
+	/// vector is sorted ascending by the para id and doesn't contain multiple entries with the same
+	/// sender.
+	hrmp_mqc_heads: Vec<(ParaId, Hash)>,
 }
 ```
 
@@ -155,10 +145,20 @@ struct TransientValidationData {
 	max_head_data_size: u32,
 	/// The balance of the parachain at the moment of validation.
 	balance: Balance,
-	/// The list of MQC heads for the inbound channels paired with the sender para ids. This
-	/// vector is sorted ascending by the para id and doesn't contain multiple entries with the same
-	/// sender. This informs the collator.
-	hrmp_mqc_heads: Vec<(ParaId, Hash)>,
+	/// Whether the parachain is allowed to upgrade its validation code.
+	///
+	/// This is `Some` if so, and contains the number of the minimum relay-chain
+	/// height at which the upgrade will be applied, if an upgrade is signaled
+	/// now.
+	///
+	/// A parachain should enact its side of the upgrade at the end of the first
+	/// parablock executing in the context of a relay-chain block with at least this
+	/// height. This may be equal to the current perceived relay-chain block height, in
+	/// which case the code upgrade should be applied at the end of the signaling
+	/// block.
+	///
+	/// This informs a relay-chain backing check and the parachain logic.
+	code_upgrade_allowed: Option<BlockNumber>,
 }
 ```
 
@@ -219,8 +219,8 @@ This struct encapsulates the outputs of candidate validation.
 struct ValidationOutputs {
 	/// The head-data produced by validation.
 	head_data: HeadData,
-	/// The validation data, persisted and transient.
-	validation_data: ValidationData,
+	/// The validation data, persisted.
+	validation_data: PersistedValidationData,
 	/// Messages directed to other paras routed via the relay chain.
 	horizontal_messages: Vec<OutboundHrmpMessage>,
 	/// 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 e054b82f078010ef473596176937a61f9445ab7b..0fdd871e274e8d7aea425214472f9370d5345388 100644
--- a/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md
+++ b/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md
@@ -393,11 +393,13 @@ enum CandidateValidationMessage {
 	/// the para is not free at the relay-parent, an error is returned.
 	ValidateFromChainState(CandidateDescriptor, PoV, ResponseChannel<Result<ValidationResult>>),
 
-	/// Validate a candidate with provided parameters. Explicitly provide the `OmittedValidationData`
+	/// Validate a candidate with provided parameters. Explicitly provide the `PersistedValidationData`
 	/// and `ValidationCode` so this can do full validation without needing to access the state of
-	/// the relay-chain.
+	/// the relay-chain. Optionally provide the `TransientValidationData` which will lead to checks
+	/// on the output.
 	ValidateFromExhaustive(
-		OmittedValidationData,
+		PersistedValidationData,
+		Option<TransientValidationData>,
 		ValidationCode,
 		CandidateDescriptor,
 		PoV,
diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs
index ae85e751bf531dc57e3e9417e7b078e363383f81..9b51404fc06bf69108f30c74dad1f79dce08d124 100644
--- a/polkadot/runtime/parachains/src/configuration.rs
+++ b/polkadot/runtime/parachains/src/configuration.rs
@@ -19,13 +19,12 @@
 //! Configuration can change only at session boundaries and is buffered until then.
 
 use sp_std::prelude::*;
-use primitives::v1::{ValidatorId, GlobalValidationData};
+use primitives::v1::ValidatorId;
 use frame_support::{
 	decl_storage, decl_module, decl_error,
 	dispatch::DispatchResult,
 	weights::{DispatchClass, Weight},
 };
-use sp_runtime::traits::One;
 use codec::{Encode, Decode};
 use frame_system::ensure_root;
 
@@ -220,16 +219,6 @@ 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: <frame_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 0050e85724bb08c95609d6538e58f288a02b1847..1e04c2a407911b108a0afda8b43b039fb88fd784 100644
--- a/polkadot/runtime/parachains/src/inclusion.rs
+++ b/polkadot/runtime/parachains/src/inclusion.rs
@@ -22,7 +22,6 @@
 
 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, CommittedCandidateReceipt,
@@ -435,9 +434,8 @@ 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) {
+							let persisted_validation_data =
+								match crate::util::make_persisted_validation_data::<T>(para_id) {
 									Some(l) => l,
 									None => {
 										// We don't want to error out here because it will
@@ -445,16 +443,12 @@ impl<T: Trait> Module<T> {
 										// doing anything.
 										return Ok(Vec::new());
 									}
-								}
-							);
+								};
 
-							let expected = validation_data_hash(
-								&global_validation_data,
-								&local_validation_data,
-							);
+							let expected = persisted_validation_data.hash();
 
 							ensure!(
-								expected == candidate.descriptor().validation_data_hash,
+								expected == candidate.descriptor().persisted_validation_data_hash,
 								Error::<T>::ValidationDataHashMismatch,
 							);
 						}
@@ -722,7 +716,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.persisted_validation_data_hash,
 			&candidate.descriptor.pov_hash,
 		);
 
@@ -851,7 +845,7 @@ mod tests {
 		head_data: HeadData,
 		pov_hash: Hash,
 		relay_parent: Hash,
-		validation_data_hash: Hash,
+		persisted_validation_data_hash: Hash,
 		new_validation_code: Option<ValidationCode>,
 	}
 
@@ -862,7 +856,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,
+					persisted_validation_data_hash: self.persisted_validation_data_hash,
 					..Default::default()
 				},
 				commitments: CandidateCommitments {
@@ -875,9 +869,9 @@ 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))
+		let persisted_validation_data
+			= crate::util::make_persisted_validation_data::<Test>(para_id)?;
+		Some(persisted_validation_data.hash())
 	}
 
 	#[test]
@@ -1306,7 +1300,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(),
+					persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 				collator_sign_candidate(
@@ -1338,14 +1332,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(),
+					persisted_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(),
+					persisted_validation_data_hash: make_vdata_hash(chain_b).unwrap(),
 					..Default::default()
 				}.build();
 
@@ -1392,7 +1386,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(),
+					persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 				collator_sign_candidate(
@@ -1427,7 +1421,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(),
+					persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 				collator_sign_candidate(
@@ -1459,7 +1453,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(),
+					persisted_validation_data_hash: make_vdata_hash(thread_a).unwrap(),
 					..Default::default()
 				}.build();
 
@@ -1497,7 +1491,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(),
+					persisted_validation_data_hash: make_vdata_hash(thread_a).unwrap(),
 					..Default::default()
 				}.build();
 
@@ -1534,7 +1528,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(),
+					persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 
@@ -1580,7 +1574,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(),
+					persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 
@@ -1619,7 +1613,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(),
+					persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 					..Default::default()
 				}.build();
 
@@ -1660,7 +1654,7 @@ mod tests {
 					para_id: chain_a,
 					relay_parent: System::parent_hash(),
 					pov_hash: Hash::from([1; 32]),
-					validation_data_hash: [42u8; 32].into(),
+					persisted_validation_data_hash: [42u8; 32].into(),
 					..Default::default()
 				}.build();
 
@@ -1750,7 +1744,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(),
+				persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 				..Default::default()
 			}.build();
 			collator_sign_candidate(
@@ -1762,7 +1756,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(),
+				persisted_validation_data_hash: make_vdata_hash(chain_b).unwrap(),
 				..Default::default()
 			}.build();
 			collator_sign_candidate(
@@ -1774,7 +1768,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(),
+				persisted_validation_data_hash: make_vdata_hash(thread_a).unwrap(),
 				..Default::default()
 			}.build();
 			collator_sign_candidate(
@@ -1906,7 +1900,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(),
+				persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(),
 				new_validation_code: Some(vec![1, 2, 3].into()),
 				..Default::default()
 			}.build();
diff --git a/polkadot/runtime/parachains/src/lib.rs b/polkadot/runtime/parachains/src/lib.rs
index b41968b480f43293d19243a0f93507199818bffa..b707c3e39eaf4ff1b6b7b8a83dd86f8a3249f1c0 100644
--- a/polkadot/runtime/parachains/src/lib.rs
+++ b/polkadot/runtime/parachains/src/lib.rs
@@ -38,6 +38,8 @@ pub mod validity;
 
 pub mod runtime_api_impl;
 
+mod util;
+
 #[cfg(test)]
 mod mock;
 
diff --git a/polkadot/runtime/parachains/src/paras.rs b/polkadot/runtime/parachains/src/paras.rs
index abc69d0bceddc18150a7efe512f16f7cc7be909d..f2a64de6c3d0c877535d3f2dd36922859ebb5f57 100644
--- a/polkadot/runtime/parachains/src/paras.rs
+++ b/polkadot/runtime/parachains/src/paras.rs
@@ -26,10 +26,10 @@
 use sp_std::prelude::*;
 #[cfg(feature = "std")]
 use sp_std::marker::PhantomData;
-use sp_runtime::traits::{One, BlakeTwo256, Hash as HashT, Saturating};
 use primitives::v1::{
-	Id as ParaId, ValidationCode, HeadData, LocalValidationData,
+	Id as ParaId, ValidationCode, HeadData,
 };
+use sp_runtime::traits::One;
 use frame_support::{
 	decl_storage, decl_module, decl_error,
 	traits::Get,
@@ -550,37 +550,6 @@ 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 = <frame_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 0a19e798f1795bc64a476d23cddd8ae0b7076400..716d3eed9cb1d0036a9921c2c24bfc3d759ac4d9 100644
--- a/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs
+++ b/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs
@@ -19,10 +19,10 @@
 
 use sp_std::prelude::*;
 use primitives::v1::{
-	ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, GlobalValidationData,
-	Id as ParaId, OccupiedCoreAssumption, LocalValidationData, SessionIndex, ValidationCode,
+	ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, ValidationData,
+	Id as ParaId, OccupiedCoreAssumption, SessionIndex, ValidationCode,
 	CommittedCandidateReceipt, ScheduledCore, OccupiedCore, CoreOccupied, CoreIndex,
-	GroupIndex, CandidateEvent,
+	GroupIndex, CandidateEvent, PersistedValidationData,
 };
 use sp_runtime::traits::Zero;
 use frame_support::debug;
@@ -161,36 +161,61 @@ pub fn availability_cores<T: initializer::Trait>() -> Vec<CoreState<T::BlockNumb
 	core_states
 }
 
-/// Implementation for the `global_validation_data` function of the runtime API.
-pub fn global_validation_data<T: initializer::Trait>()
-	-> GlobalValidationData<T::BlockNumber>
-{
-	<configuration::Module<T>>::global_validation_data()
-}
-
-/// Implementation for the `local_validation_data` function of the runtime API.
-pub fn local_validation_data<T: initializer::Trait>(
+fn with_assumption<Trait, T, F>(
 	para_id: ParaId,
 	assumption: OccupiedCoreAssumption,
-) -> Option<LocalValidationData<T::BlockNumber>> {
+	build: F,
+) -> Option<T> where
+	Trait: inclusion::Trait,
+	F: FnOnce() -> Option<T>,
+{
 	match assumption {
 		OccupiedCoreAssumption::Included => {
-			<inclusion::Module<T>>::force_enact(para_id);
-			<paras::Module<T>>::local_validation_data(para_id)
+			<inclusion::Module<Trait>>::force_enact(para_id);
+			build()
 		}
 		OccupiedCoreAssumption::TimedOut => {
-			<paras::Module<T>>::local_validation_data(para_id)
+			build()
 		}
 		OccupiedCoreAssumption::Free => {
-			if <inclusion::Module<T>>::pending_availability(para_id).is_some() {
+			if <inclusion::Module<Trait>>::pending_availability(para_id).is_some() {
 				None
 			} else {
-				<paras::Module<T>>::local_validation_data(para_id)
+				build()
 			}
 		}
 	}
 }
 
+/// Implementation for the `full_validation_data` function of the runtime API.
+pub fn full_validation_data<T: initializer::Trait>(
+	para_id: ParaId,
+	assumption: OccupiedCoreAssumption,
+)
+	-> Option<ValidationData<T::BlockNumber>>
+{
+	with_assumption::<T, _, _>(
+		para_id,
+		assumption,
+		|| Some(ValidationData {
+			persisted: crate::util::make_persisted_validation_data::<T>(para_id)?,
+			transient: crate::util::make_transient_validation_data::<T>(para_id)?,
+		}),
+	)
+}
+
+/// Implementation for the `persisted_validation_data` function of the runtime API.
+pub fn persisted_validation_data<T: initializer::Trait>(
+	para_id: ParaId,
+	assumption: OccupiedCoreAssumption,
+) -> Option<PersistedValidationData<T::BlockNumber>> {
+	with_assumption::<T, _, _>(
+		para_id,
+		assumption,
+		|| crate::util::make_persisted_validation_data::<T>(para_id),
+	)
+}
+
 /// Implementation for the `session_index_for_child` function of the runtime API.
 pub fn session_index_for_child<T: initializer::Trait>() -> SessionIndex {
 	// Just returns the session index from `inclusion`. Runtime APIs follow
@@ -208,26 +233,11 @@ pub fn validation_code<T: initializer::Trait>(
 	para_id: ParaId,
 	assumption: OccupiedCoreAssumption,
 ) -> Option<ValidationCode> {
-	let fetch = || {
-		<paras::Module<T>>::current_code(&para_id)
-	};
-
-	match assumption {
-		OccupiedCoreAssumption::Included => {
-			<inclusion::Module<T>>::force_enact(para_id);
-			fetch()
-		}
-		OccupiedCoreAssumption::TimedOut => {
-			fetch()
-		}
-		OccupiedCoreAssumption::Free => {
-			if <inclusion::Module<T>>::pending_availability(para_id).is_some() {
-				None
-			} else {
-				fetch()
-			}
-		}
-	}
+	with_assumption::<T, _, _>(
+		para_id,
+		assumption,
+		|| <paras::Module<T>>::current_code(&para_id),
+	)
 }
 
 /// Implementation for the `candidate_pending_availability` function of the runtime API.
diff --git a/polkadot/runtime/parachains/src/util.rs b/polkadot/runtime/parachains/src/util.rs
new file mode 100644
index 0000000000000000000000000000000000000000..028d65c5d9579dd0332e35c6fec206048e100d5f
--- /dev/null
+++ b/polkadot/runtime/parachains/src/util.rs
@@ -0,0 +1,71 @@
+// Copyright 2017-2020 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/>.
+
+//! Utilities that don't belong to any particular module but may draw
+//! on all modules.
+
+use sp_runtime::traits::{One, Saturating};
+use primitives::v1::{Id as ParaId, PersistedValidationData, TransientValidationData};
+use sp_std::prelude::*;
+
+use crate::{configuration, paras};
+
+/// Make the persisted validation data for a particular parachain.
+///
+/// This ties together the storage of several modules.
+pub fn make_persisted_validation_data<T: paras::Trait>(
+	para_id: ParaId,
+) -> Option<PersistedValidationData<T::BlockNumber>> {
+	let relay_parent_number = <frame_system::Module<T>>::block_number() - One::one();
+
+	Some(PersistedValidationData {
+		parent_head: <paras::Module<T>>::para_head(&para_id)?,
+		block_number: relay_parent_number,
+		hrmp_mqc_heads: Vec::new(),
+	})
+}
+
+/// Make the transient validation data for a particular parachain.
+///
+/// This ties together the storage of several modules.
+pub fn make_transient_validation_data<T: paras::Trait>(
+	para_id: ParaId,
+) -> Option<TransientValidationData<T::BlockNumber>> {
+	let config = <configuration::Module<T>>::config();
+	let relay_parent_number = <frame_system::Module<T>>::block_number() - One::one();
+
+	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.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(TransientValidationData {
+		max_code_size: config.max_code_size,
+		max_head_data_size: config.max_head_data_size,
+		balance: 0,
+		code_upgrade_allowed,
+	})
+}
diff --git a/polkadot/runtime/rococo-v1/src/lib.rs b/polkadot/runtime/rococo-v1/src/lib.rs
index f1af47613ed1bc1621c98372d7d9d63013364cb5..fe812a1389c918865db4f855736622096d3516c3 100644
--- a/polkadot/runtime/rococo-v1/src/lib.rs
+++ b/polkadot/runtime/rococo-v1/src/lib.rs
@@ -24,9 +24,9 @@ use sp_std::prelude::*;
 use codec::Encode;
 use primitives::v1::{
 	AccountId, AccountIndex, Balance, BlockNumber, Hash, Nonce, Signature, Moment,
-	GroupRotationInfo, CoreState, Id, GlobalValidationData, ValidationCode, CandidateEvent,
+	GroupRotationInfo, CoreState, Id, ValidationData, ValidationCode, CandidateEvent,
 	ValidatorId, ValidatorIndex, CommittedCandidateReceipt, OccupiedCoreAssumption,
-	LocalValidationData,
+	PersistedValidationData,
 };
 use runtime_common::{
 	SlowAdjustingFeeUpdate,
@@ -176,13 +176,14 @@ sp_api::impl_runtime_apis! {
 			runtime_api_impl::availability_cores::<Runtime>()
 		}
 
-		fn global_validation_data() -> GlobalValidationData<BlockNumber> {
-			runtime_api_impl::global_validation_data::<Runtime>()
+		fn full_validation_data(para_id: Id, assumption: OccupiedCoreAssumption)
+			-> Option<ValidationData<BlockNumber>> {
+			runtime_api_impl::full_validation_data::<Runtime>(para_id, assumption)
 		}
 
-		fn local_validation_data(para_id: Id, assumption: OccupiedCoreAssumption)
-			-> Option<LocalValidationData<BlockNumber>> {
-			runtime_api_impl::local_validation_data::<Runtime>(para_id, assumption)
+		fn persisted_validation_data(para_id: Id, assumption: OccupiedCoreAssumption)
+			-> Option<PersistedValidationData<BlockNumber>> {
+			runtime_api_impl::persisted_validation_data::<Runtime>(para_id, assumption)
 		}
 
 		fn session_index_for_child() -> SessionIndex {
diff --git a/polkadot/validation/src/pipeline.rs b/polkadot/validation/src/pipeline.rs
index 1c826a7bb1324c41136c464fc567038b01c45134..7fe18212f081166f64f95cfc78923596cb6ddd51 100644
--- a/polkadot/validation/src/pipeline.rs
+++ b/polkadot/validation/src/pipeline.rs
@@ -204,10 +204,8 @@ pub fn validate<'a>(
 	let params = ValidationParams {
 		parent_head: local_validation.parent_head.clone(),
 		block_data: pov_block.block_data.clone(),
-		max_code_size: global_validation.max_code_size,
-		max_head_data_size: global_validation.max_head_data_size,
 		relay_chain_height: global_validation.block_number,
-		code_upgrade_allowed: local_validation.code_upgrade_allowed,
+		hrmp_mqc_heads: Vec::new(),
 	};
 
 	// TODO: remove when ext does not do this.