// Copyright 2020 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 . //! The inclusion module is responsible for inclusion and availability of scheduled parachains //! and parathreads. //! //! It is responsible for carrying candidates from being backable to being backed, and then from backed //! to included. use sp_std::prelude::*; use primitives::{ parachain::{ ValidatorId, AbridgedCandidateReceipt, ValidatorIndex, Id as ParaId, AvailabilityBitfield as AvailabilityBitfield, SignedAvailabilityBitfields, SigningContext, BackedCandidate, }, }; use frame_support::{ decl_storage, decl_module, decl_error, ensure, dispatch::DispatchResult, IterableStorageMap, weights::Weight, traits::Get, }; use codec::{Encode, Decode}; use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; use sp_staking::SessionIndex; use sp_runtime::{DispatchError, traits::{One, Saturating}}; use crate::{configuration, paras, scheduler::{CoreIndex, GroupIndex, CoreAssignment}}; /// A bitfield signed by a validator indicating that it is keeping its piece of the erasure-coding /// for any backed candidates referred to by a `1` bit available. /// /// The bitfield's signature should be checked at the point of submission. Afterwards it can be /// dropped. #[derive(Encode, Decode)] #[cfg_attr(test, derive(Debug))] pub struct AvailabilityBitfieldRecord { bitfield: AvailabilityBitfield, // one bit per core. submitted_at: N, // for accounting, as meaning of bits may change over time. } /// A backed candidate pending availability. #[derive(Encode, Decode, PartialEq)] #[cfg_attr(test, derive(Debug))] pub struct CandidatePendingAvailability { /// The availability core this is assigned to. core: CoreIndex, /// The candidate receipt itself. receipt: AbridgedCandidateReceipt, /// The received availability votes. One bit per validator. availability_votes: BitVec, /// The block number of the relay-parent of the receipt. relay_parent_number: N, /// The block number of the relay-chain block this was backed in. backed_in_number: N, } pub trait Trait: system::Trait + paras::Trait + configuration::Trait { } decl_storage! { trait Store for Module as ParaInclusion { /// The latest bitfield for each validator, referred to by their index in the validator set. AvailabilityBitfields: map hasher(twox_64_concat) ValidatorIndex => Option>; /// Candidates pending availability by `ParaId`. PendingAvailability: map hasher(twox_64_concat) ParaId => Option>; /// The current validators, by their parachain session keys. Validators get(fn validators) config(validators): Vec; /// The current session index. CurrentSessionIndex: SessionIndex; } } decl_error! { pub enum Error for Module { /// Availability bitfield has unexpected size. WrongBitfieldSize, /// Multiple bitfields submitted by same validator or validators out of order by index. BitfieldDuplicateOrUnordered, /// Validator index out of bounds. ValidatorIndexOutOfBounds, /// Invalid signature InvalidBitfieldSignature, /// Candidate submitted but para not scheduled. UnscheduledCandidate, /// Candidate scheduled despite pending candidate already existing for the para. CandidateScheduledBeforeParaFree, /// Candidate included with the wrong collator. WrongCollator, /// Scheduled cores out of order. ScheduledOutOfOrder, /// Code upgrade prematurely. PrematureCodeUpgrade, /// Candidate not in parent context. CandidateNotInParentContext, /// The bitfield contains a bit relating to an unassigned availability core. UnoccupiedBitInBitfield, /// Invalid group index in core assignment. InvalidGroupIndex, /// Insufficient (non-majority) backing. InsufficientBacking, /// Invalid (bad signature, unknown validator, etc.) backing. InvalidBacking, /// Collator did not sign PoV. NotCollatorSigned, /// Internal error only returned when compiled with debug assertions. InternalError, } } decl_module! { /// The parachain-candidate inclusion module. pub struct Module for enum Call where origin: ::Origin { type Error = Error; } } impl Module { /// Block initialization logic, called by initializer. pub(crate) fn initializer_initialize(_now: T::BlockNumber) -> Weight { 0 } /// Block finalization logic, called by initializer. pub(crate) fn initializer_finalize() { } /// Handle an incoming session change. pub(crate) fn initializer_on_new_session( notification: &crate::initializer::SessionChangeNotification ) { // unlike most drain methods, drained elements are not cleared on `Drop` of the iterator // and require consumption. for _ in >::drain() { } for _ in >::drain() { } Validators::set(notification.validators.clone()); // substrate forces us to clone, stupidly. CurrentSessionIndex::set(notification.session_index); } /// Process a set of incoming bitfields. Return a vec of cores freed by candidates /// becoming available. pub(crate) fn process_bitfields( signed_bitfields: SignedAvailabilityBitfields, core_lookup: impl Fn(CoreIndex) -> Option, ) -> Result, DispatchError> { let validators = Validators::get(); let session_index = CurrentSessionIndex::get(); let config = >::config(); let parachains = >::parachains(); let n_bits = parachains.len() + config.parathread_cores as usize; let mut assigned_paras_record: Vec<_> = (0..n_bits) .map(|bit_index| core_lookup(CoreIndex::from(bit_index as u32))) .map(|core_para| core_para.map(|p| (p, PendingAvailability::::get(&p)))) .collect(); // do sanity checks on the bitfields: // 1. no more than one bitfield per validator // 2. bitfields are ascending by validator index. // 3. each bitfield has exactly `n_bits` // 4. signature is valid. { let occupied_bitmask: BitVec = assigned_paras_record.iter() .map(|p| p.as_ref() .map_or(false, |(_id, pending_availability)| pending_availability.is_some()) ) .collect(); let mut last_index = None; let signing_context = SigningContext { parent_hash: >::parent_hash(), session_index, }; for signed_bitfield in &signed_bitfields { ensure!( signed_bitfield.payload().0.len() == n_bits, Error::::WrongBitfieldSize, ); ensure!( last_index.map_or(true, |last| last < signed_bitfield.validator_index()), Error::::BitfieldDuplicateOrUnordered, ); ensure!( signed_bitfield.validator_index() < validators.len() as ValidatorIndex, Error::::ValidatorIndexOutOfBounds, ); ensure!( occupied_bitmask.clone() & signed_bitfield.payload().0.clone() == signed_bitfield.payload().0, Error::::UnoccupiedBitInBitfield, ); let validator_public = &validators[signed_bitfield.validator_index() as usize]; signed_bitfield.check_signature(&signing_context, validator_public).map_err(|_| Error::::InvalidBitfieldSignature)?; last_index = Some(signed_bitfield.validator_index()); } } let now = >::block_number(); for signed_bitfield in signed_bitfields { for (bit_idx, _) in signed_bitfield.payload().0.iter().enumerate().filter(|(_, is_av)| **is_av) { let record = assigned_paras_record[bit_idx] .as_mut() .expect("validator bitfields checked not to contain bits corresponding to unoccupied cores; qed"); // defensive check - this is constructed by loading the availability bitfield record, // which is always `Some` if the core is occupied - that's why we're here. let val_idx = signed_bitfield.validator_index() as usize; if let Some(mut bit) = record.1.as_mut() .and_then(|r| r.availability_votes.get_mut(val_idx)) { *bit = true; } else if cfg!(debug_assertions) { ensure!(false, Error::::InternalError); } } let validator_index = signed_bitfield.validator_index(); let record = AvailabilityBitfieldRecord { bitfield: signed_bitfield.into_payload(), submitted_at: now, }; >::insert(&validator_index, record); } let threshold = availability_threshold(validators.len()); let mut freed_cores = Vec::with_capacity(n_bits); for (para_id, pending_availability) in assigned_paras_record.into_iter() .filter_map(|x| x) .filter_map(|(id, p)| p.map(|p| (id, p))) { if pending_availability.availability_votes.count_ones() >= threshold { >::remove(¶_id); Self::enact_candidate( pending_availability.relay_parent_number, pending_availability.receipt, ); freed_cores.push(pending_availability.core); } else { >::insert(¶_id, &pending_availability); } } // TODO: pass available candidates onwards to validity module once implemented. // https://github.com/paritytech/polkadot/issues/1251 Ok(freed_cores) } /// Process candidates that have been backed. Provide a set of candidates and scheduled cores. /// /// Both should be sorted ascending by core index, and the candidates should be a subset of /// scheduled cores. If these conditions are not met, the execution of the function fails. pub(crate) fn process_candidates( candidates: Vec>, scheduled: Vec, group_validators: impl Fn(GroupIndex) -> Option>, ) -> Result, DispatchError> { ensure!(candidates.len() <= scheduled.len(), Error::::UnscheduledCandidate); if scheduled.is_empty() { return Ok(Vec::new()); } let validators = Validators::get(); let parent_hash = >::parent_hash(); let config = >::config(); let now = >::block_number(); let relay_parent_number = now - One::one(); // do all checks before writing storage. let core_indices = { let mut skip = 0; let mut core_indices = Vec::with_capacity(candidates.len()); let mut last_core = None; let mut check_assignment_in_order = |assignment: &CoreAssignment| -> DispatchResult { ensure!( last_core.map_or(true, |core| assignment.core > core), Error::::ScheduledOutOfOrder, ); last_core = Some(assignment.core); Ok(()) }; let signing_context = SigningContext { parent_hash, session_index: CurrentSessionIndex::get(), }; // We combine an outer loop over candidates with an inner loop over the scheduled, // where each iteration of the outer loop picks up at the position // in scheduled just after the past iteration left off. // // If the candidates appear in the same order as they appear in `scheduled`, // then they should always be found. If the end of `scheduled` is reached, // then the candidate was either not scheduled or out-of-order. // // In the meantime, we do certain sanity checks on the candidates and on the scheduled // list. 'a: for candidate in &candidates { let para_id = candidate.candidate.parachain_index; // we require that the candidate is in the context of the parent block. ensure!( candidate.candidate.relay_parent == parent_hash, Error::::CandidateNotInParentContext, ); let code_upgrade_allowed = >::last_code_upgrade(para_id, true) .map_or( true, |last| last <= relay_parent_number && relay_parent_number.saturating_sub(last) >= config.validation_upgrade_frequency, ); ensure!(code_upgrade_allowed, Error::::PrematureCodeUpgrade); ensure!( candidate.candidate.check_signature().is_ok(), Error::::NotCollatorSigned, ); for (i, assignment) in scheduled[skip..].iter().enumerate() { check_assignment_in_order(assignment)?; if candidate.candidate.parachain_index == assignment.para_id { if let Some(required_collator) = assignment.required_collator() { ensure!( required_collator == &candidate.candidate.collator, Error::::WrongCollator, ); } ensure!( >::get(&assignment.para_id).is_none(), Error::::CandidateScheduledBeforeParaFree, ); // account for already skipped, and then skip this one. skip = i + skip + 1; let group_vals = group_validators(assignment.group_idx) .ok_or_else(|| Error::::InvalidGroupIndex)?; // check the signatures in the backing and that it is a majority. { let maybe_amount_validated = primitives::parachain::check_candidate_backing( &candidate, &signing_context, group_vals.len(), |idx| group_vals.get(idx) .and_then(|i| validators.get(*i as usize)) .map(|v| v.clone()), ); match maybe_amount_validated { Ok(amount_validated) => ensure!( amount_validated * 2 > group_vals.len(), Error::::InsufficientBacking, ), Err(()) => { Err(Error::::InvalidBacking)?; } } } core_indices.push(assignment.core); continue 'a; } } // end of loop reached means that the candidate didn't appear in the non-traversed // section of the `scheduled` slice. either it was not scheduled or didn't appear in // `candidates` in the correct order. ensure!( false, Error::::UnscheduledCandidate, ); }; // check remainder of scheduled cores, if any. for assignment in scheduled[skip..].iter() { check_assignment_in_order(assignment)?; } core_indices }; // one more sweep for actually writing to storage. for (candidate, core) in candidates.into_iter().zip(core_indices.iter().cloned()) { let para_id = candidate.candidate.parachain_index; // initialize all availability votes to 0. let availability_votes: BitVec = bitvec::bitvec![BitOrderLsb0, u8; 0; validators.len()]; >::insert(¶_id, CandidatePendingAvailability { core, receipt: candidate.candidate, availability_votes, relay_parent_number, backed_in_number: now, }); } Ok(core_indices) } fn enact_candidate( relay_parent_number: T::BlockNumber, receipt: AbridgedCandidateReceipt, ) -> Weight { let commitments = receipt.commitments; let config = >::config(); // initial weight is config read. let mut weight = T::DbWeight::get().reads_writes(1, 0); if let Some(new_code) = commitments.new_validation_code { weight += >::schedule_code_upgrade( receipt.parachain_index, new_code, relay_parent_number + config.validation_upgrade_delay, ); } weight + >::note_new_head( receipt.parachain_index, receipt.head_data, relay_parent_number, ) } /// Cleans up all paras pending availability that the predicate returns true for. /// /// The predicate accepts the index of the core and the block number the core has been occupied /// since (i.e. the block number the candidate was backed at in this fork of the relay chain). /// /// Returns a vector of cleaned-up core IDs. pub(crate) fn collect_pending(pred: impl Fn(CoreIndex, T::BlockNumber) -> bool) -> Vec { let mut cleaned_up_ids = Vec::new(); let mut cleaned_up_cores = Vec::new(); for (para_id, pending_record) in >::iter() { if pred(pending_record.core, pending_record.backed_in_number) { cleaned_up_ids.push(para_id); cleaned_up_cores.push(pending_record.core); } } for para_id in cleaned_up_ids { >::remove(¶_id); } cleaned_up_cores } } const fn availability_threshold(n_validators: usize) -> usize { let mut threshold = (n_validators * 2) / 3; threshold += (n_validators * 2) % 3; threshold } #[cfg(test)] mod tests { use super::*; use primitives::{BlockNumber, Hash}; use primitives::parachain::{ SignedAvailabilityBitfield, CompactStatement as Statement, ValidityAttestation, CollatorId, CandidateCommitments, SignedStatement, }; use frame_support::traits::{OnFinalize, OnInitialize}; use keyring::Sr25519Keyring; use crate::mock::{ new_test_ext, Configuration, Paras, System, Inclusion, GenesisConfig as MockGenesisConfig, Test, }; use crate::initializer::SessionChangeNotification; use crate::configuration::HostConfiguration; use crate::paras::ParaGenesisArgs; use crate::scheduler::AssignmentKind; fn default_config() -> HostConfiguration { let mut config = HostConfiguration::default(); config.parathread_cores = 1; config } fn genesis_config(paras: Vec<(ParaId, bool)>) -> MockGenesisConfig { MockGenesisConfig { paras: paras::GenesisConfig { paras: paras.into_iter().map(|(id, is_chain)| (id, ParaGenesisArgs { genesis_head: Vec::new().into(), validation_code: Vec::new().into(), parachain: is_chain, })).collect(), ..Default::default() }, configuration: configuration::GenesisConfig { config: default_config(), ..Default::default() }, ..Default::default() } } #[derive(Debug, Clone, Copy, PartialEq)] enum BackingKind { #[allow(unused)] Unanimous, Threshold, Lacking, } fn collator_sign_candidate( collator: Sr25519Keyring, candidate: &mut AbridgedCandidateReceipt, ) { candidate.collator = collator.public().into(); let payload = primitives::parachain::collator_signature_payload( &candidate.relay_parent, &candidate.parachain_index, &candidate.pov_block_hash, ); candidate.signature = collator.sign(&payload[..]).into(); assert!(candidate.check_signature().is_ok()); } fn back_candidate( candidate: AbridgedCandidateReceipt, validators: &[Sr25519Keyring], group: &[ValidatorIndex], signing_context: &SigningContext, kind: BackingKind, ) -> BackedCandidate { let mut validator_indices = bitvec::bitvec![BitOrderLsb0, u8; 0; group.len()]; let threshold = (group.len() / 2) + 1; let signing = match kind { BackingKind::Unanimous => group.len(), BackingKind::Threshold => threshold, BackingKind::Lacking => threshold.saturating_sub(1), }; let mut validity_votes = Vec::with_capacity(signing); let candidate_hash = candidate.hash(); for (idx_in_group, val_idx) in group.iter().enumerate().take(signing) { let key: Sr25519Keyring = validators[*val_idx as usize]; *validator_indices.get_mut(idx_in_group).unwrap() = true; let signature = SignedStatement::sign( Statement::Valid(candidate_hash), signing_context, *val_idx, &key.pair().into(), ).signature().clone(); validity_votes.push(ValidityAttestation::Explicit(signature).into()); } let backed = BackedCandidate { candidate, validity_votes, validator_indices, }; let should_pass = match kind { BackingKind::Unanimous | BackingKind::Threshold => true, BackingKind::Lacking => false, }; let successfully_backed = primitives::parachain::check_candidate_backing( &backed, signing_context, group.len(), |i| Some(validators[group[i] as usize].public().into()), ).ok().unwrap_or(0) * 2 > group.len(); if should_pass { assert!(successfully_backed); } else { assert!(!successfully_backed); } backed } fn run_to_block( to: BlockNumber, new_session: impl Fn(BlockNumber) -> Option>, ) { while System::block_number() < to { let b = System::block_number(); Inclusion::initializer_finalize(); Paras::initializer_finalize(); System::on_finalize(b); System::on_initialize(b + 1); System::set_block_number(b + 1); if let Some(notification) = new_session(b + 1) { Paras::initializer_on_new_session(¬ification); Inclusion::initializer_on_new_session(¬ification); } Paras::initializer_initialize(b + 1); Inclusion::initializer_initialize(b + 1); } } fn default_bitfield() -> AvailabilityBitfield { let n_bits = Paras::parachains().len() + Configuration::config().parathread_cores as usize; AvailabilityBitfield(bitvec::bitvec![BitOrderLsb0, u8; 0; n_bits]) } fn default_availability_votes() -> BitVec { bitvec::bitvec![BitOrderLsb0, u8; 0; Validators::get().len()] } fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec { val_ids.iter().map(|v| v.public().into()).collect() } fn sign_bitfield( key: &Sr25519Keyring, validator_index: ValidatorIndex, bitfield: AvailabilityBitfield, signing_context: &SigningContext, ) -> SignedAvailabilityBitfield { SignedAvailabilityBitfield::sign( bitfield, &signing_context, validator_index, &key.pair().into(), ) } #[test] fn collect_pending_cleans_up_pending() { let chain_a = ParaId::from(1); let chain_b = ParaId::from(2); let thread_a = ParaId::from(3); let paras = vec![(chain_a, true), (chain_b, true), (thread_a, false)]; new_test_ext(genesis_config(paras)).execute_with(|| { >::insert(chain_a, CandidatePendingAvailability { core: CoreIndex::from(0), receipt: Default::default(), availability_votes: default_availability_votes(), relay_parent_number: 0, backed_in_number: 0, }); >::insert(chain_b, CandidatePendingAvailability { core: CoreIndex::from(1), receipt: Default::default(), availability_votes: default_availability_votes(), relay_parent_number: 0, backed_in_number: 0, }); run_to_block(5, |_| None); assert!(>::get(&chain_a).is_some()); assert!(>::get(&chain_b).is_some()); Inclusion::collect_pending(|core, _since| core == CoreIndex::from(0)); assert!(>::get(&chain_a).is_none()); assert!(>::get(&chain_b).is_some()); }); } #[test] fn bitfield_checks() { let chain_a = ParaId::from(1); let chain_b = ParaId::from(2); let thread_a = ParaId::from(3); let paras = vec![(chain_a, true), (chain_b, true), (thread_a, false)]; let validators = vec![ Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie, Sr25519Keyring::Dave, Sr25519Keyring::Ferdie, ]; let validator_public = validator_pubkeys(&validators); new_test_ext(genesis_config(paras)).execute_with(|| { Validators::set(validator_public.clone()); CurrentSessionIndex::set(5); let signing_context = SigningContext { parent_hash: System::parent_hash(), session_index: 5, }; let core_lookup = |core| match core { core if core == CoreIndex::from(0) => Some(chain_a), core if core == CoreIndex::from(1) => Some(chain_b), core if core == CoreIndex::from(2) => Some(thread_a), _ => panic!("Core out of bounds for 2 parachains and 1 parathread core."), }; // wrong number of bits. { let mut bare_bitfield = default_bitfield(); bare_bitfield.0.push(false); let signed = sign_bitfield( &validators[0], 0, bare_bitfield, &signing_context, ); assert!(Inclusion::process_bitfields( vec![signed], &core_lookup, ).is_err()); } // duplicate. { let bare_bitfield = default_bitfield(); let signed = sign_bitfield( &validators[0], 0, bare_bitfield, &signing_context, ); assert!(Inclusion::process_bitfields( vec![signed.clone(), signed], &core_lookup, ).is_err()); } // out of order. { let bare_bitfield = default_bitfield(); let signed_0 = sign_bitfield( &validators[0], 0, bare_bitfield.clone(), &signing_context, ); let signed_1 = sign_bitfield( &validators[1], 1, bare_bitfield, &signing_context, ); assert!(Inclusion::process_bitfields( vec![signed_1, signed_0], &core_lookup, ).is_err()); } // non-pending bit set. { let mut bare_bitfield = default_bitfield(); *bare_bitfield.0.get_mut(0).unwrap() = true; let signed = sign_bitfield( &validators[0], 0, bare_bitfield, &signing_context, ); assert!(Inclusion::process_bitfields( vec![signed], &core_lookup, ).is_err()); } // empty bitfield signed: always OK, but kind of useless. { let bare_bitfield = default_bitfield(); let signed = sign_bitfield( &validators[0], 0, bare_bitfield, &signing_context, ); assert!(Inclusion::process_bitfields( vec![signed], &core_lookup, ).is_ok()); } // bitfield signed with pending bit signed. { let mut bare_bitfield = default_bitfield(); assert_eq!(core_lookup(CoreIndex::from(0)), Some(chain_a)); >::insert(chain_a, CandidatePendingAvailability { core: CoreIndex::from(0), receipt: Default::default(), availability_votes: default_availability_votes(), relay_parent_number: 0, backed_in_number: 0, }); *bare_bitfield.0.get_mut(0).unwrap() = true; let signed = sign_bitfield( &validators[0], 0, bare_bitfield, &signing_context, ); assert!(Inclusion::process_bitfields( vec![signed], &core_lookup, ).is_ok()); } }); } #[test] fn supermajority_bitfields_trigger_availability() { let chain_a = ParaId::from(1); let chain_b = ParaId::from(2); let thread_a = ParaId::from(3); let paras = vec![(chain_a, true), (chain_b, true), (thread_a, false)]; let validators = vec![ Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie, Sr25519Keyring::Dave, Sr25519Keyring::Ferdie, ]; let validator_public = validator_pubkeys(&validators); new_test_ext(genesis_config(paras)).execute_with(|| { Validators::set(validator_public.clone()); CurrentSessionIndex::set(5); let signing_context = SigningContext { parent_hash: System::parent_hash(), session_index: 5, }; let core_lookup = |core| match core { core if core == CoreIndex::from(0) => Some(chain_a), core if core == CoreIndex::from(1) => Some(chain_b), core if core == CoreIndex::from(2) => Some(thread_a), _ => panic!("Core out of bounds for 2 parachains and 1 parathread core."), }; >::insert(chain_a, CandidatePendingAvailability { core: CoreIndex::from(0), receipt: AbridgedCandidateReceipt { parachain_index: chain_a, head_data: vec![1, 2, 3, 4].into(), ..Default::default() }, availability_votes: default_availability_votes(), relay_parent_number: 0, backed_in_number: 0, }); >::insert(chain_b, CandidatePendingAvailability { core: CoreIndex::from(1), receipt: AbridgedCandidateReceipt { parachain_index: chain_b, head_data: vec![5, 6, 7, 8].into(), ..Default::default() }, availability_votes: default_availability_votes(), relay_parent_number: 0, backed_in_number: 0, }); // this bitfield signals that a and b are available. let a_and_b_available = { let mut bare_bitfield = default_bitfield(); *bare_bitfield.0.get_mut(0).unwrap() = true; *bare_bitfield.0.get_mut(1).unwrap() = true; bare_bitfield }; // this bitfield signals that only a is available. let a_available = { let mut bare_bitfield = default_bitfield(); *bare_bitfield.0.get_mut(0).unwrap() = true; bare_bitfield }; let threshold = availability_threshold(validators.len()); // 4 of 5 first value >= 2/3 assert_eq!(threshold, 4); let signed_bitfields = validators.iter().enumerate().filter_map(|(i, key)| { let to_sign = if i < 3 { a_and_b_available.clone() } else if i < 4 { a_available.clone() } else { // sign nothing. return None }; Some(sign_bitfield( key, i as ValidatorIndex, to_sign, &signing_context, )) }).collect(); assert!(Inclusion::process_bitfields( signed_bitfields, &core_lookup, ).is_ok()); // chain A had 4 signing off, which is >= threshold. // chain B has 3 signing off, which is < threshold. assert!(>::get(&chain_a).is_none()); assert_eq!( >::get(&chain_b).unwrap().availability_votes, { // check that votes from first 3 were tracked. let mut votes = default_availability_votes(); *votes.get_mut(0).unwrap() = true; *votes.get_mut(1).unwrap() = true; *votes.get_mut(2).unwrap() = true; votes }, ); // and check that chain head was enacted. assert_eq!(Paras::para_head(&chain_a), Some(vec![1, 2, 3, 4].into())); }); } #[test] fn candidate_checks() { let chain_a = ParaId::from(1); let chain_b = ParaId::from(2); let thread_a = ParaId::from(3); let paras = vec![(chain_a, true), (chain_b, true), (thread_a, false)]; let validators = vec![ Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie, Sr25519Keyring::Dave, Sr25519Keyring::Ferdie, ]; let validator_public = validator_pubkeys(&validators); new_test_ext(genesis_config(paras)).execute_with(|| { Validators::set(validator_public.clone()); CurrentSessionIndex::set(5); run_to_block(5, |_| None); let signing_context = SigningContext { parent_hash: System::parent_hash(), session_index: 5, }; let group_validators = |group_index: GroupIndex| match group_index { group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]), group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]), group_index if group_index == GroupIndex::from(2) => Some(vec![4]), _ => panic!("Group index out of bounds for 2 parachains and 1 parathread core"), }; let thread_collator: CollatorId = Sr25519Keyring::Two.public().into(); let chain_a_assignment = CoreAssignment { core: CoreIndex::from(0), para_id: chain_b, kind: AssignmentKind::Parachain, group_idx: GroupIndex::from(0), }; let chain_b_assignment = CoreAssignment { core: CoreIndex::from(1), para_id: chain_b, kind: AssignmentKind::Parachain, group_idx: GroupIndex::from(1), }; let thread_a_assignment = CoreAssignment { core: CoreIndex::from(2), para_id: chain_b, kind: AssignmentKind::Parathread(thread_collator.clone(), 0), group_idx: GroupIndex::from(2), }; // unscheduled candidate. { let mut candidate = AbridgedCandidateReceipt { parachain_index: chain_a, relay_parent: System::parent_hash(), pov_block_hash: Hash::from([1; 32]), ..Default::default() }; collator_sign_candidate( Sr25519Keyring::One, &mut candidate, ); let backed = back_candidate( candidate, &validators, group_validators(GroupIndex::from(0)).unwrap().as_ref(), &signing_context, BackingKind::Threshold, ); assert!(Inclusion::process_candidates( vec![backed], vec![chain_b_assignment.clone()], &group_validators, ).is_err()); } // candidates out of order. { let mut candidate_a = AbridgedCandidateReceipt { parachain_index: chain_a, relay_parent: System::parent_hash(), pov_block_hash: Hash::from([1; 32]), ..Default::default() }; let mut candidate_b = AbridgedCandidateReceipt { parachain_index: chain_b, relay_parent: System::parent_hash(), pov_block_hash: Hash::from([2; 32]), ..Default::default() }; collator_sign_candidate( Sr25519Keyring::One, &mut candidate_a, ); collator_sign_candidate( Sr25519Keyring::Two, &mut candidate_b, ); let backed_a = back_candidate( candidate_a, &validators, group_validators(GroupIndex::from(0)).unwrap().as_ref(), &signing_context, BackingKind::Threshold, ); let backed_b = back_candidate( candidate_b, &validators, group_validators(GroupIndex::from(1)).unwrap().as_ref(), &signing_context, BackingKind::Threshold, ); assert!(Inclusion::process_candidates( vec![backed_b, backed_a], vec![chain_a_assignment.clone(), chain_b_assignment.clone()], &group_validators, ).is_err()); } // candidate not backed. { let mut candidate = AbridgedCandidateReceipt { parachain_index: chain_a, relay_parent: System::parent_hash(), pov_block_hash: Hash::from([1; 32]), ..Default::default() }; collator_sign_candidate( Sr25519Keyring::One, &mut candidate, ); let backed = back_candidate( candidate, &validators, group_validators(GroupIndex::from(0)).unwrap().as_ref(), &signing_context, BackingKind::Lacking, ); assert!(Inclusion::process_candidates( vec![backed], vec![chain_a_assignment.clone()], &group_validators, ).is_err()); } // candidate not in parent context. { let wrong_parent_hash = Hash::from([222; 32]); assert!(System::parent_hash() != wrong_parent_hash); let mut candidate = AbridgedCandidateReceipt { parachain_index: chain_a, relay_parent: wrong_parent_hash, pov_block_hash: Hash::from([1; 32]), ..Default::default() }; collator_sign_candidate( Sr25519Keyring::One, &mut candidate, ); let backed = back_candidate( candidate, &validators, group_validators(GroupIndex::from(0)).unwrap().as_ref(), &signing_context, BackingKind::Threshold, ); assert!(Inclusion::process_candidates( vec![backed], vec![chain_a_assignment.clone()], &group_validators, ).is_err()); } // candidate has wrong collator. { let mut candidate = AbridgedCandidateReceipt { parachain_index: thread_a, relay_parent: System::parent_hash(), pov_block_hash: Hash::from([1; 32]), ..Default::default() }; assert!(CollatorId::from(Sr25519Keyring::One.public()) != thread_collator); collator_sign_candidate( Sr25519Keyring::One, &mut candidate, ); let backed = back_candidate( candidate, &validators, group_validators(GroupIndex::from(2)).unwrap().as_ref(), &signing_context, BackingKind::Threshold, ); assert!(Inclusion::process_candidates( vec![backed], vec![ chain_a_assignment.clone(), chain_b_assignment.clone(), thread_a_assignment.clone(), ], &group_validators, ).is_err()); } // candidate not well-signed by collator. { let mut candidate = AbridgedCandidateReceipt { parachain_index: thread_a, relay_parent: System::parent_hash(), pov_block_hash: Hash::from([1; 32]), ..Default::default() }; assert_eq!(CollatorId::from(Sr25519Keyring::Two.public()), thread_collator); collator_sign_candidate( Sr25519Keyring::Two, &mut candidate, ); candidate.pov_block_hash = Hash::from([2; 32]); let backed = back_candidate( candidate, &validators, group_validators(GroupIndex::from(2)).unwrap().as_ref(), &signing_context, BackingKind::Threshold, ); assert!(Inclusion::process_candidates( vec![backed], vec![thread_a_assignment.clone()], &group_validators, ).is_err()); } // para occupied - reject. { let mut candidate = AbridgedCandidateReceipt { parachain_index: chain_a, relay_parent: System::parent_hash(), pov_block_hash: Hash::from([1; 32]), ..Default::default() }; collator_sign_candidate( Sr25519Keyring::One, &mut candidate, ); let backed = back_candidate( candidate, &validators, group_validators(GroupIndex::from(0)).unwrap().as_ref(), &signing_context, BackingKind::Threshold, ); >::insert(&chain_a, CandidatePendingAvailability { core: CoreIndex::from(0), receipt: Default::default(), availability_votes: default_availability_votes(), relay_parent_number: 3, backed_in_number: 4, }); assert!(Inclusion::process_candidates( vec![backed], vec![chain_a_assignment.clone()], &group_validators, ).is_err()); >::remove(&chain_a); } // interfering code upgrade - reject { let mut candidate = AbridgedCandidateReceipt { parachain_index: chain_a, relay_parent: System::parent_hash(), pov_block_hash: Hash::from([1; 32]), commitments: CandidateCommitments { new_validation_code: Some(vec![5, 6, 7, 8].into()), ..Default::default() }, ..Default::default() }; collator_sign_candidate( Sr25519Keyring::One, &mut candidate, ); let backed = back_candidate( candidate, &validators, group_validators(GroupIndex::from(0)).unwrap().as_ref(), &signing_context, BackingKind::Threshold, ); Paras::schedule_code_upgrade( chain_a, vec![1, 2, 3, 4].into(), 10, ); assert_eq!(Paras::last_code_upgrade(chain_a, true), Some(10)); assert!(Inclusion::process_candidates( vec![backed], vec![thread_a_assignment.clone()], &group_validators, ).is_err()); } }); } #[test] fn backing_works() { let chain_a = ParaId::from(1); let chain_b = ParaId::from(2); let thread_a = ParaId::from(3); let paras = vec![(chain_a, true), (chain_b, true), (thread_a, false)]; let validators = vec![ Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie, Sr25519Keyring::Dave, Sr25519Keyring::Ferdie, ]; let validator_public = validator_pubkeys(&validators); new_test_ext(genesis_config(paras)).execute_with(|| { Validators::set(validator_public.clone()); CurrentSessionIndex::set(5); run_to_block(5, |_| None); let signing_context = SigningContext { parent_hash: System::parent_hash(), session_index: 5, }; let group_validators = |group_index: GroupIndex| match group_index { group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]), group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]), group_index if group_index == GroupIndex::from(2) => Some(vec![4]), _ => panic!("Group index out of bounds for 2 parachains and 1 parathread core"), }; let thread_collator: CollatorId = Sr25519Keyring::Two.public().into(); let chain_a_assignment = CoreAssignment { core: CoreIndex::from(0), para_id: chain_a, kind: AssignmentKind::Parachain, group_idx: GroupIndex::from(0), }; let chain_b_assignment = CoreAssignment { core: CoreIndex::from(1), para_id: chain_b, kind: AssignmentKind::Parachain, group_idx: GroupIndex::from(1), }; let thread_a_assignment = CoreAssignment { core: CoreIndex::from(2), para_id: thread_a, kind: AssignmentKind::Parathread(thread_collator.clone(), 0), group_idx: GroupIndex::from(2), }; let mut candidate_a = AbridgedCandidateReceipt { parachain_index: chain_a, relay_parent: System::parent_hash(), pov_block_hash: Hash::from([1; 32]), ..Default::default() }; collator_sign_candidate( Sr25519Keyring::One, &mut candidate_a, ); let mut candidate_b = AbridgedCandidateReceipt { parachain_index: chain_b, relay_parent: System::parent_hash(), pov_block_hash: Hash::from([2; 32]), ..Default::default() }; collator_sign_candidate( Sr25519Keyring::One, &mut candidate_b, ); let mut candidate_c = AbridgedCandidateReceipt { parachain_index: thread_a, relay_parent: System::parent_hash(), pov_block_hash: Hash::from([3; 32]), ..Default::default() }; collator_sign_candidate( Sr25519Keyring::Two, &mut candidate_c, ); let backed_a = back_candidate( candidate_a.clone(), &validators, group_validators(GroupIndex::from(0)).unwrap().as_ref(), &signing_context, BackingKind::Threshold, ); let backed_b = back_candidate( candidate_b.clone(), &validators, group_validators(GroupIndex::from(1)).unwrap().as_ref(), &signing_context, BackingKind::Threshold, ); let backed_c = back_candidate( candidate_c.clone(), &validators, group_validators(GroupIndex::from(2)).unwrap().as_ref(), &signing_context, BackingKind::Threshold, ); let occupied_cores = Inclusion::process_candidates( vec![backed_a, backed_b, backed_c], vec![ chain_a_assignment.clone(), chain_b_assignment.clone(), thread_a_assignment.clone(), ], &group_validators, ).expect("candidates scheduled, in order, and backed"); assert_eq!(occupied_cores, vec![CoreIndex::from(0), CoreIndex::from(1), CoreIndex::from(2)]); assert_eq!( >::get(&chain_a), Some(CandidatePendingAvailability { core: CoreIndex::from(0), receipt: candidate_a, availability_votes: default_availability_votes(), relay_parent_number: System::block_number() - 1, backed_in_number: System::block_number(), }) ); assert_eq!( >::get(&chain_b), Some(CandidatePendingAvailability { core: CoreIndex::from(1), receipt: candidate_b, availability_votes: default_availability_votes(), relay_parent_number: System::block_number() - 1, backed_in_number: System::block_number(), }) ); assert_eq!( >::get(&thread_a), Some(CandidatePendingAvailability { core: CoreIndex::from(2), receipt: candidate_c, availability_votes: default_availability_votes(), relay_parent_number: System::block_number() - 1, backed_in_number: System::block_number(), }) ); }); } #[test] fn session_change_wipes_and_updates_session_info() { let chain_a = ParaId::from(1); let chain_b = ParaId::from(2); let thread_a = ParaId::from(3); let paras = vec![(chain_a, true), (chain_b, true), (thread_a, false)]; let validators = vec![ Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie, Sr25519Keyring::Dave, Sr25519Keyring::Ferdie, ]; let validator_public = validator_pubkeys(&validators); new_test_ext(genesis_config(paras)).execute_with(|| { Validators::set(validator_public.clone()); CurrentSessionIndex::set(5); let validators_new = vec![ Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie, ]; let validator_public_new = validator_pubkeys(&validators_new); run_to_block(10, |_| None); >::insert( &0, AvailabilityBitfieldRecord { bitfield: default_bitfield(), submitted_at: 9, }, ); >::insert( &1, AvailabilityBitfieldRecord { bitfield: default_bitfield(), submitted_at: 9, }, ); >::insert( &4, AvailabilityBitfieldRecord { bitfield: default_bitfield(), submitted_at: 9, }, ); >::insert(&chain_a, CandidatePendingAvailability { core: CoreIndex::from(0), receipt: Default::default(), availability_votes: default_availability_votes(), relay_parent_number: 5, backed_in_number: 6, }); >::insert(&chain_b, CandidatePendingAvailability { core: CoreIndex::from(1), receipt: Default::default(), availability_votes: default_availability_votes(), relay_parent_number: 6, backed_in_number: 7, }); run_to_block(11, |_| None); assert_eq!(Validators::get(), validator_public); assert_eq!(CurrentSessionIndex::get(), 5); assert!(>::get(&0).is_some()); assert!(>::get(&1).is_some()); assert!(>::get(&4).is_some()); assert!(>::get(&chain_a).is_some()); assert!(>::get(&chain_b).is_some()); run_to_block(12, |n| match n { 12 => Some(SessionChangeNotification { validators: validator_public_new.clone(), queued: Vec::new(), prev_config: default_config(), new_config: default_config(), random_seed: Default::default(), session_index: 6, }), _ => None, }); assert_eq!(Validators::get(), validator_public_new); assert_eq!(CurrentSessionIndex::get(), 6); assert!(>::get(&0).is_none()); assert!(>::get(&1).is_none()); assert!(>::get(&4).is_none()); assert!(>::get(&chain_a).is_none()); assert!(>::get(&chain_b).is_none()); assert!(>::iter().collect::>().is_empty()); assert!(>::iter().collect::>().is_empty()); }); } }