// Copyright 2017 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Polkadot is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . //! Main parachains logic. For now this is just the determination of which validators do what. use rstd::prelude::*; use rstd::{result, collections::btree_map::BTreeMap}; use parity_codec::Decode; use srml_support::{decl_storage, decl_module, ensure}; use bitvec::{bitvec, BigEndian}; use sr_primitives::traits::{Hash as HashT, EnsureOrigin, BlakeTwo256, Saturating, One}; use sr_primitives::weights::SimpleDispatchInfo; use primitives::{Hash, Balance, parachain::{ self, Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, AccountIdConversion, ParachainDispatchOrigin, UpwardMessage, BlockIngressRoots, ActiveParas, CollatorId }}; use session; use srml_support::{ StorageValue, StorageMap, storage::AppendableStorageMap, Parameter, Dispatchable, dispatch::Result, traits::{Currency, WithdrawReason, ExistenceRequirement} }; use inherents::{ProvideInherent, InherentData, RuntimeString, MakeFatalError, InherentIdentifier}; use system::{ensure_none, self}; use crate::registrar::Registrar; // ranges for iteration of general block number don't work, so this // is a utility to get around that. struct BlockNumberRange { low: N, high: N, } impl Iterator for BlockNumberRange { type Item = N; fn next(&mut self) -> Option { if self.low >= self.high { return None } let item = self.low.clone(); self.low = self.low.clone().saturating_add(One::one()); Some(item) } } // creates a range iterator between `low` and `high`. `low` must be <= `high`. fn number_range(low: N, high: N) -> BlockNumberRange { BlockNumberRange { low, high } } // wrapper trait because an associated type of `Currency` // doesn't work.` pub trait ParachainCurrency { fn free_balance(para_id: ParaId) -> Balance; fn deduct(para_id: ParaId, amount: Balance) -> Result; } impl> ParachainCurrency for T where T::Balance: From + Into, ParaId: AccountIdConversion, { fn free_balance(para_id: ParaId) -> Balance { let para_account = para_id.into_account(); T::free_balance(¶_account).into() } fn deduct(para_id: ParaId, amount: Balance) -> Result { let para_account = para_id.into_account(); // burn the fee. let _ = T::withdraw( ¶_account, amount.into(), WithdrawReason::Fee, ExistenceRequirement::KeepAlive, )?; Ok(()) } } pub trait Trait: session::Trait { /// The outer origin type. type Origin: From + Into>; /// The outer call dispatch type. type Call: Parameter + Dispatchable::Origin>; /// Some way of interacting with balances for fees. type ParachainCurrency: ParachainCurrency; /// Means to determine what the current set of active parachains are. type ActiveParachains: ActiveParas; /// The way that we are able to register parachains. type Registrar: Registrar; } /// Origin for the parachains module. #[derive(PartialEq, Eq, Clone)] #[cfg_attr(feature = "std", derive(Debug))] pub enum RawOrigin { /// It comes from a parachain. Parachain(ParaId), } /// Origin for the parachain module. pub type Origin = RawOrigin; // result of as trie_db::NodeCodec>::hashed_null_node() const EMPTY_TRIE_ROOT: [u8; 32] = [ 3, 23, 10, 46, 117, 151, 183, 183, 227, 216, 76, 5, 57, 29, 19, 154, 98, 177, 87, 231, 135, 134, 216, 192, 130, 242, 157, 207, 76, 17, 19, 20 ]; /// Total number of individual messages allowed in the parachain -> relay-chain message queue. const MAX_QUEUE_COUNT: usize = 100; /// Total size of messages allowed in the parachain -> relay-chain message queue before which no /// further messages may be added to it. If it exceeds this then the queue may contain only a /// single message. const WATERMARK_QUEUE_SIZE: usize = 20000; decl_storage! { trait Store for Module as Parachains { // The parachains registered at present. pub Code get(parachain_code): map ParaId => Option>; // The heads of the parachains registered at present. pub Heads get(parachain_head): map ParaId => Option>; // The watermark heights of the parachains registered at present. // For every parachain, this is the block height from which all messages targeting // that parachain have been processed. Can be `None` only if the parachain doesn't exist. pub Watermarks get(watermark): map ParaId => Option; /// Unrouted ingress. Maps (BlockNumber, to_chain) pairs to [(from_chain, egress_root)]. /// /// There may be an entry under (i, p) in this map for every i between the parachain's /// watermark and the current block. pub UnroutedIngress: map (T::BlockNumber, ParaId) => Option>; /// Messages ready to be dispatched onto the relay chain. It is subject to /// `MAX_MESSAGE_COUNT` and `WATERMARK_MESSAGE_SIZE`. pub RelayDispatchQueue: map ParaId => Vec; /// Size of the dispatch queues. Separated from actual data in order to avoid costly /// decoding when checking receipt validity. First item in tuple is the count of messages // second if the total length (in bytes) of the message payloads. pub RelayDispatchQueueSize: map ParaId => (u32, u32); /// The ordered list of ParaIds that have a `RelayDispatchQueue` entry. NeedsDispatch: Vec; // Did the parachain heads get updated in this block? DidUpdate: bool; } } decl_module! { /// Parachains module. pub struct Module for enum Call where origin: ::Origin { /// Provide candidate receipts for parachains, in ascending order by id. #[weight = SimpleDispatchInfo::FixedNormal(1_000_000)] fn set_heads(origin, heads: Vec) -> Result { ensure_none(origin)?; ensure!(!::exists(), "Parachain heads must be updated only once in the block"); let mut active_parachains = Self::active_parachains(); active_parachains.sort_by_key(|(id, _)| id); // TODO: verify that the heads each come from the right collator, if one is specified. let parachain_count = active_parachains.len(); ensure!(heads.len() <= parachain_count, "Too many parachain candidates"); if !active_parachains.is_empty() { // perform integrity checks before writing to storage. { let mut last_id = None; let mut iter = active_parachains.iter(); for head in &heads { let id = head.parachain_index(); // proposed heads must be ascending order by parachain ID without duplicate. ensure!( last_id.as_ref().map_or(true, |x| x < &id), "candidate out of order" ); // must be unknown since active parachains are always sorted. let (_, maybe_required_collator) = iter.find(|para| para.0 == id) .ok_or("candidate for unregistered parachain {}")?; if let Some(required_collator) = maybe_required_collator { ensure!(required_collator == &head.candidate.collator, "invalid collator"); } Self::check_upward_messages( id, &head.candidate.upward_messages, MAX_QUEUE_COUNT, WATERMARK_QUEUE_SIZE, )?; let id_slice = active_parachains.into_iter().map(|(a, b)| a).collect(); Self::check_egress_queue_roots(&head, &id_slice[..])?; last_id = Some(head.parachain_index()); } } Self::check_candidates(&heads)?; let current_number = >::block_number(); Self::update_routing( current_number, &heads ); Self::dispatch_upward_messages( current_number, MAX_QUEUE_COUNT, WATERMARK_QUEUE_SIZE, Self::dispatch_message, ); } ::put(true); Ok(()) } fn on_finalize(_n: T::BlockNumber) { assert!(::DidUpdate::take(), "Parachain heads must be updated once in the block"); } } } fn majority_of(list_len: usize) -> usize { list_len / 2 + list_len % 2 } fn localized_payload(statement: Statement, parent_hash: ::primitives::Hash) -> Vec { use parity_codec::Encode; let mut encoded = statement.encode(); encoded.extend(parent_hash.as_ref()); encoded } impl Module { /// Initialize the state of a new parachain/parathread. fn initialize_para( id: ParaId, code: Vec, initial_head_data: Vec, ) { ::insert(id, code); ::insert(id, initial_head_data); // Because there are no ordering guarantees that inherents // are applied before regular transactions, a parachain candidate could // be registered before the `UpdateHeads` inherent is processed. If so, messages // could be sent to a parachain in the block it is registered. >::insert(id, >::block_number().saturating_sub(One::one())); } fn cleanup_para( id: ParaId, ) { ::remove(id); ::remove(id); let watermark = >::take(id); // clear all routing entries _to_. But not those _from_. if let Some(watermark) = watermark { let now = >::block_number(); // iterate over all blocks between watermark and now + 1 (since messages might // have already been sent to `id` in this block. for unrouted_block in number_range(watermark, now).map(|n| n.saturating_add(One::one())) { >::remove(&(unrouted_block, id)); } } } /// Dispatch some messages from a parachain. fn dispatch_message( id: ParaId, origin: ParachainDispatchOrigin, data: &[u8], ) { if let Some(message_call) = T::Call::decode(&mut &data[..]) { let origin: ::Origin = match origin { ParachainDispatchOrigin::Signed => system::RawOrigin::Signed(id.into_account()).into(), ParachainDispatchOrigin::Parachain => RawOrigin::Parachain(id).into(), ParachainDispatchOrigin::Root => system::RawOrigin::Root.into(), }; let _ok = message_call.dispatch(origin).is_ok(); // Not much to do with the result as it is. It's up to the parachain to ensure that the // message makes sense. } } /// Ensure all is well with the upward messages. fn check_upward_messages( id: ParaId, upward_messages: &[UpwardMessage], max_queue_count: usize, watermark_queue_size: usize, ) -> Result { // Either there are no more messages to add... if !upward_messages.is_empty() { let (count, size) = ::get(id); ensure!( // ...or we are appending one message onto an empty queue... upward_messages.len() + count as usize == 1 // ...or... || ( // ...the total messages in the queue ends up being no greater than the // limit... upward_messages.len() + count as usize <= max_queue_count && // ...and the total size of the payloads in the queue ends up being no // greater than the limit. upward_messages.iter() .fold(size as usize, |a, x| a + x.data.len()) <= watermark_queue_size ), "Messages added when queue full" ); if !id.is_system() { for m in upward_messages.iter() { ensure!(m.origin != ParachainDispatchOrigin::Root, "bad message origin"); } } } Ok(()) } /// Update routing information from the parachain heads. This queues upwards /// messages to the relay chain as well. fn update_routing( now: T::BlockNumber, heads: &[AttestedCandidate], ) { // TODO: per-chain watermark // https://github.com/paritytech/polkadot/issues/286 let watermark = now.saturating_sub(One::one()); let mut ingress_update = BTreeMap::new(); // we sort them in order to provide a fast lookup to ensure we can avoid duplicates in the // needs_dispatch queue. let mut ordered_needs_dispatch = NeedsDispatch::get(); ordered_needs_dispatch.sort_unstable(); for head in heads.iter() { let id = head.parachain_index(); ::insert(id, &head.candidate.head_data.0); let last_watermark = >::mutate(id, |mark| { rstd::mem::replace(mark, Some(watermark)) }); if let Some(last_watermark) = last_watermark { // Discard routed ingress. for routed_height in number_range(last_watermark, watermark) { >::remove(&(routed_height, id)); } } // place our egress root to `to` into the ingress table for (now, `to`). for &(to, root) in &head.candidate.egress_queue_roots { ingress_update.entry(to).or_insert_with(Vec::new).push((id, root)); } // Queue up upwards messages (from parachains to relay chain). Self::queue_upward_messages( id, &head.candidate.upward_messages, &ordered_needs_dispatch[..] ); } // apply the ingress update. for (to, ingress_roots) in ingress_update { >::insert((now, to), ingress_roots); } } /// Place any new upward messages into our queue for later dispatch. fn queue_upward_messages( id: ParaId, upward_messages: &[UpwardMessage], ordered_needs_dispatch: &[ParaId], ) { if !upward_messages.is_empty() { ::mutate(id, |&mut(ref mut count, ref mut len)| { *count += upward_messages.len() as u32; *len += upward_messages.iter() .fold(0, |a, x| a + x.data.len()) as u32; }); // Should never be able to fail assuming our state is uncorrupted, but best not // to panic, even if it does. let _ = RelayDispatchQueue::append(id, upward_messages); if ordered_needs_dispatch.binary_search(&id).is_err() { NeedsDispatch::append(id); } } } /// Simple FIFO dispatcher. fn dispatch_upward_messages( now: T::BlockNumber, max_queue_count: usize, watermark_queue_size: usize, mut dispatch_message: impl FnMut(ParaId, ParachainDispatchOrigin, &[u8]), ) { let queueds = NeedsDispatch::get(); let mut drained_count = 0usize; let mut dispatched_count = 0usize; let mut dispatched_size = 0usize; for id in queueds.iter() { drained_count += 1; let (count, size) = ::get(id); let count = count as usize; let size = size as usize; if dispatched_count == 0 || ( dispatched_count + count <= max_queue_count && dispatched_size + size <= watermark_queue_size ) { if count > 0 { // still dispatching messages... RelayDispatchQueueSize::remove(id); let messages = RelayDispatchQueue::take(id); for UpwardMessage { origin, data } in messages.into_iter() { dispatch_message(*id, origin, &data); } dispatched_count += count; dispatched_size += size; if dispatched_count >= max_queue_count || dispatched_size >= watermark_queue_size { break } } } } NeedsDispatch::put_ref(&queueds[drained_count..]); } /// Calculate the current block's duty roster using system's random seed. pub fn calculate_duty_roster() -> DutyRoster { let parachains = Self::active_parachains(); let parachain_count = parachains.len(); // TODO: should be maintaining its own PV "authority" set rather than getting them from Aura let validator_count = crate::Aura::authorities().len(); let validators_per_parachain = if parachain_count == 0 { 0 } else { (validator_count - 1) / parachain_count }; let mut roles_val = (0..validator_count).map(|i| match i { i if i < parachain_count * validators_per_parachain => { let idx = i / validators_per_parachain; Chain::Parachain(parachains[idx].0.clone()) } _ => Chain::Relay, }).collect::>(); let mut seed = { let phrase = b"validator_role_pairs"; let seed = system::Module::::random(&phrase[..]); let seed_len = seed.as_ref().len(); let needed_bytes = validator_count * 4; // hash only the needed bits of the random seed. // if earlier bits are influencable, they will not factor into // the seed used here. let seed_off = if needed_bytes >= seed_len { 0 } else { seed_len - needed_bytes }; BlakeTwo256::hash(&seed.as_ref()[seed_off..]) }; // shuffle for i in 0..(validator_count - 1) { // 4 bytes of entropy used per cycle, 32 bytes entropy per hash let offset = (i * 4 % 32) as usize; // number of roles remaining to select from. let remaining = (validator_count - i) as usize; // 8 32-bit ints per 256-bit seed. let val_index = u32::decode(&mut &seed[offset..offset + 4]) .expect("using 4 bytes for a 32-bit quantity") as usize % remaining; if offset == 28 { // into the last 4 bytes - rehash to gather new entropy seed = BlakeTwo256::hash(seed.as_ref()); } // exchange last item with randomly chosen first. roles_val.swap(remaining - 1, val_index); } DutyRoster { validator_duty: roles_val, } } /// Calculate the ingress to a specific parachain. /// Complexity: O(n) in the number of blocks since the parachain's watermark. /// invoked off-chain. /// /// Yields a structure containing all unrouted ingress to the parachain. pub fn ingress(to: ParaId) -> Option> { let watermark = >::get(to)?; let now = >::block_number(); Some(number_range(watermark.saturating_add(One::one()),now) .filter_map(|unrouted_height| { >::get(&(unrouted_height, to)).map(|roots| { (unrouted_height, BlockIngressRoots(roots)) }) }) .collect()) } /// Get the parachain status necessary for validation. pub fn parachain_status(id: ¶chain::Id) -> Option { let balance = T::ParachainCurrency::free_balance(*id); Self::parachain_head(id).map(|head_data| parachain::Status { head_data: parachain::HeadData(head_data), balance, // TODO: https://github.com/paritytech/polkadot/issues/92 // plug in some real values here. most likely governable. fee_schedule: parachain::FeeSchedule { base: 0, per_byte: 0, } }) } /// Get the currently active set of parachains. pub fn active_parachains() -> Vec<(ParaId, Option)> { T::ActiveParas::active_paras() } fn check_egress_queue_roots(head: &AttestedCandidate, active_parachains: &[ParaId]) -> Result { let mut last_egress_id = None; let mut iter = active_parachains.iter(); for (egress_para_id, root) in &head.candidate.egress_queue_roots { // egress routes should be ascending order by parachain ID without duplicate. ensure!( last_egress_id.as_ref().map_or(true, |x| x < &egress_para_id), "Egress routes out of order by ID" ); // a parachain can't route to self ensure!( *egress_para_id != head.candidate.parachain_index, "Parachain routing to self" ); // no empty trie roots ensure!( *root != EMPTY_TRIE_ROOT.into(), "Empty trie root included" ); // can't route to a parachain which doesn't exist ensure!( iter.find(|x| x == &egress_para_id).is_some(), "Routing to non-existent parachain" ); last_egress_id = Some(egress_para_id) } Ok(()) } // check the attestations on these candidates. The candidates should have been checked // that each candidates' chain ID is valid. fn check_candidates(attested_candidates: &[AttestedCandidate]) -> Result{ use primitives::parachain::ValidityAttestation; use sr_primitives::traits::Verify; // returns groups of slices that have the same chain ID. // assumes the inner slice is sorted by id. struct GroupedDutyIter<'a> { next_idx: usize, inner: &'a [(usize, ParaId)], } impl<'a> GroupedDutyIter<'a> { fn new(inner: &'a [(usize, ParaId)]) -> Self { GroupedDutyIter { next_idx: 0, inner } } fn group_for(&mut self, wanted_id: ParaId) -> Option<&'a [(usize, ParaId)]> { while let Some((id, keys)) = self.next() { if wanted_id == id { return Some(keys) } } None } } impl<'a> Iterator for GroupedDutyIter<'a> { type Item = (ParaId, &'a [(usize, ParaId)]); fn next(&mut self) -> Option { if self.next_idx == self.inner.len() { return None } let start_idx = self.next_idx; self.next_idx += 1; let start_id = self.inner[start_idx].1; while self.inner.get(self.next_idx).map_or(false, |&(_, ref id)| id == &start_id) { self.next_idx += 1; } Some((start_id, &self.inner[start_idx..self.next_idx])) } } let authorities = super::Aura::authorities(); let duty_roster = Self::calculate_duty_roster(); // convert a duty roster, which is originally a Vec, where each // item corresponds to the same position in the session keys, into // a list containing (index, parachain duty) where indices are into the session keys. // this list is sorted ascending by parachain duty, just like the // parachain candidates are. let make_sorted_duties = |duty: &[Chain]| { let mut sorted_duties = Vec::with_capacity(duty.len()); for (val_idx, duty) in duty.iter().enumerate() { let id = match duty { Chain::Relay => continue, Chain::Parachain(id) => id, }; let idx = sorted_duties.binary_search_by_key(&id, |&(_, ref id)| id) .unwrap_or_else(|idx| idx); sorted_duties.insert(idx, (val_idx, *id)); } sorted_duties }; let sorted_validators = make_sorted_duties(&duty_roster.validator_duty); let parent_hash = super::System::parent_hash(); let localized_payload = |statement: Statement| localized_payload(statement, parent_hash); let mut validator_groups = GroupedDutyIter::new(&sorted_validators[..]); for candidate in attested_candidates { let para_id = candidate.parachain_index(); let validator_group = validator_groups.group_for(para_id) .ok_or("no validator group for parachain")?; ensure!( candidate.validity_votes.len() >= majority_of(validator_group.len()), "Not enough validity attestations" ); let fees = candidate.candidate().fees; T::ParachainCurrency::deduct(para_id, fees)?; let mut candidate_hash = None; let mut encoded_implicit = None; let mut encoded_explicit = None; // track which voters have voted already, 1 bit per authority. let mut track_voters = bitvec![0; authorities.len()]; for (auth_index, validity_attestation) in &candidate.validity_votes { let auth_index = *auth_index as usize; // protect against double-votes. match validator_group.iter().find(|&(idx, _)| *idx == auth_index) { None => return Err("Attesting validator not on this chain's validation duty."), Some(&(idx, _)) => { if track_voters.get(idx) { return Err("Voter already attested validity once") } track_voters.set(idx, true) } } let (payload, sig) = match validity_attestation { ValidityAttestation::Implicit(sig) => { let payload = encoded_implicit.get_or_insert_with(|| localized_payload( Statement::Candidate(candidate.candidate.clone()), )); (payload, sig) } ValidityAttestation::Explicit(sig) => { let hash = candidate_hash .get_or_insert_with(|| candidate.candidate.hash()) .clone(); let payload = encoded_explicit.get_or_insert_with(|| localized_payload( Statement::Valid(hash), )); (payload, sig) } }; ensure!( sig.verify(&payload[..], &authorities[auth_index]), "Candidate validity attestation signature is bad." ); } } Ok(()) } /* // TODO: Consider integrating if needed. (https://github.com/paritytech/polkadot/issues/223) /// Extract the parachain heads from the block. pub fn parachain_heads(&self) -> &[CandidateReceipt] { let x = self.inner.extrinsics.get(PARACHAINS_SET_POSITION as usize).and_then(|xt| match xt.function { Call::Parachains(ParachainsCall::set_heads(ref x)) => Some(&x[..]), _ => None }); match x { Some(x) => x, None => panic!("Invalid polkadot block asserted at {:?}", self.file_line), } } */ } pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"newheads"; pub type InherentType = Vec; impl ProvideInherent for Module { type Call = Call; type Error = MakeFatalError; const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; fn create_inherent(data: &InherentData) -> Option { let data = data.get_data::(&INHERENT_IDENTIFIER) .expect("Parachain heads could not be decoded.") .expect("No parachain heads found in inherent data."); Some(Call::set_heads(data)) } } /// Ensure that the origin `o` represents a parachain. /// Returns `Ok` with the parachain ID that effected the extrinsic or an `Err` otherwise. pub fn ensure_parachain(o: OuterOrigin) -> result::Result where OuterOrigin: Into> { match o.into() { Ok(RawOrigin::Parachain(id)) => Ok(id), _ => Err("bad origin: expected to be a parachain origin"), } } pub struct EnsureParachain(ParaId); impl< O: Into> + From, ParaId, > EnsureOrigin for EnsureParachain { type Success = ParaId; fn try_origin(o: O) -> result::Result { o.into().and_then(|o| match o { RawOrigin::Parachain(id) => Ok(id), r => Err(O::from(r)), }) } } #[cfg(test)] mod tests { use super::*; use super::Call as ParachainsCall; use sr_io::{TestExternalities, with_externalities}; use substrate_primitives::{H256, Blake2Hasher}; use substrate_trie::NodeCodec; use sr_primitives::{ Perbill, traits::{BlakeTwo256, IdentityLookup, ConvertInto}, testing::{UintAuthorityId, Header}, }; use primitives::{ parachain::{CandidateReceipt, HeadData, ValidityAttestation, ValidatorIndex}, SessionKey, BlockNumber, AuraId }; use keyring::Ed25519Keyring; use srml_support::{ impl_outer_origin, impl_outer_dispatch, assert_ok, assert_err, parameter_types, }; use crate::parachains; impl_outer_origin! { pub enum Origin for Test { parachains } } impl_outer_dispatch! { pub enum Call for Test where origin: Origin { parachains::Parachains, } } #[derive(Clone, Eq, PartialEq)] pub struct Test; parameter_types! { pub const BlockHashCount: u64 = 250; pub const MaximumBlockWeight: u32 = 4 * 1024 * 1024; pub const MaximumBlockLength: u32 = 4 * 1024 * 1024; pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); } impl system::Trait for Test { type Origin = Origin; type Index = crate::Nonce; type BlockNumber = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; type WeightMultiplierUpdate = (); type Event = (); type BlockHashCount = BlockHashCount; type MaximumBlockWeight = MaximumBlockWeight; type MaximumBlockLength = MaximumBlockLength; type AvailableBlockRatio = AvailableBlockRatio; } parameter_types! { pub const Period: BlockNumber = 1; pub const Offset: BlockNumber = 0; } impl session::Trait for Test { type OnSessionEnding = (); type Keys = UintAuthorityId; type ShouldEndSession = session::PeriodicSessions; type SessionHandler = (); type Event = (); } impl timestamp::Trait for Test { type Moment = u64; type OnTimestampSet = (); } impl aura::Trait for Test { type HandleReport = aura::StakingSlasher; type AuthorityId = AuraId; } parameter_types! { pub const ExistentialDeposit: Balance = 0; pub const TransferFee: Balance = 0; pub const CreationFee: Balance = 0; pub const TransactionBaseFee: Balance = 0; pub const TransactionByteFee: Balance = 0; } impl balances::Trait for Test { type Balance = Balance; type OnFreeBalanceZero = (); type OnNewAccount = (); type Event = (); type TransactionPayment = (); type DustRemoval = (); type TransferPayment = (); type ExistentialDeposit = ExistentialDeposit; type TransferFee = TransferFee; type CreationFee = CreationFee; type TransactionBaseFee = TransactionBaseFee; type TransactionByteFee = TransactionByteFee; type WeightToFee = ConvertInto; } parameter_types! { pub const SessionsPerEra: session::SessionIndex = 6; pub const BondingDuration: staking::EraIndex = 24 * 28; } impl staking::Trait for Test { type OnRewardMinted = (); type CurrencyToVote = (); type Event = (); type Currency = balances::Module; type Slash = (); type Reward = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SessionInterface = Self; type Time = timestamp::Module; } impl Trait for Test { type Origin = Origin; type Call = Call; type ParachainCurrency = balances::Module; } type Parachains = Module; type System = system::Module; fn new_test_ext(parachains: Vec<(ParaId, Vec, Vec)>) -> TestExternalities { let mut t = system::GenesisConfig::default().build_storage::().unwrap().0; let authority_keys = [ Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie, Ed25519Keyring::Dave, Ed25519Keyring::Eve, Ed25519Keyring::Ferdie, Ed25519Keyring::One, Ed25519Keyring::Two, ]; t.extend(session::GenesisConfig::{ validators: validator_keys.iter().map(|k| crate::AccountId::from(*k)).collect(), keys: vec![], }.build_storage().unwrap().0); t.extend(GenesisConfig::{ parachains, _phdata: Default::default(), }.build_storage().unwrap().0); t.extend(aura::GenesisConfig::{ authorities: authority_keys.iter().map(|k| SessionKey::from(*k)).collect(), }.build_storage().unwrap().0); t.into() } fn set_heads(v: Vec) -> ParachainsCall { ParachainsCall::set_heads(v) } fn make_attestations(candidate: &mut AttestedCandidate) { let mut vote_implicit = false; let parent_hash = crate::System::parent_hash(); let duty_roster = Parachains::calculate_duty_roster(); let candidate_hash = candidate.candidate.hash(); let authorities = crate::Aura::authorities(); let extract_key = |public: SessionKey| { Ed25519Keyring::from_raw_public(public.0).unwrap() }; let validation_entries = duty_roster.validator_duty.iter() .enumerate(); for (idx, &duty) in validation_entries { if duty != Chain::Parachain(candidate.parachain_index()) { continue } vote_implicit = !vote_implicit; let key = extract_key(authorities[idx].clone()); let statement = if vote_implicit { Statement::Candidate(candidate.candidate.clone()) } else { Statement::Valid(candidate_hash.clone()) }; let payload = localized_payload(statement, parent_hash); let signature = key.sign(&payload[..]).into(); candidate.validity_votes.push((idx as ValidatorIndex, if vote_implicit { ValidityAttestation::Implicit(signature) } else { ValidityAttestation::Explicit(signature) })); } } fn new_candidate_with_egress_roots(egress_queue_roots: Vec<(ParaId, H256)>) -> AttestedCandidate { AttestedCandidate { validity_votes: vec![], candidate: CandidateReceipt { parachain_index: 0.into(), collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), egress_queue_roots, fees: 0, block_data_hash: Default::default(), upward_messages: vec![], } } } fn new_candidate_with_upward_messages( id: u32, upward_messages: Vec<(ParachainDispatchOrigin, Vec)> ) -> AttestedCandidate { AttestedCandidate { validity_votes: vec![], candidate: CandidateReceipt { parachain_index: id.into(), collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), upward_messages: upward_messages.into_iter() .map(|x| UpwardMessage { origin: x.0, data: x.1 }) .collect(), } } } #[test] fn check_dispatch_upward_works() { let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), (2u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains.clone()), || { let parachains = vec![0.into(), 1.into(), 2.into()]; Parachains::queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![0; 4] } ]); Parachains::queue_upward_messages(1.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1; 4] } ]); let mut dispatched: Vec<(ParaId, ParachainDispatchOrigin, Vec)> = vec![]; let dummy = |id, origin, data: &[u8]| dispatched.push((id, origin, data.to_vec())); Parachains::dispatch_upward_messages(0, ¶chains, 2, 3, dummy); assert_eq!(dispatched, vec![ (0.into(), ParachainDispatchOrigin::Parachain, vec![0; 4]) ]); assert!(::get(ParaId::from(0)).is_empty()); assert_eq!(::get(ParaId::from(1)).len(), 1); }); with_externalities(&mut new_test_ext(parachains.clone()), || { let parachains = vec![0.into(), 1.into(), 2.into()]; Parachains::queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![0; 2] } ]); Parachains::queue_upward_messages(1.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1; 2] } ]); Parachains::queue_upward_messages(2.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![2] } ]); let mut dispatched: Vec<(ParaId, ParachainDispatchOrigin, Vec)> = vec![]; let dummy = |id, origin, data: &[u8]| dispatched.push((id, origin, data.to_vec())); Parachains::dispatch_upward_messages(0, ¶chains, 2, 3, dummy); assert_eq!(dispatched, vec![ (0.into(), ParachainDispatchOrigin::Parachain, vec![0; 2]), (2.into(), ParachainDispatchOrigin::Parachain, vec![2]) ]); assert!(::get(ParaId::from(0)).is_empty()); assert_eq!(::get(ParaId::from(1)).len(), 1); assert!(::get(ParaId::from(2)).is_empty()); }); with_externalities(&mut new_test_ext(parachains.clone()), || { let parachains = vec![0.into(), 1.into(), 2.into()]; Parachains::queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![0; 2] } ]); Parachains::queue_upward_messages(1.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1; 2] } ]); Parachains::queue_upward_messages(2.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![2] } ]); let mut dispatched: Vec<(ParaId, ParachainDispatchOrigin, Vec)> = vec![]; let dummy = |id, origin, data: &[u8]| dispatched.push((id, origin, data.to_vec())); Parachains::dispatch_upward_messages(1, ¶chains, 2, 3, dummy); assert_eq!(dispatched, vec![ (1.into(), ParachainDispatchOrigin::Parachain, vec![1; 2]), (2.into(), ParachainDispatchOrigin::Parachain, vec![2]) ]); assert_eq!(::get(ParaId::from(0)).len(), 1); assert!(::get(ParaId::from(1)).is_empty()); assert!(::get(ParaId::from(2)).is_empty()); }); with_externalities(&mut new_test_ext(parachains.clone()), || { let parachains = vec![0.into(), 1.into(), 2.into()]; Parachains::queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![0; 2] } ]); Parachains::queue_upward_messages(1.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1; 2] } ]); Parachains::queue_upward_messages(2.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![2] } ]); let mut dispatched: Vec<(ParaId, ParachainDispatchOrigin, Vec)> = vec![]; let dummy = |id, origin, data: &[u8]| dispatched.push((id, origin, data.to_vec())); Parachains::dispatch_upward_messages(2, ¶chains, 2, 3, dummy); assert_eq!(dispatched, vec![ (2.into(), ParachainDispatchOrigin::Parachain, vec![2]), (0.into(), ParachainDispatchOrigin::Parachain, vec![0; 2]) ]); assert!(::get(ParaId::from(0)).is_empty()); assert_eq!(::get(ParaId::from(1)).len(), 1); assert!(::get(ParaId::from(2)).is_empty()); }); } #[test] fn check_queue_upward_messages_works() { let parachains = vec![ (0u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] } ]; assert_ok!(Parachains::check_upward_messages(0.into(), &messages, 2, 3)); // all good. Parachains::queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, ]); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1, 2] } ]; assert_ok!(Parachains::check_upward_messages(0.into(), &messages, 2, 3)); Parachains::queue_upward_messages(0.into(), &messages); assert_eq!(::get(ParaId::from(0)), vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, UpwardMessage { origin: ParachainDispatchOrigin::Parachain, data: vec![1, 2] }, ]); }); } #[test] fn check_queue_full_upward_messages_fails() { let parachains = vec![ (0u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { // oversize, but ok since it's just one and the queue is empty. let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0; 4] }, ]; assert_ok!(Parachains::check_upward_messages(0.into(), &messages, 2, 3)); // oversize and bad since it's not just one. let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0; 4] }, ]; assert_err!( Parachains::check_upward_messages(0.into(), &messages, 2, 3), "Messages added when queue full" ); // too many messages. let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![1] }, UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![2] }, ]; assert_err!( Parachains::check_upward_messages(0.into(), &messages, 2, 3), "Messages added when queue full" ); }); } #[test] fn check_queued_too_many_upward_messages_fails() { let parachains = vec![ (0u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { // too many messages. Parachains::queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, ]); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![1] }, UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![2] }, ]; assert_err!( Parachains::check_upward_messages(0.into(), &messages, 2, 3), "Messages added when queue full" ); }); } #[test] fn check_queued_total_oversize_upward_messages_fails() { let parachains = vec![ (0u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { // too much data. Parachains::queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0, 1] }, ]); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![2, 3] }, ]; assert_err!( Parachains::check_upward_messages(0.into(), &messages, 2, 3), "Messages added when queue full" ); }); } #[test] fn check_queued_pre_jumbo_upward_messages_fails() { let parachains = vec![ (0u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { // bad - already an oversize messages queued. Parachains::queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0; 4] }, ]); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] } ]; assert_err!( Parachains::check_upward_messages(0.into(), &messages, 2, 3), "Messages added when queue full" ); }); } #[test] fn check_queued_post_jumbo_upward_messages_fails() { let parachains = vec![ (0u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { // bad - oversized and already a message queued. Parachains::queue_upward_messages(0.into(), &vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0] }, ]); let messages = vec![ UpwardMessage { origin: ParachainDispatchOrigin::Signed, data: vec![0; 4] } ]; assert_err!( Parachains::check_upward_messages(0.into(), &messages, 2, 3), "Messages added when queue full" ); }); } #[test] fn upward_queuing_works() { // That the list of egress queue roots is in ascending order by `ParaId`. let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { // parachain 0 is self let mut candidates = vec![ new_candidate_with_upward_messages(0, vec![ (ParachainDispatchOrigin::Signed, vec![1]), ]), new_candidate_with_upward_messages(1, vec![ (ParachainDispatchOrigin::Parachain, vec![2]), ]) ]; candidates.iter_mut().for_each(make_attestations); assert_ok!(Parachains::dispatch( set_heads(candidates), Origin::NONE, )); assert!(::get(ParaId::from(0)).is_empty()); assert!(::get(ParaId::from(1)).is_empty()); }); } #[test] fn active_parachains_should_work() { let parachains = vec![ (5u32.into(), vec![1,2,3], vec![1]), (100u32.into(), vec![4,5,6], vec![2]), ]; with_externalities(&mut new_test_ext(parachains), || { assert_eq!(Parachains::active_parachains(), vec![5u32.into(), 100u32.into()]); assert_eq!(Parachains::parachain_code(&5u32.into()), Some(vec![1,2,3])); assert_eq!(Parachains::parachain_code(&100u32.into()), Some(vec![4,5,6])); }); } #[test] fn register_deregister() { let parachains = vec![ (5u32.into(), vec![1,2,3], vec![1]), (100u32.into(), vec![4,5,6], vec![2,]), ]; with_externalities(&mut new_test_ext(parachains), || { assert_eq!(Parachains::active_parachains(), vec![5u32.into(), 100u32.into()]); assert_eq!(Parachains::parachain_code(&5u32.into()), Some(vec![1,2,3])); assert_eq!(Parachains::parachain_code(&100u32.into()), Some(vec![4,5,6])); assert_ok!(Parachains::register_parachain(99u32.into(), vec![7,8,9], vec![1, 1, 1])); assert_eq!(Parachains::active_parachains(), vec![5u32.into(), 99u32.into(), 100u32.into()]); assert_eq!(Parachains::parachain_code(&99u32.into()), Some(vec![7,8,9])); assert_ok!(Parachains::deregister_parachain(5u32.into())); assert_eq!(Parachains::active_parachains(), vec![99u32.into(), 100u32.into()]); assert_eq!(Parachains::parachain_code(&5u32.into()), None); }); } #[test] fn duty_roster_works() { let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { let check_roster = |duty_roster: &DutyRoster| { assert_eq!(duty_roster.validator_duty.len(), 8); for i in (0..2).map(ParaId::from) { assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Parachain(i)).count(), 3); } assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Relay).count(), 2); }; let duty_roster_0 = Parachains::calculate_duty_roster(); check_roster(&duty_roster_0); System::initialize(&1, &H256::from([1; 32]), &Default::default(), &Default::default()); let duty_roster_1 = Parachains::calculate_duty_roster(); check_roster(&duty_roster_1); assert!(duty_roster_0 != duty_roster_1); System::initialize(&2, &H256::from([2; 32]), &Default::default(), &Default::default()); let duty_roster_2 = Parachains::calculate_duty_roster(); check_roster(&duty_roster_2); assert!(duty_roster_0 != duty_roster_2); assert!(duty_roster_1 != duty_roster_2); }); } #[test] fn unattested_candidate_is_rejected() { let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { let candidate = AttestedCandidate { validity_votes: vec![], candidate: CandidateReceipt { parachain_index: 0.into(), collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), upward_messages: vec![], }, }; assert!(Parachains::dispatch(set_heads(vec![candidate]), Origin::NONE).is_err()); }) } #[test] fn attested_candidates_accepted_in_order() { let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { let mut candidate_a = AttestedCandidate { validity_votes: vec![], candidate: CandidateReceipt { parachain_index: 0.into(), collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), upward_messages: vec![], } }; let mut candidate_b = AttestedCandidate { validity_votes: vec![], candidate: CandidateReceipt { parachain_index: 1.into(), collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![2, 3, 4]), egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), upward_messages: vec![], } }; make_attestations(&mut candidate_a); make_attestations(&mut candidate_b); assert!(Parachains::dispatch( set_heads(vec![candidate_b.clone(), candidate_a.clone()]), Origin::NONE, ).is_err()); assert_ok!(Parachains::dispatch( set_heads(vec![candidate_a.clone(), candidate_b.clone()]), Origin::NONE, )); }); } #[test] fn duplicate_vote_is_rejected() { let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { let mut candidate = AttestedCandidate { validity_votes: vec![], candidate: CandidateReceipt { parachain_index: 0.into(), collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), upward_messages: vec![], } }; make_attestations(&mut candidate); let mut double_validity = candidate.clone(); double_validity.validity_votes.push(candidate.validity_votes[0].clone()); assert!(Parachains::dispatch( set_heads(vec![double_validity]), Origin::NONE, ).is_err()); }); } #[test] fn ingress_works() { use sr_primitives::traits::OnFinalize; let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), (99u32.into(), vec![1, 2, 3], vec![4, 5, 6]), ]; with_externalities(&mut new_test_ext(parachains), || { assert_eq!(Parachains::ingress(ParaId::from(1)), Some(Vec::new())); assert_eq!(Parachains::ingress(ParaId::from(99)), Some(Vec::new())); for i in 1..10 { System::set_block_number(i); let from_a = vec![(1.into(), [i as u8; 32].into())]; let mut candidate_a = AttestedCandidate { validity_votes: vec![], candidate: CandidateReceipt { parachain_index: 0.into(), collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), egress_queue_roots: from_a.clone(), fees: 0, block_data_hash: Default::default(), upward_messages: vec![], } }; let from_b = vec![(99.into(), [i as u8; 32].into())]; let mut candidate_b = AttestedCandidate { validity_votes: vec![], candidate: CandidateReceipt { parachain_index: 1.into(), collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), egress_queue_roots: from_b.clone(), fees: 0, block_data_hash: Default::default(), upward_messages: vec![], } }; make_attestations(&mut candidate_a); make_attestations(&mut candidate_b); assert_ok!(Parachains::dispatch( set_heads(vec![candidate_a, candidate_b]), Origin::NONE, )); Parachains::on_finalize(i); } System::set_block_number(10); assert_ok!(Parachains::dispatch( set_heads(vec![]), Origin::NONE, )); // parachain 1 has had a bunch of parachain candidates included, // which raises the watermark. assert_eq!( Parachains::ingress(ParaId::from(1)), Some(vec![ (9, BlockIngressRoots(vec![ (0.into(), [9; 32].into()) ])) ]), ); // parachain 99 hasn't had any candidates included, so the // ingress is piling up. assert_eq!( Parachains::ingress(ParaId::from(99)), Some((1..10).map(|i| (i, BlockIngressRoots( vec![(1.into(), [i as u8; 32].into())] ))).collect::>()), ); assert_ok!(Parachains::deregister_parachain(1u32.into())); // after deregistering, there is no ingress to 1, but unrouted messages // from 1 stick around. assert_eq!(Parachains::ingress(ParaId::from(1)), None); assert_eq!(Parachains::ingress(ParaId::from(99)), Some((1..10).map(|i| (i, BlockIngressRoots( vec![(1.into(), [i as u8; 32].into())] ))).collect::>())); Parachains::on_finalize(10); System::set_block_number(11); let mut candidate_c = AttestedCandidate { validity_votes: vec![], candidate: CandidateReceipt { parachain_index: 99.into(), collator: Default::default(), signature: Default::default(), head_data: HeadData(vec![1, 2, 3]), egress_queue_roots: Vec::new(), fees: 0, block_data_hash: Default::default(), upward_messages: vec![], } }; make_attestations(&mut candidate_c); assert_ok!(Parachains::dispatch( set_heads(vec![candidate_c]), Origin::NONE, )); Parachains::on_finalize(11); System::set_block_number(12); // at the next block, ingress to 99 should be empty. assert_eq!(Parachains::ingress(ParaId::from(99)), Some(Vec::new())); }); } #[test] fn egress_routed_to_non_existent_parachain_is_rejected() { // That no parachain is routed to which doesn't exist let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { // parachain 99 does not exist let non_existent = vec![(99.into(), [1; 32].into())]; let mut candidate = new_candidate_with_egress_roots(non_existent); make_attestations(&mut candidate); let result = Parachains::dispatch( set_heads(vec![candidate.clone()]), Origin::NONE, ); assert_eq!(Err("Routing to non-existent parachain"), result); }); } #[test] fn egress_routed_to_self_is_rejected() { // That the parachain doesn't route to self let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { // parachain 0 is self let to_self = vec![(0.into(), [1; 32].into())]; let mut candidate = new_candidate_with_egress_roots(to_self); make_attestations(&mut candidate); let result = Parachains::dispatch( set_heads(vec![candidate.clone()]), Origin::NONE, ); assert_eq!(Err("Parachain routing to self"), result); }); } #[test] fn egress_queue_roots_out_of_order_rejected() { // That the list of egress queue roots is in ascending order by `ParaId`. let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { // parachain 0 is self let out_of_order = vec![(1.into(), [1; 32].into()), ((0.into(), [1; 32].into()))]; let mut candidate = new_candidate_with_egress_roots(out_of_order); make_attestations(&mut candidate); let result = Parachains::dispatch( set_heads(vec![candidate.clone()]), Origin::NONE, ); assert_eq!(Err("Egress routes out of order by ID"), result); }); } #[test] fn egress_queue_roots_empty_trie_roots_rejected() { let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), (2u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { // parachain 0 is self let contains_empty_trie_root = vec![(1.into(), [1; 32].into()), ((2.into(), EMPTY_TRIE_ROOT.into()))]; let mut candidate = new_candidate_with_egress_roots(contains_empty_trie_root); make_attestations(&mut candidate); let result = Parachains::dispatch( set_heads(vec![candidate.clone()]), Origin::NONE, ); assert_eq!(Err("Empty trie root included"), result); }); } #[test] fn empty_trie_root_const_is_blake2_hashed_null_node() { let hashed_null_node = as trie_db::NodeCodec>::hashed_null_node(); assert_eq!(hashed_null_node, EMPTY_TRIE_ROOT.into()) } }