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 alloc::{
collections::{BTreeMap, BTreeSet, VecDeque},
vec,
vec::Vec,
};
use bitvec::prelude::*;
use codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_application_crypto::ByteArray;
use sp_core::RuntimeDebug;
use sp_runtime::traits::Header as HeaderT;
use sp_staking::SessionIndex;
pub mod async_backing;
pub const DEFAULT_CLAIM_QUEUE_OFFSET: u8 = 0;
#[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,
}
}
}
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 {
let collator = value.collator.as_slice();
Self {
para_id: value.para_id,
relay_parent: value.relay_parent,
version: InternalVersion(collator[0]),
core_index: u16::from_ne_bytes(clone_into_array(&collator[1..=2])),
session_index: SessionIndex::from_ne_bytes(clone_into_array(&collator[3..=6])),
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: Copy + AsRef<[u8]>> 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,
}
}
pub fn check_collator_signature(&self) -> Result<(), ()> {
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,
)
}
}
#[cfg(feature = "test")]
pub trait MutateDescriptorV2<H> {
fn set_relay_parent(&mut self, relay_parent: H);
fn set_para_id(&mut self, para_id: Id);
fn set_pov_hash(&mut self, pov_hash: Hash);
fn set_version(&mut self, version: InternalVersion);
fn set_persisted_validation_data_hash(&mut self, persisted_validation_data_hash: Hash);
fn set_validation_code_hash(&mut self, validation_code_hash: ValidationCodeHash);
fn set_erasure_root(&mut self, erasure_root: Hash);
fn set_para_head(&mut self, para_head: Hash);
fn set_core_index(&mut self, core_index: CoreIndex);
fn set_session_index(&mut self, session_index: SessionIndex);
}
#[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;
}
fn set_version(&mut self, version: InternalVersion) {
self.version = version;
}
fn set_core_index(&mut self, core_index: CoreIndex) {
self.core_index = core_index.0 as u16;
}
fn set_session_index(&mut self, session_index: SessionIndex) {
self.session_index = session_index;
}
fn set_persisted_validation_data_hash(&mut self, persisted_validation_data_hash: Hash) {
self.persisted_validation_data_hash = persisted_validation_data_hash;
}
fn set_validation_code_hash(&mut self, validation_code_hash: ValidationCodeHash) {
self.validation_code_hash = validation_code_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;
}
}
#[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: 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> {
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![];
pub fn skip_ump_signals<'a>(
upward_messages: impl Iterator<Item = &'a Vec<u8>>,
) -> impl Iterator<Item = &'a Vec<u8>> {
upward_messages.take_while(|message| *message != &UMP_SEPARATOR)
}
impl CandidateCommitments {
pub fn core_selector(
&self,
) -> Result<Option<(CoreSelector, ClaimQueueOffset)>, CommittedCandidateReceiptError> {
let mut signals_iter =
self.upward_messages.iter().skip_while(|message| *message != &UMP_SEPARATOR);
if signals_iter.next().is_some() {
let Some(core_selector_message) = signals_iter.next() else { return Ok(None) };
if signals_iter.next().is_some() {
return Err(CommittedCandidateReceiptError::TooManyUMPSignals)
}
match UMPSignal::decode(&mut core_selector_message.as_slice())
.map_err(|_| CommittedCandidateReceiptError::UmpSignalDecode)?
{
UMPSignal::SelectCore(core_index_selector, cq_offset) =>
Ok(Some((core_index_selector, cq_offset))),
}
} else {
Ok(None)
}
}
}
#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(thiserror::Error))]
pub enum CommittedCandidateReceiptError {
#[cfg_attr(feature = "std", error("The specified core index is invalid"))]
InvalidCoreIndex,
#[cfg_attr(
feature = "std",
error("The core index in commitments doesn't match the one in descriptor")
)]
CoreIndexMismatch,
#[cfg_attr(feature = "std", error("The core selector or claim queue offset is invalid"))]
InvalidSelectedCore,
#[cfg_attr(feature = "std", error("Could not decode UMP signal"))]
UmpSignalDecode,
#[cfg_attr(
feature = "std",
error("The parachain is not assigned to any core at specified claim queue offset")
)]
NoAssignment,
#[cfg_attr(feature = "std", error("Core selector not present"))]
NoCoreSelected,
#[cfg_attr(feature = "std", error("Unknown internal version"))]
UnknownVersion(InternalVersion),
#[cfg_attr(feature = "std", error("Too many UMP signals"))]
TooManyUMPSignals,
}
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_core_index(
&self,
cores_per_para: &TransposedClaimQueue,
) -> Result<(), CommittedCandidateReceiptError> {
match self.descriptor.version() {
CandidateDescriptorVersion::V1 => return Ok(()),
CandidateDescriptorVersion::V2 => {},
CandidateDescriptorVersion::Unknown =>
return Err(CommittedCandidateReceiptError::UnknownVersion(self.descriptor.version)),
}
let (maybe_core_index_selector, cq_offset) = self.commitments.core_selector()?.map_or_else(
|| (None, ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET)),
|(sel, off)| (Some(sel), off),
);
let assigned_cores = cores_per_para
.get(&self.descriptor.para_id())
.ok_or(CommittedCandidateReceiptError::NoAssignment)?
.get(&cq_offset.0)
.ok_or(CommittedCandidateReceiptError::NoAssignment)?;
if assigned_cores.is_empty() {
return Err(CommittedCandidateReceiptError::NoAssignment)
}
let descriptor_core_index = CoreIndex(self.descriptor.core_index as u32);
let core_index_selector = if let Some(core_index_selector) = maybe_core_index_selector {
core_index_selector
} else if assigned_cores.len() > 1 {
if !assigned_cores.contains(&descriptor_core_index) {
return Err(CommittedCandidateReceiptError::InvalidCoreIndex)
} else {
return Ok(())
}
} else {
CoreSelector(0)
};
let core_index = assigned_cores
.iter()
.nth(core_index_selector.0 as usize % assigned_cores.len())
.ok_or(CommittedCandidateReceiptError::InvalidSelectedCore)
.copied()?;
if core_index != descriptor_core_index {
return Err(CommittedCandidateReceiptError::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
}
#[cfg(feature = "test")]
pub fn candidate_mut(&mut self) -> &mut CommittedCandidateReceiptV2<H> {
&mut 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>,
}
impl<H, N> OccupiedCore<H, N> {
pub fn para_id(&self) -> Id {
self.candidate_descriptor.para_id
}
}
#[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<N> CoreState<N> {
#[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,
}
}
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 {
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()),
}
}
}
pub type TransposedClaimQueue = BTreeMap<ParaId, BTreeMap<u8, BTreeSet<CoreIndex>>>;
pub fn transpose_claim_queue(
claim_queue: BTreeMap<CoreIndex, VecDeque<Id>>,
) -> TransposedClaimQueue {
let mut per_para_claim_queue = BTreeMap::new();
for (core, paras) in claim_queue {
for (depth, para) in paras.into_iter().enumerate() {
let depths: &mut BTreeMap<u8, BTreeSet<CoreIndex>> =
per_para_claim_queue.entry(para).or_insert_with(|| Default::default());
depths.entry(depth as u8).or_default().insert(core);
}
}
per_para_claim_queue
}
#[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 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();
new_ccr.descriptor = old_ccr.descriptor.clone().into();
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();
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_core_index(&BTreeMap::new()),
Err(CommittedCandidateReceiptError::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());
let mut cq = BTreeMap::new();
cq.insert(
CoreIndex(123),
vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
);
assert_eq!(new_ccr.check_core_index(&transpose_claim_queue(cq)), 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);
let mut cq = BTreeMap::new();
cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
assert!(new_ccr.check_core_index(&transpose_claim_queue(cq)).is_ok());
new_ccr.commitments.upward_messages.force_push(vec![0, 13, 200].encode());
assert_eq!(
new_ccr.commitments.core_selector(),
Err(CommittedCandidateReceiptError::UmpSignalDecode)
);
new_ccr.commitments.upward_messages.clear();
let mut cq = BTreeMap::new();
cq.insert(
CoreIndex(0),
vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
);
cq.insert(
CoreIndex(100),
vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
);
assert_eq!(new_ccr.check_core_index(&transpose_claim_queue(cq.clone())), Ok(()));
new_ccr.descriptor.set_core_index(CoreIndex(1));
assert_eq!(
new_ccr.check_core_index(&transpose_claim_queue(cq.clone())),
Err(CommittedCandidateReceiptError::InvalidCoreIndex)
);
new_ccr.descriptor.set_core_index(CoreIndex(0));
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());
assert_eq!(
new_ccr.check_core_index(&transpose_claim_queue(Default::default())),
Err(CommittedCandidateReceiptError::NoAssignment)
);
new_ccr.descriptor.set_core_index(CoreIndex(1));
assert_eq!(
new_ccr.check_core_index(&transpose_claim_queue(cq.clone())),
Err(CommittedCandidateReceiptError::CoreIndexMismatch)
);
new_ccr.descriptor.set_core_index(CoreIndex(0));
new_ccr
.commitments
.upward_messages
.force_push(UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(1)).encode());
assert_eq!(
new_ccr.check_core_index(&transpose_claim_queue(cq)),
Err(CommittedCandidateReceiptError::TooManyUMPSignals)
);
}
#[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)));
let mut cq = BTreeMap::new();
cq.insert(
CoreIndex(123),
vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(),
);
assert_eq!(new_ccr.check_core_index(&transpose_claim_queue(cq)), Ok(()));
assert_eq!(new_ccr.hash(), v2_ccr.hash());
}
#[test]
fn test_v1_descriptors_with_ump_signal() {
let mut ccr = dummy_old_committed_candidate_receipt();
ccr.descriptor.para_id = ParaId::new(1024);
ccr.descriptor.signature = dummy_collator_signature();
ccr.descriptor.collator = dummy_collator_id();
ccr.commitments.upward_messages.force_push(UMP_SEPARATOR);
ccr.commitments
.upward_messages
.force_push(UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(1)).encode());
let encoded_ccr: Vec<u8> = ccr.encode();
let v1_ccr: CommittedCandidateReceiptV2 =
Decode::decode(&mut encoded_ccr.as_slice()).unwrap();
assert_eq!(v1_ccr.descriptor.version(), CandidateDescriptorVersion::V1);
assert!(v1_ccr.commitments.core_selector().unwrap().is_some());
let mut cq = BTreeMap::new();
cq.insert(CoreIndex(0), vec![v1_ccr.descriptor.para_id()].into());
cq.insert(CoreIndex(1), vec![v1_ccr.descriptor.para_id()].into());
assert!(v1_ccr.check_core_index(&transpose_claim_queue(cq)).is_ok());
assert_eq!(v1_ccr.descriptor.core_index(), None);
}
#[test]
fn test_core_select_is_optional() {
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();
let mut cq = BTreeMap::new();
cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
assert!(new_ccr.check_core_index(&transpose_claim_queue(cq)).is_ok());
let mut cq = BTreeMap::new();
cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into());
cq.insert(CoreIndex(1), vec![new_ccr.descriptor.para_id()].into());
assert_eq!(new_ccr.check_core_index(&transpose_claim_queue(cq)), Ok(()));
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());
}
}