use crate::{ValidatorIndex, ValidityAttestation};
use super::{
async_backing::Constraints, BlakeTwo256, BlockNumber, CandidateCommitments,
CandidateDescriptor, CandidateHash, CollatorId, CollatorSignature, CoreIndex, GroupIndex, Hash,
HashT, HeadData, Header, Id, Id as ParaId, MultiDisputeStatementSet, ScheduledCore,
UncheckedSignedAvailabilityBitfields, ValidationCodeHash,
};
use bitvec::prelude::*;
use sp_application_crypto::ByteArray;
use alloc::{vec, vec::Vec};
use codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_core::RuntimeDebug;
use sp_runtime::traits::Header as HeaderT;
use sp_staking::SessionIndex;
pub mod async_backing;
#[derive(PartialEq, Eq, Encode, Decode, Clone, TypeInfo, RuntimeDebug, Copy)]
#[cfg_attr(feature = "std", derive(Hash))]
pub struct InternalVersion(pub u8);
#[derive(PartialEq, Eq, Clone, TypeInfo, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Hash))]
pub enum CandidateDescriptorVersion {
V1,
V2,
Unknown,
}
#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Hash))]
pub struct CandidateDescriptorV2<H = Hash> {
para_id: ParaId,
relay_parent: H,
version: InternalVersion,
core_index: u16,
session_index: SessionIndex,
reserved1: [u8; 25],
persisted_validation_data_hash: Hash,
pov_hash: Hash,
erasure_root: Hash,
reserved2: [u8; 64],
para_head: Hash,
validation_code_hash: ValidationCodeHash,
}
impl<H: Copy> From<CandidateDescriptorV2<H>> for CandidateDescriptor<H> {
fn from(value: CandidateDescriptorV2<H>) -> Self {
Self {
para_id: value.para_id,
relay_parent: value.relay_parent,
collator: value.rebuild_collator_field(),
persisted_validation_data_hash: value.persisted_validation_data_hash,
pov_hash: value.pov_hash,
erasure_root: value.erasure_root,
signature: value.rebuild_signature_field(),
para_head: value.para_head,
validation_code_hash: value.validation_code_hash,
}
}
}
#[cfg(any(feature = "runtime-benchmarks", feature = "test"))]
impl<H: Encode + Decode + Copy> From<CandidateDescriptor<H>> for CandidateDescriptorV2<H> {
fn from(value: CandidateDescriptor<H>) -> Self {
Decode::decode(&mut value.encode().as_slice()).unwrap()
}
}
impl<H> CandidateDescriptorV2<H> {
pub fn new(
para_id: Id,
relay_parent: H,
core_index: CoreIndex,
session_index: SessionIndex,
persisted_validation_data_hash: Hash,
pov_hash: Hash,
erasure_root: Hash,
para_head: Hash,
validation_code_hash: ValidationCodeHash,
) -> Self {
Self {
para_id,
relay_parent,
version: InternalVersion(0),
core_index: core_index.0 as u16,
session_index,
reserved1: [0; 25],
persisted_validation_data_hash,
pov_hash,
erasure_root,
reserved2: [0; 64],
para_head,
validation_code_hash,
}
}
#[cfg(feature = "test")]
pub fn set_pov_hash(&mut self, pov_hash: Hash) {
self.pov_hash = pov_hash;
}
#[cfg(feature = "test")]
pub fn set_version(&mut self, version: InternalVersion) {
self.version = version;
}
}
#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Hash))]
pub struct CandidateReceiptV2<H = Hash> {
pub descriptor: CandidateDescriptorV2<H>,
pub commitments_hash: Hash,
}
#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Hash))]
pub struct CommittedCandidateReceiptV2<H = Hash> {
pub descriptor: CandidateDescriptorV2<H>,
pub commitments: CandidateCommitments,
}
#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(PartialEq))]
pub enum CandidateEvent<H = Hash> {
#[codec(index = 0)]
CandidateBacked(CandidateReceiptV2<H>, HeadData, CoreIndex, GroupIndex),
#[codec(index = 1)]
CandidateIncluded(CandidateReceiptV2<H>, HeadData, CoreIndex, GroupIndex),
#[codec(index = 2)]
CandidateTimedOut(CandidateReceiptV2<H>, HeadData, CoreIndex),
}
impl<H: Encode + Copy> From<CandidateEvent<H>> for super::v8::CandidateEvent<H> {
fn from(value: CandidateEvent<H>) -> Self {
match value {
CandidateEvent::CandidateBacked(receipt, head_data, core_index, group_index) =>
super::v8::CandidateEvent::CandidateBacked(
receipt.into(),
head_data,
core_index,
group_index,
),
CandidateEvent::CandidateIncluded(receipt, head_data, core_index, group_index) =>
super::v8::CandidateEvent::CandidateIncluded(
receipt.into(),
head_data,
core_index,
group_index,
),
CandidateEvent::CandidateTimedOut(receipt, head_data, core_index) =>
super::v8::CandidateEvent::CandidateTimedOut(receipt.into(), head_data, core_index),
}
}
}
impl<H> CandidateReceiptV2<H> {
pub fn descriptor(&self) -> &CandidateDescriptorV2<H> {
&self.descriptor
}
pub fn hash(&self) -> CandidateHash
where
H: Encode,
{
CandidateHash(BlakeTwo256::hash_of(self))
}
}
impl<H: Clone> CommittedCandidateReceiptV2<H> {
pub fn to_plain(&self) -> CandidateReceiptV2<H> {
CandidateReceiptV2 {
descriptor: self.descriptor.clone(),
commitments_hash: self.commitments.hash(),
}
}
pub fn hash(&self) -> CandidateHash
where
H: Encode,
{
self.to_plain().hash()
}
pub fn corresponds_to(&self, receipt: &CandidateReceiptV2<H>) -> bool
where
H: PartialEq,
{
receipt.descriptor == self.descriptor && receipt.commitments_hash == self.commitments.hash()
}
}
impl PartialOrd for CommittedCandidateReceiptV2 {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CommittedCandidateReceiptV2 {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.descriptor
.para_id
.cmp(&other.descriptor.para_id)
.then_with(|| self.commitments.head_data.cmp(&other.commitments.head_data))
}
}
impl<H: Copy> From<CommittedCandidateReceiptV2<H>> for super::v8::CommittedCandidateReceipt<H> {
fn from(value: CommittedCandidateReceiptV2<H>) -> Self {
Self { descriptor: value.descriptor.into(), commitments: value.commitments }
}
}
impl<H: Copy> From<CandidateReceiptV2<H>> for super::v8::CandidateReceipt<H> {
fn from(value: CandidateReceiptV2<H>) -> Self {
Self { descriptor: value.descriptor.into(), commitments_hash: value.commitments_hash }
}
}
#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
pub struct CoreSelector(pub u8);
#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
pub struct ClaimQueueOffset(pub u8);
#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
pub enum UMPSignal {
SelectCore(CoreSelector, ClaimQueueOffset),
}
pub const UMP_SEPARATOR: Vec<u8> = vec![];
impl CandidateCommitments {
pub fn selected_core(&self) -> Option<(CoreSelector, ClaimQueueOffset)> {
if self.upward_messages.len() < 2 {
return None
}
let separator_pos =
self.upward_messages.iter().rposition(|message| message == &UMP_SEPARATOR)?;
let message = self.upward_messages.get(separator_pos + 1)?;
match UMPSignal::decode(&mut message.as_slice()).ok()? {
UMPSignal::SelectCore(core_selector, cq_offset) => Some((core_selector, cq_offset)),
}
}
}
#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
pub enum CandidateReceiptError {
InvalidCoreIndex,
CoreIndexMismatch,
InvalidSelectedCore,
NoAssignment,
NoCoreSelected,
UnknownVersion(InternalVersion),
}
macro_rules! impl_getter {
($field:ident, $type:ident) => {
pub fn $field(&self) -> $type {
self.$field
}
};
}
impl<H: Copy> CandidateDescriptorV2<H> {
impl_getter!(erasure_root, Hash);
impl_getter!(para_head, Hash);
impl_getter!(relay_parent, H);
impl_getter!(para_id, ParaId);
impl_getter!(persisted_validation_data_hash, Hash);
impl_getter!(pov_hash, Hash);
impl_getter!(validation_code_hash, ValidationCodeHash);
pub fn version(&self) -> CandidateDescriptorVersion {
if self.reserved2 != [0u8; 64] || self.reserved1 != [0u8; 25] {
return CandidateDescriptorVersion::V1
}
match self.version.0 {
0 => CandidateDescriptorVersion::V2,
_ => CandidateDescriptorVersion::Unknown,
}
}
fn rebuild_collator_field(&self) -> CollatorId {
let mut collator_id = Vec::with_capacity(32);
let core_index: [u8; 2] = self.core_index.to_ne_bytes();
let session_index: [u8; 4] = self.session_index.to_ne_bytes();
collator_id.push(self.version.0);
collator_id.extend_from_slice(core_index.as_slice());
collator_id.extend_from_slice(session_index.as_slice());
collator_id.extend_from_slice(self.reserved1.as_slice());
CollatorId::from_slice(&collator_id.as_slice())
.expect("Slice size is exactly 32 bytes; qed")
}
pub fn collator(&self) -> Option<CollatorId> {
if self.version() == CandidateDescriptorVersion::V1 {
Some(self.rebuild_collator_field())
} else {
None
}
}
fn rebuild_signature_field(&self) -> CollatorSignature {
CollatorSignature::from_slice(self.reserved2.as_slice())
.expect("Slice size is exactly 64 bytes; qed")
}
pub fn signature(&self) -> Option<CollatorSignature> {
if self.version() == CandidateDescriptorVersion::V1 {
return Some(self.rebuild_signature_field())
}
None
}
pub fn core_index(&self) -> Option<CoreIndex> {
if self.version() == CandidateDescriptorVersion::V1 {
return None
}
Some(CoreIndex(self.core_index as u32))
}
pub fn session_index(&self) -> Option<SessionIndex> {
if self.version() == CandidateDescriptorVersion::V1 {
return None
}
Some(self.session_index)
}
}
impl<H: Copy> CommittedCandidateReceiptV2<H> {
pub fn check(&self, assigned_cores: &[CoreIndex]) -> Result<(), CandidateReceiptError> {
if self.descriptor.version() == CandidateDescriptorVersion::V1 {
return Ok(())
}
if self.descriptor.version() == CandidateDescriptorVersion::Unknown {
return Err(CandidateReceiptError::UnknownVersion(self.descriptor.version))
}
if assigned_cores.is_empty() {
return Err(CandidateReceiptError::NoAssignment)
}
let descriptor_core_index = CoreIndex(self.descriptor.core_index as u32);
let (core_selector, _cq_offset) =
self.commitments.selected_core().ok_or(CandidateReceiptError::NoCoreSelected)?;
let core_index = assigned_cores
.get(core_selector.0 as usize % assigned_cores.len())
.ok_or(CandidateReceiptError::InvalidCoreIndex)?;
if *core_index != descriptor_core_index {
return Err(CandidateReceiptError::CoreIndexMismatch)
}
Ok(())
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct BackedCandidate<H = Hash> {
candidate: CommittedCandidateReceiptV2<H>,
validity_votes: Vec<ValidityAttestation>,
validator_indices: BitVec<u8, bitvec::order::Lsb0>,
}
#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, TypeInfo)]
pub struct InherentData<HDR: HeaderT = Header> {
pub bitfields: UncheckedSignedAvailabilityBitfields,
pub backed_candidates: Vec<BackedCandidate<HDR::Hash>>,
pub disputes: MultiDisputeStatementSet,
pub parent_header: HDR,
}
impl<H> BackedCandidate<H> {
pub fn new(
candidate: CommittedCandidateReceiptV2<H>,
validity_votes: Vec<ValidityAttestation>,
validator_indices: BitVec<u8, bitvec::order::Lsb0>,
core_index: Option<CoreIndex>,
) -> Self {
let mut instance = Self { candidate, validity_votes, validator_indices };
if let Some(core_index) = core_index {
instance.inject_core_index(core_index);
}
instance
}
pub fn candidate(&self) -> &CommittedCandidateReceiptV2<H> {
&self.candidate
}
pub fn descriptor(&self) -> &CandidateDescriptorV2<H> {
&self.candidate.descriptor
}
#[cfg(feature = "test")]
pub fn descriptor_mut(&mut self) -> &mut CandidateDescriptorV2<H> {
&mut self.candidate.descriptor
}
pub fn validity_votes(&self) -> &[ValidityAttestation] {
&self.validity_votes
}
pub fn validity_votes_mut(&mut self) -> &mut Vec<ValidityAttestation> {
&mut self.validity_votes
}
pub fn hash(&self) -> CandidateHash
where
H: Clone + Encode,
{
self.candidate.to_plain().hash()
}
pub fn receipt(&self) -> CandidateReceiptV2<H>
where
H: Clone,
{
self.candidate.to_plain()
}
pub fn validator_indices_and_core_index(
&self,
core_index_enabled: bool,
) -> (&BitSlice<u8, bitvec::order::Lsb0>, Option<CoreIndex>) {
if core_index_enabled {
let core_idx_offset = self.validator_indices.len().saturating_sub(8);
if core_idx_offset > 0 {
let (validator_indices_slice, core_idx_slice) =
self.validator_indices.split_at(core_idx_offset);
return (
validator_indices_slice,
Some(CoreIndex(core_idx_slice.load::<u8>() as u32)),
);
}
}
(&self.validator_indices, None)
}
fn inject_core_index(&mut self, core_index: CoreIndex) {
let core_index_to_inject: BitVec<u8, bitvec::order::Lsb0> =
BitVec::from_vec(vec![core_index.0 as u8]);
self.validator_indices.extend(core_index_to_inject);
}
pub fn set_validator_indices_and_core_index(
&mut self,
new_indices: BitVec<u8, bitvec::order::Lsb0>,
maybe_core_index: Option<CoreIndex>,
) {
self.validator_indices = new_indices;
if let Some(core_index) = maybe_core_index {
self.inject_core_index(core_index);
}
}
}
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
#[cfg_attr(feature = "std", derive(PartialEq))]
pub struct ScrapedOnChainVotes<H: Encode + Decode = Hash> {
pub session: SessionIndex,
pub backing_validators_per_candidate:
Vec<(CandidateReceiptV2<H>, Vec<(ValidatorIndex, ValidityAttestation)>)>,
pub disputes: MultiDisputeStatementSet,
}
impl<H: Encode + Decode + Copy> From<ScrapedOnChainVotes<H>> for super::v8::ScrapedOnChainVotes<H> {
fn from(value: ScrapedOnChainVotes<H>) -> Self {
Self {
session: value.session,
backing_validators_per_candidate: value
.backing_validators_per_candidate
.into_iter()
.map(|(receipt, validators)| (receipt.into(), validators))
.collect::<Vec<_>>(),
disputes: value.disputes,
}
}
}
#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(PartialEq))]
pub struct OccupiedCore<H = Hash, N = BlockNumber> {
pub next_up_on_available: Option<ScheduledCore>,
pub occupied_since: N,
pub time_out_at: N,
pub next_up_on_time_out: Option<ScheduledCore>,
pub availability: BitVec<u8, bitvec::order::Lsb0>,
pub group_responsible: GroupIndex,
pub candidate_hash: CandidateHash,
pub candidate_descriptor: CandidateDescriptorV2<H>,
}
#[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(PartialEq))]
pub enum CoreState<H = Hash, N = BlockNumber> {
#[codec(index = 0)]
Occupied(OccupiedCore<H, N>),
#[codec(index = 1)]
Scheduled(ScheduledCore),
#[codec(index = 2)]
Free,
}
impl<H: Copy> From<OccupiedCore<H>> for super::v8::OccupiedCore<H> {
fn from(value: OccupiedCore<H>) -> Self {
Self {
next_up_on_available: value.next_up_on_available,
occupied_since: value.occupied_since,
time_out_at: value.time_out_at,
next_up_on_time_out: value.next_up_on_time_out,
availability: value.availability,
group_responsible: value.group_responsible,
candidate_hash: value.candidate_hash,
candidate_descriptor: value.candidate_descriptor.into(),
}
}
}
impl<H: Copy> From<CoreState<H>> for super::v8::CoreState<H> {
fn from(value: CoreState<H>) -> Self {
match value {
CoreState::Free => super::v8::CoreState::Free,
CoreState::Scheduled(core) => super::v8::CoreState::Scheduled(core),
CoreState::Occupied(occupied_core) =>
super::v8::CoreState::Occupied(occupied_core.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
v8::{
tests::dummy_committed_candidate_receipt as dummy_old_committed_candidate_receipt,
CommittedCandidateReceipt, Hash, HeadData, ValidationCode,
},
vstaging::{CandidateDescriptorV2, CommittedCandidateReceiptV2},
};
fn dummy_collator_signature() -> CollatorSignature {
CollatorSignature::from_slice(&mut (0..64).into_iter().collect::<Vec<_>>().as_slice())
.expect("64 bytes; qed")
}
fn dummy_collator_id() -> CollatorId {
CollatorId::from_slice(&mut (0..32).into_iter().collect::<Vec<_>>().as_slice())
.expect("32 bytes; qed")
}
pub fn dummy_committed_candidate_receipt_v2() -> CommittedCandidateReceiptV2 {
let zeros = Hash::zero();
let reserved2 = [0; 64];
CommittedCandidateReceiptV2 {
descriptor: CandidateDescriptorV2 {
para_id: 0.into(),
relay_parent: zeros,
version: InternalVersion(0),
core_index: 123,
session_index: 1,
reserved1: Default::default(),
persisted_validation_data_hash: zeros,
pov_hash: zeros,
erasure_root: zeros,
reserved2,
para_head: zeros,
validation_code_hash: ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]).hash(),
},
commitments: CandidateCommitments {
head_data: HeadData(vec![]),
upward_messages: vec![].try_into().expect("empty vec fits within bounds"),
new_validation_code: None,
horizontal_messages: vec![].try_into().expect("empty vec fits within bounds"),
processed_downward_messages: 0,
hrmp_watermark: 0_u32,
},
}
}
#[test]
fn is_binary_compatibile() {
let old_ccr = dummy_old_committed_candidate_receipt();
let new_ccr = dummy_committed_candidate_receipt_v2();
assert_eq!(old_ccr.encoded_size(), new_ccr.encoded_size());
let encoded_old = old_ccr.encode();
let new_ccr: CommittedCandidateReceiptV2 =
Decode::decode(&mut encoded_old.as_slice()).unwrap();
assert_eq!(old_ccr.hash(), new_ccr.hash());
}
#[test]
fn invalid_version_descriptor() {
let mut new_ccr = dummy_committed_candidate_receipt_v2();
assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::V2);
new_ccr.descriptor.version = InternalVersion(100);
let new_ccr: CommittedCandidateReceiptV2 =
Decode::decode(&mut new_ccr.encode().as_slice()).unwrap();
assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::Unknown);
assert_eq!(
new_ccr.check(&vec![].as_slice()),
Err(CandidateReceiptError::UnknownVersion(InternalVersion(100)))
)
}
#[test]
fn test_ump_commitment() {
let mut new_ccr = dummy_committed_candidate_receipt_v2();
new_ccr.descriptor.core_index = 123;
new_ccr.descriptor.para_id = ParaId::new(1000);
new_ccr.commitments.upward_messages.force_push(vec![0u8; 256]);
new_ccr.commitments.upward_messages.force_push(vec![0xff; 256]);
new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
new_ccr
.commitments
.upward_messages
.force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
assert_eq!(new_ccr.check(&vec![CoreIndex(123)]), Ok(()));
}
#[test]
fn test_invalid_ump_commitment() {
let mut new_ccr = dummy_committed_candidate_receipt_v2();
new_ccr.descriptor.core_index = 0;
new_ccr.descriptor.para_id = ParaId::new(1000);
new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
assert_eq!(
new_ccr.check(&vec![CoreIndex(0), CoreIndex(100)]),
Err(CandidateReceiptError::NoCoreSelected)
);
new_ccr.commitments.upward_messages.force_push(vec![0, 13, 200].encode());
assert_eq!(new_ccr.commitments.selected_core(), None);
assert_eq!(
new_ccr.check(&vec![CoreIndex(0), CoreIndex(100)]),
Err(CandidateReceiptError::NoCoreSelected)
);
new_ccr.commitments.upward_messages.clear();
new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
new_ccr
.commitments
.upward_messages
.force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
new_ccr
.commitments
.upward_messages
.force_push(UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(1)).encode());
assert_eq!(new_ccr.check(&vec![CoreIndex(0), CoreIndex(100)]), Ok(()));
}
#[test]
fn test_version2_receipts_decoded_as_v1() {
let mut new_ccr = dummy_committed_candidate_receipt_v2();
new_ccr.descriptor.core_index = 123;
new_ccr.descriptor.para_id = ParaId::new(1000);
new_ccr.commitments.upward_messages.force_push(vec![0u8; 256]);
new_ccr.commitments.upward_messages.force_push(vec![0xff; 256]);
new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
new_ccr
.commitments
.upward_messages
.force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
let encoded_ccr = new_ccr.encode();
let decoded_ccr: CommittedCandidateReceipt =
Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
assert_eq!(decoded_ccr.descriptor.relay_parent, new_ccr.descriptor.relay_parent());
assert_eq!(decoded_ccr.descriptor.para_id, new_ccr.descriptor.para_id());
assert_eq!(new_ccr.hash(), decoded_ccr.hash());
let encoded_ccr = new_ccr.encode();
let v2_ccr: CommittedCandidateReceiptV2 =
Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
assert_eq!(v2_ccr.descriptor.core_index(), Some(CoreIndex(123)));
assert_eq!(new_ccr.check(&vec![CoreIndex(123)]), Ok(()));
assert_eq!(new_ccr.hash(), v2_ccr.hash());
}
#[test]
fn test_core_select_is_mandatory() {
let mut old_ccr = dummy_old_committed_candidate_receipt();
old_ccr.descriptor.para_id = ParaId::new(1000);
let encoded_ccr: Vec<u8> = old_ccr.encode();
let new_ccr: CommittedCandidateReceiptV2 =
Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
assert_eq!(new_ccr.check(&vec![CoreIndex(0)]), Err(CandidateReceiptError::NoCoreSelected));
old_ccr.descriptor.signature = dummy_collator_signature();
old_ccr.descriptor.collator = dummy_collator_id();
let old_ccr_hash = old_ccr.hash();
let encoded_ccr: Vec<u8> = old_ccr.encode();
let new_ccr: CommittedCandidateReceiptV2 =
Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
assert_eq!(new_ccr.descriptor.signature(), Some(old_ccr.descriptor.signature));
assert_eq!(new_ccr.descriptor.collator(), Some(old_ccr.descriptor.collator));
assert_eq!(new_ccr.descriptor.core_index(), None);
assert_eq!(new_ccr.descriptor.para_id(), ParaId::new(1000));
assert_eq!(old_ccr_hash, new_ccr.hash());
}
}