diff --git a/cumulus/client/consensus/common/src/tests.rs b/cumulus/client/consensus/common/src/tests.rs
index 94e2304011be0bfbf003685de32ad5fd5fc8f424..79e620db3bfa0c82860fa5fcce28a8c5fea5b975 100644
--- a/cumulus/client/consensus/common/src/tests.rs
+++ b/cumulus/client/consensus/common/src/tests.rs
@@ -20,7 +20,7 @@ use async_trait::async_trait;
 use codec::Encode;
 use cumulus_client_pov_recovery::RecoveryKind;
 use cumulus_primitives_core::{
-	relay_chain::{BlockId, BlockNumber, CoreState},
+	relay_chain::{vstaging::CoreState, BlockId, BlockNumber},
 	CumulusDigestItem, InboundDownwardMessage, InboundHrmpMessage,
 };
 use cumulus_relay_chain_interface::{
diff --git a/cumulus/client/network/src/lib.rs b/cumulus/client/network/src/lib.rs
index 01ad15bed4daec41ec6c66874739d4f0420bc84c..3b9c0fc81ecece9567ac51e0652b68838528171c 100644
--- a/cumulus/client/network/src/lib.rs
+++ b/cumulus/client/network/src/lib.rs
@@ -32,8 +32,8 @@ use polkadot_node_primitives::{CollationSecondedSignal, Statement};
 use polkadot_node_subsystem::messages::RuntimeApiRequest;
 use polkadot_parachain_primitives::primitives::HeadData;
 use polkadot_primitives::{
-	CandidateReceipt, CompactStatement, Hash as PHash, Id as ParaId, OccupiedCoreAssumption,
-	SigningContext, UncheckedSigned,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, CompactStatement, Hash as PHash,
+	Id as ParaId, OccupiedCoreAssumption, SigningContext, UncheckedSigned,
 };
 
 use codec::{Decode, DecodeAll, Encode};
@@ -79,7 +79,7 @@ impl Decode for BlockAnnounceData {
 		let relay_parent = match PHash::decode(input) {
 			Ok(p) => p,
 			// For being backwards compatible, we support missing relay-chain parent.
-			Err(_) => receipt.descriptor.relay_parent,
+			Err(_) => receipt.descriptor.relay_parent(),
 		};
 
 		Ok(Self { receipt, statement, relay_parent })
@@ -108,7 +108,7 @@ impl BlockAnnounceData {
 			return Err(Validation::Failure { disconnect: true })
 		}
 
-		if HeadData(encoded_header).hash() != self.receipt.descriptor.para_head {
+		if HeadData(encoded_header).hash() != self.receipt.descriptor.para_head() {
 			tracing::debug!(
 				target: LOG_TARGET,
 				"Receipt para head hash doesn't match the hash of the header in the block announcement",
@@ -302,7 +302,7 @@ where
 		}
 		.map_err(|e| Box::new(BlockAnnounceError(format!("{:?}", e))) as Box<_>)?;
 
-		Ok(candidate_receipts.into_iter().map(|cr| cr.descriptor.para_head))
+		Ok(candidate_receipts.into_iter().map(|cr| cr.descriptor.para_head()))
 	}
 
 	/// Handle a block announcement with empty data (no statement) attached to it.
@@ -399,7 +399,7 @@ where
 				return Ok(e)
 			}
 
-			let relay_parent = block_announce_data.receipt.descriptor.relay_parent;
+			let relay_parent = block_announce_data.receipt.descriptor.relay_parent();
 
 			relay_chain_interface
 				.wait_for_block(relay_parent)
diff --git a/cumulus/client/network/src/tests.rs b/cumulus/client/network/src/tests.rs
index 4b347364521009a5426100e4945da84bceee40c1..009f922008b1204bebed6c5a68ad8dbb1daf6efb 100644
--- a/cumulus/client/network/src/tests.rs
+++ b/cumulus/client/network/src/tests.rs
@@ -26,10 +26,11 @@ use futures::{executor::block_on, poll, task::Poll, FutureExt, Stream, StreamExt
 use parking_lot::Mutex;
 use polkadot_node_primitives::{SignedFullStatement, Statement};
 use polkadot_primitives::{
+	vstaging::{CommittedCandidateReceiptV2, CoreState},
 	BlockNumber, CandidateCommitments, CandidateDescriptor, CollatorPair,
-	CommittedCandidateReceipt, CoreState, Hash as PHash, HeadData, InboundDownwardMessage,
-	InboundHrmpMessage, OccupiedCoreAssumption, PersistedValidationData, SessionIndex,
-	SigningContext, ValidationCodeHash, ValidatorId,
+	CommittedCandidateReceipt, Hash as PHash, HeadData, InboundDownwardMessage, InboundHrmpMessage,
+	OccupiedCoreAssumption, PersistedValidationData, SessionIndex, SigningContext,
+	ValidationCodeHash, ValidatorId,
 };
 use polkadot_test_client::{
 	Client as PClient, ClientBlockImportExt, DefaultTestClientBuilderExt, FullBackend as PBackend,
@@ -166,7 +167,7 @@ impl RelayChainInterface for DummyRelayChainInterface {
 		&self,
 		_: PHash,
 		_: ParaId,
-	) -> RelayChainResult<Option<CommittedCandidateReceipt>> {
+	) -> RelayChainResult<Option<CommittedCandidateReceiptV2>> {
 		if self.data.lock().runtime_version >=
 			RuntimeApiRequest::CANDIDATES_PENDING_AVAILABILITY_RUNTIME_REQUIREMENT
 		{
@@ -174,7 +175,7 @@ impl RelayChainInterface for DummyRelayChainInterface {
 		}
 
 		if self.data.lock().has_pending_availability {
-			Ok(Some(dummy_candidate()))
+			Ok(Some(dummy_candidate().into()))
 		} else {
 			Ok(None)
 		}
@@ -184,7 +185,7 @@ impl RelayChainInterface for DummyRelayChainInterface {
 		&self,
 		_: PHash,
 		_: ParaId,
-	) -> RelayChainResult<Vec<CommittedCandidateReceipt>> {
+	) -> RelayChainResult<Vec<CommittedCandidateReceiptV2>> {
 		if self.data.lock().runtime_version <
 			RuntimeApiRequest::CANDIDATES_PENDING_AVAILABILITY_RUNTIME_REQUIREMENT
 		{
@@ -192,7 +193,7 @@ impl RelayChainInterface for DummyRelayChainInterface {
 		}
 
 		if self.data.lock().has_pending_availability {
-			Ok(vec![dummy_candidate()])
+			Ok(vec![dummy_candidate().into()])
 		} else {
 			Ok(vec![])
 		}
@@ -412,7 +413,7 @@ async fn make_gossip_message_and_header(
 			validation_code_hash: ValidationCodeHash::from(PHash::random()),
 		},
 	};
-	let statement = Statement::Seconded(candidate_receipt);
+	let statement = Statement::Seconded(candidate_receipt.into());
 	let signed = SignedFullStatement::sign(
 		&keystore,
 		statement,
@@ -525,7 +526,7 @@ fn legacy_block_announce_data_handling() {
 
 	let block_data =
 		BlockAnnounceData::decode(&mut &data[..]).expect("Decoding works from legacy works");
-	assert_eq!(receipt.descriptor.relay_parent, block_data.relay_parent);
+	assert_eq!(receipt.descriptor.relay_parent(), block_data.relay_parent);
 
 	let data = block_data.encode();
 	LegacyBlockAnnounceData::decode(&mut &data[..]).expect("Decoding works");
@@ -600,7 +601,8 @@ async fn check_statement_seconded() {
 				erasure_root: PHash::random(),
 				signature: sp_core::sr25519::Signature::default().into(),
 				validation_code_hash: ValidationCodeHash::from(PHash::random()),
-			},
+			}
+			.into(),
 		},
 		statement: signed_statement.convert_payload().into(),
 		relay_parent,
diff --git a/cumulus/client/pov-recovery/src/lib.rs b/cumulus/client/pov-recovery/src/lib.rs
index 043cba12d1937807ded90c755377f8c99b976433..87349aef0c93e10e1ad1c87723721a151e8452d6 100644
--- a/cumulus/client/pov-recovery/src/lib.rs
+++ b/cumulus/client/pov-recovery/src/lib.rs
@@ -56,7 +56,11 @@ use polkadot_node_primitives::{PoV, POV_BOMB_LIMIT};
 use polkadot_node_subsystem::messages::{AvailabilityRecoveryMessage, RuntimeApiRequest};
 use polkadot_overseer::Handle as OverseerHandle;
 use polkadot_primitives::{
-	CandidateReceipt, CommittedCandidateReceipt, Id as ParaId, SessionIndex,
+	vstaging::{
+		CandidateReceiptV2 as CandidateReceipt,
+		CommittedCandidateReceiptV2 as CommittedCandidateReceipt,
+	},
+	Id as ParaId, SessionIndex,
 };
 
 use cumulus_primitives_core::ParachainBlockData;
diff --git a/cumulus/client/pov-recovery/src/tests.rs b/cumulus/client/pov-recovery/src/tests.rs
index 94dec32485ccb7478771a7d8e909056d7312dfee..d528a92a52a80a033052bf0cb5b80c841aba2f21 100644
--- a/cumulus/client/pov-recovery/src/tests.rs
+++ b/cumulus/client/pov-recovery/src/tests.rs
@@ -18,7 +18,7 @@ use super::*;
 use assert_matches::assert_matches;
 use codec::{Decode, Encode};
 use cumulus_primitives_core::relay_chain::{
-	BlockId, CandidateCommitments, CandidateDescriptor, CoreIndex, CoreState,
+	vstaging::CoreState, BlockId, CandidateCommitments, CandidateDescriptor, CoreIndex,
 };
 use cumulus_relay_chain_interface::{
 	InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption, PHash, PHeader,
@@ -532,7 +532,8 @@ fn make_candidate_chain(candidate_number_range: Range<u32>) -> Vec<CommittedCand
 				signature: collator.sign(&[0u8; 132]).into(),
 				para_head: PHash::zero(),
 				validation_code_hash: PHash::zero().into(),
-			},
+			}
+			.into(),
 			commitments: CandidateCommitments {
 				head_data: head_data.encode().into(),
 				upward_messages: vec![].try_into().expect("empty vec fits within bounds"),
diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs
index 3a204b0f383aa22f94b7f3f7c86e748af555e64a..f29e7f3ed7c7c5eb55cbe80a2c428c112e58cd60 100644
--- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs
+++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs
@@ -24,8 +24,9 @@ use std::{
 use async_trait::async_trait;
 use cumulus_primitives_core::{
 	relay_chain::{
-		runtime_api::ParachainHost, Block as PBlock, BlockId, BlockNumber,
-		CommittedCandidateReceipt, CoreIndex, CoreState, Hash as PHash, Header as PHeader,
+		runtime_api::ParachainHost,
+		vstaging::{CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState},
+		Block as PBlock, BlockId, BlockNumber, CoreIndex, Hash as PHash, Header as PHeader,
 		InboundHrmpMessage, OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId,
 	},
 	InboundDownwardMessage, ParaId, PersistedValidationData,
diff --git a/cumulus/client/relay-chain-interface/src/lib.rs b/cumulus/client/relay-chain-interface/src/lib.rs
index 2eed71c4d7d6788a43b1c66c31145d75e6ae82b0..4a49eada292ac83ff489e4a8f12d2aa60e7ef300 100644
--- a/cumulus/client/relay-chain-interface/src/lib.rs
+++ b/cumulus/client/relay-chain-interface/src/lib.rs
@@ -33,9 +33,9 @@ use sp_api::ApiError;
 use cumulus_primitives_core::relay_chain::{BlockId, Hash as RelayHash};
 pub use cumulus_primitives_core::{
 	relay_chain::{
-		BlockNumber, CommittedCandidateReceipt, CoreIndex, CoreState, Hash as PHash,
-		Header as PHeader, InboundHrmpMessage, OccupiedCoreAssumption, SessionIndex,
-		ValidationCodeHash, ValidatorId,
+		vstaging::{CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState},
+		BlockNumber, CoreIndex, Hash as PHash, Header as PHeader, InboundHrmpMessage,
+		OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId,
 	},
 	InboundDownwardMessage, ParaId, PersistedValidationData,
 };
diff --git a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs
index 7d6b5bfe3ec730cc827e104c21377175543fab26..1086e3a52ec013c33335cf671833a279ed6783cd 100644
--- a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs
+++ b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs
@@ -26,8 +26,8 @@ use futures::{Stream, StreamExt};
 use polkadot_core_primitives::{Block, BlockNumber, Hash, Header};
 use polkadot_overseer::{ChainApiBackend, RuntimeApiSubsystemClient};
 use polkadot_primitives::{
-	async_backing::{AsyncBackingParams, BackingState},
-	slashing, ApprovalVotingParams, CoreIndex, NodeFeatures,
+	async_backing::AsyncBackingParams, slashing, vstaging::async_backing::BackingState,
+	ApprovalVotingParams, CoreIndex, NodeFeatures,
 };
 use sc_authority_discovery::{AuthorityDiscovery, Error as AuthorityDiscoveryError};
 use sc_client_api::AuxStore;
@@ -143,7 +143,10 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient {
 	async fn availability_cores(
 		&self,
 		at: Hash,
-	) -> Result<Vec<polkadot_primitives::CoreState<Hash, BlockNumber>>, sp_api::ApiError> {
+	) -> Result<
+		Vec<polkadot_primitives::vstaging::CoreState<Hash, polkadot_core_primitives::BlockNumber>>,
+		sp_api::ApiError,
+	> {
 		Ok(self.rpc_client.parachain_host_availability_cores(at).await?)
 	}
 
@@ -212,8 +215,11 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient {
 	async fn candidate_pending_availability(
 		&self,
 		at: Hash,
-		para_id: ParaId,
-	) -> Result<Option<polkadot_primitives::CommittedCandidateReceipt<Hash>>, sp_api::ApiError> {
+		para_id: cumulus_primitives_core::ParaId,
+	) -> Result<
+		Option<polkadot_primitives::vstaging::CommittedCandidateReceiptV2<Hash>>,
+		sp_api::ApiError,
+	> {
 		Ok(self
 			.rpc_client
 			.parachain_host_candidate_pending_availability(at, para_id)
@@ -223,7 +229,7 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient {
 	async fn candidate_events(
 		&self,
 		at: Hash,
-	) -> Result<Vec<polkadot_primitives::CandidateEvent<Hash>>, sp_api::ApiError> {
+	) -> Result<Vec<polkadot_primitives::vstaging::CandidateEvent<Hash>>, sp_api::ApiError> {
 		Ok(self.rpc_client.parachain_host_candidate_events(at).await?)
 	}
 
@@ -266,7 +272,8 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient {
 	async fn on_chain_votes(
 		&self,
 		at: Hash,
-	) -> Result<Option<polkadot_primitives::ScrapedOnChainVotes<Hash>>, sp_api::ApiError> {
+	) -> Result<Option<polkadot_primitives::vstaging::ScrapedOnChainVotes<Hash>>, sp_api::ApiError>
+	{
 		Ok(self.rpc_client.parachain_host_on_chain_votes(at).await?)
 	}
 
@@ -437,8 +444,11 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient {
 	async fn candidates_pending_availability(
 		&self,
 		at: Hash,
-		para_id: ParaId,
-	) -> Result<Vec<polkadot_primitives::CommittedCandidateReceipt<Hash>>, sp_api::ApiError> {
+		para_id: cumulus_primitives_core::ParaId,
+	) -> Result<
+		Vec<polkadot_primitives::vstaging::CommittedCandidateReceiptV2<Hash>>,
+		sp_api::ApiError,
+	> {
 		Ok(self
 			.rpc_client
 			.parachain_host_candidates_pending_availability(at, para_id)
diff --git a/cumulus/client/relay-chain-rpc-interface/src/lib.rs b/cumulus/client/relay-chain-rpc-interface/src/lib.rs
index f53cdeffea94ba638f8abe8f5a9cd4add97e6563..0e2f6c054c403607754e494a3ce6b595ad2bcd6b 100644
--- a/cumulus/client/relay-chain-rpc-interface/src/lib.rs
+++ b/cumulus/client/relay-chain-rpc-interface/src/lib.rs
@@ -18,8 +18,9 @@ use async_trait::async_trait;
 use core::time::Duration;
 use cumulus_primitives_core::{
 	relay_chain::{
-		CommittedCandidateReceipt, Hash as RelayHash, Header as RelayHeader, InboundHrmpMessage,
-		OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId,
+		vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, Hash as RelayHash,
+		Header as RelayHeader, InboundHrmpMessage, OccupiedCoreAssumption, SessionIndex,
+		ValidationCodeHash, ValidatorId,
 	},
 	InboundDownwardMessage, ParaId, PersistedValidationData,
 };
diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs
index d8e5abaddc6b4364c286b53036be8c9e9446a31e..d7785d92c73a5fe02a1872bfd7a8f27a0b844d8e 100644
--- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs
+++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs
@@ -32,13 +32,18 @@ use codec::{Decode, Encode};
 
 use cumulus_primitives_core::{
 	relay_chain::{
-		async_backing::{AsyncBackingParams, BackingState},
-		slashing, ApprovalVotingParams, BlockNumber, CandidateCommitments, CandidateEvent,
-		CandidateHash, CommittedCandidateReceipt, CoreIndex, CoreState, DisputeState,
-		ExecutorParams, GroupRotationInfo, Hash as RelayHash, Header as RelayHeader,
-		InboundHrmpMessage, NodeFeatures, OccupiedCoreAssumption, PvfCheckStatement,
-		ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash,
-		ValidatorId, ValidatorIndex, ValidatorSignature,
+		async_backing::AsyncBackingParams,
+		slashing,
+		vstaging::{
+			async_backing::BackingState, CandidateEvent,
+			CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState,
+			ScrapedOnChainVotes,
+		},
+		ApprovalVotingParams, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex,
+		DisputeState, ExecutorParams, GroupRotationInfo, Hash as RelayHash, Header as RelayHeader,
+		InboundHrmpMessage, NodeFeatures, OccupiedCoreAssumption, PvfCheckStatement, SessionIndex,
+		SessionInfo, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex,
+		ValidatorSignature,
 	},
 	InboundDownwardMessage, ParaId, PersistedValidationData,
 };
diff --git a/polkadot/node/collation-generation/Cargo.toml b/polkadot/node/collation-generation/Cargo.toml
index 4b0a5f7248ab383884365105b164f27f2a090bf1..855b6b0e86eb5ba8e624c572937c841662276646 100644
--- a/polkadot/node/collation-generation/Cargo.toml
+++ b/polkadot/node/collation-generation/Cargo.toml
@@ -28,3 +28,4 @@ polkadot-primitives-test-helpers = { workspace = true }
 assert_matches = { workspace = true }
 rstest = { workspace = true }
 sp-keyring = { workspace = true, default-features = true }
+polkadot-primitives = { workspace = true, features = ["test"] }
diff --git a/polkadot/node/collation-generation/src/lib.rs b/polkadot/node/collation-generation/src/lib.rs
index 50adbddea4136e08e64893c419fb6bb28e5134f3..f04f69cbd38036774ef58612bd70f0f870c0fb87 100644
--- a/polkadot/node/collation-generation/src/lib.rs
+++ b/polkadot/node/collation-generation/src/lib.rs
@@ -48,9 +48,10 @@ use polkadot_node_subsystem_util::{
 	request_validators, runtime::fetch_claim_queue,
 };
 use polkadot_primitives::{
-	collator_signature_payload, CandidateCommitments, CandidateDescriptor, CandidateReceipt,
-	CollatorPair, CoreIndex, CoreState, Hash, Id as ParaId, OccupiedCoreAssumption,
-	PersistedValidationData, ScheduledCore, ValidationCodeHash,
+	collator_signature_payload,
+	vstaging::{CandidateReceiptV2 as CandidateReceipt, CoreState},
+	CandidateCommitments, CandidateDescriptor, CollatorPair, CoreIndex, Hash, Id as ParaId,
+	OccupiedCoreAssumption, PersistedValidationData, ScheduledCore, ValidationCodeHash,
 };
 use sp_core::crypto::Pair;
 use std::sync::Arc;
@@ -607,7 +608,8 @@ async fn construct_and_distribute_receipt(
 			erasure_root,
 			para_head: commitments.head_data.hash(),
 			validation_code_hash,
-		},
+		}
+		.into(),
 	};
 
 	gum::debug!(
diff --git a/polkadot/node/collation-generation/src/tests.rs b/polkadot/node/collation-generation/src/tests.rs
index 7f76988bb03552280a17c9444de02101e92b383b..78b35fde0ea27ed46d5912c72d404b3bf97dfcab 100644
--- a/polkadot/node/collation-generation/src/tests.rs
+++ b/polkadot/node/collation-generation/src/tests.rs
@@ -30,12 +30,12 @@ use polkadot_node_subsystem::{
 use polkadot_node_subsystem_test_helpers::{subsystem_test_harness, TestSubsystemContextHandle};
 use polkadot_node_subsystem_util::TimeoutExt;
 use polkadot_primitives::{
-	async_backing::{BackingState, CandidatePendingAvailability},
+	vstaging::async_backing::{BackingState, CandidatePendingAvailability},
 	AsyncBackingParams, BlockNumber, CollatorPair, HeadData, PersistedValidationData,
 	ScheduledCore, ValidationCode,
 };
 use polkadot_primitives_test_helpers::{
-	dummy_candidate_descriptor, dummy_hash, dummy_head_data, dummy_validator, make_candidate,
+	dummy_candidate_descriptor_v2, dummy_hash, dummy_head_data, dummy_validator, make_candidate,
 };
 use rstest::rstest;
 use sp_keyring::sr25519::Keyring as Sr25519Keyring;
@@ -504,22 +504,22 @@ fn sends_distribute_collation_message(#[case] runtime_version: u32) {
 			// descriptor has a valid signature, then just copy in the generated signature
 			// and check the rest of the fields for equality.
 			assert!(CollatorPair::verify(
-				&descriptor.signature,
+				&descriptor.signature().unwrap(),
 				&collator_signature_payload(
-					&descriptor.relay_parent,
-					&descriptor.para_id,
-					&descriptor.persisted_validation_data_hash,
-					&descriptor.pov_hash,
-					&descriptor.validation_code_hash,
+					&descriptor.relay_parent(),
+					&descriptor.para_id(),
+					&descriptor.persisted_validation_data_hash(),
+					&descriptor.pov_hash(),
+					&descriptor.validation_code_hash(),
 				)
 				.as_ref(),
-				&descriptor.collator,
+				&descriptor.collator().unwrap(),
 			));
 			let expect_descriptor = {
 				let mut expect_descriptor = expect_descriptor;
-				expect_descriptor.signature = descriptor.signature.clone();
-				expect_descriptor.erasure_root = descriptor.erasure_root;
-				expect_descriptor
+				expect_descriptor.signature = descriptor.signature().clone().unwrap();
+				expect_descriptor.erasure_root = descriptor.erasure_root();
+				expect_descriptor.into()
 			};
 			assert_eq!(descriptor, expect_descriptor);
 		},
@@ -658,7 +658,7 @@ fn fallback_when_no_validation_code_hash_api(#[case] runtime_version: u32) {
 			..
 		}) => {
 			let CandidateReceipt { descriptor, .. } = candidate_receipt;
-			assert_eq!(expect_validation_code_hash, descriptor.validation_code_hash);
+			assert_eq!(expect_validation_code_hash, descriptor.validation_code_hash());
 		},
 		_ => panic!("received wrong message type"),
 	}
@@ -752,9 +752,9 @@ fn submit_collation_leads_to_distribution() {
 			}) => {
 				let CandidateReceipt { descriptor, .. } = candidate_receipt;
 				assert_eq!(parent_head_data_hash, parent_head.hash());
-				assert_eq!(descriptor.persisted_validation_data_hash, expected_pvd.hash());
-				assert_eq!(descriptor.para_head, dummy_head_data().hash());
-				assert_eq!(descriptor.validation_code_hash, validation_code_hash);
+				assert_eq!(descriptor.persisted_validation_data_hash(), expected_pvd.hash());
+				assert_eq!(descriptor.para_head(), dummy_head_data().hash());
+				assert_eq!(descriptor.validation_code_hash(), validation_code_hash);
 			}
 		);
 
@@ -772,16 +772,17 @@ fn distribute_collation_for_occupied_core_with_async_backing_enabled(#[case] run
 	let para_id = ParaId::from(5);
 
 	// One core, in occupied state. The data in `CoreState` and `ClaimQueue` should match.
-	let cores: Vec<CoreState> = vec![CoreState::Occupied(polkadot_primitives::OccupiedCore {
-		next_up_on_available: Some(ScheduledCore { para_id, collator: None }),
-		occupied_since: 1,
-		time_out_at: 10,
-		next_up_on_time_out: Some(ScheduledCore { para_id, collator: None }),
-		availability: Default::default(), // doesn't matter
-		group_responsible: polkadot_primitives::GroupIndex(0),
-		candidate_hash: Default::default(),
-		candidate_descriptor: dummy_candidate_descriptor(dummy_hash()),
-	})];
+	let cores: Vec<CoreState> =
+		vec![CoreState::Occupied(polkadot_primitives::vstaging::OccupiedCore {
+			next_up_on_available: Some(ScheduledCore { para_id, collator: None }),
+			occupied_since: 1,
+			time_out_at: 10,
+			next_up_on_time_out: Some(ScheduledCore { para_id, collator: None }),
+			availability: Default::default(), // doesn't matter
+			group_responsible: polkadot_primitives::GroupIndex(0),
+			candidate_hash: Default::default(),
+			candidate_descriptor: dummy_candidate_descriptor_v2(dummy_hash()),
+		})];
 	let claim_queue = BTreeMap::from([(CoreIndex::from(0), VecDeque::from([para_id]))]).into();
 
 	test_harness(|mut virtual_overseer| async move {
@@ -882,7 +883,7 @@ fn distribute_collation_for_occupied_cores_with_async_backing_enabled_and_elasti
 	let cores = (0..3)
 		.into_iter()
 		.map(|idx| {
-			CoreState::Occupied(polkadot_primitives::OccupiedCore {
+			CoreState::Occupied(polkadot_primitives::vstaging::OccupiedCore {
 				next_up_on_available: Some(ScheduledCore { para_id, collator: None }),
 				occupied_since: 0,
 				time_out_at: 10,
@@ -890,7 +891,7 @@ fn distribute_collation_for_occupied_cores_with_async_backing_enabled_and_elasti
 				availability: Default::default(), // doesn't matter
 				group_responsible: polkadot_primitives::GroupIndex(idx as u32),
 				candidate_hash: Default::default(),
-				candidate_descriptor: dummy_candidate_descriptor(dummy_hash()),
+				candidate_descriptor: dummy_candidate_descriptor_v2(dummy_hash()),
 			})
 		})
 		.collect::<Vec<_>>();
@@ -1008,16 +1009,17 @@ fn no_collation_is_distributed_for_occupied_core_with_async_backing_disabled(
 	let para_id = ParaId::from(5);
 
 	// One core, in occupied state. The data in `CoreState` and `ClaimQueue` should match.
-	let cores: Vec<CoreState> = vec![CoreState::Occupied(polkadot_primitives::OccupiedCore {
-		next_up_on_available: Some(ScheduledCore { para_id, collator: None }),
-		occupied_since: 1,
-		time_out_at: 10,
-		next_up_on_time_out: Some(ScheduledCore { para_id, collator: None }),
-		availability: Default::default(), // doesn't matter
-		group_responsible: polkadot_primitives::GroupIndex(0),
-		candidate_hash: Default::default(),
-		candidate_descriptor: dummy_candidate_descriptor(dummy_hash()),
-	})];
+	let cores: Vec<CoreState> =
+		vec![CoreState::Occupied(polkadot_primitives::vstaging::OccupiedCore {
+			next_up_on_available: Some(ScheduledCore { para_id, collator: None }),
+			occupied_since: 1,
+			time_out_at: 10,
+			next_up_on_time_out: Some(ScheduledCore { para_id, collator: None }),
+			availability: Default::default(), // doesn't matter
+			group_responsible: polkadot_primitives::GroupIndex(0),
+			candidate_hash: Default::default(),
+			candidate_descriptor: dummy_candidate_descriptor_v2(dummy_hash()),
+		})];
 	let claim_queue = BTreeMap::from([(CoreIndex::from(0), VecDeque::from([para_id]))]).into();
 
 	test_harness(|mut virtual_overseer| async move {
@@ -1248,9 +1250,9 @@ mod helpers {
 					..
 				}) => {
 					assert_eq!(parent_head_data_hash, parent_head.hash());
-					assert_eq!(candidate_receipt.descriptor().persisted_validation_data_hash, pvd.hash());
-					assert_eq!(candidate_receipt.descriptor().para_head, dummy_head_data().hash());
-					assert_eq!(candidate_receipt.descriptor().validation_code_hash, validation_code_hash);
+					assert_eq!(candidate_receipt.descriptor().persisted_validation_data_hash(), pvd.hash());
+					assert_eq!(candidate_receipt.descriptor().para_head(), dummy_head_data().hash());
+					assert_eq!(candidate_receipt.descriptor().validation_code_hash(), validation_code_hash);
 				}
 			);
 		}
diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml
index 2c3db866566ce882afc0874e5f8a20dd3148dbbc..f9754d2babc909e1dacfe3b81e3d2a3009939b8e 100644
--- a/polkadot/node/core/approval-voting/Cargo.toml
+++ b/polkadot/node/core/approval-voting/Cargo.toml
@@ -53,6 +53,7 @@ kvdb-memorydb = { workspace = true }
 polkadot-primitives-test-helpers = { workspace = true }
 log = { workspace = true, default-features = true }
 sp-tracing = { workspace = true }
+polkadot-primitives = { workspace = true, features = ["test"] }
 
 polkadot-subsystem-bench = { workspace = true }
 
diff --git a/polkadot/node/core/approval-voting/src/approval_checking.rs b/polkadot/node/core/approval-voting/src/approval_checking.rs
index 3774edc69981b34a0dd14c08d57e8a90a704d88a..3b7262a46826eeba32811ad58f449b93dfaae61a 100644
--- a/polkadot/node/core/approval-voting/src/approval_checking.rs
+++ b/polkadot/node/core/approval-voting/src/approval_checking.rs
@@ -509,13 +509,13 @@ mod tests {
 	use crate::{approval_db, BTreeMap};
 	use bitvec::{bitvec, order::Lsb0 as BitOrderLsb0, vec::BitVec};
 	use polkadot_primitives::GroupIndex;
-	use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_hash};
+	use polkadot_primitives_test_helpers::{dummy_candidate_receipt_v2, dummy_hash};
 
 	#[test]
 	fn pending_is_not_approved() {
 		let candidate = CandidateEntry::from_v1(
 			approval_db::v1::CandidateEntry {
-				candidate: dummy_candidate_receipt(dummy_hash()),
+				candidate: dummy_candidate_receipt_v2(dummy_hash()),
 				session: 0,
 				block_assignments: BTreeMap::default(),
 				approvals: BitVec::default(),
@@ -550,7 +550,7 @@ mod tests {
 	fn exact_takes_only_assignments_up_to() {
 		let mut candidate: CandidateEntry = CandidateEntry::from_v1(
 			approval_db::v1::CandidateEntry {
-				candidate: dummy_candidate_receipt(dummy_hash()),
+				candidate: dummy_candidate_receipt_v2(dummy_hash()),
 				session: 0,
 				block_assignments: BTreeMap::default(),
 				approvals: bitvec![u8, BitOrderLsb0; 0; 10],
@@ -624,7 +624,7 @@ mod tests {
 	fn one_honest_node_always_approves() {
 		let mut candidate: CandidateEntry = CandidateEntry::from_v1(
 			approval_db::v1::CandidateEntry {
-				candidate: dummy_candidate_receipt(dummy_hash()),
+				candidate: dummy_candidate_receipt_v2(dummy_hash()),
 				session: 0,
 				block_assignments: BTreeMap::default(),
 				approvals: bitvec![u8, BitOrderLsb0; 0; 10],
@@ -1097,7 +1097,7 @@ mod tests {
 
 		let mut candidate: CandidateEntry = CandidateEntry::from_v1(
 			approval_db::v1::CandidateEntry {
-				candidate: dummy_candidate_receipt(dummy_hash()),
+				candidate: dummy_candidate_receipt_v2(dummy_hash()),
 				session: 0,
 				block_assignments: BTreeMap::default(),
 				approvals: bitvec![u8, BitOrderLsb0; 0; 3],
diff --git a/polkadot/node/core/approval-voting/src/approval_db/v1/mod.rs b/polkadot/node/core/approval-voting/src/approval_db/v1/mod.rs
index 53e9db64f63641ba7a816a75609b771fc594afb6..87a1d20b92f5315f2613e62174a666b24ee9bce5 100644
--- a/polkadot/node/core/approval-voting/src/approval_db/v1/mod.rs
+++ b/polkadot/node/core/approval-voting/src/approval_db/v1/mod.rs
@@ -25,8 +25,8 @@
 use codec::{Decode, Encode};
 use polkadot_node_primitives::approval::v1::{AssignmentCert, DelayTranche};
 use polkadot_primitives::{
-	BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash, SessionIndex,
-	ValidatorIndex, ValidatorSignature,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, BlockNumber, CandidateHash, CoreIndex,
+	GroupIndex, Hash, SessionIndex, ValidatorIndex, ValidatorSignature,
 };
 use sp_consensus_slots::Slot;
 use std::collections::BTreeMap;
diff --git a/polkadot/node/core/approval-voting/src/approval_db/v2/mod.rs b/polkadot/node/core/approval-voting/src/approval_db/v2/mod.rs
index cd9256a5d47eda0da75b3ca0238fbdd2060d1294..63c6cbf40b891f0bb8e66464a92221ac53229458 100644
--- a/polkadot/node/core/approval-voting/src/approval_db/v2/mod.rs
+++ b/polkadot/node/core/approval-voting/src/approval_db/v2/mod.rs
@@ -21,8 +21,8 @@ use polkadot_node_primitives::approval::{v1::DelayTranche, v2::AssignmentCertV2}
 use polkadot_node_subsystem::{SubsystemError, SubsystemResult};
 use polkadot_node_subsystem_util::database::{DBTransaction, Database};
 use polkadot_primitives::{
-	BlockNumber, CandidateHash, CandidateIndex, CandidateReceipt, CoreIndex, GroupIndex, Hash,
-	SessionIndex, ValidatorIndex, ValidatorSignature,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, BlockNumber, CandidateHash, CandidateIndex,
+	CoreIndex, GroupIndex, Hash, SessionIndex, ValidatorIndex, ValidatorSignature,
 };
 
 use sp_consensus_slots::Slot;
diff --git a/polkadot/node/core/approval-voting/src/approval_db/v2/tests.rs b/polkadot/node/core/approval-voting/src/approval_db/v2/tests.rs
index 06a3cc1e306b643b18dff67d11baca79ca378848..866702f861c18abedeb992a3dfacb1989e85b856 100644
--- a/polkadot/node/core/approval-voting/src/approval_db/v2/tests.rs
+++ b/polkadot/node/core/approval-voting/src/approval_db/v2/tests.rs
@@ -26,7 +26,8 @@ use crate::{
 	ops::{add_block_entry, canonicalize, force_approve, NewCandidateInfo},
 };
 use polkadot_primitives::{
-	BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash,
+	vstaging::{CandidateReceiptV2 as CandidateReceipt, MutateDescriptorV2},
+	BlockNumber, CandidateHash, CoreIndex, GroupIndex, Hash,
 };
 
 use polkadot_node_subsystem_util::database::Database;
@@ -35,7 +36,8 @@ use sp_consensus_slots::Slot;
 use std::{collections::HashMap, sync::Arc};
 
 use polkadot_primitives_test_helpers::{
-	dummy_candidate_receipt, dummy_candidate_receipt_bad_sig, dummy_hash,
+	dummy_candidate_receipt_bad_sig, dummy_candidate_receipt_v2,
+	dummy_candidate_receipt_v2_bad_sig, dummy_hash,
 };
 
 const DATA_COL: u32 = 0;
@@ -72,10 +74,10 @@ fn make_block_entry(
 }
 
 fn make_candidate(para_id: ParaId, relay_parent: Hash) -> CandidateReceipt {
-	let mut c = dummy_candidate_receipt(dummy_hash());
+	let mut c = dummy_candidate_receipt_v2(dummy_hash());
 
-	c.descriptor.para_id = para_id;
-	c.descriptor.relay_parent = relay_parent;
+	c.descriptor.set_para_id(para_id);
+	c.descriptor.set_relay_parent(relay_parent);
 
 	c
 }
@@ -95,7 +97,7 @@ fn read_write() {
 		make_block_entry(hash_a, Default::default(), 1, vec![(CoreIndex(0), candidate_hash)]);
 
 	let candidate_entry = CandidateEntry {
-		candidate: dummy_candidate_receipt_bad_sig(dummy_hash(), None),
+		candidate: dummy_candidate_receipt_v2_bad_sig(dummy_hash(), None),
 		session: 5,
 		block_assignments: vec![(
 			hash_a,
diff --git a/polkadot/node/core/approval-voting/src/approval_db/v3/mod.rs b/polkadot/node/core/approval-voting/src/approval_db/v3/mod.rs
index 7118fb6770fd79d80454524bd33abb2ba3d8f2ce..bc34f88af80ab97e0a2b777cfe4982b0f60dfa3e 100644
--- a/polkadot/node/core/approval-voting/src/approval_db/v3/mod.rs
+++ b/polkadot/node/core/approval-voting/src/approval_db/v3/mod.rs
@@ -25,8 +25,8 @@ use polkadot_node_subsystem::SubsystemResult;
 use polkadot_node_subsystem_util::database::{DBTransaction, Database};
 use polkadot_overseer::SubsystemError;
 use polkadot_primitives::{
-	BlockNumber, CandidateHash, CandidateIndex, CandidateReceipt, CoreIndex, GroupIndex, Hash,
-	SessionIndex, ValidatorIndex, ValidatorSignature,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, BlockNumber, CandidateHash, CandidateIndex,
+	CoreIndex, GroupIndex, Hash, SessionIndex, ValidatorIndex, ValidatorSignature,
 };
 
 use sp_consensus_slots::Slot;
diff --git a/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs b/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs
index d2a1d7d400b1495688865c0a0d30b04e44d14e20..372dd49803cb4e1edb04b5fccd7eda131e009484 100644
--- a/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs
+++ b/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs
@@ -25,7 +25,8 @@ use crate::{
 	ops::{add_block_entry, canonicalize, force_approve, NewCandidateInfo},
 };
 use polkadot_primitives::{
-	BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash,
+	vstaging::{CandidateReceiptV2 as CandidateReceipt, MutateDescriptorV2},
+	BlockNumber, CandidateHash, CoreIndex, GroupIndex, Hash,
 };
 
 use polkadot_node_subsystem_util::database::Database;
@@ -34,7 +35,7 @@ use sp_consensus_slots::Slot;
 use std::{collections::HashMap, sync::Arc};
 
 use polkadot_primitives_test_helpers::{
-	dummy_candidate_receipt, dummy_candidate_receipt_bad_sig, dummy_hash,
+	dummy_candidate_receipt_v2, dummy_candidate_receipt_v2_bad_sig, dummy_hash,
 };
 
 const DATA_COL: u32 = 0;
@@ -72,12 +73,12 @@ fn make_block_entry(
 }
 
 fn make_candidate(para_id: ParaId, relay_parent: Hash) -> CandidateReceipt {
-	let mut c = dummy_candidate_receipt(dummy_hash());
+	let mut c = dummy_candidate_receipt_v2(dummy_hash());
 
-	c.descriptor.para_id = para_id;
-	c.descriptor.relay_parent = relay_parent;
+	c.descriptor.set_para_id(para_id);
+	c.descriptor.set_relay_parent(relay_parent);
 
-	c
+	c.into()
 }
 
 #[test]
@@ -86,7 +87,7 @@ fn read_write() {
 
 	let hash_a = Hash::repeat_byte(1);
 	let hash_b = Hash::repeat_byte(2);
-	let candidate_hash = dummy_candidate_receipt_bad_sig(dummy_hash(), None).hash();
+	let candidate_hash = dummy_candidate_receipt_v2_bad_sig(dummy_hash(), None).hash();
 
 	let range = StoredBlockRange(10, 20);
 	let at_height = vec![hash_a, hash_b];
@@ -95,7 +96,7 @@ fn read_write() {
 		make_block_entry(hash_a, Default::default(), 1, vec![(CoreIndex(0), candidate_hash)]);
 
 	let candidate_entry = CandidateEntry {
-		candidate: dummy_candidate_receipt_bad_sig(dummy_hash(), None),
+		candidate: dummy_candidate_receipt_v2_bad_sig(dummy_hash(), None),
 		session: 5,
 		block_assignments: vec![(
 			hash_a,
diff --git a/polkadot/node/core/approval-voting/src/import.rs b/polkadot/node/core/approval-voting/src/import.rs
index e50a2f91148990b65390a8dc595391135f8940c9..be7b3103ab135f91a25f6ce998e982569e67322c 100644
--- a/polkadot/node/core/approval-voting/src/import.rs
+++ b/polkadot/node/core/approval-voting/src/import.rs
@@ -45,8 +45,9 @@ use polkadot_node_subsystem::{
 use polkadot_node_subsystem_util::{determine_new_blocks, runtime::RuntimeInfo};
 use polkadot_overseer::SubsystemSender;
 use polkadot_primitives::{
-	node_features, BlockNumber, CandidateEvent, CandidateHash, CandidateReceipt, ConsensusLog,
-	CoreIndex, GroupIndex, Hash, Header, SessionIndex,
+	node_features,
+	vstaging::{CandidateEvent, CandidateReceiptV2 as CandidateReceipt},
+	BlockNumber, CandidateHash, ConsensusLog, CoreIndex, GroupIndex, Hash, Header, SessionIndex,
 };
 use sc_keystore::LocalKeystore;
 use sp_consensus_slots::Slot;
@@ -618,10 +619,10 @@ pub(crate) mod tests {
 	use polkadot_node_subsystem_test_helpers::make_subsystem_context;
 	use polkadot_node_subsystem_util::database::Database;
 	use polkadot_primitives::{
-		node_features::FeatureIndex, ExecutorParams, Id as ParaId, IndexedVec, NodeFeatures,
-		SessionInfo, ValidatorId, ValidatorIndex,
+		node_features::FeatureIndex, vstaging::MutateDescriptorV2, ExecutorParams, Id as ParaId,
+		IndexedVec, NodeFeatures, SessionInfo, ValidatorId, ValidatorIndex,
 	};
-	use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_hash};
+	use polkadot_primitives_test_helpers::{dummy_candidate_receipt_v2, dummy_hash};
 	use schnellru::{ByLength, LruMap};
 	pub(crate) use sp_consensus_babe::{
 		digests::{CompatibleDigestItem, PreDigest, SecondaryVRFPreDigest},
@@ -764,9 +765,9 @@ pub(crate) mod tests {
 
 			let hash = header.hash();
 			let make_candidate = |para_id| {
-				let mut r = dummy_candidate_receipt(dummy_hash());
-				r.descriptor.para_id = para_id;
-				r.descriptor.relay_parent = hash;
+				let mut r = dummy_candidate_receipt_v2(dummy_hash());
+				r.descriptor.set_para_id(para_id);
+				r.descriptor.set_relay_parent(hash);
 				r
 			};
 			let candidates = vec![
@@ -917,9 +918,9 @@ pub(crate) mod tests {
 
 		let hash = header.hash();
 		let make_candidate = |para_id| {
-			let mut r = dummy_candidate_receipt(dummy_hash());
-			r.descriptor.para_id = para_id;
-			r.descriptor.relay_parent = hash;
+			let mut r = dummy_candidate_receipt_v2(dummy_hash());
+			r.descriptor.set_para_id(para_id);
+			r.descriptor.set_relay_parent(hash);
 			r
 		};
 		let candidates = vec![
@@ -1056,9 +1057,9 @@ pub(crate) mod tests {
 
 		let hash = header.hash();
 		let make_candidate = |para_id| {
-			let mut r = dummy_candidate_receipt(dummy_hash());
-			r.descriptor.para_id = para_id;
-			r.descriptor.relay_parent = hash;
+			let mut r = dummy_candidate_receipt_v2(dummy_hash());
+			r.descriptor.set_para_id(para_id);
+			r.descriptor.set_relay_parent(hash);
 			r
 		};
 		let candidates = vec![
@@ -1150,9 +1151,9 @@ pub(crate) mod tests {
 
 		let hash = header.hash();
 		let make_candidate = |para_id| {
-			let mut r = dummy_candidate_receipt(dummy_hash());
-			r.descriptor.para_id = para_id;
-			r.descriptor.relay_parent = hash;
+			let mut r = dummy_candidate_receipt_v2(dummy_hash());
+			r.descriptor.set_para_id(para_id);
+			r.descriptor.set_relay_parent(hash);
 			r
 		};
 		let candidates = vec![
@@ -1340,9 +1341,9 @@ pub(crate) mod tests {
 
 		let hash = header.hash();
 		let make_candidate = |para_id| {
-			let mut r = dummy_candidate_receipt(dummy_hash());
-			r.descriptor.para_id = para_id;
-			r.descriptor.relay_parent = hash;
+			let mut r = dummy_candidate_receipt_v2(dummy_hash());
+			r.descriptor.set_para_id(para_id);
+			r.descriptor.set_relay_parent(hash);
 			r
 		};
 		let candidates = vec![
diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs
index 0cb977c58021cec9f0a3fb23c203e5f61047a78a..2176cc7675beb972b6e323e1341e23a461a32620 100644
--- a/polkadot/node/core/approval-voting/src/lib.rs
+++ b/polkadot/node/core/approval-voting/src/lib.rs
@@ -52,9 +52,10 @@ use polkadot_node_subsystem_util::{
 	TimeoutExt,
 };
 use polkadot_primitives::{
-	ApprovalVoteMultipleCandidates, ApprovalVotingParams, BlockNumber, CandidateHash,
-	CandidateIndex, CandidateReceipt, CoreIndex, ExecutorParams, GroupIndex, Hash, SessionIndex,
-	SessionInfo, ValidatorId, ValidatorIndex, ValidatorPair, ValidatorSignature,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, ApprovalVoteMultipleCandidates,
+	ApprovalVotingParams, BlockNumber, CandidateHash, CandidateIndex, CoreIndex, ExecutorParams,
+	GroupIndex, Hash, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, ValidatorPair,
+	ValidatorSignature,
 };
 use sc_keystore::LocalKeystore;
 use sp_application_crypto::Pair;
@@ -2824,7 +2825,7 @@ where
 			target: LOG_TARGET,
 			validator_index = approval.validator.0,
 			candidate_hash = ?approved_candidate_hash,
-			para_id = ?candidate_entry.candidate_receipt().descriptor.para_id,
+			para_id = ?candidate_entry.candidate_receipt().descriptor.para_id(),
 			"Importing approval vote",
 		);
 
@@ -2923,7 +2924,7 @@ where
 	let block_hash = block_entry.block_hash();
 	let block_number = block_entry.block_number();
 	let session_index = block_entry.session();
-	let para_id = candidate_entry.candidate_receipt().descriptor().para_id;
+	let para_id = candidate_entry.candidate_receipt().descriptor().para_id();
 	let tick_now = state.clock.tick_now();
 
 	let (is_approved, status) = if let Some((approval_entry, status)) = state
@@ -3221,7 +3222,7 @@ async fn process_wakeup<Sender: SubsystemSender<RuntimeApiMessage>>(
 		gum::trace!(
 			target: LOG_TARGET,
 			?candidate_hash,
-			para_id = ?candidate_receipt.descriptor.para_id,
+			para_id = ?candidate_receipt.descriptor.para_id(),
 			block_hash = ?relay_block,
 			"Launching approval work.",
 		);
@@ -3352,7 +3353,7 @@ async fn launch_approval<
 	}
 
 	let candidate_hash = candidate.hash();
-	let para_id = candidate.descriptor.para_id;
+	let para_id = candidate.descriptor.para_id();
 	gum::trace!(target: LOG_TARGET, ?candidate_hash, ?para_id, "Recovering data.");
 
 	let timer = metrics.time_recover_and_approve();
@@ -3370,7 +3371,7 @@ async fn launch_approval<
 		.send_message(RuntimeApiMessage::Request(
 			block_hash,
 			RuntimeApiRequest::ValidationCodeByHash(
-				candidate.descriptor.validation_code_hash,
+				candidate.descriptor.validation_code_hash(),
 				code_tx,
 			),
 		))
@@ -3393,7 +3394,7 @@ async fn launch_approval<
 							?para_id,
 							?candidate_hash,
 							"Data unavailable for candidate {:?}",
-							(candidate_hash, candidate.descriptor.para_id),
+							(candidate_hash, candidate.descriptor.para_id()),
 						);
 						// do nothing. we'll just be a no-show and that'll cause others to rise up.
 						metrics_guard.take().on_approval_unavailable();
@@ -3404,7 +3405,7 @@ async fn launch_approval<
 							?para_id,
 							?candidate_hash,
 							"Channel closed while recovering data for candidate {:?}",
-							(candidate_hash, candidate.descriptor.para_id),
+							(candidate_hash, candidate.descriptor.para_id()),
 						);
 						// do nothing. we'll just be a no-show and that'll cause others to rise up.
 						metrics_guard.take().on_approval_unavailable();
@@ -3415,7 +3416,7 @@ async fn launch_approval<
 							?para_id,
 							?candidate_hash,
 							"Data recovery invalid for candidate {:?}",
-							(candidate_hash, candidate.descriptor.para_id),
+							(candidate_hash, candidate.descriptor.para_id()),
 						);
 						issue_local_invalid_statement(
 							&mut sender,
@@ -3438,7 +3439,7 @@ async fn launch_approval<
 				gum::warn!(
 					target: LOG_TARGET,
 					"Validation code unavailable for block {:?} in the state of block {:?} (a recent descendant)",
-					candidate.descriptor.relay_parent,
+					candidate.descriptor.relay_parent(),
 					block_hash,
 				);
 
diff --git a/polkadot/node/core/approval-voting/src/ops.rs b/polkadot/node/core/approval-voting/src/ops.rs
index 2a8fdba5aa3642f5b702e72bc2641d58106faa7a..f105580009faa4641504074c5901c4860f3cb7f6 100644
--- a/polkadot/node/core/approval-voting/src/ops.rs
+++ b/polkadot/node/core/approval-voting/src/ops.rs
@@ -20,7 +20,9 @@
 use polkadot_node_subsystem::{SubsystemError, SubsystemResult};
 
 use bitvec::order::Lsb0 as BitOrderLsb0;
-use polkadot_primitives::{BlockNumber, CandidateHash, CandidateReceipt, GroupIndex, Hash};
+use polkadot_primitives::{
+	vstaging::CandidateReceiptV2 as CandidateReceipt, BlockNumber, CandidateHash, GroupIndex, Hash,
+};
 
 use std::collections::{hash_map::Entry, BTreeMap, HashMap};
 
diff --git a/polkadot/node/core/approval-voting/src/persisted_entries.rs b/polkadot/node/core/approval-voting/src/persisted_entries.rs
index 16e231aa1a2dc1021cd8e111f2fb0533d0f74782..d891af01c3ab85621c72c4c0e6beda52335387da 100644
--- a/polkadot/node/core/approval-voting/src/persisted_entries.rs
+++ b/polkadot/node/core/approval-voting/src/persisted_entries.rs
@@ -26,8 +26,8 @@ use polkadot_node_primitives::approval::{
 	v2::{AssignmentCertV2, CandidateBitfield},
 };
 use polkadot_primitives::{
-	BlockNumber, CandidateHash, CandidateIndex, CandidateReceipt, CoreIndex, GroupIndex, Hash,
-	SessionIndex, ValidatorIndex, ValidatorSignature,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, BlockNumber, CandidateHash, CandidateIndex,
+	CoreIndex, GroupIndex, Hash, SessionIndex, ValidatorIndex, ValidatorSignature,
 };
 use sp_consensus_slots::Slot;
 
diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs
index db5ffd441c0d06c39c70627994f9fde5ba729908..099ab419dfbfc8373490d43c294023e2611eba26 100644
--- a/polkadot/node/core/approval-voting/src/tests.rs
+++ b/polkadot/node/core/approval-voting/src/tests.rs
@@ -42,8 +42,9 @@ use polkadot_node_subsystem_test_helpers as test_helpers;
 use polkadot_node_subsystem_util::TimeoutExt;
 use polkadot_overseer::SpawnGlue;
 use polkadot_primitives::{
-	ApprovalVote, CandidateCommitments, CandidateEvent, CoreIndex, DisputeStatement, GroupIndex,
-	Header, Id as ParaId, IndexedVec, NodeFeatures, ValidDisputeStatementKind, ValidationCode,
+	vstaging::{CandidateEvent, MutateDescriptorV2},
+	ApprovalVote, CandidateCommitments, CoreIndex, DisputeStatement, GroupIndex, Header,
+	Id as ParaId, IndexedVec, NodeFeatures, ValidDisputeStatementKind, ValidationCode,
 	ValidatorSignature,
 };
 use std::{cmp::max, time::Duration};
@@ -69,7 +70,9 @@ use super::{
 	},
 };
 
-use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_candidate_receipt_bad_sig};
+use polkadot_primitives_test_helpers::{
+	dummy_candidate_receipt_v2, dummy_candidate_receipt_v2_bad_sig,
+};
 
 const SLOT_DURATION_MILLIS: u64 = 5000;
 
@@ -638,8 +641,8 @@ where
 }
 
 fn make_candidate(para_id: ParaId, hash: &Hash) -> CandidateReceipt {
-	let mut r = dummy_candidate_receipt_bad_sig(*hash, Some(Default::default()));
-	r.descriptor.para_id = para_id;
+	let mut r = dummy_candidate_receipt_v2_bad_sig(*hash, Some(Default::default()));
+	r.descriptor.set_para_id(para_id);
 	r
 }
 
@@ -1282,7 +1285,7 @@ fn subsystem_rejects_approval_if_no_block_entry() {
 		let block_hash = Hash::repeat_byte(0x01);
 		let candidate_index = 0;
 		let validator = ValidatorIndex(0);
-		let candidate_hash = dummy_candidate_receipt(block_hash).hash();
+		let candidate_hash = dummy_candidate_receipt_v2(block_hash).hash();
 		let session_index = 1;
 
 		let rx = import_approval(
@@ -1324,9 +1327,9 @@ fn subsystem_rejects_approval_before_assignment() {
 
 		let candidate_hash = {
 			let mut candidate_receipt =
-				dummy_candidate_receipt_bad_sig(block_hash, Some(Default::default()));
-			candidate_receipt.descriptor.para_id = ParaId::from(0_u32);
-			candidate_receipt.descriptor.relay_parent = block_hash;
+				dummy_candidate_receipt_v2_bad_sig(block_hash, Some(Default::default()));
+			candidate_receipt.descriptor.set_para_id(ParaId::from(0_u32));
+			candidate_receipt.descriptor.set_relay_parent(block_hash);
 			candidate_receipt.hash()
 		};
 
@@ -1390,15 +1393,17 @@ fn subsystem_accepts_duplicate_assignment() {
 		let block_hash = Hash::repeat_byte(0x01);
 
 		let candidate_receipt1 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(1_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(1_u32));
 			receipt
-		};
+		}
+		.into();
 		let candidate_receipt2 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(2_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(2_u32));
 			receipt
-		};
+		}
+		.into();
 		let candidate_index1 = 0;
 		let candidate_index2 = 1;
 
@@ -1572,9 +1577,9 @@ fn subsystem_accepts_and_imports_approval_after_assignment() {
 
 		let candidate_hash = {
 			let mut candidate_receipt =
-				dummy_candidate_receipt_bad_sig(block_hash, Some(Default::default()));
-			candidate_receipt.descriptor.para_id = ParaId::from(0_u32);
-			candidate_receipt.descriptor.relay_parent = block_hash;
+				dummy_candidate_receipt_v2_bad_sig(block_hash, Some(Default::default()));
+			candidate_receipt.descriptor.set_para_id(ParaId::from(0_u32));
+			candidate_receipt.descriptor.set_relay_parent(block_hash);
 			candidate_receipt.hash()
 		};
 
@@ -1643,9 +1648,9 @@ fn subsystem_second_approval_import_only_schedules_wakeups() {
 
 		let candidate_hash = {
 			let mut candidate_receipt =
-				dummy_candidate_receipt_bad_sig(block_hash, Some(Default::default()));
-			candidate_receipt.descriptor.para_id = ParaId::from(0_u32);
-			candidate_receipt.descriptor.relay_parent = block_hash;
+				dummy_candidate_receipt_v2_bad_sig(block_hash, Some(Default::default()));
+			candidate_receipt.descriptor.set_para_id(ParaId::from(0_u32));
+			candidate_receipt.descriptor.set_relay_parent(block_hash);
 			candidate_receipt.hash()
 		};
 
@@ -2402,13 +2407,13 @@ fn subsystem_import_checked_approval_sets_one_block_bit_at_a_time() {
 		let block_hash = Hash::repeat_byte(0x01);
 
 		let candidate_receipt1 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(1_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(1_u32));
 			receipt
 		};
 		let candidate_receipt2 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(2_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(2_u32));
 			receipt
 		};
 		let candidate_hash1 = candidate_receipt1.hash();
@@ -2566,18 +2571,18 @@ fn inclusion_events_can_be_unordered_by_core_index() {
 		let block_hash = Hash::repeat_byte(0x01);
 
 		let candidate_receipt0 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(0_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(0_u32));
 			receipt
 		};
 		let candidate_receipt1 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(1_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(1_u32));
 			receipt
 		};
 		let candidate_receipt2 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(2_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(2_u32));
 			receipt
 		};
 		let candidate_index0 = 0;
@@ -2710,8 +2715,8 @@ fn approved_ancestor_test(
 			.iter()
 			.enumerate()
 			.map(|(i, hash)| {
-				let mut candidate_receipt = dummy_candidate_receipt(*hash);
-				candidate_receipt.descriptor.para_id = i.into();
+				let mut candidate_receipt = dummy_candidate_receipt_v2(*hash);
+				candidate_receipt.descriptor.set_para_id(i.into());
 				candidate_receipt
 			})
 			.collect();
@@ -2882,7 +2887,7 @@ fn subsystem_validate_approvals_cache() {
 		let block_hash = Hash::repeat_byte(0x01);
 		let fork_block_hash = Hash::repeat_byte(0x02);
 		let candidate_commitments = CandidateCommitments::default();
-		let mut candidate_receipt = dummy_candidate_receipt(block_hash);
+		let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash);
 		candidate_receipt.commitments_hash = candidate_commitments.hash();
 		let candidate_hash = candidate_receipt.hash();
 		let slot = Slot::from(1);
@@ -3012,13 +3017,13 @@ fn subsystem_doesnt_distribute_duplicate_compact_assignments() {
 		let block_hash = Hash::repeat_byte(0x01);
 
 		let candidate_receipt1 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(1_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(1_u32));
 			receipt
 		};
 		let candidate_receipt2 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(2_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(2_u32));
 			receipt
 		};
 		let candidate_index1 = 0;
@@ -3263,7 +3268,7 @@ where
 		);
 
 		let block_hash = Hash::repeat_byte(0x01);
-		let candidate_receipt = dummy_candidate_receipt(block_hash);
+		let candidate_receipt = dummy_candidate_receipt_v2(block_hash);
 		let candidate_hash = candidate_receipt.hash();
 		let slot = Slot::from(1);
 		let candidate_index = 0;
@@ -3965,8 +3970,8 @@ fn test_approval_is_sent_on_max_approval_coalesce_count() {
 		let candidate_commitments = CandidateCommitments::default();
 
 		let candidate_receipt1 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(1_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(1_u32));
 			receipt.commitments_hash = candidate_commitments.hash();
 			receipt
 		};
@@ -3974,8 +3979,8 @@ fn test_approval_is_sent_on_max_approval_coalesce_count() {
 		let candidate_hash1 = candidate_receipt1.hash();
 
 		let candidate_receipt2 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(2_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(2_u32));
 			receipt.commitments_hash = candidate_commitments.hash();
 			receipt
 		};
@@ -4266,8 +4271,8 @@ fn test_approval_is_sent_on_max_approval_coalesce_wait() {
 		let candidate_commitments = CandidateCommitments::default();
 
 		let candidate_receipt1 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(1_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(1_u32));
 			receipt.commitments_hash = candidate_commitments.hash();
 			receipt
 		};
@@ -4275,8 +4280,8 @@ fn test_approval_is_sent_on_max_approval_coalesce_wait() {
 		let candidate_hash1 = candidate_receipt1.hash();
 
 		let candidate_receipt2 = {
-			let mut receipt = dummy_candidate_receipt(block_hash);
-			receipt.descriptor.para_id = ParaId::from(2_u32);
+			let mut receipt = dummy_candidate_receipt_v2(block_hash);
+			receipt.descriptor.set_para_id(ParaId::from(2_u32));
 			receipt.commitments_hash = candidate_commitments.hash();
 			receipt
 		};
@@ -4420,7 +4425,7 @@ async fn setup_overseer_with_two_blocks_each_with_one_assignment_triggered(
 	let block_hash = Hash::repeat_byte(0x01);
 	let fork_block_hash = Hash::repeat_byte(0x02);
 	let candidate_commitments = CandidateCommitments::default();
-	let mut candidate_receipt = dummy_candidate_receipt(block_hash);
+	let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash);
 	candidate_receipt.commitments_hash = candidate_commitments.hash();
 	let candidate_hash = candidate_receipt.hash();
 	let slot = Slot::from(1);
@@ -4549,7 +4554,7 @@ fn subsystem_relaunches_approval_work_on_restart() {
 		let block_hash = Hash::repeat_byte(0x01);
 		let fork_block_hash = Hash::repeat_byte(0x02);
 		let candidate_commitments = CandidateCommitments::default();
-		let mut candidate_receipt = dummy_candidate_receipt(block_hash);
+		let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash);
 		candidate_receipt.commitments_hash = candidate_commitments.hash();
 		let slot = Slot::from(1);
 		clock.inner.lock().set_tick(slot_to_tick(slot + 2));
@@ -4805,7 +4810,7 @@ fn subsystem_sends_pending_approvals_on_approval_restart() {
 		let block_hash = Hash::repeat_byte(0x01);
 		let fork_block_hash = Hash::repeat_byte(0x02);
 		let candidate_commitments = CandidateCommitments::default();
-		let mut candidate_receipt = dummy_candidate_receipt(block_hash);
+		let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash);
 		candidate_receipt.commitments_hash = candidate_commitments.hash();
 		let slot = Slot::from(1);
 
@@ -4815,7 +4820,7 @@ fn subsystem_sends_pending_approvals_on_approval_restart() {
 			fork_block_hash,
 			slot,
 			sync_oracle_handle,
-			candidate_receipt,
+			candidate_receipt.into(),
 		)
 		.await;
 		chain_builder.build(&mut virtual_overseer).await;
diff --git a/polkadot/node/core/av-store/src/lib.rs b/polkadot/node/core/av-store/src/lib.rs
index 9473040e8f5ed63bb5c207efb9a8abfa38e317f5..9da2973773a01923b8108d73871f875fd1e96cb3 100644
--- a/polkadot/node/core/av-store/src/lib.rs
+++ b/polkadot/node/core/av-store/src/lib.rs
@@ -47,8 +47,8 @@ use polkadot_node_subsystem::{
 };
 use polkadot_node_subsystem_util as util;
 use polkadot_primitives::{
-	BlockNumber, CandidateEvent, CandidateHash, CandidateReceipt, ChunkIndex, CoreIndex, Hash,
-	Header, NodeFeatures, ValidatorIndex,
+	vstaging::{CandidateEvent, CandidateReceiptV2 as CandidateReceipt},
+	BlockNumber, CandidateHash, ChunkIndex, CoreIndex, Hash, Header, NodeFeatures, ValidatorIndex,
 };
 use util::availability_chunks::availability_chunk_indices;
 
diff --git a/polkadot/node/core/av-store/src/tests.rs b/polkadot/node/core/av-store/src/tests.rs
index 958917a3104fdb3101b6bb81a63e8370a73474f9..80043e56976b1b75c76894f664d54c20d0813107 100644
--- a/polkadot/node/core/av-store/src/tests.rs
+++ b/polkadot/node/core/av-store/src/tests.rs
@@ -31,8 +31,8 @@ use polkadot_node_subsystem::{
 use polkadot_node_subsystem_test_helpers as test_helpers;
 use polkadot_node_subsystem_util::{database::Database, TimeoutExt};
 use polkadot_primitives::{
-	node_features, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, HeadData, Header,
-	PersistedValidationData, ValidatorId,
+	node_features, vstaging::CandidateReceiptV2 as CandidateReceipt, CandidateHash, CoreIndex,
+	GroupIndex, HeadData, Header, PersistedValidationData, ValidatorId,
 };
 use polkadot_primitives_test_helpers::TestCandidateBuilder;
 use sp_keyring::Sr25519Keyring;
diff --git a/polkadot/node/core/backing/Cargo.toml b/polkadot/node/core/backing/Cargo.toml
index bd56a3ad693bff0094b6dd8b0ccb956b0aa49ee8..cd1acf9daa9390e7a24f3956e40f9c7477b32efc 100644
--- a/polkadot/node/core/backing/Cargo.toml
+++ b/polkadot/node/core/backing/Cargo.toml
@@ -36,3 +36,4 @@ assert_matches = { workspace = true }
 rstest = { workspace = true }
 polkadot-node-subsystem-test-helpers = { workspace = true }
 polkadot-primitives-test-helpers = { workspace = true }
+polkadot-primitives = { workspace = true, features = ["test"] }
diff --git a/polkadot/node/core/backing/src/error.rs b/polkadot/node/core/backing/src/error.rs
index 568f7140264457af301776f9fb3f5641ef312a07..e09d8425f78a07b3d9c8151cdc96ba2f5139e3a5 100644
--- a/polkadot/node/core/backing/src/error.rs
+++ b/polkadot/node/core/backing/src/error.rs
@@ -24,7 +24,7 @@ use polkadot_node_subsystem::{
 	RuntimeApiError, SubsystemError,
 };
 use polkadot_node_subsystem_util::{runtime, Error as UtilError};
-use polkadot_primitives::{BackedCandidate, ValidationCodeHash};
+use polkadot_primitives::{vstaging::BackedCandidate, ValidationCodeHash};
 
 use crate::{ParaId, LOG_TARGET};
 
diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs
index 4463fb34b5103da3ad618d985b80622071cc162d..b5362d32ad88d356bf45588589d6b02f24bd92eb 100644
--- a/polkadot/node/core/backing/src/lib.rs
+++ b/polkadot/node/core/backing/src/lib.rs
@@ -108,10 +108,14 @@ use polkadot_node_subsystem_util::{
 };
 use polkadot_parachain_primitives::primitives::IsSystem;
 use polkadot_primitives::{
-	node_features::FeatureIndex, BackedCandidate, CandidateCommitments, CandidateHash,
-	CandidateReceipt, CommittedCandidateReceipt, CoreIndex, CoreState, ExecutorParams, GroupIndex,
-	GroupRotationInfo, Hash, Id as ParaId, IndexedVec, NodeFeatures, PersistedValidationData,
-	SessionIndex, SigningContext, ValidationCode, ValidatorId, ValidatorIndex, ValidatorSignature,
+	node_features::FeatureIndex,
+	vstaging::{
+		BackedCandidate, CandidateReceiptV2 as CandidateReceipt,
+		CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState,
+	},
+	CandidateCommitments, CandidateHash, CoreIndex, ExecutorParams, GroupIndex, GroupRotationInfo,
+	Hash, Id as ParaId, IndexedVec, NodeFeatures, PersistedValidationData, SessionIndex,
+	SigningContext, ValidationCode, ValidatorId, ValidatorIndex, ValidatorSignature,
 	ValidityAttestation,
 };
 use polkadot_statement_table::{
@@ -627,7 +631,7 @@ async fn request_candidate_validation(
 	executor_params: ExecutorParams,
 ) -> Result<ValidationResult, Error> {
 	let (tx, rx) = oneshot::channel();
-	let is_system = candidate_receipt.descriptor.para_id.is_system();
+	let is_system = candidate_receipt.descriptor.para_id().is_system();
 
 	sender
 		.send_message(CandidateValidationMessage::ValidateFromExhaustive {
@@ -692,7 +696,7 @@ async fn validate_and_make_available(
 	} = params;
 
 	let validation_code = {
-		let validation_code_hash = candidate.descriptor().validation_code_hash;
+		let validation_code_hash = candidate.descriptor().validation_code_hash();
 		let (tx, rx) = oneshot::channel();
 		sender
 			.send_message(RuntimeApiMessage::Request(
@@ -725,7 +729,7 @@ async fn validate_and_make_available(
 				&mut sender,
 				relay_parent,
 				from_validator,
-				candidate.descriptor.para_id,
+				candidate.descriptor.para_id(),
 				candidate_hash,
 				pov_hash,
 			)
@@ -772,7 +776,7 @@ async fn validate_and_make_available(
 				pov.clone(),
 				candidate.hash(),
 				validation_data.clone(),
-				candidate.descriptor.erasure_root,
+				candidate.descriptor.erasure_root(),
 				core_index,
 				node_features,
 			)
@@ -1054,7 +1058,7 @@ fn core_index_from_statement(
 	}
 
 	if let StatementWithPVD::Seconded(candidate, _pvd) = statement.payload() {
-		let candidate_para_id = candidate.descriptor.para_id;
+		let candidate_para_id = candidate.descriptor.para_id();
 		let mut assigned_paras = claim_queue.iter_claims_for_core(&core_index);
 
 		if !assigned_paras.any(|id| id == &candidate_para_id) {
@@ -1445,14 +1449,14 @@ async fn handle_validated_candidate_command<Context>(
 							let candidate_hash = candidate.hash();
 							gum::debug!(
 								target: LOG_TARGET,
-								relay_parent = ?candidate.descriptor().relay_parent,
+								relay_parent = ?candidate.descriptor().relay_parent(),
 								?candidate_hash,
 								"Attempted to second candidate but was rejected by prospective parachains",
 							);
 
 							// Ensure the collator is reported.
 							ctx.send_message(CollatorProtocolMessage::Invalid(
-								candidate.descriptor().relay_parent,
+								candidate.descriptor().relay_parent(),
 								candidate,
 							))
 							.await;
@@ -1487,7 +1491,7 @@ async fn handle_validated_candidate_command<Context>(
 									Some(d) => d,
 								};
 
-								leaf_data.add_seconded_candidate(candidate.descriptor().para_id);
+								leaf_data.add_seconded_candidate(candidate.descriptor().para_id());
 							}
 
 							rp_state.issued_statements.insert(candidate_hash);
@@ -1636,7 +1640,7 @@ async fn import_statement<Context>(
 				let (tx, rx) = oneshot::channel();
 				ctx.send_message(ProspectiveParachainsMessage::IntroduceSecondedCandidate(
 					IntroduceSecondedCandidateRequest {
-						candidate_para: candidate.descriptor().para_id,
+						candidate_para: candidate.descriptor.para_id(),
 						candidate_receipt: candidate.clone(),
 						persisted_validation_data: pvd.clone(),
 					},
@@ -1665,7 +1669,7 @@ async fn import_statement<Context>(
 					persisted_validation_data: pvd.clone(),
 					// This is set after importing when seconding locally.
 					seconded_locally: false,
-					relay_parent: candidate.descriptor().relay_parent,
+					relay_parent: candidate.descriptor.relay_parent(),
 				},
 			);
 		}
@@ -1709,7 +1713,7 @@ async fn post_import_statement_actions<Context>(
 				&rp_state.table_context,
 				rp_state.inject_core_index,
 			) {
-				let para_id = backed.candidate().descriptor.para_id;
+				let para_id = backed.candidate().descriptor.para_id();
 				gum::debug!(
 					target: LOG_TARGET,
 					candidate_hash = ?candidate_hash,
@@ -1967,7 +1971,7 @@ async fn maybe_validate_and_import<Context>(
 						.get_candidate(&candidate_hash)
 						.ok_or(Error::CandidateNotFound)?
 						.to_plain(),
-					pov_hash: receipt.descriptor.pov_hash,
+					pov_hash: receipt.descriptor.pov_hash(),
 					from_validator: statement.validator_index(),
 					backing: Vec::new(),
 				};
@@ -2068,9 +2072,9 @@ async fn handle_second_message<Context>(
 	let _timer = metrics.time_process_second();
 
 	let candidate_hash = candidate.hash();
-	let relay_parent = candidate.descriptor().relay_parent;
+	let relay_parent = candidate.descriptor().relay_parent();
 
-	if candidate.descriptor().persisted_validation_data_hash != persisted_validation_data.hash() {
+	if candidate.descriptor().persisted_validation_data_hash() != persisted_validation_data.hash() {
 		gum::warn!(
 			target: LOG_TARGET,
 			?candidate_hash,
@@ -2104,12 +2108,12 @@ async fn handle_second_message<Context>(
 	let assigned_paras = rp_state.assigned_core.and_then(|core| rp_state.claim_queue.0.get(&core));
 
 	// Sanity check that candidate is from our assignment.
-	if !matches!(assigned_paras, Some(paras) if paras.contains(&candidate.descriptor().para_id)) {
+	if !matches!(assigned_paras, Some(paras) if paras.contains(&candidate.descriptor().para_id())) {
 		gum::debug!(
 			target: LOG_TARGET,
 			our_assignment_core = ?rp_state.assigned_core,
 			our_assignment_paras = ?assigned_paras,
-			collation = ?candidate.descriptor().para_id,
+			collation = ?candidate.descriptor().para_id(),
 			"Subsystem asked to second for para outside of our assignment",
 		);
 		return Ok(());
@@ -2119,7 +2123,7 @@ async fn handle_second_message<Context>(
 		target: LOG_TARGET,
 		our_assignment_core = ?rp_state.assigned_core,
 		our_assignment_paras = ?assigned_paras,
-		collation = ?candidate.descriptor().para_id,
+		collation = ?candidate.descriptor().para_id(),
 		"Current assignments vs collation",
 	);
 
diff --git a/polkadot/node/core/backing/src/tests/mod.rs b/polkadot/node/core/backing/src/tests/mod.rs
index d9c1fc9499e50ed29490559ffde2416357d68e70..dbb974a634fe6a330995c9bf3e1697843729e2c8 100644
--- a/polkadot/node/core/backing/src/tests/mod.rs
+++ b/polkadot/node/core/backing/src/tests/mod.rs
@@ -22,19 +22,19 @@ use polkadot_node_primitives::{BlockData, InvalidCandidate, SignedFullStatement,
 use polkadot_node_subsystem::{
 	errors::RuntimeApiError,
 	messages::{
-		AllMessages, CollatorProtocolMessage, RuntimeApiMessage, RuntimeApiRequest,
+		AllMessages, CollatorProtocolMessage, PvfExecKind, RuntimeApiMessage, RuntimeApiRequest,
 		ValidationFailed,
 	},
 	ActiveLeavesUpdate, FromOrchestra, OverseerSignal, TimeoutExt,
 };
 use polkadot_node_subsystem_test_helpers as test_helpers;
 use polkadot_primitives::{
-	node_features, CandidateDescriptor, GroupRotationInfo, HeadData, PersistedValidationData,
-	ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES,
+	node_features, vstaging::MutateDescriptorV2, CandidateDescriptor, GroupRotationInfo, HeadData,
+	PersistedValidationData, ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES,
 };
 use polkadot_primitives_test_helpers::{
 	dummy_candidate_receipt_bad_sig, dummy_collator, dummy_collator_signature,
-	dummy_committed_candidate_receipt, dummy_hash, validator_pubkeys,
+	dummy_committed_candidate_receipt_v2, dummy_hash, validator_pubkeys,
 };
 use polkadot_statement_table::v2::Misbehavior;
 use rstest::rstest;
@@ -236,7 +236,8 @@ impl TestCandidateBuilder {
 				para_head: self.head_data.hash(),
 				validation_code_hash: ValidationCode(self.validation_code).hash(),
 				persisted_validation_data_hash: self.persisted_validation_data_hash,
-			},
+			}
+			.into(),
 			commitments: CandidateCommitments {
 				head_data: self.head_data,
 				upward_messages: Default::default(),
@@ -433,7 +434,7 @@ async fn assert_validate_from_exhaustive(
 			},
 		) if validation_data == *assert_pvd &&
 			validation_code == *assert_validation_code &&
-			*pov == *assert_pov && &candidate_receipt.descriptor == assert_candidate.descriptor() &&
+			*pov == *assert_pov && candidate_receipt.descriptor == assert_candidate.descriptor &&
 			exec_kind == PvfExecKind::BackingSystemParas &&
 			candidate_receipt.commitments_hash == assert_candidate.commitments.hash() =>
 		{
@@ -650,7 +651,7 @@ fn backing_works(#[case] elastic_scaling_mvp: bool) {
 				},
 			) if validation_data == pvd_ab &&
 				validation_code == validation_code_ab &&
-				*pov == pov_ab && &candidate_receipt.descriptor == candidate_a.descriptor() &&
+				*pov == pov_ab && candidate_receipt.descriptor == candidate_a.descriptor &&
 				exec_kind == PvfExecKind::BackingSystemParas &&
 				candidate_receipt.commitments_hash == candidate_a_commitments_hash =>
 			{
@@ -1121,7 +1122,7 @@ fn extract_core_index_from_statement_works() {
 	.flatten()
 	.expect("should be signed");
 
-	candidate.descriptor.para_id = test_state.chain_ids[1];
+	candidate.descriptor.set_para_id(test_state.chain_ids[1]);
 
 	let signed_statement_3 = SignedFullStatementWithPVD::sign(
 		&test_state.keystore,
@@ -1286,7 +1287,7 @@ fn backing_works_while_validation_ongoing() {
 				},
 			) if validation_data == pvd_abc &&
 				validation_code == validation_code_abc &&
-				*pov == pov_abc && &candidate_receipt.descriptor == candidate_a.descriptor() &&
+				*pov == pov_abc && candidate_receipt.descriptor == candidate_a.descriptor &&
 				exec_kind == PvfExecKind::BackingSystemParas &&
 				candidate_a_commitments_hash == candidate_receipt.commitments_hash =>
 			{
@@ -1453,7 +1454,7 @@ fn backing_misbehavior_works() {
 				},
 			) if validation_data == pvd_a &&
 				validation_code == validation_code_a &&
-				*pov == pov_a && &candidate_receipt.descriptor == candidate_a.descriptor() &&
+				*pov == pov_a && candidate_receipt.descriptor == candidate_a.descriptor &&
 				exec_kind == PvfExecKind::BackingSystemParas &&
 				candidate_a_commitments_hash == candidate_receipt.commitments_hash =>
 			{
@@ -1620,7 +1621,7 @@ fn backing_dont_second_invalid() {
 				},
 			) if validation_data == pvd_a &&
 				validation_code == validation_code_a &&
-				*pov == pov_block_a && &candidate_receipt.descriptor == candidate_a.descriptor() &&
+				*pov == pov_block_a && candidate_receipt.descriptor == candidate_a.descriptor &&
 				exec_kind == PvfExecKind::BackingSystemParas &&
 				candidate_a.commitments.hash() == candidate_receipt.commitments_hash =>
 			{
@@ -1660,7 +1661,7 @@ fn backing_dont_second_invalid() {
 				},
 			) if validation_data == pvd_b &&
 				validation_code == validation_code_b &&
-				*pov == pov_block_b && &candidate_receipt.descriptor == candidate_b.descriptor() &&
+				*pov == pov_block_b && candidate_receipt.descriptor == candidate_b.descriptor &&
 				exec_kind == PvfExecKind::BackingSystemParas &&
 				candidate_b.commitments.hash() == candidate_receipt.commitments_hash =>
 			{
@@ -1787,7 +1788,7 @@ fn backing_second_after_first_fails_works() {
 				},
 			) if validation_data == pvd_a &&
 				validation_code == validation_code_a &&
-				*pov == pov_a && &candidate_receipt.descriptor == candidate.descriptor() &&
+				*pov == pov_a && candidate_receipt.descriptor == candidate.descriptor &&
 				exec_kind == PvfExecKind::BackingSystemParas &&
 				candidate.commitments.hash() == candidate_receipt.commitments_hash =>
 			{
@@ -1931,7 +1932,7 @@ fn backing_works_after_failed_validation() {
 				},
 			) if validation_data == pvd_a &&
 				validation_code == validation_code_a &&
-				*pov == pov_a && &candidate_receipt.descriptor == candidate.descriptor() &&
+				*pov == pov_a && candidate_receipt.descriptor == candidate.descriptor &&
 				exec_kind == PvfExecKind::BackingSystemParas &&
 				candidate.commitments.hash() == candidate_receipt.commitments_hash =>
 			{
@@ -1999,7 +2000,7 @@ fn candidate_backing_reorders_votes() {
 	};
 
 	let attested = TableAttestedCandidate {
-		candidate: dummy_committed_candidate_receipt(dummy_hash()),
+		candidate: dummy_committed_candidate_receipt_v2(dummy_hash()),
 		validity_votes: vec![
 			(ValidatorIndex(5), fake_attestation(5)),
 			(ValidatorIndex(3), fake_attestation(3)),
@@ -2210,7 +2211,7 @@ fn retry_works() {
 				},
 			) if validation_data == pvd_a &&
 				validation_code == validation_code_a &&
-				*pov == pov_a && &candidate_receipt.descriptor == candidate.descriptor() &&
+				*pov == pov_a && candidate_receipt.descriptor == candidate.descriptor &&
 				exec_kind == PvfExecKind::BackingSystemParas &&
 				candidate.commitments.hash() == candidate_receipt.commitments_hash
 		);
@@ -2752,7 +2753,7 @@ fn validator_ignores_statements_from_disabled_validators() {
 				}
 			) if validation_data == pvd &&
 				validation_code == expected_validation_code &&
-				*pov == expected_pov && &candidate_receipt.descriptor == candidate.descriptor() &&
+				*pov == expected_pov && candidate_receipt.descriptor == candidate.descriptor &&
 				exec_kind == PvfExecKind::BackingSystemParas &&
 				candidate_commitments_hash == candidate_receipt.commitments_hash =>
 			{
diff --git a/polkadot/node/core/backing/src/tests/prospective_parachains.rs b/polkadot/node/core/backing/src/tests/prospective_parachains.rs
index 57b2fabd43b0606ffe4ea7ffdabd22b2a335e2b7..caddd24080578d74ea4d4964f8ec590eaebb162e 100644
--- a/polkadot/node/core/backing/src/tests/prospective_parachains.rs
+++ b/polkadot/node/core/backing/src/tests/prospective_parachains.rs
@@ -20,7 +20,7 @@ use polkadot_node_subsystem::{
 	messages::{ChainApiMessage, HypotheticalMembership},
 	ActivatedLeaf, TimeoutExt,
 };
-use polkadot_primitives::{AsyncBackingParams, BlockNumber, Header, OccupiedCore};
+use polkadot_primitives::{vstaging::OccupiedCore, AsyncBackingParams, BlockNumber, Header};
 
 use super::*;
 
@@ -275,7 +275,7 @@ async fn assert_validate_seconded_candidate(
 		}) if &validation_data == assert_pvd &&
 			&validation_code == assert_validation_code &&
 			&*pov == assert_pov &&
-			&candidate_receipt.descriptor == candidate.descriptor() &&
+			candidate_receipt.descriptor == candidate.descriptor &&
 			exec_kind == PvfExecKind::BackingSystemParas &&
 			candidate.commitments.hash() == candidate_receipt.commitments_hash =>
 		{
@@ -890,7 +890,7 @@ fn prospective_parachains_reject_candidate() {
 			AllMessages::CollatorProtocol(CollatorProtocolMessage::Invalid(
 				relay_parent,
 				candidate_receipt,
-			)) if candidate_receipt.descriptor() == candidate.descriptor() &&
+			)) if candidate_receipt.descriptor == candidate.descriptor &&
 				candidate_receipt.commitments_hash == candidate.commitments.hash() &&
 				relay_parent == leaf_a_parent
 		);
@@ -1011,7 +1011,7 @@ fn second_multiple_candidates_per_relay_parent() {
 
 			assert_validate_seconded_candidate(
 				&mut virtual_overseer,
-				candidate.descriptor().relay_parent,
+				candidate.descriptor.relay_parent(),
 				&candidate,
 				&pov,
 				&pvd,
@@ -1064,13 +1064,13 @@ fn second_multiple_candidates_per_relay_parent() {
 						parent_hash,
 						_signed_statement,
 					)
-				) if parent_hash == candidate.descriptor().relay_parent => {}
+				) if parent_hash == candidate.descriptor.relay_parent() => {}
 			);
 
 			assert_matches!(
 				virtual_overseer.recv().await,
 				AllMessages::CollatorProtocol(CollatorProtocolMessage::Seconded(hash, statement)) => {
-					assert_eq!(candidate.descriptor().relay_parent, hash);
+					assert_eq!(candidate.descriptor.relay_parent(), hash);
 					assert_matches!(statement.payload(), Statement::Seconded(_));
 				}
 			);
@@ -1179,7 +1179,7 @@ fn backing_works() {
 
 		assert_validate_seconded_candidate(
 			&mut virtual_overseer,
-			candidate_a.descriptor().relay_parent,
+			candidate_a.descriptor.relay_parent(),
 			&candidate_a,
 			&pov,
 			&pvd,
@@ -1544,7 +1544,7 @@ fn seconding_sanity_check_occupy_same_depth() {
 
 			assert_validate_seconded_candidate(
 				&mut virtual_overseer,
-				candidate.descriptor().relay_parent,
+				candidate.descriptor.relay_parent(),
 				&candidate,
 				&pov,
 				&pvd,
@@ -1599,13 +1599,13 @@ fn seconding_sanity_check_occupy_same_depth() {
 						parent_hash,
 						_signed_statement,
 					)
-				) if parent_hash == candidate.descriptor().relay_parent => {}
+				) if parent_hash == candidate.descriptor.relay_parent() => {}
 			);
 
 			assert_matches!(
 				virtual_overseer.recv().await,
 				AllMessages::CollatorProtocol(CollatorProtocolMessage::Seconded(hash, statement)) => {
-					assert_eq!(candidate.descriptor().relay_parent, hash);
+					assert_eq!(candidate.descriptor.relay_parent(), hash);
 					assert_matches!(statement.payload(), Statement::Seconded(_));
 				}
 			);
@@ -1637,7 +1637,7 @@ fn occupied_core_assignment() {
 			time_out_at: 200_u32,
 			next_up_on_time_out: None,
 			availability: Default::default(),
-			candidate_descriptor,
+			candidate_descriptor: candidate_descriptor.into(),
 			candidate_hash: Default::default(),
 		});
 
diff --git a/polkadot/node/core/bitfield-signing/src/lib.rs b/polkadot/node/core/bitfield-signing/src/lib.rs
index 474de1c66abd98cf0f2bfbef89ed10ec6670cc46..7c67853503f691e145ab97f8845d4081de24c26c 100644
--- a/polkadot/node/core/bitfield-signing/src/lib.rs
+++ b/polkadot/node/core/bitfield-signing/src/lib.rs
@@ -34,7 +34,7 @@ use polkadot_node_subsystem::{
 use polkadot_node_subsystem_util::{
 	self as util, request_availability_cores, runtime::recv_runtime, Validator,
 };
-use polkadot_primitives::{AvailabilityBitfield, CoreState, Hash, ValidatorIndex};
+use polkadot_primitives::{vstaging::CoreState, AvailabilityBitfield, Hash, ValidatorIndex};
 use sp_keystore::{Error as KeystoreError, KeystorePtr};
 use std::{collections::HashMap, time::Duration};
 use wasm_timer::{Delay, Instant};
diff --git a/polkadot/node/core/bitfield-signing/src/tests.rs b/polkadot/node/core/bitfield-signing/src/tests.rs
index c08018375cf30834890c1d2ef4e1e740d321f1c8..9123414844a6c7632529e58eaa5b03dbc0200b55 100644
--- a/polkadot/node/core/bitfield-signing/src/tests.rs
+++ b/polkadot/node/core/bitfield-signing/src/tests.rs
@@ -17,8 +17,8 @@
 use super::*;
 use futures::{executor::block_on, pin_mut, StreamExt};
 use polkadot_node_subsystem::messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest};
-use polkadot_primitives::{CandidateHash, OccupiedCore};
-use polkadot_primitives_test_helpers::dummy_candidate_descriptor;
+use polkadot_primitives::{vstaging::OccupiedCore, CandidateHash};
+use polkadot_primitives_test_helpers::dummy_candidate_descriptor_v2;
 
 fn occupied_core(para_id: u32, candidate_hash: CandidateHash) -> CoreState {
 	CoreState::Occupied(OccupiedCore {
@@ -29,7 +29,7 @@ fn occupied_core(para_id: u32, candidate_hash: CandidateHash) -> CoreState {
 		next_up_on_time_out: None,
 		availability: Default::default(),
 		candidate_hash,
-		candidate_descriptor: dummy_candidate_descriptor(Hash::zero()),
+		candidate_descriptor: dummy_candidate_descriptor_v2(Hash::zero()),
 	})
 }
 
diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs
index e875be9b5df9cb35612430547c5bf6fe0ffc4d16..a48669c248254c406041dba7845264984ef7a8a9 100644
--- a/polkadot/node/core/candidate-validation/src/lib.rs
+++ b/polkadot/node/core/candidate-validation/src/lib.rs
@@ -45,8 +45,11 @@ use polkadot_primitives::{
 		DEFAULT_APPROVAL_EXECUTION_TIMEOUT, DEFAULT_BACKING_EXECUTION_TIMEOUT,
 		DEFAULT_LENIENT_PREPARATION_TIMEOUT, DEFAULT_PRECHECK_PREPARATION_TIMEOUT,
 	},
-	AuthorityDiscoveryId, CandidateCommitments, CandidateDescriptor, CandidateEvent,
-	CandidateReceipt, ExecutorParams, Hash, PersistedValidationData,
+	vstaging::{
+		CandidateDescriptorV2 as CandidateDescriptor, CandidateEvent,
+		CandidateReceiptV2 as CandidateReceipt,
+	},
+	AuthorityDiscoveryId, CandidateCommitments, ExecutorParams, Hash, PersistedValidationData,
 	PvfExecKind as RuntimePvfExecKind, PvfPrepKind, SessionIndex, ValidationCode,
 	ValidationCodeHash, ValidatorId,
 };
@@ -437,7 +440,7 @@ where
 		.into_iter()
 		.filter_map(|e| match e {
 			CandidateEvent::CandidateBacked(receipt, ..) => {
-				let h = receipt.descriptor.validation_code_hash;
+				let h = receipt.descriptor.validation_code_hash();
 				if already_prepared.contains(&h) {
 					None
 				} else {
@@ -646,7 +649,7 @@ async fn validate_candidate_exhaustive(
 	let _timer = metrics.time_validate_candidate_exhaustive();
 
 	let validation_code_hash = validation_code.hash();
-	let para_id = candidate_receipt.descriptor.para_id;
+	let para_id = candidate_receipt.descriptor.para_id();
 	gum::debug!(
 		target: LOG_TARGET,
 		?validation_code_hash,
@@ -747,7 +750,7 @@ async fn validate_candidate_exhaustive(
 			Err(ValidationFailed(e.to_string()))
 		},
 		Ok(res) =>
-			if res.head_data.hash() != candidate_receipt.descriptor.para_head {
+			if res.head_data.hash() != candidate_receipt.descriptor.para_head() {
 				gum::info!(target: LOG_TARGET, ?para_id, "Invalid candidate (para_head)");
 				Ok(ValidationResult::Invalid(InvalidCandidate::ParaHeadHashMismatch))
 			} else {
@@ -992,11 +995,11 @@ fn perform_basic_checks(
 		return Err(InvalidCandidate::ParamsTooLarge(encoded_pov_size as u64))
 	}
 
-	if pov_hash != candidate.pov_hash {
+	if pov_hash != candidate.pov_hash() {
 		return Err(InvalidCandidate::PoVHashMismatch)
 	}
 
-	if *validation_code_hash != candidate.validation_code_hash {
+	if *validation_code_hash != candidate.validation_code_hash() {
 		return Err(InvalidCandidate::CodeHashMismatch)
 	}
 
diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs
index 2f7baf4abb61e8d6a49d23d2f12d4cd058f8bc5a..997a347631a0523864b5279af82bcec7e28186ee 100644
--- a/polkadot/node/core/candidate-validation/src/tests.rs
+++ b/polkadot/node/core/candidate-validation/src/tests.rs
@@ -26,8 +26,8 @@ use polkadot_node_subsystem::messages::AllMessages;
 use polkadot_node_subsystem_util::reexports::SubsystemContext;
 use polkadot_overseer::ActivatedLeaf;
 use polkadot_primitives::{
-	CoreIndex, GroupIndex, HeadData, Id as ParaId, OccupiedCoreAssumption, SessionInfo,
-	UpwardMessage, ValidatorId,
+	vstaging::CandidateDescriptorV2, CandidateDescriptor, CoreIndex, GroupIndex, HeadData,
+	Id as ParaId, OccupiedCoreAssumption, SessionInfo, UpwardMessage, ValidatorId,
 };
 use polkadot_primitives_test_helpers::{
 	dummy_collator, dummy_collator_signature, dummy_hash, make_valid_candidate_descriptor,
@@ -106,7 +106,8 @@ fn correctly_checks_included_assumption() {
 		dummy_hash(),
 		dummy_hash(),
 		Sr25519Keyring::Alice,
-	);
+	)
+	.into();
 
 	let pool = TaskExecutor::new();
 	let (mut ctx, mut ctx_handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context::<
@@ -180,7 +181,8 @@ fn correctly_checks_timed_out_assumption() {
 		dummy_hash(),
 		dummy_hash(),
 		Sr25519Keyring::Alice,
-	);
+	)
+	.into();
 
 	let pool = TaskExecutor::new();
 	let (mut ctx, mut ctx_handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context::<
@@ -252,7 +254,8 @@ fn check_is_bad_request_if_no_validation_data() {
 		dummy_hash(),
 		dummy_hash(),
 		Sr25519Keyring::Alice,
-	);
+	)
+	.into();
 
 	let pool = TaskExecutor::new();
 	let (mut ctx, mut ctx_handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context::<
@@ -308,7 +311,8 @@ fn check_is_bad_request_if_no_validation_code() {
 		dummy_hash(),
 		dummy_hash(),
 		Sr25519Keyring::Alice,
-	);
+	)
+	.into();
 
 	let pool = TaskExecutor::new();
 	let (mut ctx, mut ctx_handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context::<
@@ -376,7 +380,8 @@ fn check_does_not_match() {
 		dummy_hash(),
 		dummy_hash(),
 		Sr25519Keyring::Alice,
-	);
+	)
+	.into();
 
 	let pool = TaskExecutor::new();
 	let (mut ctx, mut ctx_handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context::<
@@ -478,7 +483,8 @@ fn candidate_validation_ok_is_ok() {
 		head_data.hash(),
 		dummy_hash(),
 		Sr25519Keyring::Alice,
-	);
+	)
+	.into();
 
 	let check = perform_basic_checks(
 		&descriptor,
@@ -546,7 +552,8 @@ fn candidate_validation_bad_return_is_invalid() {
 		dummy_hash(),
 		dummy_hash(),
 		Sr25519Keyring::Alice,
-	);
+	)
+	.into();
 
 	let check = perform_basic_checks(
 		&descriptor,
@@ -580,7 +587,7 @@ fn perform_basic_checks_on_valid_candidate(
 	validation_code: &ValidationCode,
 	validation_data: &PersistedValidationData,
 	head_data_hash: Hash,
-) -> CandidateDescriptor {
+) -> CandidateDescriptorV2 {
 	let descriptor = make_valid_candidate_descriptor(
 		ParaId::from(1_u32),
 		dummy_hash(),
@@ -590,7 +597,8 @@ fn perform_basic_checks_on_valid_candidate(
 		head_data_hash,
 		head_data_hash,
 		Sr25519Keyring::Alice,
-	);
+	)
+	.into();
 
 	let check = perform_basic_checks(
 		&descriptor,
@@ -784,7 +792,8 @@ fn candidate_validation_retry_on_error_helper(
 		dummy_hash(),
 		dummy_hash(),
 		Sr25519Keyring::Alice,
-	);
+	)
+	.into();
 
 	let check = perform_basic_checks(
 		&descriptor,
@@ -824,7 +833,8 @@ fn candidate_validation_timeout_is_internal_error() {
 		dummy_hash(),
 		dummy_hash(),
 		Sr25519Keyring::Alice,
-	);
+	)
+	.into();
 
 	let check = perform_basic_checks(
 		&descriptor,
@@ -869,9 +879,11 @@ fn candidate_validation_commitment_hash_mismatch_is_invalid() {
 			head_data.hash(),
 			dummy_hash(),
 			Sr25519Keyring::Alice,
-		),
+		)
+		.into(),
 		commitments_hash: Hash::zero(),
-	};
+	}
+	.into();
 
 	// This will result in different commitments for this candidate.
 	let validation_result = WasmValidationResult {
@@ -915,7 +927,8 @@ fn candidate_validation_code_mismatch_is_invalid() {
 		dummy_hash(),
 		dummy_hash(),
 		Sr25519Keyring::Alice,
-	);
+	)
+	.into();
 
 	let check = perform_basic_checks(
 		&descriptor,
@@ -970,7 +983,8 @@ fn compressed_code_works() {
 		head_data.hash(),
 		dummy_hash(),
 		Sr25519Keyring::Alice,
-	);
+	)
+	.into();
 
 	let validation_result = WasmValidationResult {
 		head_data,
@@ -1239,7 +1253,8 @@ fn dummy_candidate_backed(
 		signature: dummy_collator_signature(),
 		para_head: zeros,
 		validation_code_hash,
-	};
+	}
+	.into();
 
 	CandidateEvent::CandidateBacked(
 		CandidateReceipt { descriptor, commitments_hash: zeros },
diff --git a/polkadot/node/core/dispute-coordinator/Cargo.toml b/polkadot/node/core/dispute-coordinator/Cargo.toml
index eb4600b235b9f2cfde8f030ebb3b9626d6ea8869..344b66af1933c1a544759186050a9bcde0a89380 100644
--- a/polkadot/node/core/dispute-coordinator/Cargo.toml
+++ b/polkadot/node/core/dispute-coordinator/Cargo.toml
@@ -37,6 +37,7 @@ polkadot-primitives-test-helpers = { workspace = true }
 futures-timer = { workspace = true }
 sp-application-crypto = { workspace = true, default-features = true }
 sp-tracing = { workspace = true, default-features = true }
+polkadot-primitives = { workspace = true, features = ["test"] }
 
 [features]
 # If not enabled, the dispute coordinator will do nothing.
diff --git a/polkadot/node/core/dispute-coordinator/src/db/v1.rs b/polkadot/node/core/dispute-coordinator/src/db/v1.rs
index 0101791550ee8192e705c84e06dbe9d269970fec..962dfcbbcfac517290c2c4405b8f66b2379c4822 100644
--- a/polkadot/node/core/dispute-coordinator/src/db/v1.rs
+++ b/polkadot/node/core/dispute-coordinator/src/db/v1.rs
@@ -25,8 +25,9 @@
 use polkadot_node_primitives::DisputeStatus;
 use polkadot_node_subsystem_util::database::{DBTransaction, Database};
 use polkadot_primitives::{
-	CandidateHash, CandidateReceipt, Hash, InvalidDisputeStatementKind, SessionIndex,
-	ValidDisputeStatementKind, ValidatorIndex, ValidatorSignature,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, CandidateHash, Hash,
+	InvalidDisputeStatementKind, SessionIndex, ValidDisputeStatementKind, ValidatorIndex,
+	ValidatorSignature,
 };
 
 use std::sync::Arc;
@@ -377,7 +378,9 @@ mod tests {
 	use super::*;
 	use polkadot_node_primitives::DISPUTE_WINDOW;
 	use polkadot_primitives::{Hash, Id as ParaId};
-	use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_hash};
+	use polkadot_primitives_test_helpers::{
+		dummy_candidate_receipt, dummy_candidate_receipt_v2, dummy_hash,
+	};
 
 	fn make_db() -> DbBackend {
 		let db = kvdb_memorydb::create(1);
@@ -403,7 +406,7 @@ mod tests {
 				session,
 				candidate_hash,
 				CandidateVotes {
-					candidate_receipt: dummy_candidate_receipt(dummy_hash()),
+					candidate_receipt: dummy_candidate_receipt_v2(dummy_hash()),
 					valid: Vec::new(),
 					invalid: Vec::new(),
 				},
@@ -495,7 +498,7 @@ mod tests {
 			1,
 			CandidateHash(Hash::repeat_byte(1)),
 			CandidateVotes {
-				candidate_receipt: dummy_candidate_receipt(dummy_hash()),
+				candidate_receipt: dummy_candidate_receipt_v2(dummy_hash()),
 				valid: Vec::new(),
 				invalid: Vec::new(),
 			},
@@ -508,7 +511,7 @@ mod tests {
 					let mut receipt = dummy_candidate_receipt(dummy_hash());
 					receipt.descriptor.para_id = ParaId::from(5_u32);
 
-					receipt
+					receipt.into()
 				},
 				valid: Vec::new(),
 				invalid: Vec::new(),
@@ -532,7 +535,7 @@ mod tests {
 				.unwrap()
 				.candidate_receipt
 				.descriptor
-				.para_id,
+				.para_id(),
 			ParaId::from(5),
 		);
 
@@ -556,7 +559,7 @@ mod tests {
 				.unwrap()
 				.candidate_receipt
 				.descriptor
-				.para_id,
+				.para_id(),
 			ParaId::from(5),
 		);
 	}
@@ -571,13 +574,13 @@ mod tests {
 			1,
 			CandidateHash(Hash::repeat_byte(1)),
 			CandidateVotes {
-				candidate_receipt: dummy_candidate_receipt(Hash::random()),
+				candidate_receipt: dummy_candidate_receipt_v2(Hash::random()),
 				valid: Vec::new(),
 				invalid: Vec::new(),
 			},
 		);
 
-		let receipt = dummy_candidate_receipt(dummy_hash());
+		let receipt = dummy_candidate_receipt_v2(dummy_hash());
 
 		overlay_db.write_candidate_votes(
 			1,
@@ -621,7 +624,7 @@ mod tests {
 		let very_recent = current_session - 1;
 
 		let blank_candidate_votes = || CandidateVotes {
-			candidate_receipt: dummy_candidate_receipt(dummy_hash()),
+			candidate_receipt: dummy_candidate_receipt_v2(dummy_hash()),
 			valid: Vec::new(),
 			invalid: Vec::new(),
 		};
diff --git a/polkadot/node/core/dispute-coordinator/src/import.rs b/polkadot/node/core/dispute-coordinator/src/import.rs
index d3a4625f0d24b2ff0fc3bcc914d629e604909193..4263dda54b9b26e1789ed9b8352209ed2c0d04c4 100644
--- a/polkadot/node/core/dispute-coordinator/src/import.rs
+++ b/polkadot/node/core/dispute-coordinator/src/import.rs
@@ -34,9 +34,9 @@ use polkadot_node_primitives::{
 use polkadot_node_subsystem::overseer;
 use polkadot_node_subsystem_util::runtime::RuntimeInfo;
 use polkadot_primitives::{
-	CandidateHash, CandidateReceipt, DisputeStatement, ExecutorParams, Hash, IndexedVec,
-	SessionIndex, SessionInfo, ValidDisputeStatementKind, ValidatorId, ValidatorIndex,
-	ValidatorPair, ValidatorSignature,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, CandidateHash, DisputeStatement,
+	ExecutorParams, Hash, IndexedVec, SessionIndex, SessionInfo, ValidDisputeStatementKind,
+	ValidatorId, ValidatorIndex, ValidatorPair, ValidatorSignature,
 };
 use sc_keystore::LocalKeystore;
 
diff --git a/polkadot/node/core/dispute-coordinator/src/initialized.rs b/polkadot/node/core/dispute-coordinator/src/initialized.rs
index 9cf9047b72734cec0d665675170e7e93ef42f31f..7fc22d5904c5182c45150c1328e06187b6e8c955 100644
--- a/polkadot/node/core/dispute-coordinator/src/initialized.rs
+++ b/polkadot/node/core/dispute-coordinator/src/initialized.rs
@@ -44,9 +44,10 @@ use polkadot_node_subsystem_util::runtime::{
 	self, key_ownership_proof, submit_report_dispute_lost, RuntimeInfo,
 };
 use polkadot_primitives::{
-	slashing, BlockNumber, CandidateHash, CandidateReceipt, CompactStatement, DisputeStatement,
-	DisputeStatementSet, Hash, ScrapedOnChainVotes, SessionIndex, ValidDisputeStatementKind,
-	ValidatorId, ValidatorIndex,
+	slashing,
+	vstaging::{CandidateReceiptV2 as CandidateReceipt, ScrapedOnChainVotes},
+	BlockNumber, CandidateHash, CompactStatement, DisputeStatement, DisputeStatementSet, Hash,
+	SessionIndex, ValidDisputeStatementKind, ValidatorId, ValidatorIndex,
 };
 use schnellru::{LruMap, UnlimitedCompact};
 
@@ -607,7 +608,7 @@ impl Initialized {
 		// the new active leaf as if we received them via gossip.
 		for (candidate_receipt, backers) in backing_validators_per_candidate {
 			// Obtain the session info, for sake of `ValidatorId`s
-			let relay_parent = candidate_receipt.descriptor.relay_parent;
+			let relay_parent = candidate_receipt.descriptor.relay_parent();
 			let session_info = match self
 				.runtime_info
 				.get_session_info_by_index(ctx.sender(), relay_parent, session)
@@ -958,9 +959,9 @@ impl Initialized {
 		let votes_in_db = overlay_db.load_candidate_votes(session, &candidate_hash)?;
 		let relay_parent = match &candidate_receipt {
 			MaybeCandidateReceipt::Provides(candidate_receipt) =>
-				candidate_receipt.descriptor().relay_parent,
+				candidate_receipt.descriptor().relay_parent(),
 			MaybeCandidateReceipt::AssumeBackingVotePresent(candidate_hash) => match &votes_in_db {
-				Some(votes) => votes.candidate_receipt.descriptor().relay_parent,
+				Some(votes) => votes.candidate_receipt.descriptor().relay_parent(),
 				None => {
 					gum::warn!(
 						target: LOG_TARGET,
@@ -1451,7 +1452,7 @@ impl Initialized {
 			ctx,
 			&mut self.runtime_info,
 			session,
-			candidate_receipt.descriptor.relay_parent,
+			candidate_receipt.descriptor.relay_parent(),
 			self.offchain_disabled_validators.iter(session),
 		)
 		.await
diff --git a/polkadot/node/core/dispute-coordinator/src/lib.rs b/polkadot/node/core/dispute-coordinator/src/lib.rs
index 84408eb9630539df3bace2ad5a0d1c25c0848018..3078ada5d53f168ec5fda2a84a443c7a92604353 100644
--- a/polkadot/node/core/dispute-coordinator/src/lib.rs
+++ b/polkadot/node/core/dispute-coordinator/src/lib.rs
@@ -46,7 +46,7 @@ use polkadot_node_subsystem_util::{
 	runtime::{Config as RuntimeInfoConfig, RuntimeInfo},
 };
 use polkadot_primitives::{
-	DisputeStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidatorIndex,
+	vstaging::ScrapedOnChainVotes, DisputeStatement, SessionIndex, SessionInfo, ValidatorIndex,
 };
 
 use crate::{
diff --git a/polkadot/node/core/dispute-coordinator/src/participation/mod.rs b/polkadot/node/core/dispute-coordinator/src/participation/mod.rs
index 2220f65e20a7ca2908e0b97ce1e3841fa7186814..770c44f7d60983c4c006297536e0ec215e92945c 100644
--- a/polkadot/node/core/dispute-coordinator/src/participation/mod.rs
+++ b/polkadot/node/core/dispute-coordinator/src/participation/mod.rs
@@ -31,7 +31,10 @@ use polkadot_node_subsystem::{
 	overseer, ActiveLeavesUpdate, RecoveryError,
 };
 use polkadot_node_subsystem_util::runtime::get_validation_code_by_hash;
-use polkadot_primitives::{BlockNumber, CandidateHash, CandidateReceipt, Hash, SessionIndex};
+use polkadot_primitives::{
+	vstaging::CandidateReceiptV2 as CandidateReceipt, BlockNumber, CandidateHash, Hash,
+	SessionIndex,
+};
 
 use crate::LOG_TARGET;
 
@@ -348,7 +351,7 @@ async fn participate(
 	let validation_code = match get_validation_code_by_hash(
 		&mut sender,
 		block_hash,
-		req.candidate_receipt().descriptor.validation_code_hash,
+		req.candidate_receipt().descriptor.validation_code_hash(),
 	)
 	.await
 	{
@@ -357,7 +360,7 @@ async fn participate(
 			gum::warn!(
 				target: LOG_TARGET,
 				"Validation code unavailable for code hash {:?} in the state of block {:?}",
-				req.candidate_receipt().descriptor.validation_code_hash,
+				req.candidate_receipt().descriptor.validation_code_hash(),
 				block_hash,
 			);
 
diff --git a/polkadot/node/core/dispute-coordinator/src/participation/queues/mod.rs b/polkadot/node/core/dispute-coordinator/src/participation/queues/mod.rs
index d9e86def168c97812fb7bcfb462b49ef7c75548d..4d317d38590af680b813e92564fecb874bd6a86f 100644
--- a/polkadot/node/core/dispute-coordinator/src/participation/queues/mod.rs
+++ b/polkadot/node/core/dispute-coordinator/src/participation/queues/mod.rs
@@ -22,7 +22,8 @@ use std::{
 use futures::channel::oneshot;
 use polkadot_node_subsystem::{messages::ChainApiMessage, overseer};
 use polkadot_primitives::{
-	BlockNumber, CandidateHash, CandidateReceipt, ExecutorParams, Hash, SessionIndex,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, BlockNumber, CandidateHash, ExecutorParams,
+	Hash, SessionIndex,
 };
 
 use crate::{
@@ -405,7 +406,7 @@ impl CandidateComparator {
 		candidate: &CandidateReceipt,
 	) -> FatalResult<Self> {
 		let candidate_hash = candidate.hash();
-		let n = get_block_number(sender, candidate.descriptor().relay_parent).await?;
+		let n = get_block_number(sender, candidate.descriptor().relay_parent()).await?;
 
 		if n.is_none() {
 			gum::warn!(
diff --git a/polkadot/node/core/dispute-coordinator/src/participation/queues/tests.rs b/polkadot/node/core/dispute-coordinator/src/participation/queues/tests.rs
index 9176d00b2f5c4ed25166f9c10d0718678653ec5d..a25387a7eb5a922cebe68ebf38152b461418eec8 100644
--- a/polkadot/node/core/dispute-coordinator/src/participation/queues/tests.rs
+++ b/polkadot/node/core/dispute-coordinator/src/participation/queues/tests.rs
@@ -17,13 +17,13 @@
 use crate::{metrics::Metrics, ParticipationPriority};
 use assert_matches::assert_matches;
 use polkadot_primitives::{BlockNumber, Hash};
-use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_hash};
+use polkadot_primitives_test_helpers::{dummy_candidate_receipt_v2, dummy_hash};
 
 use super::{CandidateComparator, ParticipationRequest, QueueError, Queues};
 
 /// Make a `ParticipationRequest` based on the given commitments hash.
 fn make_participation_request(hash: Hash) -> ParticipationRequest {
-	let mut receipt = dummy_candidate_receipt(dummy_hash());
+	let mut receipt = dummy_candidate_receipt_v2(dummy_hash());
 	// make it differ:
 	receipt.commitments_hash = hash;
 	let request_timer = Metrics::default().time_participation_pipeline();
diff --git a/polkadot/node/core/dispute-coordinator/src/participation/tests.rs b/polkadot/node/core/dispute-coordinator/src/participation/tests.rs
index a6ab6f16df05e1c20b84fe0c62e446607b983a71..23f7984965b39f85334c579aab6093382489993c 100644
--- a/polkadot/node/core/dispute-coordinator/src/participation/tests.rs
+++ b/polkadot/node/core/dispute-coordinator/src/participation/tests.rs
@@ -68,7 +68,8 @@ async fn participate_with_commitments_hash<Context>(
 		let mut receipt = dummy_candidate_receipt_bad_sig(dummy_hash(), dummy_hash());
 		receipt.commitments_hash = commitments_hash;
 		receipt
-	};
+	}
+	.into();
 	let session = 1;
 
 	let request_timer = participation.metrics.time_participation_pipeline();
diff --git a/polkadot/node/core/dispute-coordinator/src/scraping/mod.rs b/polkadot/node/core/dispute-coordinator/src/scraping/mod.rs
index 4c45d9dcc2202a4f78e6e03576fa8e8a3af2e296..9aaad9d1c5284b01d85ccb40799d7f0668f8e87f 100644
--- a/polkadot/node/core/dispute-coordinator/src/scraping/mod.rs
+++ b/polkadot/node/core/dispute-coordinator/src/scraping/mod.rs
@@ -28,8 +28,9 @@ use polkadot_node_subsystem_util::runtime::{
 	self, get_candidate_events, get_on_chain_votes, get_unapplied_slashes,
 };
 use polkadot_primitives::{
-	slashing::PendingSlashes, BlockNumber, CandidateEvent, CandidateHash, CandidateReceipt, Hash,
-	ScrapedOnChainVotes, SessionIndex,
+	slashing::PendingSlashes,
+	vstaging::{CandidateEvent, CandidateReceiptV2 as CandidateReceipt, ScrapedOnChainVotes},
+	BlockNumber, CandidateHash, Hash, SessionIndex,
 };
 
 use crate::{
diff --git a/polkadot/node/core/dispute-coordinator/src/scraping/tests.rs b/polkadot/node/core/dispute-coordinator/src/scraping/tests.rs
index ed2400387ef7f52693c3ab9d6f35f69234caab99..fe04193014c60447f474bdd6cbb57456bf971877 100644
--- a/polkadot/node/core/dispute-coordinator/src/scraping/tests.rs
+++ b/polkadot/node/core/dispute-coordinator/src/scraping/tests.rs
@@ -36,8 +36,9 @@ use polkadot_node_subsystem_test_helpers::{
 };
 use polkadot_node_subsystem_util::{reexports::SubsystemContext, TimeoutExt};
 use polkadot_primitives::{
-	BlakeTwo256, BlockNumber, CandidateDescriptor, CandidateEvent, CandidateReceipt, CoreIndex,
-	GroupIndex, Hash, HashT, HeadData, Id as ParaId,
+	vstaging::{CandidateEvent, CandidateReceiptV2 as CandidateReceipt},
+	BlakeTwo256, BlockNumber, CandidateDescriptor, CoreIndex, GroupIndex, Hash, HashT, HeadData,
+	Id as ParaId,
 };
 use polkadot_primitives_test_helpers::{dummy_collator, dummy_collator_signature, dummy_hash};
 
@@ -135,7 +136,8 @@ fn make_candidate_receipt(relay_parent: Hash) -> CandidateReceipt {
 		signature: dummy_collator_signature(),
 		para_head: zeros,
 		validation_code_hash: zeros.into(),
-	};
+	}
+	.into();
 	CandidateReceipt { descriptor, commitments_hash: zeros }
 }
 
diff --git a/polkadot/node/core/dispute-coordinator/src/tests.rs b/polkadot/node/core/dispute-coordinator/src/tests.rs
index 48762a1d80be9220cf27a14b173d12129243985a..9383f71804ed0f84be5e3c94580a37501a733b56 100644
--- a/polkadot/node/core/dispute-coordinator/src/tests.rs
+++ b/polkadot/node/core/dispute-coordinator/src/tests.rs
@@ -60,13 +60,18 @@ use polkadot_node_subsystem_test_helpers::{
 	make_buffered_subsystem_context, mock::new_leaf, TestSubsystemContextHandle,
 };
 use polkadot_primitives::{
-	ApprovalVote, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash,
-	CandidateReceipt, CoreIndex, DisputeStatement, ExecutorParams, GroupIndex, Hash, HeadData,
-	Header, IndexedVec, MultiDisputeStatementSet, NodeFeatures, ScrapedOnChainVotes, SessionIndex,
-	SessionInfo, SigningContext, ValidDisputeStatementKind, ValidatorId, ValidatorIndex,
-	ValidatorSignature,
+	vstaging::{
+		CandidateEvent, CandidateReceiptV2 as CandidateReceipt, MutateDescriptorV2,
+		ScrapedOnChainVotes,
+	},
+	ApprovalVote, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeStatement,
+	ExecutorParams, GroupIndex, Hash, HeadData, Header, IndexedVec, MultiDisputeStatementSet,
+	NodeFeatures, SessionIndex, SessionInfo, SigningContext, ValidDisputeStatementKind,
+	ValidatorId, ValidatorIndex, ValidatorSignature,
+};
+use polkadot_primitives_test_helpers::{
+	dummy_candidate_receipt_v2_bad_sig, dummy_digest, dummy_hash,
 };
-use polkadot_primitives_test_helpers::{dummy_candidate_receipt_bad_sig, dummy_digest, dummy_hash};
 
 use crate::{
 	backend::Backend,
@@ -648,11 +653,11 @@ fn make_valid_candidate_receipt() -> CandidateReceipt {
 }
 
 fn make_invalid_candidate_receipt() -> CandidateReceipt {
-	dummy_candidate_receipt_bad_sig(Default::default(), Some(Default::default()))
+	dummy_candidate_receipt_v2_bad_sig(Default::default(), Some(Default::default()))
 }
 
 fn make_another_valid_candidate_receipt(relay_parent: Hash) -> CandidateReceipt {
-	let mut candidate_receipt = dummy_candidate_receipt_bad_sig(relay_parent, dummy_hash());
+	let mut candidate_receipt = dummy_candidate_receipt_v2_bad_sig(relay_parent, dummy_hash());
 	candidate_receipt.commitments_hash = CandidateCommitments::default().hash();
 	candidate_receipt
 }
@@ -3858,14 +3863,15 @@ fn participation_requests_reprioritized_for_newly_included() {
 			for repetition in 1..=3u8 {
 				// Building candidate receipts
 				let mut candidate_receipt = make_valid_candidate_receipt();
-				candidate_receipt.descriptor.pov_hash = Hash::from(
+				candidate_receipt.descriptor.set_pov_hash(Hash::from(
 					[repetition; 32], // Altering this receipt so its hash will be changed
-				);
+				));
 				// Set consecutive parents (starting from zero). They will order the candidates for
 				// participation.
 				let parent_block_num: BlockNumber = repetition as BlockNumber - 1;
-				candidate_receipt.descriptor.relay_parent =
-					*test_state.block_num_to_header.get(&parent_block_num).unwrap();
+				candidate_receipt.descriptor.set_relay_parent(
+					*test_state.block_num_to_header.get(&parent_block_num).unwrap(),
+				);
 				receipts.push(candidate_receipt.clone());
 			}
 
diff --git a/polkadot/node/core/parachains-inherent/src/lib.rs b/polkadot/node/core/parachains-inherent/src/lib.rs
index 1de3cab32bed1678822438b22b95b43028a01784..5f3092f6a881c064849059de6dbbaef5590391db 100644
--- a/polkadot/node/core/parachains-inherent/src/lib.rs
+++ b/polkadot/node/core/parachains-inherent/src/lib.rs
@@ -29,7 +29,7 @@ use futures::{select, FutureExt};
 use polkadot_node_subsystem::{
 	errors::SubsystemError, messages::ProvisionerMessage, overseer::Handle,
 };
-use polkadot_primitives::{Block, Hash, InherentData as ParachainsInherentData};
+use polkadot_primitives::{vstaging::InherentData as ParachainsInherentData, Block, Hash};
 use std::{sync::Arc, time};
 
 pub(crate) const LOG_TARGET: &str = "parachain::parachains-inherent";
diff --git a/polkadot/node/core/prospective-parachains/Cargo.toml b/polkadot/node/core/prospective-parachains/Cargo.toml
index 705014e67a05eff6a8c5954bdb0834faef10cc0e..5629e4ef7fbe25af6e769d195829819becb35831 100644
--- a/polkadot/node/core/prospective-parachains/Cargo.toml
+++ b/polkadot/node/core/prospective-parachains/Cargo.toml
@@ -23,6 +23,7 @@ polkadot-node-subsystem-util = { workspace = true, default-features = true }
 assert_matches = { workspace = true }
 polkadot-node-subsystem-test-helpers = { workspace = true }
 polkadot-primitives-test-helpers = { workspace = true }
+polkadot-primitives = { workspace = true, features = ["test"] }
 sp-tracing = { workspace = true }
 sp-core = { workspace = true, default-features = true }
 rand = { workspace = true }
diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs
index b060897d439168e4dd04acd1e3e896429d7c3ed4..265d1498ee96e3007bac3ce0f0e02b49aa9affd3 100644
--- a/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs
+++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs
@@ -136,8 +136,9 @@ use polkadot_node_subsystem_util::inclusion_emulator::{
 	ProspectiveCandidate, RelayChainBlockInfo,
 };
 use polkadot_primitives::{
-	BlockNumber, CandidateCommitments, CandidateHash, CommittedCandidateReceipt, Hash, HeadData,
-	PersistedValidationData, ValidationCodeHash,
+	vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, BlockNumber,
+	CandidateCommitments, CandidateHash, Hash, HeadData, PersistedValidationData,
+	ValidationCodeHash,
 };
 use thiserror::Error;
 
@@ -371,7 +372,8 @@ impl CandidateEntry {
 		persisted_validation_data: PersistedValidationData,
 		state: CandidateState,
 	) -> Result<Self, CandidateEntryError> {
-		if persisted_validation_data.hash() != candidate.descriptor.persisted_validation_data_hash {
+		if persisted_validation_data.hash() != candidate.descriptor.persisted_validation_data_hash()
+		{
 			return Err(CandidateEntryError::PersistedValidationDataMismatch)
 		}
 
@@ -386,13 +388,13 @@ impl CandidateEntry {
 			candidate_hash,
 			parent_head_data_hash,
 			output_head_data_hash,
-			relay_parent: candidate.descriptor.relay_parent,
+			relay_parent: candidate.descriptor.relay_parent(),
 			state,
 			candidate: Arc::new(ProspectiveCandidate {
 				commitments: candidate.commitments,
 				persisted_validation_data,
-				pov_hash: candidate.descriptor.pov_hash,
-				validation_code_hash: candidate.descriptor.validation_code_hash,
+				pov_hash: candidate.descriptor.pov_hash(),
+				validation_code_hash: candidate.descriptor.validation_code_hash(),
 			}),
 		})
 	}
@@ -407,8 +409,8 @@ impl HypotheticalOrConcreteCandidate for CandidateEntry {
 		Some(&self.candidate.persisted_validation_data)
 	}
 
-	fn validation_code_hash(&self) -> Option<&ValidationCodeHash> {
-		Some(&self.candidate.validation_code_hash)
+	fn validation_code_hash(&self) -> Option<ValidationCodeHash> {
+		Some(self.candidate.validation_code_hash)
 	}
 
 	fn parent_head_data_hash(&self) -> Hash {
@@ -1090,7 +1092,7 @@ impl FragmentChain {
 				&relay_parent,
 				&constraints,
 				commitments,
-				validation_code_hash,
+				&validation_code_hash,
 				pvd,
 			)
 			.map_err(Error::CheckAgainstConstraints)?;
diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs
index 3332cbeb03cbbb47c1fe3603f3d4a5a094c7a676..9708b0871c2a85f7c6aab4bc5565e818728a2653 100644
--- a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs
+++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs
@@ -18,7 +18,8 @@ use super::*;
 use assert_matches::assert_matches;
 use polkadot_node_subsystem_util::inclusion_emulator::InboundHrmpLimitations;
 use polkadot_primitives::{
-	BlockNumber, CandidateCommitments, CandidateDescriptor, HeadData, Id as ParaId,
+	vstaging::MutateDescriptorV2, BlockNumber, CandidateCommitments, CandidateDescriptor, HeadData,
+	Id as ParaId,
 };
 use polkadot_primitives_test_helpers as test_helpers;
 use rand::{seq::SliceRandom, thread_rng};
@@ -73,7 +74,8 @@ fn make_committed_candidate(
 			signature: test_helpers::dummy_collator_signature(),
 			para_head: para_head.hash(),
 			validation_code_hash: Hash::repeat_byte(42).into(),
-		},
+		}
+		.into(),
 		commitments: CandidateCommitments {
 			upward_messages: Default::default(),
 			horizontal_messages: Default::default(),
@@ -283,7 +285,7 @@ fn candidate_storage_methods() {
 		candidate.commitments.head_data = HeadData(vec![1; 10]);
 		let mut pvd = pvd.clone();
 		pvd.parent_head = HeadData(vec![1; 10]);
-		candidate.descriptor.persisted_validation_data_hash = pvd.hash();
+		candidate.descriptor.set_persisted_validation_data_hash(pvd.hash());
 		assert_matches!(
 			CandidateEntry::new_seconded(candidate_hash, candidate, pvd),
 			Err(CandidateEntryError::ZeroLengthCycle)
@@ -291,7 +293,7 @@ fn candidate_storage_methods() {
 	}
 	assert!(!storage.contains(&candidate_hash));
 	assert_eq!(storage.possible_backed_para_children(&parent_head_hash).count(), 0);
-	assert_eq!(storage.head_data_by_hash(&candidate.descriptor.para_head), None);
+	assert_eq!(storage.head_data_by_hash(&candidate.descriptor.para_head()), None);
 	assert_eq!(storage.head_data_by_hash(&parent_head_hash), None);
 
 	// Add a valid candidate.
@@ -305,9 +307,9 @@ fn candidate_storage_methods() {
 	storage.add_candidate_entry(candidate_entry.clone()).unwrap();
 	assert!(storage.contains(&candidate_hash));
 	assert_eq!(storage.possible_backed_para_children(&parent_head_hash).count(), 0);
-	assert_eq!(storage.possible_backed_para_children(&candidate.descriptor.para_head).count(), 0);
+	assert_eq!(storage.possible_backed_para_children(&candidate.descriptor.para_head()).count(), 0);
 	assert_eq!(
-		storage.head_data_by_hash(&candidate.descriptor.para_head).unwrap(),
+		storage.head_data_by_hash(&candidate.descriptor.para_head()).unwrap(),
 		&candidate.commitments.head_data
 	);
 	assert_eq!(storage.head_data_by_hash(&parent_head_hash).unwrap(), &pvd.parent_head);
@@ -323,7 +325,7 @@ fn candidate_storage_methods() {
 			.collect::<Vec<_>>(),
 		vec![candidate_hash]
 	);
-	assert_eq!(storage.possible_backed_para_children(&candidate.descriptor.para_head).count(), 0);
+	assert_eq!(storage.possible_backed_para_children(&candidate.descriptor.para_head()).count(), 0);
 
 	// Re-adding a candidate fails.
 	assert_matches!(
@@ -339,7 +341,7 @@ fn candidate_storage_methods() {
 	storage.remove_candidate(&candidate_hash);
 	assert!(!storage.contains(&candidate_hash));
 	assert_eq!(storage.possible_backed_para_children(&parent_head_hash).count(), 0);
-	assert_eq!(storage.head_data_by_hash(&candidate.descriptor.para_head), None);
+	assert_eq!(storage.head_data_by_hash(&candidate.descriptor.para_head()), None);
 	assert_eq!(storage.head_data_by_hash(&parent_head_hash), None);
 
 	storage
@@ -354,7 +356,7 @@ fn candidate_storage_methods() {
 			.collect::<Vec<_>>(),
 		vec![candidate_hash]
 	);
-	assert_eq!(storage.possible_backed_para_children(&candidate.descriptor.para_head).count(), 0);
+	assert_eq!(storage.possible_backed_para_children(&candidate.descriptor.para_head()).count(), 0);
 
 	// Now add a second candidate in Seconded state. This will be a fork.
 	let (pvd_2, candidate_2) = make_committed_candidate(
diff --git a/polkadot/node/core/prospective-parachains/src/lib.rs b/polkadot/node/core/prospective-parachains/src/lib.rs
index b8b5f159e71cdd9a6f01f1a4a30bad5f9f2dc912..34c1d8823bfce77b7f6c90688cf752674e5332b4 100644
--- a/polkadot/node/core/prospective-parachains/src/lib.rs
+++ b/polkadot/node/core/prospective-parachains/src/lib.rs
@@ -49,9 +49,11 @@ use polkadot_node_subsystem_util::{
 	runtime::{fetch_claim_queue, prospective_parachains_mode, ProspectiveParachainsMode},
 };
 use polkadot_primitives::{
-	async_backing::CandidatePendingAvailability, BlockNumber, CandidateHash,
-	CommittedCandidateReceipt, CoreState, Hash, HeadData, Header, Id as ParaId,
-	PersistedValidationData,
+	vstaging::{
+		async_backing::CandidatePendingAvailability,
+		CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState,
+	},
+	BlockNumber, CandidateHash, Hash, HeadData, Header, Id as ParaId, PersistedValidationData,
 };
 
 use crate::{
@@ -453,12 +455,13 @@ async fn preprocess_candidates_pending_availability<Context>(
 
 	for (i, pending) in pending_availability.into_iter().enumerate() {
 		let Some(relay_parent) =
-			fetch_block_info(ctx, cache, pending.descriptor.relay_parent).await?
+			fetch_block_info(ctx, cache, pending.descriptor.relay_parent()).await?
 		else {
+			let para_id = pending.descriptor.para_id();
 			gum::debug!(
 				target: LOG_TARGET,
 				?pending.candidate_hash,
-				?pending.descriptor.para_id,
+				?para_id,
 				index = ?i,
 				?expected_count,
 				"Had to stop processing pending candidates early due to missing info.",
diff --git a/polkadot/node/core/prospective-parachains/src/tests.rs b/polkadot/node/core/prospective-parachains/src/tests.rs
index 14a093239e8ecbaa1fd6c4e3c294fddb21795af7..3f1eaa4e41ed85610c7c5e6fd0f597040653704a 100644
--- a/polkadot/node/core/prospective-parachains/src/tests.rs
+++ b/polkadot/node/core/prospective-parachains/src/tests.rs
@@ -25,9 +25,12 @@ use polkadot_node_subsystem::{
 };
 use polkadot_node_subsystem_test_helpers as test_helpers;
 use polkadot_primitives::{
-	async_backing::{AsyncBackingParams, BackingState, Constraints, InboundHrmpLimitations},
-	CommittedCandidateReceipt, CoreIndex, HeadData, Header, PersistedValidationData, ScheduledCore,
-	ValidationCodeHash,
+	async_backing::{AsyncBackingParams, Constraints, InboundHrmpLimitations},
+	vstaging::{
+		async_backing::BackingState, CommittedCandidateReceiptV2 as CommittedCandidateReceipt,
+		MutateDescriptorV2,
+	},
+	CoreIndex, HeadData, Header, PersistedValidationData, ScheduledCore, ValidationCodeHash,
 };
 use polkadot_primitives_test_helpers::make_candidate;
 use rstest::rstest;
@@ -393,15 +396,15 @@ async fn handle_leaf_activation(
 		);
 
 		for pending in pending_availability {
-			if !used_relay_parents.contains(&pending.descriptor.relay_parent) {
+			if !used_relay_parents.contains(&pending.descriptor.relay_parent()) {
 				send_block_header(
 					virtual_overseer,
-					pending.descriptor.relay_parent,
+					pending.descriptor.relay_parent(),
 					pending.relay_parent_number,
 				)
 				.await;
 
-				used_relay_parents.insert(pending.descriptor.relay_parent);
+				used_relay_parents.insert(pending.descriptor.relay_parent());
 			}
 		}
 	}
@@ -436,7 +439,7 @@ async fn introduce_seconded_candidate(
 	pvd: PersistedValidationData,
 ) {
 	let req = IntroduceSecondedCandidateRequest {
-		candidate_para: candidate.descriptor().para_id,
+		candidate_para: candidate.descriptor.para_id(),
 		candidate_receipt: candidate,
 		persisted_validation_data: pvd,
 	};
@@ -455,7 +458,7 @@ async fn introduce_seconded_candidate_failed(
 	pvd: PersistedValidationData,
 ) {
 	let req = IntroduceSecondedCandidateRequest {
-		candidate_para: candidate.descriptor().para_id,
+		candidate_para: candidate.descriptor.para_id(),
 		candidate_receipt: candidate,
 		persisted_validation_data: pvd,
 	};
@@ -476,7 +479,7 @@ async fn back_candidate(
 	virtual_overseer
 		.send(overseer::FromOrchestra::Communication {
 			msg: ProspectiveParachainsMessage::CandidateBacked(
-				candidate.descriptor.para_id,
+				candidate.descriptor.para_id(),
 				candidate_hash,
 			),
 		})
@@ -568,7 +571,7 @@ macro_rules! make_and_back_candidate {
 			$test_state.validation_code_hash,
 		);
 		// Set a field to make this candidate unique.
-		candidate.descriptor.para_head = Hash::from_low_u64_le($index);
+		candidate.descriptor.set_para_head(Hash::from_low_u64_le($index));
 		let candidate_hash = candidate.hash();
 		introduce_seconded_candidate(&mut $virtual_overseer, candidate.clone(), pvd).await;
 		back_candidate(&mut $virtual_overseer, &candidate, candidate_hash).await;
@@ -1378,7 +1381,7 @@ fn check_backable_query_single_candidate() {
 			test_state.validation_code_hash,
 		);
 		// Set a field to make this candidate unique.
-		candidate_b.descriptor.para_head = Hash::from_low_u64_le(1000);
+		candidate_b.descriptor.set_para_head(Hash::from_low_u64_le(1000));
 		let candidate_hash_b = candidate_b.hash();
 
 		// Introduce candidates.
diff --git a/polkadot/node/core/provisioner/Cargo.toml b/polkadot/node/core/provisioner/Cargo.toml
index 5869e494c70ff40365397d3ffa104f5bad6c421e..64a598b420f7a0390250b56070fee3d6739f3252 100644
--- a/polkadot/node/core/provisioner/Cargo.toml
+++ b/polkadot/node/core/provisioner/Cargo.toml
@@ -27,4 +27,6 @@ sp-application-crypto = { workspace = true, default-features = true }
 sp-keystore = { workspace = true, default-features = true }
 polkadot-node-subsystem-test-helpers = { workspace = true }
 polkadot-primitives-test-helpers = { workspace = true }
+polkadot-primitives = { workspace = true, features = ["test"] }
+
 rstest = { workspace = true }
diff --git a/polkadot/node/core/provisioner/src/disputes/prioritized_selection/tests.rs b/polkadot/node/core/provisioner/src/disputes/prioritized_selection/tests.rs
index ecb7aac78396c8769170ca720addf91f056e09bc..8c0d478b67df4e65553ab5ed6614ff1a920d7491 100644
--- a/polkadot/node/core/provisioner/src/disputes/prioritized_selection/tests.rs
+++ b/polkadot/node/core/provisioner/src/disputes/prioritized_selection/tests.rs
@@ -427,7 +427,7 @@ impl TestDisputes {
 		let onchain_votes_count = self.validators_count * 80 / 100;
 		let session_idx = 0;
 		let lf = leaf();
-		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt(lf.hash);
+		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt_v2(lf.hash);
 		for _ in 0..dispute_count {
 			let d = (session_idx, CandidateHash(Hash::random()), DisputeStatus::Active);
 			self.add_offchain_dispute(d, local_votes_count, dummy_receipt.clone());
@@ -445,7 +445,7 @@ impl TestDisputes {
 		let onchain_votes_count = self.validators_count * 40 / 100;
 		let session_idx = 1;
 		let lf = leaf();
-		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt(lf.hash);
+		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt_v2(lf.hash);
 		for _ in 0..dispute_count {
 			let d = (session_idx, CandidateHash(Hash::random()), DisputeStatus::Active);
 			self.add_offchain_dispute(d, local_votes_count, dummy_receipt.clone());
@@ -462,7 +462,7 @@ impl TestDisputes {
 		let local_votes_count = self.validators_count * 90 / 100;
 		let session_idx = 2;
 		let lf = leaf();
-		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt(lf.hash);
+		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt_v2(lf.hash);
 		for _ in 0..dispute_count {
 			let d = (session_idx, CandidateHash(Hash::random()), DisputeStatus::Confirmed);
 			self.add_offchain_dispute(d, local_votes_count, dummy_receipt.clone());
@@ -478,7 +478,7 @@ impl TestDisputes {
 		let onchain_votes_count = self.validators_count * 75 / 100;
 		let session_idx = 3;
 		let lf = leaf();
-		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt(lf.hash);
+		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt_v2(lf.hash);
 		for _ in 0..dispute_count {
 			let d = (session_idx, CandidateHash(Hash::random()), DisputeStatus::ConcludedFor(0));
 			self.add_offchain_dispute(d, local_votes_count, dummy_receipt.clone());
@@ -494,7 +494,7 @@ impl TestDisputes {
 		let local_votes_count = self.validators_count * 90 / 100;
 		let session_idx = 4;
 		let lf = leaf();
-		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt(lf.hash);
+		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt_v2(lf.hash);
 		for _ in 0..dispute_count {
 			let d = (session_idx, CandidateHash(Hash::random()), DisputeStatus::ConcludedFor(0));
 			self.add_offchain_dispute(d, local_votes_count, dummy_receipt.clone());
@@ -510,7 +510,7 @@ impl TestDisputes {
 		let onchain_votes_count = self.validators_count * 10 / 100;
 		let session_idx = 5;
 		let lf = leaf();
-		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt(lf.hash);
+		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt_v2(lf.hash);
 		for _ in 0..dispute_count {
 			let d = (session_idx, CandidateHash(Hash::random()), DisputeStatus::Active);
 			self.add_offchain_dispute(d, local_votes_count, dummy_receipt.clone());
@@ -527,7 +527,7 @@ impl TestDisputes {
 		let local_votes_count = self.validators_count * 10 / 100;
 		let session_idx = 6;
 		let lf = leaf();
-		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt(lf.hash);
+		let dummy_receipt = polkadot_primitives_test_helpers::dummy_candidate_receipt_v2(lf.hash);
 		for _ in 0..dispute_count {
 			let d = (session_idx, CandidateHash(Hash::random()), DisputeStatus::Active);
 			self.add_offchain_dispute(d, local_votes_count, dummy_receipt.clone());
diff --git a/polkadot/node/core/provisioner/src/lib.rs b/polkadot/node/core/provisioner/src/lib.rs
index 9a06d9cff0cc3c435e2d06f3245220640faccbd1..a95df6c5f8808950f4531c0ba5f20cac07a959ab 100644
--- a/polkadot/node/core/provisioner/src/lib.rs
+++ b/polkadot/node/core/provisioner/src/lib.rs
@@ -41,9 +41,10 @@ use polkadot_node_subsystem_util::{
 	TimeoutExt,
 };
 use polkadot_primitives::{
-	node_features::FeatureIndex, BackedCandidate, BlockNumber, CandidateHash, CandidateReceipt,
-	CoreIndex, CoreState, Hash, Id as ParaId, NodeFeatures, OccupiedCoreAssumption, SessionIndex,
-	SignedAvailabilityBitfield, ValidatorIndex,
+	node_features::FeatureIndex,
+	vstaging::{BackedCandidate, CandidateReceiptV2 as CandidateReceipt, CoreState},
+	BlockNumber, CandidateHash, CoreIndex, Hash, Id as ParaId, NodeFeatures,
+	OccupiedCoreAssumption, SessionIndex, SignedAvailabilityBitfield, ValidatorIndex,
 };
 use std::collections::{BTreeMap, HashMap};
 
@@ -361,10 +362,9 @@ fn note_provisionable_data(
 			gum::trace!(
 				target: LOG_TARGET,
 				?candidate_hash,
-				para = ?backed_candidate.descriptor().para_id,
+				para = ?backed_candidate.descriptor().para_id(),
 				"noted backed candidate",
 			);
-
 			per_relay_parent.backed_candidates.push(backed_candidate);
 		},
 		// We choose not to punish these forms of misbehavior for the time being.
@@ -650,22 +650,22 @@ async fn select_candidate_hashes_from_tracked(
 		// selection criteria
 		if let Some(candidate) = candidates.iter().find(|backed_candidate| {
 			let descriptor = &backed_candidate.descriptor;
-			descriptor.para_id == scheduled_core.para_id &&
-				descriptor.persisted_validation_data_hash == computed_validation_data_hash
+			descriptor.para_id() == scheduled_core.para_id &&
+				descriptor.persisted_validation_data_hash() == computed_validation_data_hash
 		}) {
 			let candidate_hash = candidate.hash();
 			gum::trace!(
 				target: LOG_TARGET,
 				leaf_hash=?relay_parent,
 				?candidate_hash,
-				para = ?candidate.descriptor.para_id,
+				para = ?candidate.descriptor.para_id(),
 				core = core_idx,
 				"Selected candidate receipt",
 			);
 
 			selected_candidates.insert(
-				candidate.descriptor.para_id,
-				vec![(candidate_hash, candidate.descriptor.relay_parent)],
+				candidate.descriptor.para_id(),
+				vec![(candidate_hash, candidate.descriptor.relay_parent())],
 			);
 		}
 	}
diff --git a/polkadot/node/core/provisioner/src/tests.rs b/polkadot/node/core/provisioner/src/tests.rs
index b38459302c8f18721b4ceb0515fdeac3d116cea8..a09b243f3ab1338ecff24ba239978d60f2acaebc 100644
--- a/polkadot/node/core/provisioner/src/tests.rs
+++ b/polkadot/node/core/provisioner/src/tests.rs
@@ -16,14 +16,17 @@
 
 use super::*;
 use bitvec::bitvec;
-use polkadot_primitives::{OccupiedCore, ScheduledCore};
-use polkadot_primitives_test_helpers::{dummy_candidate_descriptor, dummy_hash};
+use polkadot_primitives::{
+	vstaging::{MutateDescriptorV2, OccupiedCore},
+	ScheduledCore,
+};
+use polkadot_primitives_test_helpers::{dummy_candidate_descriptor_v2, dummy_hash};
 
 const MOCK_GROUP_SIZE: usize = 5;
 
 pub fn occupied_core(para_id: u32) -> CoreState {
-	let mut candidate_descriptor = dummy_candidate_descriptor(dummy_hash());
-	candidate_descriptor.para_id = para_id.into();
+	let mut candidate_descriptor = dummy_candidate_descriptor_v2(dummy_hash());
+	candidate_descriptor.set_para_id(para_id.into());
 
 	CoreState::Occupied(OccupiedCore {
 		group_responsible: para_id.into(),
@@ -32,7 +35,7 @@ pub fn occupied_core(para_id: u32) -> CoreState {
 		time_out_at: 200_u32,
 		next_up_on_time_out: None,
 		availability: bitvec![u8, bitvec::order::Lsb0; 0; 32],
-		candidate_descriptor,
+		candidate_descriptor: candidate_descriptor.into(),
 		candidate_hash: Default::default(),
 	})
 }
@@ -254,9 +257,10 @@ mod select_candidates {
 	use polkadot_node_subsystem_test_helpers::TestSubsystemSender;
 	use polkadot_node_subsystem_util::runtime::ProspectiveParachainsMode;
 	use polkadot_primitives::{
-		BlockNumber, CandidateCommitments, CommittedCandidateReceipt, PersistedValidationData,
+		vstaging::{CommittedCandidateReceiptV2 as CommittedCandidateReceipt, MutateDescriptorV2},
+		BlockNumber, CandidateCommitments, PersistedValidationData,
 	};
-	use polkadot_primitives_test_helpers::{dummy_candidate_descriptor, dummy_hash};
+	use polkadot_primitives_test_helpers::{dummy_candidate_descriptor_v2, dummy_hash};
 	use rstest::rstest;
 	use std::ops::Not;
 	use CoreState::{Free, Scheduled};
@@ -266,8 +270,8 @@ mod select_candidates {
 	fn dummy_candidate_template() -> CandidateReceipt {
 		let empty_hash = PersistedValidationData::<Hash, BlockNumber>::default().hash();
 
-		let mut descriptor_template = dummy_candidate_descriptor(dummy_hash());
-		descriptor_template.persisted_validation_data_hash = empty_hash;
+		let mut descriptor_template = dummy_candidate_descriptor_v2(dummy_hash());
+		descriptor_template.set_persisted_validation_data_hash(empty_hash);
 		CandidateReceipt {
 			descriptor: descriptor_template,
 			commitments_hash: CandidateCommitments::default().hash(),
@@ -283,7 +287,7 @@ mod select_candidates {
 			.take(core_count)
 			.enumerate()
 			.map(|(idx, mut candidate)| {
-				candidate.descriptor.para_id = idx.into();
+				candidate.descriptor.set_para_id(idx.into());
 				candidate
 			})
 			.collect();
@@ -559,14 +563,14 @@ mod select_candidates {
 		use RuntimeApiMessage::Request;
 
 		let mut backed = expected.clone().into_iter().fold(HashMap::new(), |mut acc, candidate| {
-			acc.entry(candidate.descriptor().para_id).or_insert(vec![]).push(candidate);
+			acc.entry(candidate.descriptor().para_id()).or_insert(vec![]).push(candidate);
 			acc
 		});
 
-		expected.sort_by_key(|c| c.candidate().descriptor.para_id);
+		expected.sort_by_key(|c| c.candidate().descriptor.para_id());
 		let mut candidates_iter = expected
 			.iter()
-			.map(|candidate| (candidate.hash(), candidate.descriptor().relay_parent));
+			.map(|candidate| (candidate.hash(), candidate.descriptor().relay_parent()));
 
 		while let Some(from_job) = receiver.next().await {
 			match from_job {
@@ -601,7 +605,7 @@ mod select_candidates {
 								candidates
 									.iter()
 									.map(|candidate| {
-										(candidate.hash(), candidate.descriptor().relay_parent)
+										(candidate.hash(), candidate.descriptor().relay_parent())
 									})
 									.collect(),
 							)
@@ -707,7 +711,7 @@ mod select_candidates {
 			.take(mock_cores.len())
 			.enumerate()
 			.map(|(idx, mut candidate)| {
-				candidate.descriptor.para_id = idx.into();
+				candidate.descriptor.set_para_id(idx.into());
 				candidate
 			})
 			.cycle()
@@ -719,11 +723,11 @@ mod select_candidates {
 					candidate
 				} else if idx < mock_cores.len() * 2 {
 					// for the second repetition of the candidates, give them the wrong hash
-					candidate.descriptor.persisted_validation_data_hash = Default::default();
+					candidate.descriptor.set_persisted_validation_data_hash(Default::default());
 					candidate
 				} else {
 					// third go-around: right hash, wrong para_id
-					candidate.descriptor.para_id = idx.into();
+					candidate.descriptor.set_para_id(idx.into());
 					candidate
 				}
 			})
@@ -807,9 +811,9 @@ mod select_candidates {
 
 		let committed_receipts: Vec<_> = (0..=mock_cores.len())
 			.map(|i| {
-				let mut descriptor = dummy_candidate_descriptor(dummy_hash());
-				descriptor.para_id = i.into();
-				descriptor.persisted_validation_data_hash = empty_hash;
+				let mut descriptor = dummy_candidate_descriptor_v2(dummy_hash());
+				descriptor.set_para_id(i.into());
+				descriptor.set_persisted_validation_data_hash(empty_hash);
 				CommittedCandidateReceipt {
 					descriptor,
 					commitments: CandidateCommitments {
@@ -917,14 +921,14 @@ mod select_candidates {
 
 		let committed_receipts: Vec<_> = (0..mock_cores.len())
 			.map(|i| {
-				let mut descriptor = dummy_candidate_descriptor(dummy_hash());
-				descriptor.para_id = if let Scheduled(scheduled_core) = &mock_cores[i] {
+				let mut descriptor = dummy_candidate_descriptor_v2(dummy_hash());
+				descriptor.set_para_id(if let Scheduled(scheduled_core) = &mock_cores[i] {
 					scheduled_core.para_id
 				} else {
 					panic!("`mock_cores` is not initialized with `Scheduled`?")
-				};
-				descriptor.persisted_validation_data_hash = empty_hash;
-				descriptor.pov_hash = Hash::from_low_u64_be(i as u64);
+				});
+				descriptor.set_persisted_validation_data_hash(empty_hash);
+				descriptor.set_pov_hash(Hash::from_low_u64_be(i as u64));
 				CommittedCandidateReceipt {
 					descriptor,
 					commitments: CandidateCommitments {
@@ -1222,8 +1226,8 @@ mod select_candidates {
 			.take(mock_cores.len() + 1)
 			.enumerate()
 			.map(|(idx, mut candidate)| {
-				candidate.descriptor.para_id = idx.into();
-				candidate.descriptor.relay_parent = Hash::repeat_byte(idx as u8);
+				candidate.descriptor.set_para_id(idx.into());
+				candidate.descriptor.set_relay_parent(Hash::repeat_byte(idx as u8));
 				candidate
 			})
 			.collect();
diff --git a/polkadot/node/core/runtime-api/src/cache.rs b/polkadot/node/core/runtime-api/src/cache.rs
index 05efbc533d0204f25d4e55cc36e4a354f655f437..7246010711e40647c2eec061ddfb6b8b7144d6e5 100644
--- a/polkadot/node/core/runtime-api/src/cache.rs
+++ b/polkadot/node/core/runtime-api/src/cache.rs
@@ -20,12 +20,16 @@ use schnellru::{ByLength, LruMap};
 use sp_consensus_babe::Epoch;
 
 use polkadot_primitives::{
-	async_backing, slashing, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber,
-	CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreIndex,
-	CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId,
+	async_backing, slashing, vstaging,
+	vstaging::{
+		CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState,
+		ScrapedOnChainVotes,
+	},
+	ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash,
+	CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId,
 	InboundDownwardMessage, InboundHrmpMessage, NodeFeatures, OccupiedCoreAssumption,
-	PersistedValidationData, ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode,
-	ValidationCodeHash, ValidatorId, ValidatorIndex,
+	PersistedValidationData, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash,
+	ValidatorId, ValidatorIndex,
 };
 
 /// For consistency we have the same capacity for all caches. We use 128 as we'll only need that
@@ -66,7 +70,7 @@ pub(crate) struct RequestResultCache {
 	key_ownership_proof: LruMap<(Hash, ValidatorId), Option<slashing::OpaqueKeyOwnershipProof>>,
 	minimum_backing_votes: LruMap<SessionIndex, u32>,
 	disabled_validators: LruMap<Hash, Vec<ValidatorIndex>>,
-	para_backing_state: LruMap<(Hash, ParaId), Option<async_backing::BackingState>>,
+	para_backing_state: LruMap<(Hash, ParaId), Option<vstaging::async_backing::BackingState>>,
 	async_backing_params: LruMap<Hash, async_backing::AsyncBackingParams>,
 	node_features: LruMap<SessionIndex, NodeFeatures>,
 	approval_voting_params: LruMap<SessionIndex, ApprovalVotingParams>,
@@ -499,14 +503,14 @@ impl RequestResultCache {
 	pub(crate) fn para_backing_state(
 		&mut self,
 		key: (Hash, ParaId),
-	) -> Option<&Option<async_backing::BackingState>> {
+	) -> Option<&Option<vstaging::async_backing::BackingState>> {
 		self.para_backing_state.get(&key).map(|v| &*v)
 	}
 
 	pub(crate) fn cache_para_backing_state(
 		&mut self,
 		key: (Hash, ParaId),
-		value: Option<async_backing::BackingState>,
+		value: Option<vstaging::async_backing::BackingState>,
 	) {
 		self.para_backing_state.insert(key, value);
 	}
@@ -601,7 +605,7 @@ pub(crate) enum RequestResult {
 	SubmitReportDisputeLost(Option<()>),
 	ApprovalVotingParams(Hash, SessionIndex, ApprovalVotingParams),
 	DisabledValidators(Hash, Vec<ValidatorIndex>),
-	ParaBackingState(Hash, ParaId, Option<async_backing::BackingState>),
+	ParaBackingState(Hash, ParaId, Option<vstaging::async_backing::BackingState>),
 	AsyncBackingParams(Hash, async_backing::AsyncBackingParams),
 	NodeFeatures(SessionIndex, NodeFeatures),
 	ClaimQueue(Hash, BTreeMap<CoreIndex, VecDeque<ParaId>>),
diff --git a/polkadot/node/core/runtime-api/src/tests.rs b/polkadot/node/core/runtime-api/src/tests.rs
index 7c382707264f117b45469a2de28ac4d3c957309b..d4fa07323886dd834ac16f3b5ac7d6e24042a18f 100644
--- a/polkadot/node/core/runtime-api/src/tests.rs
+++ b/polkadot/node/core/runtime-api/src/tests.rs
@@ -20,14 +20,20 @@ use polkadot_node_primitives::{BabeAllowedSlots, BabeEpoch, BabeEpochConfigurati
 use polkadot_node_subsystem::SpawnGlue;
 use polkadot_node_subsystem_test_helpers::make_subsystem_context;
 use polkadot_primitives::{
-	async_backing, slashing, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber,
-	CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreIndex,
-	CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Id as ParaId,
+	async_backing, slashing, vstaging,
+	vstaging::{
+		CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState,
+		ScrapedOnChainVotes,
+	},
+	ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash,
+	CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Id as ParaId,
 	InboundDownwardMessage, InboundHrmpMessage, NodeFeatures, OccupiedCoreAssumption,
-	PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo,
-	Slot, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature,
+	PersistedValidationData, PvfCheckStatement, SessionIndex, SessionInfo, Slot, ValidationCode,
+	ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature,
+};
+use polkadot_primitives_test_helpers::{
+	dummy_committed_candidate_receipt_v2, dummy_validation_code,
 };
-use polkadot_primitives_test_helpers::{dummy_committed_candidate_receipt, dummy_validation_code};
 use sp_api::ApiError;
 use sp_core::testing::TaskExecutor;
 use std::{
@@ -279,7 +285,7 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient {
 		&self,
 		_: Hash,
 		_: ParaId,
-	) -> Result<Option<async_backing::BackingState>, ApiError> {
+	) -> Result<Option<vstaging::async_backing::BackingState>, ApiError> {
 		todo!("Not required for tests")
 	}
 
@@ -699,7 +705,7 @@ fn requests_candidate_pending_availability() {
 	let para_a = ParaId::from(5_u32);
 	let para_b = ParaId::from(6_u32);
 	let spawner = sp_core::testing::TaskExecutor::new();
-	let candidate_receipt = dummy_committed_candidate_receipt(relay_parent);
+	let candidate_receipt = dummy_committed_candidate_receipt_v2(relay_parent);
 
 	let mut subsystem_client = MockSubsystemClient::default();
 	subsystem_client
diff --git a/polkadot/node/malus/src/variants/common.rs b/polkadot/node/malus/src/variants/common.rs
index 66926f48c5e7f42bfc054aef667323b3da9cedf0..7415e6c79df50df6ac0a62f668181cd5a942e3ba 100644
--- a/polkadot/node/malus/src/variants/common.rs
+++ b/polkadot/node/malus/src/variants/common.rs
@@ -24,8 +24,10 @@ use crate::{
 use polkadot_node_primitives::{InvalidCandidate, ValidationResult};
 
 use polkadot_primitives::{
-	CandidateCommitments, CandidateDescriptor, CandidateReceipt, PersistedValidationData,
-	PvfExecKind,
+	vstaging::{
+		CandidateDescriptorV2 as CandidateDescriptor, CandidateReceiptV2 as CandidateReceipt,
+	},
+	CandidateCommitments, PersistedValidationData, PvfExecKind,
 };
 
 use futures::channel::oneshot;
@@ -203,7 +205,7 @@ fn create_validation_response(
 
 	gum::debug!(
 		target: MALUS,
-		para_id = ?candidate_receipt.descriptor.para_id,
+		para_id = ?candidate_receipt.descriptor.para_id(),
 		candidate_hash = ?candidate_receipt.hash(),
 		"ValidationResult: {:?}",
 		&result
@@ -308,7 +310,7 @@ where
 								gum::info!(
 									target: MALUS,
 									?behave_maliciously,
-									para_id = ?candidate_receipt.descriptor.para_id,
+									para_id = ?candidate_receipt.descriptor.para_id(),
 									"😈 Maliciously sending invalid validation result: {:?}.",
 									&validation_result,
 								);
diff --git a/polkadot/node/malus/src/variants/dispute_finalized_candidates.rs b/polkadot/node/malus/src/variants/dispute_finalized_candidates.rs
index 7a95bdaead26e204bd4cd8b386ae02b5e12297f9..309be9e46d822321e0228a72f48312dc4378a5a1 100644
--- a/polkadot/node/malus/src/variants/dispute_finalized_candidates.rs
+++ b/polkadot/node/malus/src/variants/dispute_finalized_candidates.rs
@@ -42,7 +42,7 @@ use polkadot_cli::{
 use polkadot_node_subsystem::SpawnGlue;
 use polkadot_node_subsystem_types::{ChainApiBackend, OverseerSignal, RuntimeApiSubsystemClient};
 use polkadot_node_subsystem_util::request_candidate_events;
-use polkadot_primitives::CandidateEvent;
+use polkadot_primitives::vstaging::CandidateEvent;
 use sp_core::traits::SpawnNamed;
 
 // Filter wrapping related types.
diff --git a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs
index ab2d380fbaf486fce9aefb84669eeba5638f2b0c..2fe08c8a1c493700b4e6b882a1416b5f062ddc73 100644
--- a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs
+++ b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs
@@ -32,7 +32,7 @@ use polkadot_cli::{
 };
 use polkadot_node_primitives::{AvailableData, BlockData, PoV};
 use polkadot_node_subsystem_types::{ChainApiBackend, RuntimeApiSubsystemClient};
-use polkadot_primitives::{CandidateDescriptor, CandidateReceipt};
+use polkadot_primitives::{vstaging::CandidateReceiptV2, CandidateDescriptor};
 
 use polkadot_node_subsystem_util::request_validators;
 use sp_core::traits::SpawnNamed;
@@ -127,7 +127,7 @@ where
 
 							let validation_code = {
 								let validation_code_hash =
-									_candidate.descriptor().validation_code_hash;
+									_candidate.descriptor().validation_code_hash();
 								let (tx, rx) = oneshot::channel();
 								new_sender
 									.send_message(RuntimeApiMessage::Request(
@@ -214,7 +214,7 @@ where
 						let collator_pair = CollatorPair::generate().0;
 						let signature_payload = polkadot_primitives::collator_signature_payload(
 							&relay_parent,
-							&candidate.descriptor().para_id,
+							&candidate.descriptor().para_id(),
 							&validation_data_hash,
 							&pov_hash,
 							&validation_code_hash,
@@ -227,9 +227,9 @@ where
 						&malicious_available_data.validation_data,
 					);
 
-					let malicious_candidate = CandidateReceipt {
+					let malicious_candidate = CandidateReceiptV2 {
 						descriptor: CandidateDescriptor {
-							para_id: candidate.descriptor().para_id,
+							para_id: candidate.descriptor.para_id(),
 							relay_parent,
 							collator: collator_id,
 							persisted_validation_data_hash: validation_data_hash,
@@ -238,7 +238,8 @@ where
 							signature: collator_signature,
 							para_head: malicious_commitments.head_data.hash(),
 							validation_code_hash,
-						},
+						}
+						.into(),
 						commitments_hash: malicious_commitments.hash(),
 					};
 					let malicious_candidate_hash = malicious_candidate.hash();
diff --git a/polkadot/node/network/availability-distribution/src/requester/fetch_task/mod.rs b/polkadot/node/network/availability-distribution/src/requester/fetch_task/mod.rs
index 5be6f2d223a8e3d1ee0478defea9193f423050de..c4654b843c447c57b6e9a04ce0f3fd922999f083 100644
--- a/polkadot/node/network/availability-distribution/src/requester/fetch_task/mod.rs
+++ b/polkadot/node/network/availability-distribution/src/requester/fetch_task/mod.rs
@@ -35,8 +35,8 @@ use polkadot_node_subsystem::{
 	overseer,
 };
 use polkadot_primitives::{
-	AuthorityDiscoveryId, BlakeTwo256, CandidateHash, ChunkIndex, GroupIndex, Hash, HashT,
-	OccupiedCore, SessionIndex,
+	vstaging::OccupiedCore, AuthorityDiscoveryId, BlakeTwo256, CandidateHash, ChunkIndex,
+	GroupIndex, Hash, HashT, SessionIndex,
 };
 use sc_network::ProtocolName;
 
@@ -170,8 +170,8 @@ impl FetchTaskConfig {
 				candidate_hash: core.candidate_hash,
 				index: session_info.our_index,
 			},
-			erasure_root: core.candidate_descriptor.erasure_root,
-			relay_parent: core.candidate_descriptor.relay_parent,
+			erasure_root: core.candidate_descriptor.erasure_root(),
+			relay_parent: core.candidate_descriptor.relay_parent(),
 			metrics,
 			sender,
 			chunk_index,
diff --git a/polkadot/node/network/availability-distribution/src/requester/mod.rs b/polkadot/node/network/availability-distribution/src/requester/mod.rs
index 2338250327246a05ab7d9be469c07df8d1625cda..613a514269ee6892e25962d5f95fe9671991f85b 100644
--- a/polkadot/node/network/availability-distribution/src/requester/mod.rs
+++ b/polkadot/node/network/availability-distribution/src/requester/mod.rs
@@ -38,7 +38,7 @@ use polkadot_node_subsystem_util::{
 	availability_chunks::availability_chunk_index,
 	runtime::{get_occupied_cores, RuntimeInfo},
 };
-use polkadot_primitives::{CandidateHash, CoreIndex, Hash, OccupiedCore, SessionIndex};
+use polkadot_primitives::{vstaging::OccupiedCore, CandidateHash, CoreIndex, Hash, SessionIndex};
 
 use super::{FatalError, Metrics, Result, LOG_TARGET};
 
diff --git a/polkadot/node/network/availability-distribution/src/requester/tests.rs b/polkadot/node/network/availability-distribution/src/requester/tests.rs
index 021f6da7e2e99e834bec7b2df05741df25e17469..ebcba2a038bc8a5c261e39c8b0de298726172e3d 100644
--- a/polkadot/node/network/availability-distribution/src/requester/tests.rs
+++ b/polkadot/node/network/availability-distribution/src/requester/tests.rs
@@ -21,7 +21,7 @@ use polkadot_node_network_protocol::request_response::ReqProtocolNames;
 use polkadot_node_primitives::{BlockData, ErasureChunk, PoV};
 use polkadot_node_subsystem_util::runtime::RuntimeInfo;
 use polkadot_primitives::{
-	BlockNumber, ChunkIndex, CoreState, ExecutorParams, GroupIndex, Hash, Id as ParaId,
+	vstaging::CoreState, BlockNumber, ChunkIndex, ExecutorParams, GroupIndex, Hash, Id as ParaId,
 	ScheduledCore, SessionIndex, SessionInfo,
 };
 use sp_core::{testing::TaskExecutor, traits::SpawnNamed};
diff --git a/polkadot/node/network/availability-distribution/src/tests/mock.rs b/polkadot/node/network/availability-distribution/src/tests/mock.rs
index b41c493a10721bbdd988125d1c355ad9e4cc4824..f900cb6e61560ef699b55f42a183fc49b0b82c46 100644
--- a/polkadot/node/network/availability-distribution/src/tests/mock.rs
+++ b/polkadot/node/network/availability-distribution/src/tests/mock.rs
@@ -23,8 +23,9 @@ use sp_keyring::Sr25519Keyring;
 use polkadot_erasure_coding::{branches, obtain_chunks_v1 as obtain_chunks};
 use polkadot_node_primitives::{AvailableData, BlockData, ErasureChunk, PoV, Proof};
 use polkadot_primitives::{
+	vstaging::{CommittedCandidateReceiptV2, OccupiedCore},
 	CandidateCommitments, CandidateDescriptor, CandidateHash, ChunkIndex,
-	CommittedCandidateReceipt, GroupIndex, Hash, HeadData, Id as ParaId, IndexedVec, OccupiedCore,
+	CommittedCandidateReceipt, GroupIndex, Hash, HeadData, Id as ParaId, IndexedVec,
 	PersistedValidationData, SessionInfo, ValidatorIndex,
 };
 use polkadot_primitives_test_helpers::{
@@ -101,7 +102,7 @@ impl OccupiedCoreBuilder {
 			availability: Default::default(),
 			group_responsible: self.group_responsible,
 			candidate_hash: candidate_receipt.hash(),
-			candidate_descriptor: candidate_receipt.descriptor().clone(),
+			candidate_descriptor: candidate_receipt.descriptor.clone(),
 		};
 		(core, (candidate_receipt.hash(), chunk))
 	}
@@ -117,7 +118,7 @@ pub struct TestCandidateBuilder {
 }
 
 impl TestCandidateBuilder {
-	pub fn build(self) -> CommittedCandidateReceipt {
+	pub fn build(self) -> CommittedCandidateReceiptV2 {
 		CommittedCandidateReceipt {
 			descriptor: CandidateDescriptor {
 				para_id: self.para_id,
@@ -132,6 +133,7 @@ impl TestCandidateBuilder {
 			},
 			commitments: CandidateCommitments { head_data: self.head_data, ..Default::default() },
 		}
+		.into()
 	}
 }
 
diff --git a/polkadot/node/network/availability-distribution/src/tests/mod.rs b/polkadot/node/network/availability-distribution/src/tests/mod.rs
index 078220607c37fb3cab981e1356f620ea34445b1e..d4abd4e32d9b76bfcc99d1f8c38c3adb2cbcd547 100644
--- a/polkadot/node/network/availability-distribution/src/tests/mod.rs
+++ b/polkadot/node/network/availability-distribution/src/tests/mod.rs
@@ -22,7 +22,7 @@ use rstest::rstest;
 use polkadot_node_network_protocol::request_response::{
 	IncomingRequest, Protocol, ReqProtocolNames,
 };
-use polkadot_primitives::{node_features, Block, CoreState, Hash, NodeFeatures};
+use polkadot_primitives::{node_features, vstaging::CoreState, Block, Hash, NodeFeatures};
 use sp_keystore::KeystorePtr;
 
 use super::*;
diff --git a/polkadot/node/network/availability-distribution/src/tests/state.rs b/polkadot/node/network/availability-distribution/src/tests/state.rs
index 53d6fd2c530fab2c05a317b4fec38d23a854d00d..c6dd17a344e01490387e3ae84d8769bc78eaebe2 100644
--- a/polkadot/node/network/availability-distribution/src/tests/state.rs
+++ b/polkadot/node/network/availability-distribution/src/tests/state.rs
@@ -47,7 +47,7 @@ use polkadot_node_subsystem::{
 };
 use polkadot_node_subsystem_test_helpers as test_helpers;
 use polkadot_primitives::{
-	CandidateHash, ChunkIndex, CoreIndex, CoreState, ExecutorParams, GroupIndex, Hash,
+	vstaging::CoreState, CandidateHash, ChunkIndex, CoreIndex, ExecutorParams, GroupIndex, Hash,
 	Id as ParaId, NodeFeatures, ScheduledCore, SessionInfo, ValidatorIndex,
 };
 use test_helpers::mock::{make_ferdie_keystore, new_leaf};
diff --git a/polkadot/node/network/availability-recovery/src/lib.rs b/polkadot/node/network/availability-recovery/src/lib.rs
index 114faa2859c4c9c5947414aa49cc0025a2433605..eb54d9657d836d500874e880d9c3fad9c05b8046 100644
--- a/polkadot/node/network/availability-recovery/src/lib.rs
+++ b/polkadot/node/network/availability-recovery/src/lib.rs
@@ -66,8 +66,8 @@ use polkadot_node_subsystem_util::{
 	runtime::{ExtendedSessionInfo, RuntimeInfo},
 };
 use polkadot_primitives::{
-	node_features, BlockNumber, CandidateHash, CandidateReceipt, ChunkIndex, CoreIndex, GroupIndex,
-	Hash, SessionIndex, ValidatorIndex,
+	node_features, vstaging::CandidateReceiptV2 as CandidateReceipt, BlockNumber, CandidateHash,
+	ChunkIndex, CoreIndex, GroupIndex, Hash, SessionIndex, ValidatorIndex,
 };
 
 mod error;
@@ -540,11 +540,11 @@ async fn handle_recover<Context>(
 					threshold: recovery_threshold(n_validators)?,
 					systematic_threshold,
 					candidate_hash,
-					erasure_root: receipt.descriptor.erasure_root,
+					erasure_root: receipt.descriptor.erasure_root(),
 					metrics: metrics.clone(),
 					bypass_availability_store,
 					post_recovery_check,
-					pov_hash: receipt.descriptor.pov_hash,
+					pov_hash: receipt.descriptor.pov_hash(),
 					req_v1_protocol_name,
 					req_v2_protocol_name,
 					chunk_mapping_enabled,
diff --git a/polkadot/node/network/availability-recovery/src/tests.rs b/polkadot/node/network/availability-recovery/src/tests.rs
index 4fd9ede40ff658631142df4829aab43e55c9f105..9a46d54207821085a13e77b247b6d0b14c2346de 100644
--- a/polkadot/node/network/availability-recovery/src/tests.rs
+++ b/polkadot/node/network/availability-recovery/src/tests.rs
@@ -41,8 +41,8 @@ use polkadot_node_subsystem_test_helpers::{
 };
 use polkadot_node_subsystem_util::TimeoutExt;
 use polkadot_primitives::{
-	node_features, AuthorityDiscoveryId, Block, ExecutorParams, Hash, HeadData, IndexedVec,
-	NodeFeatures, PersistedValidationData, SessionInfo, ValidatorId,
+	node_features, vstaging::MutateDescriptorV2, AuthorityDiscoveryId, Block, ExecutorParams, Hash,
+	HeadData, IndexedVec, NodeFeatures, PersistedValidationData, SessionInfo, ValidatorId,
 };
 use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_hash};
 use sc_network::{IfDisconnected, OutboundFailure, ProtocolName, RequestFailure};
@@ -346,7 +346,7 @@ impl TestState {
 			)
 			.unwrap(),
 			current,
-			candidate,
+			candidate: candidate.into(),
 			session_index,
 			core_index,
 			node_features,
@@ -800,12 +800,12 @@ fn availability_is_recovered_from_chunks_if_no_group_provided(#[case] systematic
 		// Test another candidate, send no chunks.
 		let mut new_candidate = dummy_candidate_receipt(dummy_hash());
 
-		new_candidate.descriptor.relay_parent = test_state.candidate.descriptor.relay_parent;
+		new_candidate.descriptor.relay_parent = test_state.candidate.descriptor.relay_parent();
 
 		overseer_send(
 			&mut virtual_overseer,
 			AvailabilityRecoveryMessage::RecoverAvailableData(
-				new_candidate.clone(),
+				new_candidate.clone().into(),
 				test_state.session_index,
 				None,
 				Some(test_state.core_index),
@@ -929,12 +929,12 @@ fn availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunk
 		// Test another candidate, send no chunks.
 		let mut new_candidate = dummy_candidate_receipt(dummy_hash());
 
-		new_candidate.descriptor.relay_parent = test_state.candidate.descriptor.relay_parent;
+		new_candidate.descriptor.relay_parent = test_state.candidate.descriptor.relay_parent();
 
 		overseer_send(
 			&mut virtual_overseer,
 			AvailabilityRecoveryMessage::RecoverAvailableData(
-				new_candidate.clone(),
+				new_candidate.clone().into(),
 				test_state.session_index,
 				Some(GroupIndex(1)),
 				Some(test_state.core_index),
@@ -1218,7 +1218,7 @@ fn invalid_erasure_coding_leads_to_invalid_error(#[case] systematic_recovery: bo
 			test_state.validators.len(),
 			test_state.core_index,
 		);
-		test_state.candidate.descriptor.erasure_root = bad_erasure_root;
+		test_state.candidate.descriptor.set_erasure_root(bad_erasure_root);
 
 		let candidate_hash = test_state.candidate.hash();
 
@@ -1283,7 +1283,7 @@ fn invalid_pov_hash_leads_to_invalid_error() {
 	test_harness(subsystem, |mut virtual_overseer| async move {
 		let pov = PoV { block_data: BlockData(vec![69; 64]) };
 
-		test_state.candidate.descriptor.pov_hash = pov.hash();
+		test_state.candidate.descriptor.set_pov_hash(pov.hash());
 
 		let candidate_hash = test_state.candidate.hash();
 
@@ -1420,7 +1420,10 @@ fn recovers_from_only_chunks_if_pov_large(
 			test_state.threshold(),
 		),
 		(false, true) => {
-			test_state.candidate.descriptor.pov_hash = test_state.available_data.pov.hash();
+			test_state
+				.candidate
+				.descriptor
+				.set_pov_hash(test_state.available_data.pov.hash());
 			(
 				AvailabilityRecoverySubsystem::for_collator(
 					None,
@@ -1497,12 +1500,12 @@ fn recovers_from_only_chunks_if_pov_large(
 		// Test another candidate, send no chunks.
 		let mut new_candidate = dummy_candidate_receipt(dummy_hash());
 
-		new_candidate.descriptor.relay_parent = test_state.candidate.descriptor.relay_parent;
+		new_candidate.descriptor.relay_parent = test_state.candidate.descriptor.relay_parent();
 
 		overseer_send(
 			&mut virtual_overseer,
 			AvailabilityRecoveryMessage::RecoverAvailableData(
-				new_candidate.clone(),
+				new_candidate.clone().into(),
 				test_state.session_index,
 				Some(GroupIndex(1)),
 				Some(test_state.core_index),
@@ -1593,7 +1596,10 @@ fn fast_path_backing_group_recovers_if_pov_small(
 			Metrics::new_dummy(),
 		),
 		(false, true) => {
-			test_state.candidate.descriptor.pov_hash = test_state.available_data.pov.hash();
+			test_state
+				.candidate
+				.descriptor
+				.set_pov_hash(test_state.available_data.pov.hash());
 			AvailabilityRecoverySubsystem::for_collator(
 				None,
 				request_receiver(&req_protocol_names),
@@ -2635,7 +2641,7 @@ fn number_of_request_retries_is_bounded(
 	);
 	test_state.chunks =
 		map_chunks(chunks, &test_state.node_features, n_validators, test_state.core_index);
-	test_state.candidate.descriptor.erasure_root = erasure_root;
+	test_state.candidate.descriptor.set_erasure_root(erasure_root);
 
 	let (subsystem, retry_limit) = match systematic_recovery {
 		false => (
diff --git a/polkadot/node/network/collator-protocol/src/collator_side/collation.rs b/polkadot/node/network/collator-protocol/src/collator_side/collation.rs
index 57e1479a449b75e29a11292d720543b71c382f9c..6a570331f710b3a845d5ebcd02a007e45eb3f662 100644
--- a/polkadot/node/network/collator-protocol/src/collator_side/collation.rs
+++ b/polkadot/node/network/collator-protocol/src/collator_side/collation.rs
@@ -28,7 +28,9 @@ use polkadot_node_network_protocol::{
 };
 use polkadot_node_primitives::PoV;
 use polkadot_node_subsystem::messages::ParentHeadData;
-use polkadot_primitives::{CandidateHash, CandidateReceipt, Hash, Id as ParaId};
+use polkadot_primitives::{
+	vstaging::CandidateReceiptV2 as CandidateReceipt, CandidateHash, Hash, Id as ParaId,
+};
 
 /// The status of a collation as seen from the collator.
 pub enum CollationStatus {
diff --git a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs
index af9beb535f46abfc323efd7acde4323a4e0ddd3d..504b0d716043943875de9635832b473872e5fd7e 100644
--- a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs
+++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs
@@ -54,8 +54,9 @@ use polkadot_node_subsystem_util::{
 	TimeoutExt,
 };
 use polkadot_primitives::{
-	AuthorityDiscoveryId, CandidateHash, CandidateReceipt, CollatorPair, CoreIndex, CoreState,
-	GroupIndex, Hash, HeadData, Id as ParaId, SessionIndex,
+	vstaging::{CandidateReceiptV2 as CandidateReceipt, CoreState},
+	AuthorityDiscoveryId, CandidateHash, CollatorPair, CoreIndex, GroupIndex, Hash, HeadData,
+	Id as ParaId, SessionIndex,
 };
 
 use super::LOG_TARGET;
@@ -374,7 +375,7 @@ async fn distribute_collation<Context>(
 	result_sender: Option<oneshot::Sender<CollationSecondedSignal>>,
 	core_index: CoreIndex,
 ) -> Result<()> {
-	let candidate_relay_parent = receipt.descriptor.relay_parent;
+	let candidate_relay_parent = receipt.descriptor.relay_parent();
 	let candidate_hash = receipt.hash();
 
 	let per_relay_parent = match state.per_relay_parent.get_mut(&candidate_relay_parent) {
@@ -850,12 +851,12 @@ async fn process_msg<Context>(
 			core_index,
 		} => {
 			match state.collating_on {
-				Some(id) if candidate_receipt.descriptor.para_id != id => {
+				Some(id) if candidate_receipt.descriptor.para_id() != id => {
 					// If the ParaId of a collation requested to be distributed does not match
 					// the one we expect, we ignore the message.
 					gum::warn!(
 						target: LOG_TARGET,
-						para_id = %candidate_receipt.descriptor.para_id,
+						para_id = %candidate_receipt.descriptor.para_id(),
 						collating_on = %id,
 						"DistributeCollation for unexpected para_id",
 					);
@@ -879,7 +880,7 @@ async fn process_msg<Context>(
 				None => {
 					gum::warn!(
 						target: LOG_TARGET,
-						para_id = %candidate_receipt.descriptor.para_id,
+						para_id = %candidate_receipt.descriptor.para_id(),
 						"DistributeCollation message while not collating on any",
 					);
 				},
diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs
index 58d9ebc577260ec692e11a622d3c350f08285e58..0b3e9f4b34317b5d735ff574448c2b88873a1d31 100644
--- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs
+++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs
@@ -40,8 +40,8 @@ use polkadot_node_subsystem_util::{
 	metrics::prometheus::prometheus::HistogramTimer, runtime::ProspectiveParachainsMode,
 };
 use polkadot_primitives::{
-	CandidateHash, CandidateReceipt, CollatorId, Hash, HeadData, Id as ParaId,
-	PersistedValidationData,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, CandidateHash, CollatorId, Hash, HeadData,
+	Id as ParaId, PersistedValidationData,
 };
 use tokio_util::sync::CancellationToken;
 
@@ -71,18 +71,15 @@ pub struct FetchedCollation {
 	pub para_id: ParaId,
 	/// Candidate hash.
 	pub candidate_hash: CandidateHash,
-	/// Id of the collator the collation was fetched from.
-	pub collator_id: CollatorId,
 }
 
 impl From<&CandidateReceipt<Hash>> for FetchedCollation {
 	fn from(receipt: &CandidateReceipt<Hash>) -> Self {
 		let descriptor = receipt.descriptor();
 		Self {
-			relay_parent: descriptor.relay_parent,
-			para_id: descriptor.para_id,
+			relay_parent: descriptor.relay_parent(),
+			para_id: descriptor.para_id(),
 			candidate_hash: receipt.hash(),
-			collator_id: descriptor.collator.clone(),
 		}
 	}
 }
@@ -141,7 +138,7 @@ pub fn fetched_collation_sanity_check(
 	persisted_validation_data: &PersistedValidationData,
 	maybe_parent_head_and_hash: Option<(HeadData, Hash)>,
 ) -> Result<(), SecondingError> {
-	if persisted_validation_data.hash() != fetched.descriptor().persisted_validation_data_hash {
+	if persisted_validation_data.hash() != fetched.descriptor().persisted_validation_data_hash() {
 		Err(SecondingError::PersistedValidationDataMismatch)
 	} else if advertised
 		.prospective_candidate
diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs
index deb6ce03f43ebdcfefd698853f2b44264e36dbcc..51e987d59ce847977a9c2dda13941cea7c139c1c 100644
--- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs
+++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs
@@ -52,8 +52,8 @@ use polkadot_node_subsystem_util::{
 	runtime::{fetch_claim_queue, prospective_parachains_mode, ProspectiveParachainsMode},
 };
 use polkadot_primitives::{
-	CandidateHash, CollatorId, CoreState, Hash, HeadData, Id as ParaId, OccupiedCoreAssumption,
-	PersistedValidationData,
+	vstaging::CoreState, CandidateHash, CollatorId, Hash, HeadData, Id as ParaId,
+	OccupiedCoreAssumption, PersistedValidationData,
 };
 
 use crate::error::{Error, FetchError, Result, SecondingError};
@@ -1021,7 +1021,7 @@ async fn second_unblocked_collations<Context>(
 		for mut unblocked_collation in unblocked_collations {
 			unblocked_collation.maybe_parent_head_data = Some(head_data.clone());
 			let peer_id = unblocked_collation.collation_event.pending_collation.peer_id;
-			let relay_parent = unblocked_collation.candidate_receipt.descriptor.relay_parent;
+			let relay_parent = unblocked_collation.candidate_receipt.descriptor.relay_parent();
 
 			if let Err(err) = kick_off_seconding(ctx, state, unblocked_collation).await {
 				gum::warn!(
@@ -1328,7 +1328,7 @@ where
 		collations.retain(|collation| {
 			state
 				.per_relay_parent
-				.contains_key(&collation.candidate_receipt.descriptor.relay_parent)
+				.contains_key(&collation.candidate_receipt.descriptor.relay_parent())
 		});
 
 		!collations.is_empty()
@@ -1470,7 +1470,7 @@ async fn process_msg<Context>(
 				},
 			};
 			let output_head_data = receipt.commitments.head_data.clone();
-			let output_head_data_hash = receipt.descriptor.para_head;
+			let output_head_data_hash = receipt.descriptor.para_head();
 			let fetched_collation = FetchedCollation::from(&receipt.to_plain());
 			if let Some(CollationEvent { collator_id, pending_collation, .. }) =
 				state.fetched_candidates.remove(&fetched_collation)
@@ -1531,8 +1531,8 @@ async fn process_msg<Context>(
 		Invalid(parent, candidate_receipt) => {
 			// Remove collations which were blocked from seconding and had this candidate as parent.
 			state.blocked_from_seconding.remove(&BlockedCollationId {
-				para_id: candidate_receipt.descriptor.para_id,
-				parent_head_data_hash: candidate_receipt.descriptor.para_head,
+				para_id: candidate_receipt.descriptor.para_id(),
+				parent_head_data_hash: candidate_receipt.descriptor.para_head(),
 			});
 
 			let fetched_collation = FetchedCollation::from(&candidate_receipt);
@@ -1843,8 +1843,8 @@ async fn kick_off_seconding<Context>(
 			(CollationVersion::V2, Some(_)) | (CollationVersion::V1, _) => {
 				let pvd = request_persisted_validation_data(
 					ctx.sender(),
-					candidate_receipt.descriptor().relay_parent,
-					candidate_receipt.descriptor().para_id,
+					candidate_receipt.descriptor().relay_parent(),
+					candidate_receipt.descriptor().para_id(),
 				)
 				.await?;
 				(
@@ -1874,14 +1874,14 @@ async fn kick_off_seconding<Context>(
 				gum::debug!(
 					target: LOG_TARGET,
 					candidate_hash = ?blocked_collation.candidate_receipt.hash(),
-					relay_parent = ?blocked_collation.candidate_receipt.descriptor.relay_parent,
+					relay_parent = ?blocked_collation.candidate_receipt.descriptor.relay_parent(),
 					"Collation having parent head data hash {} is blocked from seconding. Waiting on its parent to be validated.",
 					parent_head_data_hash
 				);
 				state
 					.blocked_from_seconding
 					.entry(BlockedCollationId {
-						para_id: blocked_collation.candidate_receipt.descriptor.para_id,
+						para_id: blocked_collation.candidate_receipt.descriptor.para_id(),
 						parent_head_data_hash,
 					})
 					.or_insert_with(Vec::new)
@@ -2025,11 +2025,11 @@ async fn handle_collation_fetch_response(
 		Ok(
 			request_v1::CollationFetchingResponse::Collation(receipt, _) |
 			request_v1::CollationFetchingResponse::CollationWithParentHeadData { receipt, .. },
-		) if receipt.descriptor().para_id != pending_collation.para_id => {
+		) if receipt.descriptor().para_id() != pending_collation.para_id => {
 			gum::debug!(
 				target: LOG_TARGET,
 				expected_para_id = ?pending_collation.para_id,
-				got_para_id = ?receipt.descriptor().para_id,
+				got_para_id = ?receipt.descriptor().para_id(),
 				peer_id = ?pending_collation.peer_id,
 				"Got wrong para ID for requested collation."
 			);
diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs
index 86c8bcb6bdcdbcad7fb7fbf6f1d73666331348ff..290c4db901d5b8e7caf3d4f20f687855d6a1811c 100644
--- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs
+++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs
@@ -42,8 +42,9 @@ use polkadot_node_subsystem::{
 use polkadot_node_subsystem_test_helpers as test_helpers;
 use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt};
 use polkadot_primitives::{
-	CandidateReceipt, CollatorPair, CoreIndex, CoreState, GroupIndex, GroupRotationInfo, HeadData,
-	OccupiedCore, PersistedValidationData, ScheduledCore, ValidatorId, ValidatorIndex,
+	vstaging::{CandidateReceiptV2 as CandidateReceipt, CoreState, OccupiedCore},
+	CollatorPair, CoreIndex, GroupIndex, GroupRotationInfo, HeadData, PersistedValidationData,
+	ScheduledCore, ValidatorId, ValidatorIndex,
 };
 use polkadot_primitives_test_helpers::{
 	dummy_candidate_descriptor, dummy_candidate_receipt_bad_sig, dummy_hash,
@@ -121,7 +122,7 @@ impl Default for TestState {
 					let mut d = dummy_candidate_descriptor(dummy_hash());
 					d.para_id = chain_ids[1];
 
-					d
+					d.into()
 				},
 			}),
 		];
@@ -341,7 +342,7 @@ async fn assert_candidate_backing_second(
 			incoming_pov,
 		)) => {
 			assert_eq!(expected_relay_parent, relay_parent);
-			assert_eq!(expected_para_id, candidate_receipt.descriptor.para_id);
+			assert_eq!(expected_para_id, candidate_receipt.descriptor.para_id());
 			assert_eq!(*expected_pov, incoming_pov);
 			assert_eq!(pvd, received_pvd);
 			candidate_receipt
@@ -591,8 +592,11 @@ fn act_on_advertisement_v2() {
 
 		response_channel
 			.send(Ok((
-				request_v1::CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone())
-					.encode(),
+				request_v1::CollationFetchingResponse::Collation(
+					candidate_a.clone().into(),
+					pov.clone(),
+				)
+				.encode(),
 				ProtocolName::from(""),
 			)))
 			.expect("Sending response should succeed");
@@ -793,8 +797,11 @@ fn fetch_one_collation_at_a_time() {
 		candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash();
 		response_channel
 			.send(Ok((
-				request_v1::CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone())
-					.encode(),
+				request_v1::CollationFetchingResponse::Collation(
+					candidate_a.clone().into(),
+					pov.clone(),
+				)
+				.encode(),
 				ProtocolName::from(""),
 			)))
 			.expect("Sending response should succeed");
@@ -917,16 +924,22 @@ fn fetches_next_collation() {
 		// First request finishes now:
 		response_channel_non_exclusive
 			.send(Ok((
-				request_v1::CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone())
-					.encode(),
+				request_v1::CollationFetchingResponse::Collation(
+					candidate_a.clone().into(),
+					pov.clone(),
+				)
+				.encode(),
 				ProtocolName::from(""),
 			)))
 			.expect("Sending response should succeed");
 
 		response_channel
 			.send(Ok((
-				request_v1::CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone())
-					.encode(),
+				request_v1::CollationFetchingResponse::Collation(
+					candidate_a.clone().into(),
+					pov.clone(),
+				)
+				.encode(),
 				ProtocolName::from(""),
 			)))
 			.expect("Sending response should succeed");
@@ -1055,8 +1068,11 @@ fn fetch_next_collation_on_invalid_collation() {
 		candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash();
 		response_channel
 			.send(Ok((
-				request_v1::CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone())
-					.encode(),
+				request_v1::CollationFetchingResponse::Collation(
+					candidate_a.clone().into(),
+					pov.clone(),
+				)
+				.encode(),
 				ProtocolName::from(""),
 			)))
 			.expect("Sending response should succeed");
diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs
index dff98e22e3db49bd104d9fc59ea1b7e36705b9a0..e040163cd905bda362acae30eb33eec97fc690d9 100644
--- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs
+++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs
@@ -20,8 +20,8 @@ use super::*;
 
 use polkadot_node_subsystem::messages::ChainApiMessage;
 use polkadot_primitives::{
-	AsyncBackingParams, BlockNumber, CandidateCommitments, CommittedCandidateReceipt, Header,
-	SigningContext, ValidatorId,
+	vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, AsyncBackingParams,
+	BlockNumber, CandidateCommitments, Header, SigningContext, ValidatorId,
 };
 use rstest::rstest;
 
@@ -225,7 +225,7 @@ async fn send_seconded_statement(
 
 	overseer_send(
 		virtual_overseer,
-		CollatorProtocolMessage::Seconded(candidate.descriptor.relay_parent, stmt),
+		CollatorProtocolMessage::Seconded(candidate.descriptor.relay_parent(), stmt),
 	)
 	.await;
 }
@@ -374,7 +374,7 @@ fn v1_advertisement_accepted_and_seconded() {
 			hrmp_watermark: 0,
 		};
 		candidate.commitments_hash = commitments.hash();
-
+		let candidate: CandidateReceipt = candidate.into();
 		let pov = PoV { block_data: BlockData(vec![1]) };
 
 		response_channel
@@ -595,6 +595,7 @@ fn second_multiple_candidates_per_relay_parent() {
 				hrmp_watermark: 0,
 			};
 			candidate.commitments_hash = commitments.hash();
+			let candidate: CandidateReceipt = candidate.into();
 
 			let candidate_hash = candidate.hash();
 			let parent_head_data_hash = Hash::zero();
@@ -750,7 +751,7 @@ fn fetched_collation_sanity_check() {
 			hrmp_watermark: 0,
 		};
 		candidate.commitments_hash = commitments.hash();
-
+		let candidate: CandidateReceipt = candidate.into();
 		let candidate_hash = CandidateHash(Hash::zero());
 		let parent_head_data_hash = Hash::zero();
 
@@ -845,7 +846,6 @@ fn sanity_check_invalid_parent_head_data() {
 
 		let mut candidate = dummy_candidate_receipt_bad_sig(head_c, Some(Default::default()));
 		candidate.descriptor.para_id = test_state.chain_ids[0];
-
 		let commitments = CandidateCommitments {
 			head_data: HeadData(vec![1, 2, 3]),
 			horizontal_messages: Default::default(),
@@ -864,6 +864,7 @@ fn sanity_check_invalid_parent_head_data() {
 		pvd.parent_head = parent_head_data;
 
 		candidate.descriptor.persisted_validation_data_hash = pvd.hash();
+		let candidate: CandidateReceipt = candidate.into();
 
 		let candidate_hash = candidate.hash();
 
@@ -1068,6 +1069,7 @@ fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) {
 			processed_downward_messages: 0,
 			hrmp_watermark: 0,
 		};
+		let mut candidate_b: CandidateReceipt = candidate_b.into();
 		candidate_b.commitments_hash = candidate_b_commitments.hash();
 
 		let candidate_b_hash = candidate_b.hash();
@@ -1134,6 +1136,7 @@ fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) {
 				relay_parent_storage_root: Default::default(),
 			}
 			.hash();
+		let mut candidate_a: CandidateReceipt = candidate_a.into();
 		let candidate_a_commitments = CandidateCommitments {
 			head_data: HeadData(vec![1]),
 			horizontal_messages: Default::default(),
@@ -1144,6 +1147,7 @@ fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) {
 		};
 		candidate_a.commitments_hash = candidate_a_commitments.hash();
 
+		let candidate_a: CandidateReceipt = candidate_a.into();
 		let candidate_a_hash = candidate_a.hash();
 
 		advertise_collation(
@@ -1208,7 +1212,7 @@ fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) {
 				incoming_pov,
 			)) => {
 				assert_eq!(head_c, relay_parent);
-				assert_eq!(test_state.chain_ids[0], candidate_receipt.descriptor.para_id);
+				assert_eq!(test_state.chain_ids[0], candidate_receipt.descriptor.para_id());
 				assert_eq!(PoV { block_data: BlockData(vec![2]) }, incoming_pov);
 				assert_eq!(PersistedValidationData::<Hash, BlockNumber> {
 					parent_head: HeadData(vec![0]),
@@ -1261,7 +1265,7 @@ fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) {
 					incoming_pov,
 				)) => {
 					assert_eq!(head_c, relay_parent);
-					assert_eq!(test_state.chain_ids[0], candidate_receipt.descriptor.para_id);
+					assert_eq!(test_state.chain_ids[0], candidate_receipt.descriptor.para_id());
 					assert_eq!(PoV { block_data: BlockData(vec![1]) }, incoming_pov);
 					assert_eq!(PersistedValidationData::<Hash, BlockNumber> {
 						parent_head: HeadData(vec![1]),
diff --git a/polkadot/node/network/dispute-distribution/src/receiver/batches/batch.rs b/polkadot/node/network/dispute-distribution/src/receiver/batches/batch.rs
index 11380b7c072ee75b2a48e21bc20ed33abeee2365..c911b4bc4ae6ed97b1ad2b5c4e69963247abcc4a 100644
--- a/polkadot/node/network/dispute-distribution/src/receiver/batches/batch.rs
+++ b/polkadot/node/network/dispute-distribution/src/receiver/batches/batch.rs
@@ -22,7 +22,7 @@ use polkadot_node_network_protocol::{
 	PeerId,
 };
 use polkadot_node_primitives::SignedDisputeStatement;
-use polkadot_primitives::{CandidateReceipt, ValidatorIndex};
+use polkadot_primitives::{vstaging::CandidateReceiptV2 as CandidateReceipt, ValidatorIndex};
 
 use crate::receiver::{BATCH_COLLECTING_INTERVAL, MIN_KEEP_BATCH_ALIVE_VOTES};
 
diff --git a/polkadot/node/network/dispute-distribution/src/receiver/batches/mod.rs b/polkadot/node/network/dispute-distribution/src/receiver/batches/mod.rs
index 76c7683d1574ac3c1631e77b5afe6ce8a6113532..13b42aff1f308946dd8107a62abf0672ec78f52c 100644
--- a/polkadot/node/network/dispute-distribution/src/receiver/batches/mod.rs
+++ b/polkadot/node/network/dispute-distribution/src/receiver/batches/mod.rs
@@ -22,7 +22,7 @@ use std::{
 use futures::future::pending;
 
 use polkadot_node_network_protocol::request_response::DISPUTE_REQUEST_TIMEOUT;
-use polkadot_primitives::{CandidateHash, CandidateReceipt};
+use polkadot_primitives::{vstaging::CandidateReceiptV2 as CandidateReceipt, CandidateHash};
 
 use crate::{
 	receiver::batches::{batch::TickResult, waiting_queue::PendingWake},
diff --git a/polkadot/node/network/dispute-distribution/src/receiver/mod.rs b/polkadot/node/network/dispute-distribution/src/receiver/mod.rs
index 77c1e41aac050168e8e5b204a9ef1d1528c67014..b21965fc700442b4bf2323f3e416faede7abeb6a 100644
--- a/polkadot/node/network/dispute-distribution/src/receiver/mod.rs
+++ b/polkadot/node/network/dispute-distribution/src/receiver/mod.rs
@@ -334,7 +334,7 @@ where
 			.runtime
 			.get_session_info_by_index(
 				&mut self.sender,
-				payload.0.candidate_receipt.descriptor.relay_parent,
+				payload.0.candidate_receipt.descriptor.relay_parent(),
 				payload.0.session_index,
 			)
 			.await?;
diff --git a/polkadot/node/network/dispute-distribution/src/sender/send_task.rs b/polkadot/node/network/dispute-distribution/src/sender/send_task.rs
index 54ccd10789d0aec283748286bc4f69b6a9ae379a..f607c9431513d9cdbaf6bc07a65ccdfc8752bfd8 100644
--- a/polkadot/node/network/dispute-distribution/src/sender/send_task.rs
+++ b/polkadot/node/network/dispute-distribution/src/sender/send_task.rs
@@ -234,7 +234,7 @@ impl<M: 'static + Send + Sync> SendTask<M> {
 		runtime: &mut RuntimeInfo,
 		active_sessions: &HashMap<SessionIndex, Hash>,
 	) -> Result<HashSet<AuthorityDiscoveryId>> {
-		let ref_head = self.request.0.candidate_receipt.descriptor.relay_parent;
+		let ref_head = self.request.0.candidate_receipt.descriptor.relay_parent();
 		// Retrieve all authorities which participated in the parachain consensus of the session
 		// in which the candidate was backed.
 		let info = runtime
diff --git a/polkadot/node/network/dispute-distribution/src/tests/mock.rs b/polkadot/node/network/dispute-distribution/src/tests/mock.rs
index baa857e2eb6878a5342ef4f01b5d2aa8e3c8e777..52659ae9e0029b06ca4359253489f668afbf6325 100644
--- a/polkadot/node/network/dispute-distribution/src/tests/mock.rs
+++ b/polkadot/node/network/dispute-distribution/src/tests/mock.rs
@@ -33,10 +33,10 @@ use sp_keystore::{Keystore, KeystorePtr};
 
 use polkadot_node_primitives::{DisputeMessage, SignedDisputeStatement};
 use polkadot_primitives::{
-	AuthorityDiscoveryId, CandidateHash, CandidateReceipt, Hash, SessionIndex, SessionInfo,
-	ValidatorId, ValidatorIndex,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, AuthorityDiscoveryId, CandidateHash, Hash,
+	SessionIndex, SessionInfo, ValidatorId, ValidatorIndex,
 };
-use polkadot_primitives_test_helpers::dummy_candidate_descriptor;
+use polkadot_primitives_test_helpers::dummy_candidate_descriptor_v2;
 
 use crate::LOG_TARGET;
 
@@ -116,7 +116,7 @@ pub static MOCK_NEXT_SESSION_INFO: LazyLock<SessionInfo> = LazyLock::new(|| Sess
 
 pub fn make_candidate_receipt(relay_parent: Hash) -> CandidateReceipt {
 	CandidateReceipt {
-		descriptor: dummy_candidate_descriptor(relay_parent),
+		descriptor: dummy_candidate_descriptor_v2(relay_parent),
 		commitments_hash: Hash::random(),
 	}
 }
diff --git a/polkadot/node/network/dispute-distribution/src/tests/mod.rs b/polkadot/node/network/dispute-distribution/src/tests/mod.rs
index 60820e62ca2d6dbab4d8ba5defe037e12ed1a463..5306b22828cc584ca03eeaca3201d03ad0541c85 100644
--- a/polkadot/node/network/dispute-distribution/src/tests/mod.rs
+++ b/polkadot/node/network/dispute-distribution/src/tests/mod.rs
@@ -57,8 +57,8 @@ use polkadot_node_subsystem_test_helpers::{
 	subsystem_test_harness, TestSubsystemContextHandle,
 };
 use polkadot_primitives::{
-	AuthorityDiscoveryId, Block, CandidateHash, CandidateReceipt, ExecutorParams, Hash,
-	NodeFeatures, SessionIndex, SessionInfo,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, AuthorityDiscoveryId, Block, CandidateHash,
+	ExecutorParams, Hash, NodeFeatures, SessionIndex, SessionInfo,
 };
 
 use self::mock::{
diff --git a/polkadot/node/network/protocol/src/request_response/v1.rs b/polkadot/node/network/protocol/src/request_response/v1.rs
index 80721f1884afd04e7abd0634dc62b3357b2307b4..4f28d4cbf2d84c32a7e107c6dfaae0a267626886 100644
--- a/polkadot/node/network/protocol/src/request_response/v1.rs
+++ b/polkadot/node/network/protocol/src/request_response/v1.rs
@@ -22,8 +22,11 @@ use polkadot_node_primitives::{
 	AvailableData, DisputeMessage, ErasureChunk, PoV, Proof, UncheckedDisputeMessage,
 };
 use polkadot_primitives::{
-	CandidateHash, CandidateReceipt, CommittedCandidateReceipt, Hash, HeadData, Id as ParaId,
-	ValidatorIndex,
+	vstaging::{
+		CandidateReceiptV2 as CandidateReceipt,
+		CommittedCandidateReceiptV2 as CommittedCandidateReceipt,
+	},
+	CandidateHash, Hash, HeadData, Id as ParaId, ValidatorIndex,
 };
 
 use super::{IsRequest, Protocol};
diff --git a/polkadot/node/network/protocol/src/request_response/v2.rs b/polkadot/node/network/protocol/src/request_response/v2.rs
index ae65b39cd406e9f337a1e7d6a1d04468249c22c8..834870e5b9084d87cfaf19dbc214b64c1325425b 100644
--- a/polkadot/node/network/protocol/src/request_response/v2.rs
+++ b/polkadot/node/network/protocol/src/request_response/v2.rs
@@ -20,8 +20,8 @@ use codec::{Decode, Encode};
 
 use polkadot_node_primitives::ErasureChunk;
 use polkadot_primitives::{
-	CandidateHash, CommittedCandidateReceipt, Hash, Id as ParaId, PersistedValidationData,
-	UncheckedSignedStatement, ValidatorIndex,
+	vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CandidateHash, Hash,
+	Id as ParaId, PersistedValidationData, UncheckedSignedStatement, ValidatorIndex,
 };
 
 use super::{v1, IsRequest, Protocol};
diff --git a/polkadot/node/network/statement-distribution/Cargo.toml b/polkadot/node/network/statement-distribution/Cargo.toml
index 2a9773ddde4bd316d3133086131399829ffc5626..08059353033ddc8d0df65abac7f3c877d4f4b0ec 100644
--- a/polkadot/node/network/statement-distribution/Cargo.toml
+++ b/polkadot/node/network/statement-distribution/Cargo.toml
@@ -40,6 +40,7 @@ sp-tracing = { workspace = true, default-features = true }
 sc-keystore = { workspace = true, default-features = true }
 sc-network = { workspace = true, default-features = true }
 futures-timer = { workspace = true }
+polkadot-primitives = { workspace = true, features = ["test"] }
 polkadot-primitives-test-helpers = { workspace = true }
 rand_chacha = { workspace = true, default-features = true }
 polkadot-subsystem-bench = { workspace = true }
diff --git a/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs b/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs
index 8270b980919454a85ab323a03c40dd8d638e183d..bd6d4ebe755cde4d86be23a8cc24483741208fe9 100644
--- a/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs
+++ b/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs
@@ -37,9 +37,10 @@ use polkadot_node_subsystem::{
 	overseer, ActivatedLeaf, StatementDistributionSenderTrait,
 };
 use polkadot_primitives::{
-	AuthorityDiscoveryId, CandidateHash, CommittedCandidateReceipt, CompactStatement, Hash,
-	Id as ParaId, IndexedVec, OccupiedCoreAssumption, PersistedValidationData, SignedStatement,
-	SigningContext, UncheckedSignedStatement, ValidatorId, ValidatorIndex, ValidatorSignature,
+	vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, AuthorityDiscoveryId,
+	CandidateHash, CompactStatement, Hash, Id as ParaId, IndexedVec, OccupiedCoreAssumption,
+	PersistedValidationData, SignedStatement, SigningContext, UncheckedSignedStatement,
+	ValidatorId, ValidatorIndex, ValidatorSignature,
 };
 
 use futures::{
@@ -1641,7 +1642,7 @@ async fn handle_incoming_message<'a, Context>(
 	// In case of `Valid` we should have it cached prior, therefore this performs
 	// no Runtime API calls and always returns `Ok(Some(_))`.
 	let pvd = if let Statement::Seconded(receipt) = statement.payload() {
-		let para_id = receipt.descriptor.para_id;
+		let para_id = receipt.descriptor.para_id();
 		// Either call the Runtime API or check that validation data is cached.
 		let result = active_head
 			.fetch_persisted_validation_data(ctx.sender(), relay_parent, para_id)
diff --git a/polkadot/node/network/statement-distribution/src/legacy_v1/requester.rs b/polkadot/node/network/statement-distribution/src/legacy_v1/requester.rs
index c0346adfe101f566234ca3a37968c89f53d4d711..69bcbac76b70432984f3ca7d424ea8dc818bf682 100644
--- a/polkadot/node/network/statement-distribution/src/legacy_v1/requester.rs
+++ b/polkadot/node/network/statement-distribution/src/legacy_v1/requester.rs
@@ -29,7 +29,9 @@ use polkadot_node_network_protocol::{
 	PeerId, UnifiedReputationChange,
 };
 use polkadot_node_subsystem_util::TimeoutExt;
-use polkadot_primitives::{CandidateHash, CommittedCandidateReceipt, Hash};
+use polkadot_primitives::{
+	vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CandidateHash, Hash,
+};
 
 use crate::{
 	legacy_v1::{COST_WRONG_HASH, LOG_TARGET},
diff --git a/polkadot/node/network/statement-distribution/src/legacy_v1/responder.rs b/polkadot/node/network/statement-distribution/src/legacy_v1/responder.rs
index 8d1683759a0360871b149926b73b8a77ad5bec6a..03e1dc059989e8632ddbfba6d105718f83043938 100644
--- a/polkadot/node/network/statement-distribution/src/legacy_v1/responder.rs
+++ b/polkadot/node/network/statement-distribution/src/legacy_v1/responder.rs
@@ -29,7 +29,9 @@ use polkadot_node_network_protocol::{
 	},
 	PeerId, UnifiedReputationChange as Rep,
 };
-use polkadot_primitives::{CandidateHash, CommittedCandidateReceipt, Hash};
+use polkadot_primitives::{
+	vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CandidateHash, Hash,
+};
 
 use crate::LOG_TARGET;
 
diff --git a/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs b/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs
index 5e00fb96d74f0299dba296a6b141a2c275c3643d..d2fd016ec2f1ef6408590d8cf4b3368442d03499 100644
--- a/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs
+++ b/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs
@@ -47,7 +47,8 @@ use polkadot_primitives::{
 	SessionInfo, ValidationCode,
 };
 use polkadot_primitives_test_helpers::{
-	dummy_committed_candidate_receipt, dummy_hash, AlwaysZeroRng,
+	dummy_committed_candidate_receipt, dummy_committed_candidate_receipt_v2, dummy_hash,
+	AlwaysZeroRng,
 };
 use sc_keystore::LocalKeystore;
 use sc_network::ProtocolName;
@@ -140,7 +141,7 @@ fn active_head_accepts_only_2_seconded_per_validator() {
 	// note A
 	let a_seconded_val_0 = SignedFullStatement::sign(
 		&keystore,
-		Statement::Seconded(candidate_a.clone()),
+		Statement::Seconded(candidate_a.into()),
 		&signing_context,
 		ValidatorIndex(0),
 		&alice_public.into(),
@@ -167,7 +168,7 @@ fn active_head_accepts_only_2_seconded_per_validator() {
 	// note B
 	let statement = SignedFullStatement::sign(
 		&keystore,
-		Statement::Seconded(candidate_b.clone()),
+		Statement::Seconded(candidate_b.clone().into()),
 		&signing_context,
 		ValidatorIndex(0),
 		&alice_public.into(),
@@ -184,7 +185,7 @@ fn active_head_accepts_only_2_seconded_per_validator() {
 	// note C (beyond 2 - ignored)
 	let statement = SignedFullStatement::sign(
 		&keystore,
-		Statement::Seconded(candidate_c.clone()),
+		Statement::Seconded(candidate_c.clone().into()),
 		&signing_context,
 		ValidatorIndex(0),
 		&alice_public.into(),
@@ -202,7 +203,7 @@ fn active_head_accepts_only_2_seconded_per_validator() {
 	// note B (new validator)
 	let statement = SignedFullStatement::sign(
 		&keystore,
-		Statement::Seconded(candidate_b.clone()),
+		Statement::Seconded(candidate_b.into()),
 		&signing_context,
 		ValidatorIndex(1),
 		&bob_public.into(),
@@ -219,7 +220,7 @@ fn active_head_accepts_only_2_seconded_per_validator() {
 	// note C (new validator)
 	let statement = SignedFullStatement::sign(
 		&keystore,
-		Statement::Seconded(candidate_c.clone()),
+		Statement::Seconded(candidate_c.into()),
 		&signing_context,
 		ValidatorIndex(1),
 		&bob_public.into(),
@@ -470,7 +471,7 @@ fn peer_view_update_sends_messages() {
 
 		let statement = SignedFullStatement::sign(
 			&keystore,
-			Statement::Seconded(candidate.clone()),
+			Statement::Seconded(candidate.clone().into()),
 			&signing_context,
 			ValidatorIndex(0),
 			&alice_public.into(),
@@ -612,7 +613,7 @@ fn circulated_statement_goes_to_all_peers_with_view() {
 		let mut c = dummy_committed_candidate_receipt(dummy_hash());
 		c.descriptor.relay_parent = hash_b;
 		c.descriptor.para_id = ParaId::from(1_u32);
-		c
+		c.into()
 	};
 
 	let peer_a = PeerId::random();
@@ -746,7 +747,7 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
 		let mut c = dummy_committed_candidate_receipt(dummy_hash());
 		c.descriptor.relay_parent = hash_a;
 		c.descriptor.para_id = PARA_ID;
-		c
+		c.into()
 	};
 
 	let peer_a = PeerId::random();
@@ -1199,7 +1200,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
 
 			SignedFullStatement::sign(
 				&keystore,
-				Statement::Seconded(candidate.clone()),
+				Statement::Seconded(candidate.clone().into()),
 				&signing_context,
 				ValidatorIndex(0),
 				&alice_public.into(),
@@ -1337,7 +1338,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
 				let bad_candidate = {
 					let mut bad = candidate.clone();
 					bad.descriptor.para_id = 0xeadbeaf.into();
-					bad
+					bad.into()
 				};
 				let response = StatementFetchingResponse::Statement(bad_candidate);
 				outgoing.pending_response.send(Ok((response.encode(), ProtocolName::from("")))).unwrap();
@@ -1391,7 +1392,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
 				assert_eq!(req.candidate_hash, metadata.candidate_hash);
 				// On retry, we should have reverse order:
 				assert_eq!(outgoing.peer, Recipient::Peer(peer_c));
-				let response = StatementFetchingResponse::Statement(candidate.clone());
+				let response = StatementFetchingResponse::Statement(candidate.clone().into());
 				outgoing.pending_response.send(Ok((response.encode(), ProtocolName::from("")))).unwrap();
 			}
 		);
@@ -1517,7 +1518,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
 		req_cfg.inbound_queue.as_mut().unwrap().send(req).await.unwrap();
 		let StatementFetchingResponse::Statement(committed) =
 			Decode::decode(&mut response_rx.await.unwrap().result.unwrap().as_ref()).unwrap();
-		assert_eq!(committed, candidate);
+		assert_eq!(committed, candidate.into());
 
 		handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
 	};
@@ -1744,7 +1745,7 @@ fn delay_reputation_changes() {
 
 			SignedFullStatement::sign(
 				&keystore,
-				Statement::Seconded(candidate.clone()),
+				Statement::Seconded(candidate.clone().into()),
 				&signing_context,
 				ValidatorIndex(0),
 				&alice_public.into(),
@@ -1884,7 +1885,7 @@ fn delay_reputation_changes() {
 					bad.descriptor.para_id = 0xeadbeaf.into();
 					bad
 				};
-				let response = StatementFetchingResponse::Statement(bad_candidate);
+				let response = StatementFetchingResponse::Statement(bad_candidate.into());
 				outgoing.pending_response.send(Ok((response.encode(), ProtocolName::from("")))).unwrap();
 			}
 		);
@@ -1928,7 +1929,7 @@ fn delay_reputation_changes() {
 				assert_eq!(req.candidate_hash, metadata.candidate_hash);
 				// On retry, we should have reverse order:
 				assert_eq!(outgoing.peer, Recipient::Peer(peer_c));
-				let response = StatementFetchingResponse::Statement(candidate.clone());
+				let response = StatementFetchingResponse::Statement(candidate.clone().into());
 				outgoing.pending_response.send(Ok((response.encode(), ProtocolName::from("")))).unwrap();
 			}
 		);
@@ -2288,7 +2289,7 @@ fn share_prioritizes_backing_group() {
 
 			SignedFullStatementWithPVD::sign(
 				&keystore,
-				Statement::Seconded(candidate.clone()).supply_pvd(pvd),
+				Statement::Seconded(candidate.clone().into()).supply_pvd(pvd),
 				&signing_context,
 				ValidatorIndex(4),
 				&ferdie_public.into(),
@@ -2352,7 +2353,7 @@ fn share_prioritizes_backing_group() {
 		req_cfg.inbound_queue.as_mut().unwrap().send(req).await.unwrap();
 		let StatementFetchingResponse::Statement(committed) =
 			Decode::decode(&mut response_rx.await.unwrap().result.unwrap().as_ref()).unwrap();
-		assert_eq!(committed, candidate);
+		assert_eq!(committed, candidate.into());
 
 		handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
 	};
@@ -2514,7 +2515,7 @@ fn peer_cant_flood_with_large_statements() {
 
 			SignedFullStatement::sign(
 				&keystore,
-				Statement::Seconded(candidate.clone()),
+				Statement::Seconded(candidate.clone().into()),
 				&signing_context,
 				ValidatorIndex(0),
 				&alice_public.into(),
@@ -2595,7 +2596,7 @@ fn handle_multiple_seconded_statements() {
 	let relay_parent_hash = Hash::repeat_byte(1);
 	let pvd = dummy_pvd();
 
-	let candidate = dummy_committed_candidate_receipt(relay_parent_hash);
+	let candidate = dummy_committed_candidate_receipt_v2(relay_parent_hash);
 	let candidate_hash = candidate.hash();
 
 	// We want to ensure that our peers are not lucky
diff --git a/polkadot/node/network/statement-distribution/src/v2/candidates.rs b/polkadot/node/network/statement-distribution/src/v2/candidates.rs
index a4f2455c28401f536d449268591e2f9746a9db81..1a37d2ea086a9664067edc73cab247422565f14f 100644
--- a/polkadot/node/network/statement-distribution/src/v2/candidates.rs
+++ b/polkadot/node/network/statement-distribution/src/v2/candidates.rs
@@ -28,8 +28,8 @@
 use polkadot_node_network_protocol::PeerId;
 use polkadot_node_subsystem::messages::HypotheticalCandidate;
 use polkadot_primitives::{
-	CandidateHash, CommittedCandidateReceipt, GroupIndex, Hash, Id as ParaId,
-	PersistedValidationData,
+	vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CandidateHash, GroupIndex,
+	Hash, Id as ParaId, PersistedValidationData,
 };
 
 use std::{
@@ -154,8 +154,8 @@ impl Candidates {
 		assigned_group: GroupIndex,
 	) -> Option<PostConfirmation> {
 		let parent_hash = persisted_validation_data.parent_head.hash();
-		let relay_parent = candidate_receipt.descriptor().relay_parent;
-		let para_id = candidate_receipt.descriptor().para_id;
+		let relay_parent = candidate_receipt.descriptor.relay_parent();
+		let para_id = candidate_receipt.descriptor.para_id();
 
 		let prev_state = self.candidates.insert(
 			candidate_hash,
@@ -530,12 +530,12 @@ pub struct ConfirmedCandidate {
 impl ConfirmedCandidate {
 	/// Get the relay-parent of the candidate.
 	pub fn relay_parent(&self) -> Hash {
-		self.receipt.descriptor().relay_parent
+		self.receipt.descriptor.relay_parent()
 	}
 
 	/// Get the para-id of the candidate.
 	pub fn para_id(&self) -> ParaId {
-		self.receipt.descriptor().para_id
+		self.receipt.descriptor.para_id()
 	}
 
 	/// Get the underlying candidate receipt.
diff --git a/polkadot/node/network/statement-distribution/src/v2/mod.rs b/polkadot/node/network/statement-distribution/src/v2/mod.rs
index f9c2d0ddbae8b373485f90a510137bc7e42c9271..c79ae3953ad9fdeb9cd75074dfa8596f69a17f35 100644
--- a/polkadot/node/network/statement-distribution/src/v2/mod.rs
+++ b/polkadot/node/network/statement-distribution/src/v2/mod.rs
@@ -50,9 +50,9 @@ use polkadot_node_subsystem_util::{
 	},
 };
 use polkadot_primitives::{
-	AuthorityDiscoveryId, CandidateHash, CompactStatement, CoreIndex, CoreState, GroupIndex,
-	GroupRotationInfo, Hash, Id as ParaId, IndexedVec, SessionIndex, SessionInfo, SignedStatement,
-	SigningContext, UncheckedSignedStatement, ValidatorId, ValidatorIndex,
+	vstaging::CoreState, AuthorityDiscoveryId, CandidateHash, CompactStatement, CoreIndex,
+	GroupIndex, GroupRotationInfo, Hash, Id as ParaId, IndexedVec, SessionIndex, SessionInfo,
+	SignedStatement, SigningContext, UncheckedSignedStatement, ValidatorId, ValidatorIndex,
 };
 
 use sp_keystore::KeystorePtr;
@@ -1201,7 +1201,7 @@ pub(crate) async fn share_local_statement<Context>(
 	// have the candidate. Sanity: check the para-id is valid.
 	let expected = match statement.payload() {
 		FullStatementWithPVD::Seconded(ref c, _) =>
-			Some((c.descriptor().para_id, c.descriptor().relay_parent)),
+			Some((c.descriptor.para_id(), c.descriptor.relay_parent())),
 		FullStatementWithPVD::Valid(hash) =>
 			state.candidates.get_confirmed(&hash).map(|c| (c.para_id(), c.relay_parent())),
 	};
@@ -2277,13 +2277,13 @@ async fn fragment_chain_update_inner<Context>(
 		} = hypo
 		{
 			let confirmed_candidate = state.candidates.get_confirmed(&candidate_hash);
-			let prs = state.per_relay_parent.get_mut(&receipt.descriptor().relay_parent);
+			let prs = state.per_relay_parent.get_mut(&receipt.descriptor.relay_parent());
 			if let (Some(confirmed), Some(prs)) = (confirmed_candidate, prs) {
 				let per_session = state.per_session.get(&prs.session);
 				let group_index = confirmed.group_index();
 
 				// Sanity check if group_index is valid for this para at relay parent.
-				let Some(expected_groups) = prs.groups_per_para.get(&receipt.descriptor().para_id)
+				let Some(expected_groups) = prs.groups_per_para.get(&receipt.descriptor.para_id())
 				else {
 					continue
 				};
@@ -2296,7 +2296,7 @@ async fn fragment_chain_update_inner<Context>(
 						ctx,
 						candidate_hash,
 						confirmed.group_index(),
-						&receipt.descriptor().relay_parent,
+						&receipt.descriptor.relay_parent(),
 						prs,
 						confirmed,
 						per_session,
@@ -2888,7 +2888,7 @@ pub(crate) async fn handle_backed_candidate_message<Context>(
 		ctx,
 		state,
 		confirmed.para_id(),
-		confirmed.candidate_receipt().descriptor().para_head,
+		confirmed.candidate_receipt().descriptor.para_head(),
 	)
 	.await;
 }
diff --git a/polkadot/node/network/statement-distribution/src/v2/requests.rs b/polkadot/node/network/statement-distribution/src/v2/requests.rs
index b8ed34d26c8a5272273297203b82e9ce263a107c..74f29710956f58f758c94373ebb298b0e604bed7 100644
--- a/polkadot/node/network/statement-distribution/src/v2/requests.rs
+++ b/polkadot/node/network/statement-distribution/src/v2/requests.rs
@@ -47,9 +47,9 @@ use polkadot_node_network_protocol::{
 	PeerId, UnifiedReputationChange as Rep,
 };
 use polkadot_primitives::{
-	CandidateHash, CommittedCandidateReceipt, CompactStatement, GroupIndex, Hash, Id as ParaId,
-	PersistedValidationData, SessionIndex, SignedStatement, SigningContext, ValidatorId,
-	ValidatorIndex,
+	vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CandidateHash,
+	CompactStatement, GroupIndex, Hash, Id as ParaId, PersistedValidationData, SessionIndex,
+	SignedStatement, SigningContext, ValidatorId, ValidatorIndex,
 };
 
 use futures::{future::BoxFuture, prelude::*, stream::FuturesUnordered};
@@ -696,18 +696,18 @@ fn validate_complete_response(
 	// sanity-check candidate response.
 	// note: roughly ascending cost of operations
 	{
-		if response.candidate_receipt.descriptor.relay_parent != identifier.relay_parent {
+		if response.candidate_receipt.descriptor.relay_parent() != identifier.relay_parent {
 			return invalid_candidate_output()
 		}
 
-		if response.candidate_receipt.descriptor.persisted_validation_data_hash !=
+		if response.candidate_receipt.descriptor.persisted_validation_data_hash() !=
 			response.persisted_validation_data.hash()
 		{
 			return invalid_candidate_output()
 		}
 
 		if !allowed_para_lookup(
-			response.candidate_receipt.descriptor.para_id,
+			response.candidate_receipt.descriptor.para_id(),
 			identifier.group_index,
 		) {
 			return invalid_candidate_output()
@@ -1019,6 +1019,7 @@ mod tests {
 		candidate_receipt.descriptor.persisted_validation_data_hash =
 			persisted_validation_data.hash();
 		let candidate = candidate_receipt.hash();
+		let candidate_receipt: CommittedCandidateReceipt = candidate_receipt.into();
 		let requested_peer_1 = PeerId::random();
 		let requested_peer_2 = PeerId::random();
 
@@ -1074,7 +1075,7 @@ mod tests {
 					requested_peer: requested_peer_1,
 					props: request_properties.clone(),
 					response: Ok(AttestedCandidateResponse {
-						candidate_receipt: candidate_receipt.clone(),
+						candidate_receipt: candidate_receipt.clone().into(),
 						persisted_validation_data: persisted_validation_data.clone(),
 						statements,
 					}),
@@ -1114,7 +1115,7 @@ mod tests {
 					requested_peer: requested_peer_2,
 					props: request_properties,
 					response: Ok(AttestedCandidateResponse {
-						candidate_receipt: candidate_receipt.clone(),
+						candidate_receipt: candidate_receipt.clone().into(),
 						persisted_validation_data: persisted_validation_data.clone(),
 						statements,
 					}),
@@ -1197,7 +1198,7 @@ mod tests {
 					requested_peer,
 					props: request_properties,
 					response: Ok(AttestedCandidateResponse {
-						candidate_receipt: candidate_receipt.clone(),
+						candidate_receipt: candidate_receipt.clone().into(),
 						persisted_validation_data: persisted_validation_data.clone(),
 						statements,
 					}),
@@ -1236,6 +1237,7 @@ mod tests {
 		candidate_receipt.descriptor.persisted_validation_data_hash =
 			persisted_validation_data.hash();
 		let candidate = candidate_receipt.hash();
+		let candidate_receipt: CommittedCandidateReceipt = candidate_receipt.into();
 		let requested_peer = PeerId::random();
 
 		let identifier = request_manager
@@ -1417,7 +1419,7 @@ mod tests {
 					requested_peer: requested_peer_1,
 					props: request_properties.clone(),
 					response: Ok(AttestedCandidateResponse {
-						candidate_receipt: candidate_receipt_1.clone(),
+						candidate_receipt: candidate_receipt_1.clone().into(),
 						persisted_validation_data: persisted_validation_data_1.clone(),
 						statements,
 					}),
diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs b/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs
index 119dc832d13a197c7e76c95c898c5f49e21b299f..9f2c36ad1018e9d3c22129fc9e1a39a700590181 100644
--- a/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs
+++ b/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs
@@ -33,9 +33,9 @@ use polkadot_node_subsystem::messages::{
 use polkadot_node_subsystem_test_helpers as test_helpers;
 use polkadot_node_subsystem_util::TimeoutExt;
 use polkadot_primitives::{
-	AssignmentPair, AsyncBackingParams, Block, BlockNumber, CommittedCandidateReceipt, CoreState,
-	GroupRotationInfo, HeadData, Header, IndexedVec, PersistedValidationData, ScheduledCore,
-	SessionIndex, SessionInfo, ValidatorPair,
+	vstaging::{CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState},
+	AssignmentPair, AsyncBackingParams, Block, BlockNumber, GroupRotationInfo, HeadData, Header,
+	IndexedVec, PersistedValidationData, ScheduledCore, SessionIndex, SessionInfo, ValidatorPair,
 };
 use sc_keystore::LocalKeystore;
 use sc_network::ProtocolName;
diff --git a/polkadot/node/overseer/examples/minimal-example.rs b/polkadot/node/overseer/examples/minimal-example.rs
index 807e7405ff1b76e2c5208c26587a694fc71935d7..e1b2af733b47afc2ae2233d766a10a77ddca3896 100644
--- a/polkadot/node/overseer/examples/minimal-example.rs
+++ b/polkadot/node/overseer/examples/minimal-example.rs
@@ -31,7 +31,9 @@ use polkadot_overseer::{
 	gen::{FromOrchestra, SpawnedSubsystem},
 	HeadSupportsParachains, SubsystemError,
 };
-use polkadot_primitives::{CandidateReceipt, Hash, PersistedValidationData};
+use polkadot_primitives::{
+	vstaging::CandidateReceiptV2 as CandidateReceipt, Hash, PersistedValidationData,
+};
 use polkadot_primitives_test_helpers::{
 	dummy_candidate_descriptor, dummy_hash, dummy_validation_code,
 };
@@ -71,7 +73,7 @@ impl Subsystem1 {
 			let (tx, _) = oneshot::channel();
 
 			let candidate_receipt = CandidateReceipt {
-				descriptor: dummy_candidate_descriptor(dummy_hash()),
+				descriptor: dummy_candidate_descriptor(dummy_hash()).into(),
 				commitments_hash: Hash::zero(),
 			};
 
diff --git a/polkadot/node/overseer/src/tests.rs b/polkadot/node/overseer/src/tests.rs
index 46864a482e2a6d12ff45003dedc0638d5e38718a..c3c47335cd3e2d3b0d07506b4f5ffe334baf7bff 100644
--- a/polkadot/node/overseer/src/tests.rs
+++ b/polkadot/node/overseer/src/tests.rs
@@ -28,11 +28,12 @@ use polkadot_node_subsystem_types::messages::{
 	NetworkBridgeEvent, PvfExecKind, ReportPeerMessage, RuntimeApiRequest,
 };
 use polkadot_primitives::{
-	CandidateHash, CandidateReceipt, CollatorPair, Id as ParaId, InvalidDisputeStatementKind,
-	PersistedValidationData, SessionIndex, ValidDisputeStatementKind, ValidatorIndex,
+	vstaging::CandidateReceiptV2, CandidateHash, CollatorPair, Id as ParaId,
+	InvalidDisputeStatementKind, PersistedValidationData, SessionIndex, ValidDisputeStatementKind,
+	ValidatorIndex,
 };
 use polkadot_primitives_test_helpers::{
-	dummy_candidate_descriptor, dummy_candidate_receipt, dummy_hash, dummy_validation_code,
+	dummy_candidate_descriptor, dummy_candidate_receipt_v2, dummy_hash, dummy_validation_code,
 };
 
 use crate::{
@@ -98,8 +99,8 @@ where
 				let mut c: usize = 0;
 				loop {
 					if c < 10 {
-						let candidate_receipt = CandidateReceipt {
-							descriptor: dummy_candidate_descriptor(dummy_hash()),
+						let candidate_receipt = CandidateReceiptV2 {
+							descriptor: dummy_candidate_descriptor(dummy_hash()).into(),
 							commitments_hash: dummy_hash(),
 						};
 
@@ -799,8 +800,8 @@ where
 fn test_candidate_validation_msg() -> CandidateValidationMessage {
 	let (response_sender, _) = oneshot::channel();
 	let pov = Arc::new(PoV { block_data: BlockData(Vec::new()) });
-	let candidate_receipt = CandidateReceipt {
-		descriptor: dummy_candidate_descriptor(dummy_hash()),
+	let candidate_receipt = CandidateReceiptV2 {
+		descriptor: dummy_candidate_descriptor(dummy_hash()).into(),
 		commitments_hash: Hash::zero(),
 	};
 
@@ -859,7 +860,7 @@ fn test_statement_distribution_msg() -> StatementDistributionMessage {
 fn test_availability_recovery_msg() -> AvailabilityRecoveryMessage {
 	let (sender, _) = oneshot::channel();
 	AvailabilityRecoveryMessage::RecoverAvailableData(
-		dummy_candidate_receipt(dummy_hash()),
+		dummy_candidate_receipt_v2(dummy_hash()),
 		Default::default(),
 		None,
 		None,
@@ -918,7 +919,7 @@ fn test_dispute_coordinator_msg() -> DisputeCoordinatorMessage {
 
 fn test_dispute_distribution_msg() -> DisputeDistributionMessage {
 	let dummy_dispute_message = UncheckedDisputeMessage {
-		candidate_receipt: dummy_candidate_receipt(dummy_hash()),
+		candidate_receipt: dummy_candidate_receipt_v2(dummy_hash()),
 		session_index: 0,
 		invalid_vote: InvalidDisputeVote {
 			validator_index: ValidatorIndex(0),
diff --git a/polkadot/node/primitives/src/disputes/message.rs b/polkadot/node/primitives/src/disputes/message.rs
index f9dec073bf50112437b4526b4cf5b4109ab07433..d32ed4dadb6ee18308d8a929bdd566c8da3cd258 100644
--- a/polkadot/node/primitives/src/disputes/message.rs
+++ b/polkadot/node/primitives/src/disputes/message.rs
@@ -25,7 +25,8 @@ use codec::{Decode, Encode};
 
 use super::{InvalidDisputeVote, SignedDisputeStatement, ValidDisputeVote};
 use polkadot_primitives::{
-	CandidateReceipt, DisputeStatement, SessionIndex, SessionInfo, ValidatorIndex,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, DisputeStatement, SessionIndex, SessionInfo,
+	ValidatorIndex,
 };
 
 /// A dispute initiating/participating message that have been built from signed
diff --git a/polkadot/node/primitives/src/disputes/mod.rs b/polkadot/node/primitives/src/disputes/mod.rs
index 0f08b4733654a483431be44fce11d2100e0c263c..71e2f0b16be303e468c932576ad100ffb6833da4 100644
--- a/polkadot/node/primitives/src/disputes/mod.rs
+++ b/polkadot/node/primitives/src/disputes/mod.rs
@@ -25,9 +25,9 @@ use sp_application_crypto::AppCrypto;
 use sp_keystore::{Error as KeystoreError, KeystorePtr};
 
 use polkadot_primitives::{
-	CandidateHash, CandidateReceipt, CompactStatement, DisputeStatement, EncodeAs,
-	InvalidDisputeStatementKind, SessionIndex, SigningContext, UncheckedSigned,
-	ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature,
+	vstaging::CandidateReceiptV2 as CandidateReceipt, CandidateHash, CompactStatement,
+	DisputeStatement, EncodeAs, InvalidDisputeStatementKind, SessionIndex, SigningContext,
+	UncheckedSigned, ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature,
 };
 
 /// `DisputeMessage` and related types.
diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs
index a525b2bc97763d404ae81fe84fb811ee2d912539..e2e7aa92b11cfd112f268ac963ce6a902d417cfa 100644
--- a/polkadot/node/primitives/src/lib.rs
+++ b/polkadot/node/primitives/src/lib.rs
@@ -30,10 +30,10 @@ use futures::Future;
 use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
 
 use polkadot_primitives::{
-	BlakeTwo256, BlockNumber, CandidateCommitments, CandidateHash, ChunkIndex, CollatorPair,
-	CommittedCandidateReceipt, CompactStatement, CoreIndex, EncodeAs, Hash, HashT, HeadData,
-	Id as ParaId, PersistedValidationData, SessionIndex, Signed, UncheckedSigned, ValidationCode,
-	ValidationCodeHash, MAX_CODE_SIZE, MAX_POV_SIZE,
+	vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, BlakeTwo256, BlockNumber,
+	CandidateCommitments, CandidateHash, ChunkIndex, CollatorPair, CompactStatement, CoreIndex,
+	EncodeAs, Hash, HashT, HeadData, Id as ParaId, PersistedValidationData, SessionIndex, Signed,
+	UncheckedSigned, ValidationCode, ValidationCodeHash, MAX_CODE_SIZE, MAX_POV_SIZE,
 };
 pub use sp_consensus_babe::{
 	AllowedSlots as BabeAllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch,
diff --git a/polkadot/node/service/src/parachains_db/upgrade.rs b/polkadot/node/service/src/parachains_db/upgrade.rs
index 808acf04b4e704d4a5e5c91556d8aac1524cc545..52b010f0b5d0b1d47f97d9070f2d901a9dfb642c 100644
--- a/polkadot/node/service/src/parachains_db/upgrade.rs
+++ b/polkadot/node/service/src/parachains_db/upgrade.rs
@@ -463,7 +463,7 @@ mod tests {
 		v3::migration_helpers::{v1_to_latest_sanity_check, v2_fill_test_data},
 	};
 	use polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter;
-	use polkadot_primitives_test_helpers::dummy_candidate_receipt;
+	use polkadot_primitives_test_helpers::dummy_candidate_receipt_v2;
 
 	#[test]
 	fn test_paritydb_migrate_0_to_1() {
@@ -617,7 +617,7 @@ mod tests {
 			assert_eq!(db.num_columns(), super::columns::v3::NUM_COLUMNS as u32);
 			let db = DbAdapter::new(db, columns::v3::ORDERED_COL);
 			// Fill the approval voting column with test data.
-			v1_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt)
+			v1_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt_v2)
 				.unwrap()
 		};
 
@@ -648,7 +648,7 @@ mod tests {
 			assert_eq!(db.num_columns(), super::columns::v3::NUM_COLUMNS as u32);
 			let db = DbAdapter::new(db, columns::v3::ORDERED_COL);
 			// Fill the approval voting column with test data.
-			v2_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt)
+			v2_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt_v2)
 				.unwrap()
 		};
 
diff --git a/polkadot/node/subsystem-bench/Cargo.toml b/polkadot/node/subsystem-bench/Cargo.toml
index 293df9f6e6d5cdb370e33b4d36ab6c7b3c0ba647..8633818e775da0e4ea5213c3c5f6a436b535a664 100644
--- a/polkadot/node/subsystem-bench/Cargo.toml
+++ b/polkadot/node/subsystem-bench/Cargo.toml
@@ -28,7 +28,7 @@ polkadot-node-subsystem = { workspace = true, default-features = true }
 polkadot-node-subsystem-util = { workspace = true, default-features = true }
 polkadot-node-subsystem-types = { workspace = true, default-features = true }
 polkadot-node-primitives = { workspace = true, default-features = true }
-polkadot-primitives = { workspace = true, default-features = true }
+polkadot-primitives = { workspace = true, features = ["test"] }
 polkadot-node-network-protocol = { workspace = true, default-features = true }
 polkadot-availability-recovery = { features = ["subsystem-benchmarks"], workspace = true, default-features = true }
 polkadot-availability-distribution = { workspace = true, default-features = true }
diff --git a/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs b/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs
index a3a475ac6b983dbc7981120e24acc8196df46df1..24cd734c6ae58132ded5b166bf12544e652d3d03 100644
--- a/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs
+++ b/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs
@@ -29,8 +29,8 @@ use polkadot_node_subsystem_types::messages::{
 };
 use polkadot_overseer::AllMessages;
 use polkadot_primitives::{
-	BlockNumber, CandidateEvent, CandidateReceipt, CoreIndex, GroupIndex, Hash, Header,
-	Id as ParaId, Slot, ValidatorIndex,
+	vstaging::{CandidateEvent, CandidateReceiptV2 as CandidateReceipt},
+	BlockNumber, CoreIndex, GroupIndex, Hash, Header, Id as ParaId, Slot, ValidatorIndex,
 };
 use polkadot_primitives_test_helpers::dummy_candidate_receipt_bad_sig;
 use rand::{seq::SliceRandom, SeedableRng};
@@ -189,7 +189,7 @@ pub fn make_header(parent_hash: Hash, slot: Slot, number: u32) -> Header {
 fn make_candidate(para_id: ParaId, hash: &Hash) -> CandidateReceipt {
 	let mut r = dummy_candidate_receipt_bad_sig(*hash, Some(Default::default()));
 	r.descriptor.para_id = para_id;
-	r
+	r.into()
 }
 
 /// Helper function to create a list of candidates that are included in the block
diff --git a/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs b/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs
index 807afb0438c99c7277f264ccd2b7ced1231aea33..79de6e72fc882f33418c3931a5b0e89b4798ecfa 100644
--- a/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs
+++ b/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs
@@ -40,8 +40,8 @@ use polkadot_node_primitives::approval::{
 	v2::{CoreBitfield, IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2},
 };
 use polkadot_primitives::{
-	ApprovalVoteMultipleCandidates, CandidateEvent, CandidateHash, CandidateIndex, CoreIndex, Hash,
-	SessionInfo, Slot, ValidatorId, ValidatorIndex, ASSIGNMENT_KEY_TYPE_ID,
+	vstaging::CandidateEvent, ApprovalVoteMultipleCandidates, CandidateHash, CandidateIndex,
+	CoreIndex, Hash, SessionInfo, Slot, ValidatorId, ValidatorIndex, ASSIGNMENT_KEY_TYPE_ID,
 };
 use rand::{seq::SliceRandom, RngCore, SeedableRng};
 use rand_chacha::ChaCha20Rng;
diff --git a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs
index 29ebc4a419aee60e0d715c11dfddef06af8caa00..1b20960a3f8a604309ebc2cf22b6ff8f1c55c8d9 100644
--- a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs
+++ b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs
@@ -66,8 +66,9 @@ use polkadot_node_subsystem::{
 use polkadot_node_subsystem_test_helpers::mock::new_block_import_info;
 use polkadot_overseer::Handle as OverseerHandleReal;
 use polkadot_primitives::{
-	BlockNumber, CandidateEvent, CandidateIndex, CandidateReceipt, Hash, Header, Slot, ValidatorId,
-	ValidatorIndex, ASSIGNMENT_KEY_TYPE_ID,
+	vstaging::{CandidateEvent, CandidateReceiptV2 as CandidateReceipt},
+	BlockNumber, CandidateIndex, Hash, Header, Slot, ValidatorId, ValidatorIndex,
+	ASSIGNMENT_KEY_TYPE_ID,
 };
 use prometheus::Registry;
 use sc_keystore::LocalKeystore;
diff --git a/polkadot/node/subsystem-bench/src/lib/availability/mod.rs b/polkadot/node/subsystem-bench/src/lib/availability/mod.rs
index a99f013195fa87a9be3bd439d4e80e57f5c4ba54..8ac9796acb62ceb5605388e926df9b49c3813cc5 100644
--- a/polkadot/node/subsystem-bench/src/lib/availability/mod.rs
+++ b/polkadot/node/subsystem-bench/src/lib/availability/mod.rs
@@ -391,7 +391,7 @@ pub async fn benchmark_availability_write(
 				candidate_hash: backed_candidate.hash(),
 				n_validators: config.n_validators as u32,
 				available_data,
-				expected_erasure_root: backed_candidate.descriptor().erasure_root,
+				expected_erasure_root: backed_candidate.descriptor().erasure_root(),
 				tx,
 				core_index: CoreIndex(core_index as u32),
 				node_features: node_features_with_chunk_mapping_enabled(),
diff --git a/polkadot/node/subsystem-bench/src/lib/availability/test_state.rs b/polkadot/node/subsystem-bench/src/lib/availability/test_state.rs
index 173b23f6b76e41b925eea27133cf726d07a0f67c..511795970e6cf4e4e7a829c5696e0f88a7dfc325 100644
--- a/polkadot/node/subsystem-bench/src/lib/availability/test_state.rs
+++ b/polkadot/node/subsystem-bench/src/lib/availability/test_state.rs
@@ -34,8 +34,9 @@ use polkadot_node_subsystem_test_helpers::{
 use polkadot_node_subsystem_util::availability_chunks::availability_chunk_indices;
 use polkadot_overseer::BlockInfo;
 use polkadot_primitives::{
-	AvailabilityBitfield, BlockNumber, CandidateHash, CandidateReceipt, ChunkIndex, CoreIndex,
-	Hash, HeadData, Header, PersistedValidationData, Signed, SigningContext, ValidatorIndex,
+	vstaging::{CandidateReceiptV2 as CandidateReceipt, MutateDescriptorV2},
+	AvailabilityBitfield, BlockNumber, CandidateHash, ChunkIndex, CoreIndex, Hash, HeadData,
+	Header, PersistedValidationData, Signed, SigningContext, ValidatorIndex,
 };
 use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_hash};
 use sp_core::H256;
@@ -148,7 +149,10 @@ impl TestState {
 			test_state.chunks.push(new_chunks);
 			test_state.available_data.push(new_available_data);
 			test_state.pov_size_to_candidate.insert(pov_size, index);
-			test_state.candidate_receipt_templates.push(candidate_receipt);
+			test_state.candidate_receipt_templates.push(CandidateReceipt {
+				descriptor: candidate_receipt.descriptor.into(),
+				commitments_hash: candidate_receipt.commitments_hash,
+			});
 		}
 
 		test_state.block_infos = (1..=config.num_blocks)
@@ -189,7 +193,9 @@ impl TestState {
 					test_state.candidate_receipt_templates[candidate_index].clone();
 
 				// Make it unique.
-				candidate_receipt.descriptor.relay_parent = Hash::from_low_u64_be(index as u64);
+				candidate_receipt
+					.descriptor
+					.set_relay_parent(Hash::from_low_u64_be(index as u64));
 				// Store the new candidate in the state
 				test_state.candidate_hashes.insert(candidate_receipt.hash(), candidate_index);
 
diff --git a/polkadot/node/subsystem-bench/src/lib/mock/runtime_api.rs b/polkadot/node/subsystem-bench/src/lib/mock/runtime_api.rs
index 61523de1f1b502600d4e8208021e7a3ddf345e3c..6c54d14448a1f0d476774a85929dbf135e9ddd9e 100644
--- a/polkadot/node/subsystem-bench/src/lib/mock/runtime_api.rs
+++ b/polkadot/node/subsystem-bench/src/lib/mock/runtime_api.rs
@@ -26,9 +26,10 @@ use polkadot_node_subsystem::{
 };
 use polkadot_node_subsystem_types::OverseerSignal;
 use polkadot_primitives::{
-	node_features, ApprovalVotingParams, AsyncBackingParams, CandidateEvent, CandidateReceipt,
-	CoreState, GroupIndex, GroupRotationInfo, IndexedVec, NodeFeatures, OccupiedCore,
-	ScheduledCore, SessionIndex, SessionInfo, ValidationCode, ValidatorIndex,
+	node_features,
+	vstaging::{CandidateEvent, CandidateReceiptV2 as CandidateReceipt, CoreState, OccupiedCore},
+	ApprovalVotingParams, AsyncBackingParams, GroupIndex, GroupRotationInfo, IndexedVec,
+	NodeFeatures, ScheduledCore, SessionIndex, SessionInfo, ValidationCode, ValidatorIndex,
 };
 use sp_consensus_babe::Epoch as BabeEpoch;
 use sp_core::H256;
diff --git a/polkadot/node/subsystem-bench/src/lib/statement/test_state.rs b/polkadot/node/subsystem-bench/src/lib/statement/test_state.rs
index 88b5e8b76b6252dfb470c38d344b60246008fd97..2d2e9434b767a00b0a71b47eee7dbf5e0fcc252b 100644
--- a/polkadot/node/subsystem-bench/src/lib/statement/test_state.rs
+++ b/polkadot/node/subsystem-bench/src/lib/statement/test_state.rs
@@ -41,12 +41,15 @@ use polkadot_node_subsystem_test_helpers::{
 };
 use polkadot_overseer::BlockInfo;
 use polkadot_primitives::{
-	BlockNumber, CandidateHash, CandidateReceipt, CommittedCandidateReceipt, CompactStatement,
-	Hash, Header, Id, PersistedValidationData, SessionInfo, SignedStatement, SigningContext,
-	UncheckedSigned, ValidatorIndex, ValidatorPair,
+	vstaging::{
+		CandidateReceiptV2 as CandidateReceipt,
+		CommittedCandidateReceiptV2 as CommittedCandidateReceipt, MutateDescriptorV2,
+	},
+	BlockNumber, CandidateHash, CompactStatement, Hash, Header, Id, PersistedValidationData,
+	SessionInfo, SignedStatement, SigningContext, UncheckedSigned, ValidatorIndex, ValidatorPair,
 };
 use polkadot_primitives_test_helpers::{
-	dummy_committed_candidate_receipt, dummy_hash, dummy_head_data, dummy_pvd,
+	dummy_committed_candidate_receipt_v2, dummy_hash, dummy_head_data, dummy_pvd,
 };
 use sc_network::{config::IncomingRequest, ProtocolName};
 use sp_core::{Pair, H256};
@@ -125,8 +128,8 @@ impl TestState {
 				let candidate_index =
 					*pov_size_to_candidate.get(pov_size).expect("pov_size always exists; qed");
 				let mut receipt = receipt_templates[candidate_index].clone();
-				receipt.descriptor.para_id = Id::new(core_idx as u32 + 1);
-				receipt.descriptor.relay_parent = block_info.hash;
+				receipt.descriptor.set_para_id(Id::new(core_idx as u32 + 1));
+				receipt.descriptor.set_relay_parent(block_info.hash);
 
 				state.candidate_receipts.entry(block_info.hash).or_default().push(
 					CandidateReceipt {
@@ -240,7 +243,7 @@ fn generate_receipt_templates(
 	pov_size_to_candidate
 		.iter()
 		.map(|(&pov_size, &index)| {
-			let mut receipt = dummy_committed_candidate_receipt(dummy_hash());
+			let mut receipt = dummy_committed_candidate_receipt_v2(dummy_hash());
 			let (_, erasure_root) = derive_erasure_chunks_with_proofs_and_root(
 				n_validators,
 				&AvailableData {
@@ -249,8 +252,8 @@ fn generate_receipt_templates(
 				},
 				|_, _| {},
 			);
-			receipt.descriptor.persisted_validation_data_hash = pvd.hash();
-			receipt.descriptor.erasure_root = erasure_root;
+			receipt.descriptor.set_persisted_validation_data_hash(pvd.hash());
+			receipt.descriptor.set_erasure_root(erasure_root);
 			receipt
 		})
 		.collect()
diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs
index 0017adb4556818aaf887c7a3a8cca191e870b372..ba1ba5755be0fe53de9e07b287c84e182968bbc8 100644
--- a/polkadot/node/subsystem-types/src/messages.rs
+++ b/polkadot/node/subsystem-types/src/messages.rs
@@ -43,15 +43,18 @@ use polkadot_node_primitives::{
 	ValidationResult,
 };
 use polkadot_primitives::{
-	async_backing, slashing, ApprovalVotingParams, AuthorityDiscoveryId, BackedCandidate,
-	BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, CandidateIndex,
-	CandidateReceipt, CollatorId, CommittedCandidateReceipt, CoreIndex, CoreState, DisputeState,
-	ExecutorParams, GroupIndex, GroupRotationInfo, Hash, HeadData, Header as BlockHeader,
-	Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, MultiDisputeStatementSet,
-	NodeFeatures, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement,
-	PvfExecKind as RuntimePvfExecKind, SessionIndex, SessionInfo, SignedAvailabilityBitfield,
-	SignedAvailabilityBitfields, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex,
-	ValidatorSignature,
+	async_backing, slashing, vstaging,
+	vstaging::{
+		BackedCandidate, CandidateReceiptV2 as CandidateReceipt,
+		CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState,
+	},
+	ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash,
+	CandidateIndex, CollatorId, CoreIndex, DisputeState, ExecutorParams, GroupIndex,
+	GroupRotationInfo, Hash, HeadData, Header as BlockHeader, Id as ParaId, InboundDownwardMessage,
+	InboundHrmpMessage, MultiDisputeStatementSet, NodeFeatures, OccupiedCoreAssumption,
+	PersistedValidationData, PvfCheckStatement, PvfExecKind as RuntimePvfExecKind, SessionIndex,
+	SessionInfo, SignedAvailabilityBitfield, SignedAvailabilityBitfields, ValidationCode,
+	ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature,
 };
 use polkadot_statement_table::v2::Misbehavior;
 use std::{
@@ -708,7 +711,7 @@ pub enum RuntimeApiRequest {
 	CandidatePendingAvailability(ParaId, RuntimeApiSender<Option<CommittedCandidateReceipt>>),
 	/// Get all events concerning candidates (backing, inclusion, time-out) in the parent of
 	/// the block in whose state this request is executed.
-	CandidateEvents(RuntimeApiSender<Vec<CandidateEvent>>),
+	CandidateEvents(RuntimeApiSender<Vec<vstaging::CandidateEvent>>),
 	/// Get the execution environment parameter set by session index
 	SessionExecutorParams(SessionIndex, RuntimeApiSender<Option<ExecutorParams>>),
 	/// Get the session info for the given session, if stored.
@@ -724,7 +727,7 @@ pub enum RuntimeApiRequest {
 	/// Get information about the BABE epoch the block was included in.
 	CurrentBabeEpoch(RuntimeApiSender<BabeEpoch>),
 	/// Get all disputes in relation to a relay parent.
-	FetchOnChainVotes(RuntimeApiSender<Option<polkadot_primitives::ScrapedOnChainVotes>>),
+	FetchOnChainVotes(RuntimeApiSender<Option<polkadot_primitives::vstaging::ScrapedOnChainVotes>>),
 	/// Submits a PVF pre-checking statement into the transaction pool.
 	SubmitPvfCheckStatement(PvfCheckStatement, ValidatorSignature, RuntimeApiSender<()>),
 	/// Returns code hashes of PVFs that require pre-checking by validators in the active set.
@@ -759,7 +762,7 @@ pub enum RuntimeApiRequest {
 	/// Returns all disabled validators at a given block height.
 	DisabledValidators(RuntimeApiSender<Vec<ValidatorIndex>>),
 	/// Get the backing state of the given para.
-	ParaBackingState(ParaId, RuntimeApiSender<Option<async_backing::BackingState>>),
+	ParaBackingState(ParaId, RuntimeApiSender<Option<vstaging::async_backing::BackingState>>),
 	/// Get candidate's acceptance limitations for asynchronous backing for a relay parent.
 	///
 	/// If it's not supported by the Runtime, the async backing is said to be disabled.
@@ -1256,7 +1259,7 @@ impl HypotheticalCandidate {
 	/// Get the `ParaId` of the hypothetical candidate.
 	pub fn candidate_para(&self) -> ParaId {
 		match *self {
-			HypotheticalCandidate::Complete { ref receipt, .. } => receipt.descriptor().para_id,
+			HypotheticalCandidate::Complete { ref receipt, .. } => receipt.descriptor.para_id(),
 			HypotheticalCandidate::Incomplete { candidate_para, .. } => candidate_para,
 		}
 	}
@@ -1275,7 +1278,7 @@ impl HypotheticalCandidate {
 	pub fn relay_parent(&self) -> Hash {
 		match *self {
 			HypotheticalCandidate::Complete { ref receipt, .. } =>
-				receipt.descriptor().relay_parent,
+				receipt.descriptor.relay_parent(),
 			HypotheticalCandidate::Incomplete { candidate_relay_parent, .. } =>
 				candidate_relay_parent,
 		}
@@ -1285,7 +1288,7 @@ impl HypotheticalCandidate {
 	pub fn output_head_data_hash(&self) -> Option<Hash> {
 		match *self {
 			HypotheticalCandidate::Complete { ref receipt, .. } =>
-				Some(receipt.descriptor.para_head),
+				Some(receipt.descriptor.para_head()),
 			HypotheticalCandidate::Incomplete { .. } => None,
 		}
 	}
@@ -1308,10 +1311,10 @@ impl HypotheticalCandidate {
 	}
 
 	/// Get the validation code hash, if the candidate is complete.
-	pub fn validation_code_hash(&self) -> Option<&ValidationCodeHash> {
+	pub fn validation_code_hash(&self) -> Option<ValidationCodeHash> {
 		match *self {
 			HypotheticalCandidate::Complete { ref receipt, .. } =>
-				Some(&receipt.descriptor.validation_code_hash),
+				Some(receipt.descriptor.validation_code_hash()),
 			HypotheticalCandidate::Incomplete { .. } => None,
 		}
 	}
diff --git a/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs
index a8af8b7996f9fb5a7d362c4a592c007cd86e29d3..4b96009f44bf89ca2365f82f6303c7e48e1aacdb 100644
--- a/polkadot/node/subsystem-types/src/runtime_client.rs
+++ b/polkadot/node/subsystem-types/src/runtime_client.rs
@@ -16,12 +16,18 @@
 
 use async_trait::async_trait;
 use polkadot_primitives::{
-	async_backing, runtime_api::ParachainHost, slashing, ApprovalVotingParams, Block, BlockNumber,
-	CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreIndex,
-	CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Header, Id,
-	InboundDownwardMessage, InboundHrmpMessage, NodeFeatures, OccupiedCoreAssumption,
-	PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo,
-	ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature,
+	async_backing,
+	runtime_api::ParachainHost,
+	slashing, vstaging,
+	vstaging::{
+		CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState,
+		ScrapedOnChainVotes,
+	},
+	ApprovalVotingParams, Block, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex,
+	DisputeState, ExecutorParams, GroupRotationInfo, Hash, Header, Id, InboundDownwardMessage,
+	InboundHrmpMessage, NodeFeatures, OccupiedCoreAssumption, PersistedValidationData,
+	PvfCheckStatement, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash, ValidatorId,
+	ValidatorIndex, ValidatorSignature,
 };
 use sc_client_api::{AuxStore, HeaderBackend};
 use sc_transaction_pool_api::OffchainTransactionPoolFactory;
@@ -311,7 +317,7 @@ pub trait RuntimeApiSubsystemClient {
 		&self,
 		at: Hash,
 		para_id: Id,
-	) -> Result<Option<async_backing::BackingState>, ApiError>;
+	) -> Result<Option<vstaging::async_backing::BackingState>, ApiError>;
 
 	// === v8 ===
 
@@ -380,10 +386,7 @@ where
 		&self,
 		at: Hash,
 	) -> Result<Vec<CoreState<Hash, BlockNumber>>, ApiError> {
-		self.client
-			.runtime_api()
-			.availability_cores(at)
-			.map(|cores| cores.into_iter().map(|core| core.into()).collect::<Vec<_>>())
+		self.client.runtime_api().availability_cores(at)
 	}
 
 	async fn persisted_validation_data(
@@ -436,10 +439,7 @@ where
 		at: Hash,
 		para_id: Id,
 	) -> Result<Option<CommittedCandidateReceipt<Hash>>, ApiError> {
-		self.client
-			.runtime_api()
-			.candidate_pending_availability(at, para_id)
-			.map(|maybe_candidate| maybe_candidate.map(|candidate| candidate.into()))
+		self.client.runtime_api().candidate_pending_availability(at, para_id)
 	}
 
 	async fn candidates_pending_availability(
@@ -447,19 +447,11 @@ where
 		at: Hash,
 		para_id: Id,
 	) -> Result<Vec<CommittedCandidateReceipt<Hash>>, ApiError> {
-		self.client
-			.runtime_api()
-			.candidates_pending_availability(at, para_id)
-			.map(|candidates| {
-				candidates.into_iter().map(|candidate| candidate.into()).collect::<Vec<_>>()
-			})
+		self.client.runtime_api().candidates_pending_availability(at, para_id)
 	}
 
 	async fn candidate_events(&self, at: Hash) -> Result<Vec<CandidateEvent<Hash>>, ApiError> {
-		self.client
-			.runtime_api()
-			.candidate_events(at)
-			.map(|events| events.into_iter().map(|event| event.into()).collect::<Vec<_>>())
+		self.client.runtime_api().candidate_events(at)
 	}
 
 	async fn dmq_contents(
@@ -490,10 +482,7 @@ where
 		&self,
 		at: Hash,
 	) -> Result<Option<ScrapedOnChainVotes<Hash>>, ApiError> {
-		self.client
-			.runtime_api()
-			.on_chain_votes(at)
-			.map(|maybe_votes| maybe_votes.map(|votes| votes.into()))
+		self.client.runtime_api().on_chain_votes(at)
 	}
 
 	async fn session_executor_params(
@@ -604,13 +593,8 @@ where
 		&self,
 		at: Hash,
 		para_id: Id,
-	) -> Result<Option<async_backing::BackingState>, ApiError> {
-		self.client
-			.runtime_api()
-			.para_backing_state(at, para_id)
-			.map(|maybe_backing_state| {
-				maybe_backing_state.map(|backing_state| backing_state.into())
-			})
+	) -> Result<Option<vstaging::async_backing::BackingState>, ApiError> {
+		self.client.runtime_api().para_backing_state(at, para_id)
 	}
 
 	async fn async_backing_params(
diff --git a/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs b/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs
index 0c3b40743495c0d219b79a1a3e19f915a1c59fed..a2a6095b765cd89c84befe67876f978cc0ed890c 100644
--- a/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs
+++ b/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs
@@ -770,7 +770,7 @@ pub trait HypotheticalOrConcreteCandidate {
 	/// Return a reference to the persisted validation data, if present.
 	fn persisted_validation_data(&self) -> Option<&PersistedValidationData>;
 	/// Return a reference to the validation code hash, if present.
-	fn validation_code_hash(&self) -> Option<&ValidationCodeHash>;
+	fn validation_code_hash(&self) -> Option<ValidationCodeHash>;
 	/// Return the parent head hash.
 	fn parent_head_data_hash(&self) -> Hash;
 	/// Return the output head hash, if present.
@@ -790,7 +790,7 @@ impl HypotheticalOrConcreteCandidate for HypotheticalCandidate {
 		self.persisted_validation_data()
 	}
 
-	fn validation_code_hash(&self) -> Option<&ValidationCodeHash> {
+	fn validation_code_hash(&self) -> Option<ValidationCodeHash> {
 		self.validation_code_hash()
 	}
 
diff --git a/polkadot/node/subsystem-util/src/lib.rs b/polkadot/node/subsystem-util/src/lib.rs
index 4bab4e80fe509dd9b0d15189773265ac374ccca8..3bed18558941951b0a7bab79b8979cb703b7fd09 100644
--- a/polkadot/node/subsystem-util/src/lib.rs
+++ b/polkadot/node/subsystem-util/src/lib.rs
@@ -41,12 +41,15 @@ use codec::Encode;
 use futures::channel::{mpsc, oneshot};
 
 use polkadot_primitives::{
-	async_backing::BackingState, slashing, AsyncBackingParams, AuthorityDiscoveryId,
-	CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreIndex, CoreState, EncodeAs,
-	ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption,
-	PersistedValidationData, ScrapedOnChainVotes, SessionIndex, SessionInfo, Signed,
-	SigningContext, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex,
-	ValidatorSignature,
+	slashing,
+	vstaging::{
+		async_backing::BackingState, CandidateEvent,
+		CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes,
+	},
+	AsyncBackingParams, AuthorityDiscoveryId, CandidateHash, CoreIndex, EncodeAs, ExecutorParams,
+	GroupIndex, GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption,
+	PersistedValidationData, SessionIndex, SessionInfo, Signed, SigningContext, ValidationCode,
+	ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature,
 };
 pub use rand;
 use runtime::get_disabled_validators_with_fallback;
diff --git a/polkadot/node/subsystem-util/src/runtime/mod.rs b/polkadot/node/subsystem-util/src/runtime/mod.rs
index 2f9d3ed7b4f40cb27e9061bb6fadd4749a4f3173..d84951ae1366518c5a7037dcbe227c9ec574ea66 100644
--- a/polkadot/node/subsystem-util/src/runtime/mod.rs
+++ b/polkadot/node/subsystem-util/src/runtime/mod.rs
@@ -30,11 +30,13 @@ use polkadot_node_subsystem::{
 };
 use polkadot_node_subsystem_types::UnpinHandle;
 use polkadot_primitives::{
-	node_features::FeatureIndex, slashing, AsyncBackingParams, CandidateEvent, CandidateHash,
-	CoreIndex, CoreState, EncodeAs, ExecutorParams, GroupIndex, GroupRotationInfo, Hash,
-	Id as ParaId, IndexedVec, NodeFeatures, OccupiedCore, ScrapedOnChainVotes, SessionIndex,
-	SessionInfo, Signed, SigningContext, UncheckedSigned, ValidationCode, ValidationCodeHash,
-	ValidatorId, ValidatorIndex, LEGACY_MIN_BACKING_VOTES,
+	node_features::FeatureIndex,
+	slashing,
+	vstaging::{CandidateEvent, CoreState, OccupiedCore, ScrapedOnChainVotes},
+	AsyncBackingParams, CandidateHash, CoreIndex, EncodeAs, ExecutorParams, GroupIndex,
+	GroupRotationInfo, Hash, Id as ParaId, IndexedVec, NodeFeatures, SessionIndex, SessionInfo,
+	Signed, SigningContext, UncheckedSigned, ValidationCode, ValidationCodeHash, ValidatorId,
+	ValidatorIndex, LEGACY_MIN_BACKING_VOTES,
 };
 
 use std::collections::{BTreeMap, VecDeque};
diff --git a/polkadot/parachain/test-parachains/adder/collator/src/lib.rs b/polkadot/parachain/test-parachains/adder/collator/src/lib.rs
index daeb8bc915dd613bd83d532406e2c9bfa92af2b0..a2fb623331a00fb2b6c0eac587ef3f640d5c8033 100644
--- a/polkadot/parachain/test-parachains/adder/collator/src/lib.rs
+++ b/polkadot/parachain/test-parachains/adder/collator/src/lib.rs
@@ -236,7 +236,7 @@ impl Collator {
 					if let Ok(res) = recv.await {
 						if !matches!(
 							res.statement.payload(),
-							Statement::Seconded(s) if s.descriptor.pov_hash == compressed_pov.hash(),
+							Statement::Seconded(s) if s.descriptor.pov_hash() == compressed_pov.hash(),
 						) {
 							log::error!(
 								"Seconded statement should match our collation: {:?}",
diff --git a/polkadot/parachain/test-parachains/undying/collator/src/lib.rs b/polkadot/parachain/test-parachains/undying/collator/src/lib.rs
index 920099f4499d4467c1df6e6c6f482d489a0f8d26..448c181ae062bbaac7dc1b8312d50d26f1643ea9 100644
--- a/polkadot/parachain/test-parachains/undying/collator/src/lib.rs
+++ b/polkadot/parachain/test-parachains/undying/collator/src/lib.rs
@@ -282,7 +282,7 @@ impl Collator {
 					if let Ok(res) = recv.await {
 						if !matches!(
 							res.statement.payload(),
-							Statement::Seconded(s) if s.descriptor.pov_hash == compressed_pov.hash(),
+							Statement::Seconded(s) if s.descriptor.pov_hash() == compressed_pov.hash(),
 						) {
 							log::error!(
 								"Seconded statement should match our collation: {:?}",
diff --git a/polkadot/primitives/src/v8/mod.rs b/polkadot/primitives/src/v8/mod.rs
index cca327df42c9d0d37a8698b91931e3003ee5535a..fdcb9fe8fb7e344f1574ff156cd2fe4462bf39ef 100644
--- a/polkadot/primitives/src/v8/mod.rs
+++ b/polkadot/primitives/src/v8/mod.rs
@@ -484,7 +484,7 @@ pub fn collator_signature_payload<H: AsRef<[u8]>>(
 	payload
 }
 
-fn check_collator_signature<H: AsRef<[u8]>>(
+pub(crate) fn check_collator_signature<H: AsRef<[u8]>>(
 	relay_parent: &H,
 	para_id: &Id,
 	persisted_validation_data_hash: &Hash,
diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs
index bc687f7e2fbe751d89362070406e46d2e6a5202f..265fcd899d74fbb95939630af70509b51ff7e49c 100644
--- a/polkadot/primitives/src/vstaging/mod.rs
+++ b/polkadot/primitives/src/vstaging/mod.rs
@@ -107,14 +107,42 @@ impl<H: Copy> From<CandidateDescriptorV2<H>> for CandidateDescriptor<H> {
 	}
 }
 
-#[cfg(any(feature = "runtime-benchmarks", feature = "test"))]
-impl<H: Encode + Decode + Copy> From<CandidateDescriptor<H>> for CandidateDescriptorV2<H> {
+fn clone_into_array<A, T>(slice: &[T]) -> A
+where
+	A: Default + AsMut<[T]>,
+	T: Clone,
+{
+	let mut a = A::default();
+	<A as AsMut<[T]>>::as_mut(&mut a).clone_from_slice(slice);
+	a
+}
+
+impl<H: Copy> From<CandidateDescriptor<H>> for CandidateDescriptorV2<H> {
 	fn from(value: CandidateDescriptor<H>) -> Self {
-		Decode::decode(&mut value.encode().as_slice()).unwrap()
+		let collator = value.collator.as_slice();
+
+		Self {
+			para_id: value.para_id,
+			relay_parent: value.relay_parent,
+			// Use first byte of the `collator` field.
+			version: InternalVersion(collator[0]),
+			// Use next 2 bytes of the `collator` field.
+			core_index: u16::from_ne_bytes(clone_into_array(&collator[1..=2])),
+			// Use next 4 bytes of the `collator` field.
+			session_index: SessionIndex::from_ne_bytes(clone_into_array(&collator[3..=6])),
+			// Use remaing 25 bytes of the `collator` field.
+			reserved1: clone_into_array(&collator[7..]),
+			persisted_validation_data_hash: value.persisted_validation_data_hash,
+			pov_hash: value.pov_hash,
+			erasure_root: value.erasure_root,
+			reserved2: value.signature.into_inner().0,
+			para_head: value.para_head,
+			validation_code_hash: value.validation_code_hash,
+		}
 	}
 }
 
-impl<H> CandidateDescriptorV2<H> {
+impl<H: Copy + AsRef<[u8]>> CandidateDescriptorV2<H> {
 	/// Constructor
 	pub fn new(
 		para_id: Id,
@@ -143,17 +171,74 @@ impl<H> CandidateDescriptorV2<H> {
 		}
 	}
 
-	/// Set the PoV size in the descriptor. Only for tests.
-	#[cfg(feature = "test")]
-	pub fn set_pov_hash(&mut self, pov_hash: Hash) {
+	/// Check the signature of the collator within this descriptor.
+	pub fn check_collator_signature(&self) -> Result<(), ()> {
+		// Return `Ok` if collator signature is not included (v2+ descriptor).
+		let Some(collator) = self.collator() else { return Ok(()) };
+
+		let Some(signature) = self.signature() else { return Ok(()) };
+
+		super::v8::check_collator_signature(
+			&self.relay_parent,
+			&self.para_id,
+			&self.persisted_validation_data_hash,
+			&self.pov_hash,
+			&self.validation_code_hash,
+			&collator,
+			&signature,
+		)
+	}
+}
+
+/// A trait to allow changing the descriptor field values in tests.
+#[cfg(feature = "test")]
+
+pub trait MutateDescriptorV2<H> {
+	/// Set the relay parent of the descriptor.
+	fn set_relay_parent(&mut self, relay_parent: H);
+	/// Set the `ParaId` of the descriptor.
+	fn set_para_id(&mut self, para_id: Id);
+	/// Set the PoV hash of the descriptor.
+	fn set_pov_hash(&mut self, pov_hash: Hash);
+	/// Set the version field of the descriptor.
+	fn set_version(&mut self, version: InternalVersion);
+	/// Set the PVD of the descriptor.
+	fn set_persisted_validation_data_hash(&mut self, persisted_validation_data_hash: Hash);
+	/// Set the erasure root of the descriptor.
+	fn set_erasure_root(&mut self, erasure_root: Hash);
+	/// Set the para head of the descriptor.
+	fn set_para_head(&mut self, para_head: Hash);
+}
+
+#[cfg(feature = "test")]
+impl<H> MutateDescriptorV2<H> for CandidateDescriptorV2<H> {
+	fn set_para_id(&mut self, para_id: Id) {
+		self.para_id = para_id;
+	}
+
+	fn set_relay_parent(&mut self, relay_parent: H) {
+		self.relay_parent = relay_parent;
+	}
+
+	fn set_pov_hash(&mut self, pov_hash: Hash) {
 		self.pov_hash = pov_hash;
 	}
 
-	/// Set the version in the descriptor. Only for tests.
-	#[cfg(feature = "test")]
-	pub fn set_version(&mut self, version: InternalVersion) {
+	fn set_version(&mut self, version: InternalVersion) {
 		self.version = version;
 	}
+
+	fn set_persisted_validation_data_hash(&mut self, persisted_validation_data_hash: Hash) {
+		self.persisted_validation_data_hash = persisted_validation_data_hash;
+	}
+
+	fn set_erasure_root(&mut self, erasure_root: Hash) {
+		self.erasure_root = erasure_root;
+	}
+
+	fn set_para_head(&mut self, para_head: Hash) {
+		self.para_head = para_head;
+	}
 }
 
 /// A candidate-receipt at version 2.
@@ -233,6 +318,24 @@ impl<H> CandidateReceiptV2<H> {
 	}
 }
 
+impl<H: Copy> From<super::v8::CandidateReceipt<H>> for CandidateReceiptV2<H> {
+	fn from(value: super::v8::CandidateReceipt<H>) -> Self {
+		CandidateReceiptV2 {
+			descriptor: value.descriptor.into(),
+			commitments_hash: value.commitments_hash,
+		}
+	}
+}
+
+impl<H: Copy> From<super::v8::CommittedCandidateReceipt<H>> for CommittedCandidateReceiptV2<H> {
+	fn from(value: super::v8::CommittedCandidateReceipt<H>) -> Self {
+		CommittedCandidateReceiptV2 {
+			descriptor: value.descriptor.into(),
+			commitments: value.commitments,
+		}
+	}
+}
+
 impl<H: Clone> CommittedCandidateReceiptV2<H> {
 	/// Transforms this into a plain `CandidateReceipt`.
 	pub fn to_plain(&self) -> CandidateReceiptV2<H> {
@@ -368,7 +471,7 @@ pub enum CandidateReceiptError {
 
 macro_rules! impl_getter {
 	($field:ident, $type:ident) => {
-		/// Returns the value of $field field.
+		/// Returns the value of `$field` field.
 		pub fn $field(&self) -> $type {
 			self.$field
 		}
@@ -703,6 +806,13 @@ pub struct OccupiedCore<H = Hash, N = BlockNumber> {
 	pub candidate_descriptor: CandidateDescriptorV2<H>,
 }
 
+impl<H, N> OccupiedCore<H, N> {
+	/// Get the Para currently occupying this core.
+	pub fn para_id(&self) -> Id {
+		self.candidate_descriptor.para_id
+	}
+}
+
 /// The state of a particular availability core.
 #[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
 #[cfg_attr(feature = "std", derive(PartialEq))]
@@ -724,6 +834,28 @@ pub enum CoreState<H = Hash, N = BlockNumber> {
 	Free,
 }
 
+impl<N> CoreState<N> {
+	/// Returns the scheduled `ParaId` for the core or `None` if nothing is scheduled.
+	///
+	/// This function is deprecated. `ClaimQueue` should be used to obtain the scheduled `ParaId`s
+	/// for each core.
+	#[deprecated(
+		note = "`para_id` will be removed. Use `ClaimQueue` to query the scheduled `para_id` instead."
+	)]
+	pub fn para_id(&self) -> Option<Id> {
+		match self {
+			Self::Occupied(ref core) => core.next_up_on_available.as_ref().map(|n| n.para_id),
+			Self::Scheduled(core) => Some(core.para_id),
+			Self::Free => None,
+		}
+	}
+
+	/// Is this core state `Self::Occupied`?
+	pub fn is_occupied(&self) -> bool {
+		matches!(self, Self::Occupied(_))
+	}
+}
+
 impl<H: Copy> From<OccupiedCore<H>> for super::v8::OccupiedCore<H> {
 	fn from(value: OccupiedCore<H>) -> Self {
 		Self {
@@ -841,6 +973,25 @@ mod tests {
 		assert_eq!(old_ccr.hash(), new_ccr.hash());
 	}
 
+	#[test]
+	fn test_from_v1_descriptor() {
+		let mut old_ccr = dummy_old_committed_candidate_receipt().to_plain();
+		old_ccr.descriptor.collator = dummy_collator_id();
+		old_ccr.descriptor.signature = dummy_collator_signature();
+
+		let mut new_ccr = dummy_committed_candidate_receipt_v2().to_plain();
+
+		// Override descriptor from old candidate receipt.
+		new_ccr.descriptor = old_ccr.descriptor.clone().into();
+
+		// We get same candidate hash.
+		assert_eq!(old_ccr.hash(), new_ccr.hash());
+
+		assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::V1);
+		assert_eq!(old_ccr.descriptor.collator, new_ccr.descriptor.collator().unwrap());
+		assert_eq!(old_ccr.descriptor.signature, new_ccr.descriptor.signature().unwrap());
+	}
+
 	#[test]
 	fn invalid_version_descriptor() {
 		let mut new_ccr = dummy_committed_candidate_receipt_v2();
diff --git a/polkadot/primitives/test-helpers/src/lib.rs b/polkadot/primitives/test-helpers/src/lib.rs
index b0f78717dd97538758e735eb61f21821c17ed7e6..c2eccafef788ecebfef954338f44b11a92cbb246 100644
--- a/polkadot/primitives/test-helpers/src/lib.rs
+++ b/polkadot/primitives/test-helpers/src/lib.rs
@@ -44,7 +44,7 @@ pub fn dummy_candidate_receipt<H: AsRef<[u8]>>(relay_parent: H) -> CandidateRece
 }
 
 /// Creates a v2 candidate receipt with filler data.
-pub fn dummy_candidate_receipt_v2<H: AsRef<[u8]>>(relay_parent: H) -> CandidateReceiptV2<H> {
+pub fn dummy_candidate_receipt_v2<H: AsRef<[u8]> + Copy>(relay_parent: H) -> CandidateReceiptV2<H> {
 	CandidateReceiptV2::<H> {
 		commitments_hash: dummy_candidate_commitments(dummy_head_data()).hash(),
 		descriptor: dummy_candidate_descriptor_v2(relay_parent),
@@ -62,7 +62,7 @@ pub fn dummy_committed_candidate_receipt<H: AsRef<[u8]>>(
 }
 
 /// Creates a v2 committed candidate receipt with filler data.
-pub fn dummy_committed_candidate_receipt_v2<H: AsRef<[u8]>>(
+pub fn dummy_committed_candidate_receipt_v2<H: AsRef<[u8]> + Copy>(
 	relay_parent: H,
 ) -> CommittedCandidateReceiptV2<H> {
 	CommittedCandidateReceiptV2 {
@@ -88,6 +88,23 @@ pub fn dummy_candidate_receipt_bad_sig(
 	}
 }
 
+/// Create a candidate receipt with a bogus signature and filler data. Optionally set the commitment
+/// hash with the `commitments` arg.
+pub fn dummy_candidate_receipt_v2_bad_sig(
+	relay_parent: Hash,
+	commitments: impl Into<Option<Hash>>,
+) -> CandidateReceiptV2<Hash> {
+	let commitments_hash = if let Some(commitments) = commitments.into() {
+		commitments
+	} else {
+		dummy_candidate_commitments(dummy_head_data()).hash()
+	};
+	CandidateReceiptV2::<Hash> {
+		commitments_hash,
+		descriptor: dummy_candidate_descriptor_bad_sig(relay_parent).into(),
+	}
+}
+
 /// Create candidate commitments with filler data.
 pub fn dummy_candidate_commitments(head_data: impl Into<Option<HeadData>>) -> CandidateCommitments {
 	CandidateCommitments {
@@ -144,7 +161,9 @@ pub fn dummy_candidate_descriptor<H: AsRef<[u8]>>(relay_parent: H) -> CandidateD
 }
 
 /// Create a v2 candidate descriptor with filler data.
-pub fn dummy_candidate_descriptor_v2<H: AsRef<[u8]>>(relay_parent: H) -> CandidateDescriptorV2<H> {
+pub fn dummy_candidate_descriptor_v2<H: AsRef<[u8]> + Copy>(
+	relay_parent: H,
+) -> CandidateDescriptorV2<H> {
 	let invalid = Hash::zero();
 	let descriptor = make_valid_candidate_descriptor_v2(
 		1.into(),
@@ -208,7 +227,7 @@ pub fn make_candidate(
 	parent_head: HeadData,
 	head_data: HeadData,
 	validation_code_hash: ValidationCodeHash,
-) -> (CommittedCandidateReceipt, PersistedValidationData) {
+) -> (CommittedCandidateReceiptV2, PersistedValidationData) {
 	let pvd = dummy_pvd(parent_head, relay_parent_number);
 	let commitments = CandidateCommitments {
 		head_data,
@@ -225,7 +244,8 @@ pub fn make_candidate(
 	candidate.descriptor.para_id = para_id;
 	candidate.descriptor.persisted_validation_data_hash = pvd.hash();
 	candidate.descriptor.validation_code_hash = validation_code_hash;
-	let candidate = CommittedCandidateReceipt { descriptor: candidate.descriptor, commitments };
+	let candidate =
+		CommittedCandidateReceiptV2 { descriptor: candidate.descriptor.into(), commitments };
 
 	(candidate, pvd)
 }
@@ -269,7 +289,7 @@ pub fn make_valid_candidate_descriptor<H: AsRef<[u8]>>(
 }
 
 /// Create a v2 candidate descriptor.
-pub fn make_valid_candidate_descriptor_v2<H: AsRef<[u8]>>(
+pub fn make_valid_candidate_descriptor_v2<H: AsRef<[u8]> + Copy>(
 	para_id: ParaId,
 	relay_parent: H,
 	core_index: CoreIndex,
@@ -335,11 +355,11 @@ impl std::default::Default for TestCandidateBuilder {
 
 impl TestCandidateBuilder {
 	/// Build a `CandidateReceipt`.
-	pub fn build(self) -> CandidateReceipt {
+	pub fn build(self) -> CandidateReceiptV2 {
 		let mut descriptor = dummy_candidate_descriptor(self.relay_parent);
 		descriptor.para_id = self.para_id;
 		descriptor.pov_hash = self.pov_hash;
-		CandidateReceipt { descriptor, commitments_hash: self.commitments_hash }
+		CandidateReceipt { descriptor, commitments_hash: self.commitments_hash }.into()
 	}
 }
 
diff --git a/polkadot/runtime/parachains/src/inclusion/tests.rs b/polkadot/runtime/parachains/src/inclusion/tests.rs
index 188ba4995d8302044679cb3bcc3d66c9508ce785..8513d2dad91d1e527d76d850174d51f3d4df302a 100644
--- a/polkadot/runtime/parachains/src/inclusion/tests.rs
+++ b/polkadot/runtime/parachains/src/inclusion/tests.rs
@@ -39,7 +39,7 @@ use assert_matches::assert_matches;
 use codec::DecodeAll;
 use frame_support::assert_noop;
 use polkadot_primitives::{
-	BlockNumber, CandidateCommitments, CollatorId, CollatorSignature,
+	vstaging::MutateDescriptorV2, BlockNumber, CandidateCommitments, CollatorId, CollatorSignature,
 	CompactStatement as Statement, Hash, SignedAvailabilityBitfield, SignedStatement,
 	ValidationCode, ValidatorId, ValidityAttestation, PARACHAIN_KEY_TYPE_ID,
 };
diff --git a/polkadot/runtime/parachains/src/paras_inherent/tests.rs b/polkadot/runtime/parachains/src/paras_inherent/tests.rs
index 2c65298baf01b98569f38d7302d3658eb19cd0d6..eef26b83368f5d9fa6f362b99c22cdbd0f448186 100644
--- a/polkadot/runtime/parachains/src/paras_inherent/tests.rs
+++ b/polkadot/runtime/parachains/src/paras_inherent/tests.rs
@@ -61,7 +61,9 @@ mod enter {
 	use frame_support::assert_ok;
 	use frame_system::limits;
 	use polkadot_primitives::{
-		vstaging::{CandidateDescriptorV2, CommittedCandidateReceiptV2, InternalVersion},
+		vstaging::{
+			CandidateDescriptorV2, CommittedCandidateReceiptV2, InternalVersion, MutateDescriptorV2,
+		},
 		AvailabilityBitfield, CandidateDescriptor, UncheckedSigned,
 	};
 	use sp_runtime::Perbill;
diff --git a/polkadot/statement-table/src/lib.rs b/polkadot/statement-table/src/lib.rs
index 469c877eafc9fa4d56e6eae3187c2fb29b14a07a..68febf76feb3fe6d1b6327e5a1abe5f3b2e3a4e6 100644
--- a/polkadot/statement-table/src/lib.rs
+++ b/polkadot/statement-table/src/lib.rs
@@ -35,8 +35,8 @@ pub use generic::{Config, Context, Table};
 pub mod v2 {
 	use crate::generic;
 	use polkadot_primitives::{
-		CandidateHash, CommittedCandidateReceipt, CompactStatement as PrimitiveStatement,
-		CoreIndex, ValidatorIndex, ValidatorSignature,
+		vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CandidateHash,
+		CompactStatement as PrimitiveStatement, CoreIndex, ValidatorIndex, ValidatorSignature,
 	};
 
 	/// Statements about candidates on the network.
diff --git a/prdoc/pr_5679.prdoc b/prdoc/pr_5679.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..59c36ecb933d642fba3b56b498ae6520f8c99a11
--- /dev/null
+++ b/prdoc/pr_5679.prdoc
@@ -0,0 +1,80 @@
+title: Switch to new `CandidateReceipt` primitives
+doc:
+- audience:
+  - Node Dev
+  - Runtime Dev
+  description: |
+    This change is just plumbing work and updates all crate interfaces to use the new primitives.
+    It doesn't alter any functionality and is required before implementing RFC103 on the 
+    node side. 
+crates:
+- name: polkadot-primitives
+  bump: major
+- name: polkadot-runtime-parachains
+  bump: patch
+- name: rococo-runtime
+  bump: patch
+- name: westend-runtime
+  bump: patch
+- name: cumulus-relay-chain-inprocess-interface
+  bump: major
+- name: polkadot-service
+  bump: patch
+- name: polkadot-node-subsystem-types
+  bump: major
+- name: polkadot
+  bump: patch
+- name: cumulus-client-network
+  bump: major
+- name: cumulus-client-pov-recovery
+  bump: major
+- name: cumulus-relay-chain-interface
+  bump: major
+- name: cumulus-relay-chain-minimal-node
+  bump: major
+- name: cumulus-relay-chain-rpc-interface
+  bump: major
+- name: polkadot-node-collation-generation
+  bump: major
+- name: polkadot-node-core-approval-voting
+  bump: major
+- name: polkadot-node-core-av-store
+  bump: major
+- name: polkadot-node-core-backing
+  bump: major
+- name: polkadot-node-core-bitfield-signing
+  bump: major
+- name: polkadot-node-core-candidate-validation
+  bump: major
+- name: polkadot-node-core-dispute-coordinator
+  bump: major
+- name: polkadot-node-core-parachains-inherent
+  bump: major
+- name: polkadot-node-core-prospective-parachains
+  bump: major
+- name: polkadot-node-core-provisioner
+  bump: major
+- name: polkadot-node-core-runtime-api
+  bump: major
+- name: polkadot-availability-distribution
+  bump: major
+- name: polkadot-availability-recovery
+  bump: major
+- name: polkadot-collator-protocol
+  bump: major
+- name: polkadot-dispute-distribution
+  bump: major
+- name: polkadot-node-network-protocol
+  bump: major
+- name: polkadot-statement-distribution
+  bump: major
+- name: polkadot-node-primitives
+  bump: major
+- name: polkadot-node-subsystem-util
+  bump: major
+- name: polkadot-statement-table
+  bump: major
+- name: polkadot-overseer
+  bump: patch
+- name: cumulus-client-consensus-common
+  bump: major