// 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 codec::Decode; use bitvec::BigEndian; use sr_primitives::traits::{Hash as HashT, BlakeTwo256}; use primitives::Hash; use primitives::parachain::{Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement}; use {system, session}; use srml_support::{StorageValue, StorageMap}; use srml_support::dispatch::Result; use inherents::{ProvideInherent, InherentData, RuntimeString, MakeFatalError, InherentIdentifier}; #[cfg(any(feature = "std", test))] use sr_primitives::{StorageOverlay, ChildrenStorageOverlay}; #[cfg(any(feature = "std", test))] use rstd::marker::PhantomData; use system::ensure_inherent; pub trait Trait: session::Trait {} // 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 ]; decl_storage! { trait Store for Module as Parachains { // Vector of all parachain IDs. pub Parachains get(active_parachains): Vec; // 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>; // message routing roots (from, to). pub Routing: map (ParaId, ParaId) => Option; // Did the parachain heads get updated in this block? DidUpdate: bool; } add_extra_genesis { config(parachains): Vec<(ParaId, Vec, Vec)>; config(_phdata): PhantomData; build(|storage: &mut StorageOverlay, _: &mut ChildrenStorageOverlay, config: &GenesisConfig| { use codec::Encode; let mut p = config.parachains.clone(); p.sort_unstable_by_key(|&(ref id, _, _)| id.clone()); p.dedup_by_key(|&mut (ref id, _, _)| id.clone()); let only_ids: Vec<_> = p.iter().map(|&(ref id, _, _)| id).cloned().collect(); storage.insert(Self::hash(>::key()).to_vec(), only_ids.encode()); for (id, code, genesis) in p { let code_key = Self::hash(&>::key_for(&id)).to_vec(); let head_key = Self::hash(&>::key_for(&id)).to_vec(); // no ingress -- a chain cannot be routed to until it is live. storage.insert(code_key, code.encode()); storage.insert(head_key, genesis.encode()); } }); } } decl_module! { /// Parachains module. pub struct Module for enum Call where origin: T::Origin { /// Provide candidate receipts for parachains, in ascending order by id. fn set_heads(origin, heads: Vec) -> Result { ensure_inherent(origin)?; ensure!(!>::exists(), "Parachain heads must be updated only once in the block"); let active_parachains = Self::active_parachains(); // perform integrity checks before writing to storage. { let n_parachains = active_parachains.len(); ensure!(heads.len() <= n_parachains, "Too many parachain candidates"); let mut last_id = None; let mut iter = active_parachains.iter(); for head in &heads { // proposed heads must be ascending order by parachain ID without duplicate. ensure!( last_id.as_ref().map_or(true, |x| x < &head.parachain_index()), "Parachain candidates out of order by ID" ); // must be unknown since active parachains are always sorted. ensure!( iter.find(|x| x == &&head.parachain_index()).is_some(), "Submitted candidate for unregistered or out-of-order parachain {}" ); Self::check_egress_queue_roots(&head, &active_parachains)?; last_id = Some(head.parachain_index()); } } Self::check_attestations(&heads)?; for head in heads { let id = head.parachain_index(); >::insert(id, head.candidate.head_data.0); // update egress. for &(to, root) in &head.candidate.egress_queue_roots { >::insert((id, to), root); } } >::put(true); Ok(()) } /// Register a parachain with given code. /// Fails if given ID is already used. pub fn register_parachain(id: ParaId, code: Vec, initial_head_data: Vec) -> Result { let mut parachains = Self::active_parachains(); match parachains.binary_search(&id) { Ok(_) => fail!("Parachain already exists"), Err(idx) => parachains.insert(idx, id), } >::insert(id, code); >::put(parachains); >::insert(id, initial_head_data); Ok(()) } /// Deregister a parachain with given id pub fn deregister_parachain(id: ParaId) -> Result { let mut parachains = Self::active_parachains(); match parachains.binary_search(&id) { Ok(idx) => { parachains.remove(idx); } Err(_) => return Ok(()), } >::remove(id); >::remove(id); // clear all routing entries to and from other parachains. for other in parachains.iter().cloned() { >::remove((id, other)); >::remove((other, id)); } >::put(parachains); 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 codec::Encode; let mut encoded = statement.encode(); encoded.extend(parent_hash.as_ref()); encoded } impl Module { /// 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(); let validator_count = >::authorities().len(); let validators_per_parachain = if parachain_count != 0 { (validator_count - 1) / parachain_count } else { 0 }; 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].clone()) } _ => Chain::Relay, }).collect::>(); let mut random_seed = system::Module::::random_seed().as_ref().to_vec(); random_seed.extend(b"validator_role_pairs"); let mut seed = BlakeTwo256::hash(&random_seed); // 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. /// /// Yields a list of parachains being routed from, and the egress /// queue roots to consider. pub fn ingress(to: ParaId) -> Option> { let active_parachains = Self::active_parachains(); if !active_parachains.contains(&to) { return None } Some(active_parachains.into_iter().filter(|i| i != &to) .filter_map(move |from| { >::get((from, to.clone())).map(move |h| (from, h)) }) .collect()) } 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_attestations(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::Consensus::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 validator_group = validator_groups.group_for(candidate.parachain_index()) .ok_or("no validator group for parachain")?; ensure!( candidate.validity_votes.len() >= majority_of(validator_group.len()), "Not enough validity attestations" ); 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_id, validity_attestation) in &candidate.validity_votes { // protect against double-votes. match validator_group.iter().find(|&(idx, _)| &authorities[*idx] == auth_id) { 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[..], &auth_id), "Candidate validity attestation signature is bad." ); } } Ok(()) } /* // TODO: Consider integrating if needed. /// 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)) } } #[cfg(test)] mod tests { use super::*; use sr_io::{TestExternalities, with_externalities}; use substrate_primitives::{H256, Blake2Hasher}; use substrate_trie::NodeCodec; use sr_primitives::{generic, BuildStorage}; use sr_primitives::traits::{BlakeTwo256, IdentityLookup}; use primitives::{parachain::{CandidateReceipt, HeadData, ValidityAttestation}, SessionKey}; use keyring::{AuthorityKeyring, AccountKeyring}; use {consensus, timestamp}; impl_outer_origin! { pub enum Origin for Test {} } #[derive(Clone, Eq, PartialEq)] pub struct Test; impl consensus::Trait for Test { type InherentOfflineReport = (); type SessionKey = SessionKey; type Log = ::Log; } impl system::Trait for Test { type Origin = Origin; type Index = ::Nonce; type BlockNumber = u64; type Hash = H256; type Hashing = BlakeTwo256; type Digest = generic::Digest<::Log>; type AccountId = ::AccountId; type Lookup = IdentityLookup<::AccountId>; type Header = ::Header; type Event = (); type Log = ::Log; } impl session::Trait for Test { type ConvertAccountIdToSessionKey = (); type OnSessionChange = (); type Event = (); } impl timestamp::Trait for Test { type Moment = u64; type OnTimestampSet = (); } impl Trait for Test {} type Parachains = Module; fn new_test_ext(parachains: Vec<(ParaId, Vec, Vec)>) -> TestExternalities { let mut t = system::GenesisConfig::::default().build_storage().unwrap().0; let authority_keys = [ AuthorityKeyring::Alice, AuthorityKeyring::Bob, AuthorityKeyring::Charlie, AuthorityKeyring::Dave, AuthorityKeyring::Eve, AuthorityKeyring::Ferdie, AuthorityKeyring::One, AuthorityKeyring::Two, ]; let validator_keys = [ AccountKeyring::Alice, AccountKeyring::Bob, AccountKeyring::Charlie, AccountKeyring::Dave, AccountKeyring::Eve, AccountKeyring::Ferdie, AccountKeyring::One, AccountKeyring::Two, ]; t.extend(consensus::GenesisConfig::{ code: vec![], authorities: authority_keys.iter().map(|k| SessionKey::from(*k)).collect(), }.build_storage().unwrap().0); t.extend(session::GenesisConfig::{ session_length: 1000, validators: validator_keys.iter().map(|k| ::AccountId::from(*k)).collect(), keys: vec![], }.build_storage().unwrap().0); t.extend(GenesisConfig::{ parachains: parachains, _phdata: Default::default(), }.build_storage().unwrap().0); t.into() } fn make_attestations(candidate: &mut AttestedCandidate) { let mut vote_implicit = false; let parent_hash = ::System::parent_hash(); let duty_roster = Parachains::calculate_duty_roster(); let candidate_hash = candidate.candidate.hash(); let authorities = ::Consensus::authorities(); let extract_key = |public: SessionKey| { AuthorityKeyring::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((authorities[idx].clone(), 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]), balance_uploads: vec![], egress_queue_roots, fees: 0, block_data_hash: Default::default(), } } } #[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); }; system::Module::::set_random_seed([0u8; 32].into()); let duty_roster_0 = Parachains::calculate_duty_roster(); check_roster(&duty_roster_0); system::Module::::set_random_seed([1u8; 32].into()); let duty_roster_1 = Parachains::calculate_duty_roster(); check_roster(&duty_roster_1); assert!(duty_roster_0 != duty_roster_1); system::Module::::set_random_seed([2u8; 32].into()); 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), || { system::Module::::set_random_seed([0u8; 32].into()); 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]), balance_uploads: vec![], egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), }, }; assert!(Parachains::dispatch(Call::set_heads(vec![candidate]), Origin::INHERENT).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), || { system::Module::::set_random_seed([0u8; 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]), balance_uploads: vec![], egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), } }; 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]), balance_uploads: vec![], egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), } }; make_attestations(&mut candidate_a); make_attestations(&mut candidate_b); assert!(Parachains::dispatch( Call::set_heads(vec![candidate_b.clone(), candidate_a.clone()]), Origin::INHERENT, ).is_err()); assert!(Parachains::dispatch( Call::set_heads(vec![candidate_a.clone(), candidate_b.clone()]), Origin::INHERENT, ).is_ok()); }); } #[test] fn duplicate_vote_is_rejected() { let parachains = vec![ (0u32.into(), vec![], vec![]), (1u32.into(), vec![], vec![]), ]; with_externalities(&mut new_test_ext(parachains), || { system::Module::::set_random_seed([0u8; 32].into()); 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]), balance_uploads: vec![], egress_queue_roots: vec![], fees: 0, block_data_hash: Default::default(), } }; make_attestations(&mut candidate); let mut double_validity = candidate.clone(); double_validity.validity_votes.push(candidate.validity_votes[0].clone()); assert!(Parachains::dispatch( Call::set_heads(vec![double_validity]), Origin::INHERENT, ).is_err()); }); } #[test] fn ingress_works() { 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), || { system::Module::::set_random_seed([0u8; 32].into()); let from_a = vec![(1.into(), [1; 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]), balance_uploads: vec![], egress_queue_roots: from_a.clone(), fees: 0, block_data_hash: Default::default(), } }; let from_b = vec![(99.into(), [1; 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]), balance_uploads: vec![], egress_queue_roots: from_b.clone(), fees: 0, block_data_hash: Default::default(), } }; make_attestations(&mut candidate_a); make_attestations(&mut candidate_b); assert_eq!(Parachains::ingress(ParaId::from(1)), Some(Vec::new())); assert_eq!(Parachains::ingress(ParaId::from(99)), Some(Vec::new())); assert!(Parachains::dispatch( Call::set_heads(vec![candidate_a, candidate_b]), Origin::INHERENT, ).is_ok()); assert_eq!( Parachains::ingress(ParaId::from(1)), Some(vec![(0.into(), [1; 32].into())]), ); assert_eq!( Parachains::ingress(ParaId::from(99)), Some(vec![(1.into(), [1; 32].into())]), ); assert_ok!(Parachains::deregister_parachain(1u32.into())); // after deregistering, there is no ingress to 1 and we stop routing // from 1. assert_eq!(Parachains::ingress(ParaId::from(1)), None); 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), || { system::Module::::set_random_seed([0u8; 32].into()); // 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( Call::set_heads(vec![candidate.clone()]), Origin::INHERENT, ); 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), || { system::Module::::set_random_seed([0u8; 32].into()); // 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( Call::set_heads(vec![candidate.clone()]), Origin::INHERENT, ); 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), || { system::Module::::set_random_seed([0u8; 32].into()); // 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( Call::set_heads(vec![candidate.clone()]), Origin::INHERENT, ); 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), || { system::Module::::set_random_seed([0u8; 32].into()); // 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( Call::set_heads(vec![candidate.clone()]), Origin::INHERENT, ); 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()) } }