use polkadot_node_subsystem::messages::HypotheticalCandidate;
use polkadot_primitives::{
async_backing::Constraints as PrimitiveConstraints, vstaging::skip_ump_signals, BlockNumber,
CandidateCommitments, CandidateHash, Hash, HeadData, Id as ParaId, PersistedValidationData,
UpgradeRestriction, ValidationCodeHash,
};
use std::{collections::HashMap, sync::Arc};
#[derive(Debug, Clone, PartialEq)]
pub struct InboundHrmpLimitations {
pub valid_watermarks: Vec<BlockNumber>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OutboundHrmpChannelLimitations {
pub bytes_remaining: usize,
pub messages_remaining: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Constraints {
pub min_relay_parent_number: BlockNumber,
pub max_pov_size: usize,
pub max_code_size: usize,
pub ump_remaining: usize,
pub ump_remaining_bytes: usize,
pub max_ump_num_per_candidate: usize,
pub dmp_remaining_messages: Vec<BlockNumber>,
pub hrmp_inbound: InboundHrmpLimitations,
pub hrmp_channels_out: HashMap<ParaId, OutboundHrmpChannelLimitations>,
pub max_hrmp_num_per_candidate: usize,
pub required_parent: HeadData,
pub validation_code_hash: ValidationCodeHash,
pub upgrade_restriction: Option<UpgradeRestriction>,
pub future_validation_code: Option<(BlockNumber, ValidationCodeHash)>,
}
impl From<PrimitiveConstraints> for Constraints {
fn from(c: PrimitiveConstraints) -> Self {
Constraints {
min_relay_parent_number: c.min_relay_parent_number,
max_pov_size: c.max_pov_size as _,
max_code_size: c.max_code_size as _,
ump_remaining: c.ump_remaining as _,
ump_remaining_bytes: c.ump_remaining_bytes as _,
max_ump_num_per_candidate: c.max_ump_num_per_candidate as _,
dmp_remaining_messages: c.dmp_remaining_messages,
hrmp_inbound: InboundHrmpLimitations {
valid_watermarks: c.hrmp_inbound.valid_watermarks,
},
hrmp_channels_out: c
.hrmp_channels_out
.into_iter()
.map(|(para_id, limits)| {
(
para_id,
OutboundHrmpChannelLimitations {
bytes_remaining: limits.bytes_remaining as _,
messages_remaining: limits.messages_remaining as _,
},
)
})
.collect(),
max_hrmp_num_per_candidate: c.max_hrmp_num_per_candidate as _,
required_parent: c.required_parent,
validation_code_hash: c.validation_code_hash,
upgrade_restriction: c.upgrade_restriction,
future_validation_code: c.future_validation_code,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ModificationError {
DisallowedHrmpWatermark(BlockNumber),
NoSuchHrmpChannel(ParaId),
HrmpMessagesOverflow {
para_id: ParaId,
messages_remaining: usize,
messages_submitted: usize,
},
HrmpBytesOverflow {
para_id: ParaId,
bytes_remaining: usize,
bytes_submitted: usize,
},
UmpMessagesOverflow {
messages_remaining: usize,
messages_submitted: usize,
},
UmpBytesOverflow {
bytes_remaining: usize,
bytes_submitted: usize,
},
DmpMessagesUnderflow {
messages_remaining: usize,
messages_processed: usize,
},
AppliedNonexistentCodeUpgrade,
}
impl Constraints {
pub fn check_modifications(
&self,
modifications: &ConstraintModifications,
) -> Result<(), ModificationError> {
if let Some(HrmpWatermarkUpdate::Trunk(hrmp_watermark)) = modifications.hrmp_watermark {
if self.hrmp_inbound.valid_watermarks.iter().all(|w| w != &hrmp_watermark) {
return Err(ModificationError::DisallowedHrmpWatermark(hrmp_watermark))
}
}
for (id, outbound_hrmp_mod) in &modifications.outbound_hrmp {
if let Some(outbound) = self.hrmp_channels_out.get(&id) {
outbound.bytes_remaining.checked_sub(outbound_hrmp_mod.bytes_submitted).ok_or(
ModificationError::HrmpBytesOverflow {
para_id: *id,
bytes_remaining: outbound.bytes_remaining,
bytes_submitted: outbound_hrmp_mod.bytes_submitted,
},
)?;
outbound
.messages_remaining
.checked_sub(outbound_hrmp_mod.messages_submitted)
.ok_or(ModificationError::HrmpMessagesOverflow {
para_id: *id,
messages_remaining: outbound.messages_remaining,
messages_submitted: outbound_hrmp_mod.messages_submitted,
})?;
} else {
return Err(ModificationError::NoSuchHrmpChannel(*id))
}
}
self.ump_remaining.checked_sub(modifications.ump_messages_sent).ok_or(
ModificationError::UmpMessagesOverflow {
messages_remaining: self.ump_remaining,
messages_submitted: modifications.ump_messages_sent,
},
)?;
self.ump_remaining_bytes.checked_sub(modifications.ump_bytes_sent).ok_or(
ModificationError::UmpBytesOverflow {
bytes_remaining: self.ump_remaining_bytes,
bytes_submitted: modifications.ump_bytes_sent,
},
)?;
self.dmp_remaining_messages
.len()
.checked_sub(modifications.dmp_messages_processed)
.ok_or(ModificationError::DmpMessagesUnderflow {
messages_remaining: self.dmp_remaining_messages.len(),
messages_processed: modifications.dmp_messages_processed,
})?;
if self.future_validation_code.is_none() && modifications.code_upgrade_applied {
return Err(ModificationError::AppliedNonexistentCodeUpgrade)
}
Ok(())
}
pub fn apply_modifications(
&self,
modifications: &ConstraintModifications,
) -> Result<Self, ModificationError> {
let mut new = self.clone();
if let Some(required_parent) = modifications.required_parent.as_ref() {
new.required_parent = required_parent.clone();
}
if let Some(ref hrmp_watermark) = modifications.hrmp_watermark {
match new.hrmp_inbound.valid_watermarks.binary_search(&hrmp_watermark.watermark()) {
Ok(pos) => {
let _ = new.hrmp_inbound.valid_watermarks.drain(..pos + 1);
},
Err(pos) => match hrmp_watermark {
HrmpWatermarkUpdate::Head(_) => {
let _ = new.hrmp_inbound.valid_watermarks.drain(..pos);
},
HrmpWatermarkUpdate::Trunk(n) => {
return Err(ModificationError::DisallowedHrmpWatermark(*n))
},
},
}
}
for (id, outbound_hrmp_mod) in &modifications.outbound_hrmp {
if let Some(outbound) = new.hrmp_channels_out.get_mut(&id) {
outbound.bytes_remaining = outbound
.bytes_remaining
.checked_sub(outbound_hrmp_mod.bytes_submitted)
.ok_or(ModificationError::HrmpBytesOverflow {
para_id: *id,
bytes_remaining: outbound.bytes_remaining,
bytes_submitted: outbound_hrmp_mod.bytes_submitted,
})?;
outbound.messages_remaining = outbound
.messages_remaining
.checked_sub(outbound_hrmp_mod.messages_submitted)
.ok_or(ModificationError::HrmpMessagesOverflow {
para_id: *id,
messages_remaining: outbound.messages_remaining,
messages_submitted: outbound_hrmp_mod.messages_submitted,
})?;
} else {
return Err(ModificationError::NoSuchHrmpChannel(*id))
}
}
new.ump_remaining = new.ump_remaining.checked_sub(modifications.ump_messages_sent).ok_or(
ModificationError::UmpMessagesOverflow {
messages_remaining: new.ump_remaining,
messages_submitted: modifications.ump_messages_sent,
},
)?;
new.ump_remaining_bytes = new
.ump_remaining_bytes
.checked_sub(modifications.ump_bytes_sent)
.ok_or(ModificationError::UmpBytesOverflow {
bytes_remaining: new.ump_remaining_bytes,
bytes_submitted: modifications.ump_bytes_sent,
})?;
if modifications.dmp_messages_processed > new.dmp_remaining_messages.len() {
return Err(ModificationError::DmpMessagesUnderflow {
messages_remaining: new.dmp_remaining_messages.len(),
messages_processed: modifications.dmp_messages_processed,
})
} else {
new.dmp_remaining_messages =
new.dmp_remaining_messages[modifications.dmp_messages_processed..].to_vec();
}
if modifications.code_upgrade_applied {
new.validation_code_hash = new
.future_validation_code
.take()
.ok_or(ModificationError::AppliedNonexistentCodeUpgrade)?
.1;
}
Ok(new)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RelayChainBlockInfo {
pub hash: Hash,
pub number: BlockNumber,
pub storage_root: Hash,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct OutboundHrmpChannelModification {
pub bytes_submitted: usize,
pub messages_submitted: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum HrmpWatermarkUpdate {
Head(BlockNumber),
Trunk(BlockNumber),
}
impl HrmpWatermarkUpdate {
fn watermark(&self) -> BlockNumber {
match *self {
HrmpWatermarkUpdate::Head(n) | HrmpWatermarkUpdate::Trunk(n) => n,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ConstraintModifications {
pub required_parent: Option<HeadData>,
pub hrmp_watermark: Option<HrmpWatermarkUpdate>,
pub outbound_hrmp: HashMap<ParaId, OutboundHrmpChannelModification>,
pub ump_messages_sent: usize,
pub ump_bytes_sent: usize,
pub dmp_messages_processed: usize,
pub code_upgrade_applied: bool,
}
impl ConstraintModifications {
pub fn identity() -> Self {
ConstraintModifications {
required_parent: None,
hrmp_watermark: None,
outbound_hrmp: HashMap::new(),
ump_messages_sent: 0,
ump_bytes_sent: 0,
dmp_messages_processed: 0,
code_upgrade_applied: false,
}
}
pub fn stack(&mut self, other: &Self) {
if let Some(ref new_parent) = other.required_parent {
self.required_parent = Some(new_parent.clone());
}
if let Some(ref new_hrmp_watermark) = other.hrmp_watermark {
self.hrmp_watermark = Some(new_hrmp_watermark.clone());
}
for (id, mods) in &other.outbound_hrmp {
let record = self.outbound_hrmp.entry(*id).or_default();
record.messages_submitted += mods.messages_submitted;
record.bytes_submitted += mods.bytes_submitted;
}
self.ump_messages_sent += other.ump_messages_sent;
self.ump_bytes_sent += other.ump_bytes_sent;
self.dmp_messages_processed += other.dmp_messages_processed;
self.code_upgrade_applied |= other.code_upgrade_applied;
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ProspectiveCandidate {
pub commitments: CandidateCommitments,
pub persisted_validation_data: PersistedValidationData,
pub pov_hash: Hash,
pub validation_code_hash: ValidationCodeHash,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FragmentValidityError {
ValidationCodeMismatch(ValidationCodeHash, ValidationCodeHash),
PersistedValidationDataMismatch(PersistedValidationData, PersistedValidationData),
OutputsInvalid(ModificationError),
CodeSizeTooLarge(usize, usize),
RelayParentTooOld(BlockNumber, BlockNumber),
DmpAdvancementRule,
UmpMessagesPerCandidateOverflow {
messages_allowed: usize,
messages_submitted: usize,
},
HrmpMessagesPerCandidateOverflow {
messages_allowed: usize,
messages_submitted: usize,
},
CodeUpgradeRestricted,
HrmpMessagesDescendingOrDuplicate(usize),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Fragment {
relay_parent: RelayChainBlockInfo,
operating_constraints: Constraints,
candidate: Arc<ProspectiveCandidate>,
modifications: ConstraintModifications,
}
impl Fragment {
pub fn new(
relay_parent: RelayChainBlockInfo,
operating_constraints: Constraints,
candidate: Arc<ProspectiveCandidate>,
) -> Result<Self, FragmentValidityError> {
let modifications = Self::check_against_constraints(
&relay_parent,
&operating_constraints,
&candidate.commitments,
&candidate.validation_code_hash,
&candidate.persisted_validation_data,
)?;
Ok(Fragment { relay_parent, operating_constraints, candidate, modifications })
}
pub fn check_against_constraints(
relay_parent: &RelayChainBlockInfo,
operating_constraints: &Constraints,
commitments: &CandidateCommitments,
validation_code_hash: &ValidationCodeHash,
persisted_validation_data: &PersistedValidationData,
) -> Result<ConstraintModifications, FragmentValidityError> {
let upward_messages =
skip_ump_signals(commitments.upward_messages.iter()).collect::<Vec<_>>();
let ump_messages_sent = upward_messages.len();
let ump_bytes_sent = upward_messages.iter().map(|msg| msg.len()).sum();
let modifications = {
ConstraintModifications {
required_parent: Some(commitments.head_data.clone()),
hrmp_watermark: Some({
if commitments.hrmp_watermark == relay_parent.number {
HrmpWatermarkUpdate::Head(commitments.hrmp_watermark)
} else {
HrmpWatermarkUpdate::Trunk(commitments.hrmp_watermark)
}
}),
outbound_hrmp: {
let mut outbound_hrmp = HashMap::<_, OutboundHrmpChannelModification>::new();
let mut last_recipient = None::<ParaId>;
for (i, message) in commitments.horizontal_messages.iter().enumerate() {
if let Some(last) = last_recipient {
if last >= message.recipient {
return Err(
FragmentValidityError::HrmpMessagesDescendingOrDuplicate(i),
)
}
}
last_recipient = Some(message.recipient);
let record = outbound_hrmp.entry(message.recipient).or_default();
record.bytes_submitted += message.data.len();
record.messages_submitted += 1;
}
outbound_hrmp
},
ump_messages_sent,
ump_bytes_sent,
dmp_messages_processed: commitments.processed_downward_messages as _,
code_upgrade_applied: operating_constraints
.future_validation_code
.map_or(false, |(at, _)| relay_parent.number >= at),
}
};
validate_against_constraints(
&operating_constraints,
&relay_parent,
commitments,
persisted_validation_data,
validation_code_hash,
&modifications,
)?;
Ok(modifications)
}
pub fn relay_parent(&self) -> &RelayChainBlockInfo {
&self.relay_parent
}
pub fn operating_constraints(&self) -> &Constraints {
&self.operating_constraints
}
pub fn candidate(&self) -> &ProspectiveCandidate {
&self.candidate
}
pub fn candidate_clone(&self) -> Arc<ProspectiveCandidate> {
self.candidate.clone()
}
pub fn constraint_modifications(&self) -> &ConstraintModifications {
&self.modifications
}
}
fn validate_against_constraints(
constraints: &Constraints,
relay_parent: &RelayChainBlockInfo,
commitments: &CandidateCommitments,
persisted_validation_data: &PersistedValidationData,
validation_code_hash: &ValidationCodeHash,
modifications: &ConstraintModifications,
) -> Result<(), FragmentValidityError> {
let expected_pvd = PersistedValidationData {
parent_head: constraints.required_parent.clone(),
relay_parent_number: relay_parent.number,
relay_parent_storage_root: relay_parent.storage_root,
max_pov_size: constraints.max_pov_size as u32,
};
if expected_pvd != *persisted_validation_data {
return Err(FragmentValidityError::PersistedValidationDataMismatch(
expected_pvd,
persisted_validation_data.clone(),
))
}
if constraints.validation_code_hash != *validation_code_hash {
return Err(FragmentValidityError::ValidationCodeMismatch(
constraints.validation_code_hash,
*validation_code_hash,
))
}
if relay_parent.number < constraints.min_relay_parent_number {
return Err(FragmentValidityError::RelayParentTooOld(
constraints.min_relay_parent_number,
relay_parent.number,
))
}
if commitments.new_validation_code.is_some() {
match constraints.upgrade_restriction {
None => {},
Some(UpgradeRestriction::Present) =>
return Err(FragmentValidityError::CodeUpgradeRestricted),
}
}
let announced_code_size =
commitments.new_validation_code.as_ref().map_or(0, |code| code.0.len());
if announced_code_size > constraints.max_code_size {
return Err(FragmentValidityError::CodeSizeTooLarge(
constraints.max_code_size,
announced_code_size,
))
}
if modifications.dmp_messages_processed == 0 {
if constraints
.dmp_remaining_messages
.get(0)
.map_or(false, |&msg_sent_at| msg_sent_at <= relay_parent.number)
{
return Err(FragmentValidityError::DmpAdvancementRule)
}
}
if commitments.horizontal_messages.len() > constraints.max_hrmp_num_per_candidate {
return Err(FragmentValidityError::HrmpMessagesPerCandidateOverflow {
messages_allowed: constraints.max_hrmp_num_per_candidate,
messages_submitted: commitments.horizontal_messages.len(),
})
}
if modifications.ump_messages_sent > constraints.max_ump_num_per_candidate {
return Err(FragmentValidityError::UmpMessagesPerCandidateOverflow {
messages_allowed: constraints.max_ump_num_per_candidate,
messages_submitted: commitments.upward_messages.len(),
})
}
constraints
.check_modifications(&modifications)
.map_err(FragmentValidityError::OutputsInvalid)
}
pub trait HypotheticalOrConcreteCandidate {
fn commitments(&self) -> Option<&CandidateCommitments>;
fn persisted_validation_data(&self) -> Option<&PersistedValidationData>;
fn validation_code_hash(&self) -> Option<ValidationCodeHash>;
fn parent_head_data_hash(&self) -> Hash;
fn output_head_data_hash(&self) -> Option<Hash>;
fn relay_parent(&self) -> Hash;
fn candidate_hash(&self) -> CandidateHash;
}
impl HypotheticalOrConcreteCandidate for HypotheticalCandidate {
fn commitments(&self) -> Option<&CandidateCommitments> {
self.commitments()
}
fn persisted_validation_data(&self) -> Option<&PersistedValidationData> {
self.persisted_validation_data()
}
fn validation_code_hash(&self) -> Option<ValidationCodeHash> {
self.validation_code_hash()
}
fn parent_head_data_hash(&self) -> Hash {
self.parent_head_data_hash()
}
fn output_head_data_hash(&self) -> Option<Hash> {
self.output_head_data_hash()
}
fn relay_parent(&self) -> Hash {
self.relay_parent()
}
fn candidate_hash(&self) -> CandidateHash {
self.candidate_hash()
}
}
#[cfg(test)]
mod tests {
use super::*;
use codec::Encode;
use polkadot_primitives::{
vstaging::{ClaimQueueOffset, CoreSelector, UMPSignal, UMP_SEPARATOR},
HorizontalMessages, OutboundHrmpMessage, ValidationCode,
};
#[test]
fn stack_modifications() {
let para_a = ParaId::from(1u32);
let para_b = ParaId::from(2u32);
let para_c = ParaId::from(3u32);
let a = ConstraintModifications {
required_parent: None,
hrmp_watermark: None,
outbound_hrmp: {
let mut map = HashMap::new();
map.insert(
para_a,
OutboundHrmpChannelModification { bytes_submitted: 100, messages_submitted: 5 },
);
map.insert(
para_b,
OutboundHrmpChannelModification { bytes_submitted: 100, messages_submitted: 5 },
);
map
},
ump_messages_sent: 6,
ump_bytes_sent: 1000,
dmp_messages_processed: 5,
code_upgrade_applied: true,
};
let b = ConstraintModifications {
required_parent: None,
hrmp_watermark: None,
outbound_hrmp: {
let mut map = HashMap::new();
map.insert(
para_b,
OutboundHrmpChannelModification { bytes_submitted: 100, messages_submitted: 5 },
);
map.insert(
para_c,
OutboundHrmpChannelModification { bytes_submitted: 100, messages_submitted: 5 },
);
map
},
ump_messages_sent: 6,
ump_bytes_sent: 1000,
dmp_messages_processed: 5,
code_upgrade_applied: true,
};
let mut c = a.clone();
c.stack(&b);
assert_eq!(
c,
ConstraintModifications {
required_parent: None,
hrmp_watermark: None,
outbound_hrmp: {
let mut map = HashMap::new();
map.insert(
para_a,
OutboundHrmpChannelModification {
bytes_submitted: 100,
messages_submitted: 5,
},
);
map.insert(
para_b,
OutboundHrmpChannelModification {
bytes_submitted: 200,
messages_submitted: 10,
},
);
map.insert(
para_c,
OutboundHrmpChannelModification {
bytes_submitted: 100,
messages_submitted: 5,
},
);
map
},
ump_messages_sent: 12,
ump_bytes_sent: 2000,
dmp_messages_processed: 10,
code_upgrade_applied: true,
},
);
let mut d = ConstraintModifications::identity();
d.stack(&a);
d.stack(&b);
assert_eq!(c, d);
}
fn make_constraints() -> Constraints {
let para_a = ParaId::from(1u32);
let para_b = ParaId::from(2u32);
let para_c = ParaId::from(3u32);
Constraints {
min_relay_parent_number: 5,
max_pov_size: 1000,
max_code_size: 1000,
ump_remaining: 10,
ump_remaining_bytes: 1024,
max_ump_num_per_candidate: 5,
dmp_remaining_messages: Vec::new(),
hrmp_inbound: InboundHrmpLimitations { valid_watermarks: vec![6, 8] },
hrmp_channels_out: {
let mut map = HashMap::new();
map.insert(
para_a,
OutboundHrmpChannelLimitations { messages_remaining: 5, bytes_remaining: 512 },
);
map.insert(
para_b,
OutboundHrmpChannelLimitations {
messages_remaining: 10,
bytes_remaining: 1024,
},
);
map.insert(
para_c,
OutboundHrmpChannelLimitations { messages_remaining: 1, bytes_remaining: 128 },
);
map
},
max_hrmp_num_per_candidate: 5,
required_parent: HeadData::from(vec![1, 2, 3]),
validation_code_hash: ValidationCode(vec![4, 5, 6]).hash(),
upgrade_restriction: None,
future_validation_code: None,
}
}
#[test]
fn constraints_disallowed_trunk_watermark() {
let constraints = make_constraints();
let mut modifications = ConstraintModifications::identity();
modifications.hrmp_watermark = Some(HrmpWatermarkUpdate::Trunk(7));
assert_eq!(
constraints.check_modifications(&modifications),
Err(ModificationError::DisallowedHrmpWatermark(7)),
);
assert_eq!(
constraints.apply_modifications(&modifications),
Err(ModificationError::DisallowedHrmpWatermark(7)),
);
}
#[test]
fn constraints_always_allow_head_watermark() {
let constraints = make_constraints();
let mut modifications = ConstraintModifications::identity();
modifications.hrmp_watermark = Some(HrmpWatermarkUpdate::Head(7));
assert!(constraints.check_modifications(&modifications).is_ok());
let new_constraints = constraints.apply_modifications(&modifications).unwrap();
assert_eq!(new_constraints.hrmp_inbound.valid_watermarks, vec![8]);
}
#[test]
fn constraints_no_such_hrmp_channel() {
let constraints = make_constraints();
let mut modifications = ConstraintModifications::identity();
let bad_para = ParaId::from(100u32);
modifications.outbound_hrmp.insert(
bad_para,
OutboundHrmpChannelModification { bytes_submitted: 0, messages_submitted: 0 },
);
assert_eq!(
constraints.check_modifications(&modifications),
Err(ModificationError::NoSuchHrmpChannel(bad_para)),
);
assert_eq!(
constraints.apply_modifications(&modifications),
Err(ModificationError::NoSuchHrmpChannel(bad_para)),
);
}
#[test]
fn constraints_hrmp_messages_overflow() {
let constraints = make_constraints();
let mut modifications = ConstraintModifications::identity();
let para_a = ParaId::from(1u32);
modifications.outbound_hrmp.insert(
para_a,
OutboundHrmpChannelModification { bytes_submitted: 0, messages_submitted: 6 },
);
assert_eq!(
constraints.check_modifications(&modifications),
Err(ModificationError::HrmpMessagesOverflow {
para_id: para_a,
messages_remaining: 5,
messages_submitted: 6,
}),
);
assert_eq!(
constraints.apply_modifications(&modifications),
Err(ModificationError::HrmpMessagesOverflow {
para_id: para_a,
messages_remaining: 5,
messages_submitted: 6,
}),
);
}
#[test]
fn constraints_hrmp_bytes_overflow() {
let constraints = make_constraints();
let mut modifications = ConstraintModifications::identity();
let para_a = ParaId::from(1u32);
modifications.outbound_hrmp.insert(
para_a,
OutboundHrmpChannelModification { bytes_submitted: 513, messages_submitted: 1 },
);
assert_eq!(
constraints.check_modifications(&modifications),
Err(ModificationError::HrmpBytesOverflow {
para_id: para_a,
bytes_remaining: 512,
bytes_submitted: 513,
}),
);
assert_eq!(
constraints.apply_modifications(&modifications),
Err(ModificationError::HrmpBytesOverflow {
para_id: para_a,
bytes_remaining: 512,
bytes_submitted: 513,
}),
);
}
#[test]
fn constraints_ump_messages_overflow() {
let constraints = make_constraints();
let mut modifications = ConstraintModifications::identity();
modifications.ump_messages_sent = 11;
assert_eq!(
constraints.check_modifications(&modifications),
Err(ModificationError::UmpMessagesOverflow {
messages_remaining: 10,
messages_submitted: 11,
}),
);
assert_eq!(
constraints.apply_modifications(&modifications),
Err(ModificationError::UmpMessagesOverflow {
messages_remaining: 10,
messages_submitted: 11,
}),
);
}
#[test]
fn constraints_ump_bytes_overflow() {
let constraints = make_constraints();
let mut modifications = ConstraintModifications::identity();
modifications.ump_bytes_sent = 1025;
assert_eq!(
constraints.check_modifications(&modifications),
Err(ModificationError::UmpBytesOverflow {
bytes_remaining: 1024,
bytes_submitted: 1025,
}),
);
assert_eq!(
constraints.apply_modifications(&modifications),
Err(ModificationError::UmpBytesOverflow {
bytes_remaining: 1024,
bytes_submitted: 1025,
}),
);
}
#[test]
fn constraints_dmp_messages() {
let mut constraints = make_constraints();
let mut modifications = ConstraintModifications::identity();
assert!(constraints.check_modifications(&modifications).is_ok());
assert!(constraints.apply_modifications(&modifications).is_ok());
modifications.dmp_messages_processed = 6;
assert_eq!(
constraints.check_modifications(&modifications),
Err(ModificationError::DmpMessagesUnderflow {
messages_remaining: 0,
messages_processed: 6,
}),
);
assert_eq!(
constraints.apply_modifications(&modifications),
Err(ModificationError::DmpMessagesUnderflow {
messages_remaining: 0,
messages_processed: 6,
}),
);
constraints.dmp_remaining_messages = vec![1, 4, 8, 10];
modifications.dmp_messages_processed = 2;
assert!(constraints.check_modifications(&modifications).is_ok());
let constraints = constraints
.apply_modifications(&modifications)
.expect("modifications are valid");
assert_eq!(&constraints.dmp_remaining_messages, &[8, 10]);
}
#[test]
fn constraints_nonexistent_code_upgrade() {
let constraints = make_constraints();
let mut modifications = ConstraintModifications::identity();
modifications.code_upgrade_applied = true;
assert_eq!(
constraints.check_modifications(&modifications),
Err(ModificationError::AppliedNonexistentCodeUpgrade),
);
assert_eq!(
constraints.apply_modifications(&modifications),
Err(ModificationError::AppliedNonexistentCodeUpgrade),
);
}
fn make_candidate(
constraints: &Constraints,
relay_parent: &RelayChainBlockInfo,
) -> ProspectiveCandidate {
ProspectiveCandidate {
commitments: CandidateCommitments {
upward_messages: Default::default(),
horizontal_messages: Default::default(),
new_validation_code: None,
head_data: HeadData::from(vec![1, 2, 3, 4, 5]),
processed_downward_messages: 0,
hrmp_watermark: relay_parent.number,
},
persisted_validation_data: PersistedValidationData {
parent_head: constraints.required_parent.clone(),
relay_parent_number: relay_parent.number,
relay_parent_storage_root: relay_parent.storage_root,
max_pov_size: constraints.max_pov_size as u32,
},
pov_hash: Hash::repeat_byte(1),
validation_code_hash: constraints.validation_code_hash,
}
}
#[test]
fn fragment_validation_code_mismatch() {
let relay_parent = RelayChainBlockInfo {
number: 6,
hash: Hash::repeat_byte(0x0a),
storage_root: Hash::repeat_byte(0xff),
};
let constraints = make_constraints();
let mut candidate = make_candidate(&constraints, &relay_parent);
let expected_code = constraints.validation_code_hash;
let got_code = ValidationCode(vec![9, 9, 9]).hash();
candidate.validation_code_hash = got_code;
assert_eq!(
Fragment::new(relay_parent, constraints, Arc::new(candidate.clone())),
Err(FragmentValidityError::ValidationCodeMismatch(expected_code, got_code,)),
)
}
#[test]
fn fragment_pvd_mismatch() {
let relay_parent = RelayChainBlockInfo {
number: 6,
hash: Hash::repeat_byte(0x0a),
storage_root: Hash::repeat_byte(0xff),
};
let relay_parent_b = RelayChainBlockInfo {
number: 6,
hash: Hash::repeat_byte(0x0b),
storage_root: Hash::repeat_byte(0xee),
};
let constraints = make_constraints();
let candidate = make_candidate(&constraints, &relay_parent);
let expected_pvd = PersistedValidationData {
parent_head: constraints.required_parent.clone(),
relay_parent_number: relay_parent_b.number,
relay_parent_storage_root: relay_parent_b.storage_root,
max_pov_size: constraints.max_pov_size as u32,
};
let got_pvd = candidate.persisted_validation_data.clone();
assert_eq!(
Fragment::new(relay_parent_b, constraints, Arc::new(candidate.clone())),
Err(FragmentValidityError::PersistedValidationDataMismatch(expected_pvd, got_pvd,)),
);
}
#[test]
fn fragment_code_size_too_large() {
let relay_parent = RelayChainBlockInfo {
number: 6,
hash: Hash::repeat_byte(0x0a),
storage_root: Hash::repeat_byte(0xff),
};
let constraints = make_constraints();
let mut candidate = make_candidate(&constraints, &relay_parent);
let max_code_size = constraints.max_code_size;
candidate.commitments.new_validation_code = Some(vec![0; max_code_size + 1].into());
assert_eq!(
Fragment::new(relay_parent, constraints, Arc::new(candidate.clone())),
Err(FragmentValidityError::CodeSizeTooLarge(max_code_size, max_code_size + 1,)),
);
}
#[test]
fn ump_signals_ignored() {
let relay_parent = RelayChainBlockInfo {
number: 6,
hash: Hash::repeat_byte(0xbe),
storage_root: Hash::repeat_byte(0xff),
};
let constraints = make_constraints();
let mut candidate = make_candidate(&constraints, &relay_parent);
let max_ump = constraints.max_ump_num_per_candidate;
candidate
.commitments
.upward_messages
.try_extend((0..max_ump).map(|i| vec![i as u8]))
.unwrap();
candidate.commitments.upward_messages.force_push(UMP_SEPARATOR);
candidate
.commitments
.upward_messages
.force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode());
Fragment::new(relay_parent, constraints, Arc::new(candidate)).unwrap();
}
#[test]
fn fragment_relay_parent_too_old() {
let relay_parent = RelayChainBlockInfo {
number: 3,
hash: Hash::repeat_byte(0x0a),
storage_root: Hash::repeat_byte(0xff),
};
let constraints = make_constraints();
let candidate = make_candidate(&constraints, &relay_parent);
assert_eq!(
Fragment::new(relay_parent, constraints, Arc::new(candidate.clone())),
Err(FragmentValidityError::RelayParentTooOld(5, 3,)),
);
}
#[test]
fn fragment_hrmp_messages_overflow() {
let relay_parent = RelayChainBlockInfo {
number: 6,
hash: Hash::repeat_byte(0x0a),
storage_root: Hash::repeat_byte(0xff),
};
let constraints = make_constraints();
let mut candidate = make_candidate(&constraints, &relay_parent);
let max_hrmp = constraints.max_hrmp_num_per_candidate;
candidate
.commitments
.horizontal_messages
.try_extend((0..max_hrmp + 1).map(|i| OutboundHrmpMessage {
recipient: ParaId::from(i as u32),
data: vec![1, 2, 3],
}))
.unwrap();
assert_eq!(
Fragment::new(relay_parent, constraints, Arc::new(candidate.clone())),
Err(FragmentValidityError::HrmpMessagesPerCandidateOverflow {
messages_allowed: max_hrmp,
messages_submitted: max_hrmp + 1,
}),
);
}
#[test]
fn fragment_dmp_advancement_rule() {
let relay_parent = RelayChainBlockInfo {
number: 6,
hash: Hash::repeat_byte(0x0a),
storage_root: Hash::repeat_byte(0xff),
};
let mut constraints = make_constraints();
let mut candidate = make_candidate(&constraints, &relay_parent);
assert!(Fragment::new(
relay_parent.clone(),
constraints.clone(),
Arc::new(candidate.clone())
)
.is_ok());
constraints.dmp_remaining_messages = vec![relay_parent.number + 1];
assert!(Fragment::new(
relay_parent.clone(),
constraints.clone(),
Arc::new(candidate.clone())
)
.is_ok());
for block_number in 0..=relay_parent.number {
constraints.dmp_remaining_messages = vec![block_number];
assert_eq!(
Fragment::new(
relay_parent.clone(),
constraints.clone(),
Arc::new(candidate.clone())
),
Err(FragmentValidityError::DmpAdvancementRule),
);
}
candidate.commitments.processed_downward_messages = 1;
assert!(Fragment::new(relay_parent, constraints, Arc::new(candidate.clone())).is_ok());
}
#[test]
fn fragment_ump_messages_overflow() {
let relay_parent = RelayChainBlockInfo {
number: 6,
hash: Hash::repeat_byte(0x0a),
storage_root: Hash::repeat_byte(0xff),
};
let constraints = make_constraints();
let mut candidate = make_candidate(&constraints, &relay_parent);
let max_ump = constraints.max_ump_num_per_candidate;
candidate
.commitments
.upward_messages
.try_extend((0..max_ump + 1).map(|i| vec![i as u8]))
.unwrap();
assert_eq!(
Fragment::new(relay_parent, constraints, Arc::new(candidate.clone())),
Err(FragmentValidityError::UmpMessagesPerCandidateOverflow {
messages_allowed: max_ump,
messages_submitted: max_ump + 1,
}),
);
}
#[test]
fn fragment_code_upgrade_restricted() {
let relay_parent = RelayChainBlockInfo {
number: 6,
hash: Hash::repeat_byte(0x0a),
storage_root: Hash::repeat_byte(0xff),
};
let mut constraints = make_constraints();
let mut candidate = make_candidate(&constraints, &relay_parent);
constraints.upgrade_restriction = Some(UpgradeRestriction::Present);
candidate.commitments.new_validation_code = Some(ValidationCode(vec![1, 2, 3]));
assert_eq!(
Fragment::new(relay_parent, constraints, Arc::new(candidate.clone())),
Err(FragmentValidityError::CodeUpgradeRestricted),
);
}
#[test]
fn fragment_hrmp_messages_descending_or_duplicate() {
let relay_parent = RelayChainBlockInfo {
number: 6,
hash: Hash::repeat_byte(0x0a),
storage_root: Hash::repeat_byte(0xff),
};
let constraints = make_constraints();
let mut candidate = make_candidate(&constraints, &relay_parent);
candidate.commitments.horizontal_messages = HorizontalMessages::truncate_from(vec![
OutboundHrmpMessage { recipient: ParaId::from(0 as u32), data: vec![1, 2, 3] },
OutboundHrmpMessage { recipient: ParaId::from(0 as u32), data: vec![4, 5, 6] },
]);
assert_eq!(
Fragment::new(relay_parent.clone(), constraints.clone(), Arc::new(candidate.clone())),
Err(FragmentValidityError::HrmpMessagesDescendingOrDuplicate(1)),
);
candidate.commitments.horizontal_messages = HorizontalMessages::truncate_from(vec![
OutboundHrmpMessage { recipient: ParaId::from(1 as u32), data: vec![1, 2, 3] },
OutboundHrmpMessage { recipient: ParaId::from(0 as u32), data: vec![4, 5, 6] },
]);
assert_eq!(
Fragment::new(relay_parent, constraints, Arc::new(candidate.clone())),
Err(FragmentValidityError::HrmpMessagesDescendingOrDuplicate(1)),
);
}
}