diff --git a/primitives/src/parachain.rs b/primitives/src/parachain.rs index 195c986a23be..48d4d263b433 100644 --- a/primitives/src/parachain.rs +++ b/primitives/src/parachain.rs @@ -176,20 +176,20 @@ pub struct DutyRoster { /// These are global parameters that apply to all parachain candidates in a block. #[derive(PartialEq, Eq, Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Default))] -pub struct GlobalValidationSchedule { +pub struct GlobalValidationSchedule { /// The maximum code size permitted, in bytes. pub max_code_size: u32, /// The maximum head-data size permitted, in bytes. pub max_head_data_size: u32, /// The relay-chain block number this is in the context of. - pub block_number: BlockNumber, + pub block_number: N, } /// Extra data that is needed along with the other fields in a `CandidateReceipt` /// to fully validate the candidate. These fields are parachain-specific. #[derive(PartialEq, Eq, Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Default))] -pub struct LocalValidationData { +pub struct LocalValidationData { /// The parent head-data. pub parent_head: HeadData, /// The balance of the parachain at the moment of validation. @@ -205,28 +205,28 @@ pub struct LocalValidationData { /// height. This may be equal to the current perceived relay-chain block height, in /// which case the code upgrade should be applied at the end of the signaling /// block. - pub code_upgrade_allowed: Option, + pub code_upgrade_allowed: Option, } /// Commitments made in a `CandidateReceipt`. Many of these are outputs of validation. #[derive(PartialEq, Eq, Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Default))] -pub struct CandidateCommitments { +pub struct CandidateCommitments { /// Fees paid from the chain to the relay chain validators. pub fees: Balance, /// Messages destined to be interpreted by the Relay chain itself. pub upward_messages: Vec, /// The root of a block's erasure encoding Merkle tree. - pub erasure_root: Hash, + pub erasure_root: H, /// New validation code. pub new_validation_code: Option, } /// Get a collator signature payload on a relay-parent, block-data combo. -pub fn collator_signature_payload( - relay_parent: &Hash, +pub fn collator_signature_payload>( + relay_parent: &H, parachain_index: &Id, - pov_block_hash: &Hash, + pov_block_hash: &H, ) -> [u8; 68] { // 32-byte hash length is protected in a test below. let mut payload = [0u8; 68]; @@ -238,10 +238,10 @@ pub fn collator_signature_payload( payload } -fn check_collator_signature( - relay_parent: &Hash, +fn check_collator_signature>( + relay_parent: &H, parachain_index: &Id, - pov_block_hash: &Hash, + pov_block_hash: &H, collator: &CollatorId, signature: &CollatorSignature, ) -> Result<(),()> { @@ -258,12 +258,12 @@ fn check_collator_signature( /// All data pertaining to the execution of a parachain candidate. #[derive(PartialEq, Eq, Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Default))] -pub struct CandidateReceipt { +pub struct CandidateReceipt { /// The ID of the parachain this is a candidate for. pub parachain_index: Id, /// The hash of the relay-chain block this should be executed in /// the context of. - pub relay_parent: Hash, + pub relay_parent: H, /// The head-data pub head_data: HeadData, /// The collator's relay-chain account ID @@ -271,16 +271,16 @@ pub struct CandidateReceipt { /// Signature on blake2-256 of the block data by collator. pub signature: CollatorSignature, /// The hash of the PoV-block. - pub pov_block_hash: Hash, + pub pov_block_hash: H, /// The global validation schedule. - pub global_validation: GlobalValidationSchedule, + pub global_validation: GlobalValidationSchedule, /// The local validation data. - pub local_validation: LocalValidationData, + pub local_validation: LocalValidationData, /// Commitments made as a result of validation. - pub commitments: CandidateCommitments, + pub commitments: CandidateCommitments, } -impl CandidateReceipt { +impl, N> CandidateReceipt { /// Check integrity vs. provided block data. pub fn check_signature(&self) -> Result<(), ()> { check_collator_signature( @@ -294,7 +294,7 @@ impl CandidateReceipt { /// Abridge this `CandidateReceipt`, splitting it into an `AbridgedCandidateReceipt` /// and its omitted component. - pub fn abridge(self) -> (AbridgedCandidateReceipt, OmittedValidationData) { + pub fn abridge(self) -> (AbridgedCandidateReceipt, OmittedValidationData) { let CandidateReceipt { parachain_index, relay_parent, @@ -345,11 +345,11 @@ impl Ord for CandidateReceipt { /// is necessary for validation of the parachain candidate. #[derive(PartialEq, Eq, Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Default))] -pub struct OmittedValidationData { +pub struct OmittedValidationData { /// The global validation schedule. - pub global_validation: GlobalValidationSchedule, + pub global_validation: GlobalValidationSchedule, /// The local validation data. - pub local_validation: LocalValidationData, + pub local_validation: LocalValidationData, } /// An abridged candidate-receipt. @@ -359,14 +359,14 @@ pub struct OmittedValidationData { /// be re-generated from relay-chain state. #[derive(PartialEq, Eq, Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Default))] -pub struct AbridgedCandidateReceipt { +pub struct AbridgedCandidateReceipt { /// The ID of the parachain this is a candidate for. pub parachain_index: Id, /// The hash of the relay-chain block this should be executed in /// the context of. // NOTE: the fact that the hash includes this value means that code depends // on this for deduplication. Removing this field is likely to break things. - pub relay_parent: Hash, + pub relay_parent: H, /// The head-data pub head_data: HeadData, /// The collator's relay-chain account ID @@ -374,12 +374,23 @@ pub struct AbridgedCandidateReceipt { /// Signature on blake2-256 of the block data by collator. pub signature: CollatorSignature, /// The hash of the pov-block. - pub pov_block_hash: Hash, + pub pov_block_hash: H, /// Commitments made as a result of validation. - pub commitments: CandidateCommitments, + pub commitments: CandidateCommitments, } -impl AbridgedCandidateReceipt { +impl + Encode> AbridgedCandidateReceipt { + /// Check integrity vs. provided block data. + pub fn check_signature(&self) -> Result<(), ()> { + check_collator_signature( + &self.relay_parent, + &self.parachain_index, + &self.pov_block_hash, + &self.collator, + &self.signature, + ) + } + /// Compute the hash of the abridged candidate receipt. /// /// This is often used as the canonical hash of the receipt, rather than @@ -391,7 +402,9 @@ impl AbridgedCandidateReceipt { use runtime_primitives::traits::{BlakeTwo256, Hash}; BlakeTwo256::hash_of(self) } +} +impl AbridgedCandidateReceipt { /// Combine the abridged candidate receipt with the omitted data, /// forming a full `CandidateReceipt`. pub fn complete(self, omitted: OmittedValidationData) -> CandidateReceipt { @@ -616,13 +629,42 @@ pub enum ValidityAttestation { Explicit(ValidatorSignature), } +impl ValidityAttestation { + /// Get a reference to the signature. + pub fn signature(&self) -> &ValidatorSignature { + match *self { + ValidityAttestation::Implicit(ref sig) => sig, + ValidityAttestation::Explicit(ref sig) => sig, + } + } + + /// Produce the underlying signed payload of the attestation, given the hash of the candidate, + /// which should be known in context. + pub fn signed_payload( + &self, + candidate_hash: Hash, + signing_context: &SigningContext, + ) -> Vec { + match *self { + ValidityAttestation::Implicit(_) => ( + Statement::Candidate(candidate_hash), + signing_context, + ).encode(), + ValidityAttestation::Explicit(_) => ( + Statement::Valid(candidate_hash), + signing_context, + ).encode(), + } + } +} + /// A type returned by runtime with current session index and a parent hash. #[derive(Clone, Eq, PartialEq, Default, Decode, Encode, RuntimeDebug)] -pub struct SigningContext { +pub struct SigningContext { /// Current session index. pub session_index: sp_staking::SessionIndex, /// Hash of the parent. - pub parent_hash: Hash, + pub parent_hash: H, } /// An attested candidate. This is submitted to the relay chain by a block author. @@ -683,9 +725,9 @@ impl From> for AvailabilityBitfield { impl AvailabilityBitfield { /// Encodes the signing payload into the given buffer. - pub fn encode_signing_payload_into( + pub fn encode_signing_payload_into( &self, - signing_context: &SigningContext, + signing_context: &SigningContext, buf: &mut Vec, ) { self.0.encode_to(buf); @@ -693,10 +735,9 @@ impl AvailabilityBitfield { } /// Encodes the signing payload into a fresh byte-vector. - pub fn encode_signing_payload( + pub fn encode_signing_payload( &self, - signing_context: - &SigningContext, + signing_context: &SigningContext, ) -> Vec { let mut v = Vec::new(); self.encode_signing_payload_into(signing_context, &mut v); @@ -724,7 +765,7 @@ pub fn check_availability_bitfield_signature( bitfield: &AvailabilityBitfield, validator: &ValidatorId, signature: &ValidatorSignature, - signing_context: &SigningContext, + signing_context: &SigningContext, payload_encode_buf: Option<&mut Vec>, ) -> Result<(),()> { use runtime_primitives::traits::AppVerify; @@ -750,15 +791,67 @@ pub struct SignedAvailabilityBitfields(pub Vec); // After https://github.com/paritytech/polkadot/issues/1250 // they should be unified to this type. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] -pub struct BackedCandidate { +pub struct BackedCandidate { /// The candidate referred to. - pub candidate: AbridgedCandidateReceipt, + pub candidate: AbridgedCandidateReceipt, /// The validity votes themselves, expressed as signatures. pub validity_votes: Vec, /// The indices of the validators within the group, expressed as a bitfield. pub validator_indices: BitVec, } +/// Verify the backing of the given candidate. +/// +/// Provide a lookup from the index of a validator within the group assigned to this para, +/// as opposed to the index of the validator within the overall validator set, as well as +/// the number of validators in the group. +/// +/// Also provide the signing context. +/// +/// Returns either an error, indicating that one of the signatures was invalid or that the index +/// was out-of-bounds, or the number of signatures checked. +pub fn check_candidate_backing + Encode>( + backed: &BackedCandidate, + signing_context: &SigningContext, + group_len: usize, + validator_lookup: impl Fn(usize) -> Option, +) -> Result { + use runtime_primitives::traits::AppVerify; + + if backed.validator_indices.len() != group_len { + return Err(()) + } + + if backed.validity_votes.len() > group_len { + return Err(()) + } + + // this is known, even in runtime, to be blake2-256. + let hash: Hash = backed.candidate.hash(); + + let mut signed = 0; + for ((val_in_group_idx, _), attestation) in backed.validator_indices.iter().enumerate() + .filter(|(_, signed)| **signed) + .zip(backed.validity_votes.iter()) + { + let validator_id = validator_lookup(val_in_group_idx).ok_or(())?; + let payload = attestation.signed_payload(hash.clone(), signing_context); + let sig = attestation.signature(); + + if sig.verify(&payload[..], &validator_id) { + signed += 1; + } else { + return Err(()) + } + } + + if signed != backed.validity_votes.len() { + return Err(()) + } + + Ok(signed) +} + sp_api::decl_runtime_apis! { /// The API for querying the state of parachains on-chain. #[api_version(3)] @@ -811,9 +904,9 @@ mod tests { assert_eq!(h.as_ref().len(), 32); let _payload = collator_signature_payload( - &[1; 32].into(), + &Hash::from([1; 32]), &5u32.into(), - &[2; 32].into(), + &Hash::from([2; 32]), ); } } diff --git a/roadmap/implementors-guide/src/runtime/README.md b/roadmap/implementors-guide/src/runtime/README.md index 5367146d7db2..8ff2f7eca400 100644 --- a/roadmap/implementors-guide/src/runtime/README.md +++ b/roadmap/implementors-guide/src/runtime/README.md @@ -48,6 +48,8 @@ struct SessionChangeNotification { new_config: HostConfiguration, // A secure randomn seed for the session, gathered from BABE. random_seed: [u8; 32], + // The session index of the beginning session. + session_index: SessionIndex, } ``` diff --git a/roadmap/implementors-guide/src/runtime/inclusion.md b/roadmap/implementors-guide/src/runtime/inclusion.md index c5df672d8eaf..e2d27476967e 100644 --- a/roadmap/implementors-guide/src/runtime/inclusion.md +++ b/roadmap/implementors-guide/src/runtime/inclusion.md @@ -28,6 +28,12 @@ Storage Layout: bitfields: map ValidatorIndex => AvailabilityBitfield; /// Candidates pending availability. PendingAvailability: map ParaId => CandidatePendingAvailability; + +/// The current validators, by their parachain session keys. +Validators: Vec; + +/// The current session index. +CurrentSessionIndex: SessionIndex; ``` > TODO: `CandidateReceipt` and `AbridgedCandidateReceipt` can contain code upgrades which make them very large. the code entries should be split into a different storage map with infrequent access patterns @@ -36,6 +42,8 @@ PendingAvailability: map ParaId => CandidatePendingAvailability; 1. Clear out all candidates pending availability. 1. Clear out all validator bitfields. +1. Update `Validators` with the validators from the session change notification. +1. Update `CurrentSessionIndex` with the session index from the session change notification. ## Routines @@ -50,11 +58,15 @@ All failed checks should lead to an unrecoverable error making the block invalid 1. For all now-available candidates, invoke the `enact_candidate` routine with the candidate and relay-parent number. 1. > TODO: pass it onwards to `Validity` module. 1. Return a list of freed cores consisting of the cores where candidates have become available. -* `process_candidates(BackedCandidates, scheduled: Vec)`: - 1. check that each candidate corresponds to a scheduled core and that they are ordered in ascending order by `ParaId`. - 1. Ensure that any code upgrade scheduled by the candidate does not happen within `config.validation_upgrade_frequency` of the currently scheduled upgrade, if any, comparing against the value of `Paras::FutureCodeUpgrades` for the given para ID. - 1. check the backing of the candidate using the signatures and the bitfields. - 1. check that the upward messages are not exceeding `config.max_upward_queue_count` and `config.watermark_upward_queue_size` parameters. +* `process_candidates(BackedCandidates, scheduled: Vec, group_validators: Fn(GroupIndex) -> Option>)`: + 1. check that each candidate corresponds to a scheduled core and that they are ordered in the same order the cores appear in assignments in `scheduled`. + 1. check that `scheduled` is sorted ascending by `CoreIndex`, without duplicates. + 1. check that there is no candidate pending availability for any scheduled `ParaId`. + 1. If the core assignment includes a specific collator, ensure the backed candidate is issued by that collator. + 1. Ensure that any code upgrade scheduled by the candidate does not happen within `config.validation_upgrade_frequency` of `Paras::last_code_upgrade(para_id, true)`, if any, comparing against the value of `Paras::FutureCodeUpgrades` for the given para ID. + 1. Check the collator's signature on the pov block. + 1. check the backing of the candidate using the signatures and the bitfields, comparing against the validators assigned to the groups, fetched with the `group_validators` lookup. + 1. check that the upward messages, when combined with the existing queue size, are not exceeding `config.max_upward_queue_count` and `config.watermark_upward_queue_size` parameters. 1. create an entry in the `PendingAvailability` map for each backed candidate with a blank `availability_votes` bitfield. 1. Return a `Vec` of all scheduled cores of the list of passed assignments that a candidate was successfully backed for, sorted ascending by CoreIndex. * `enact_candidate(relay_parent_number: BlockNumber, AbridgedCandidateReceipt)`: diff --git a/roadmap/implementors-guide/src/runtime/inclusioninherent.md b/roadmap/implementors-guide/src/runtime/inclusioninherent.md index f7ecd1c73924..c65754c0e23a 100644 --- a/roadmap/implementors-guide/src/runtime/inclusioninherent.md +++ b/roadmap/implementors-guide/src/runtime/inclusioninherent.md @@ -16,9 +16,10 @@ Included: Option<()>, ## Entry Points -* `inclusion`: This entry-point accepts two parameters: [`Bitfields`](../types/availability.html#signed-availability-bitfield) and [`BackedCandidates`](../types/backing.html#backed-candidate). - 1. The `Bitfields` are first forwarded to the `process_bitfields` routine, returning a set of freed cores. Provide a `Scheduler::core_para` as a core-lookup to the `process_bitfields` routine. Annotate each of these freed cores with `FreedReason::Concluded`. +* `inclusion`: This entry-point accepts two parameters: [`Bitfields`](../types/availability.html#signed-availability-bitfield) and [`BackedCandidates`](../type-definitions.html#backed-candidate). + 1. The `Bitfields` are first forwarded to the `Inclusion::process_bitfields` routine, returning a set of freed cores. Provide a `Scheduler::core_para` as a core-lookup to the `process_bitfields` routine. Annotate each of these freed cores with `FreedReason::Concluded`. 1. If `Scheduler::availability_timeout_predicate` is `Some`, invoke `Inclusion::collect_pending` using it, and add timed-out cores to the free cores, annotated with `FreedReason::TimedOut`. 1. Invoke `Scheduler::schedule(freed)` - 1. Call `Scheduler::occupied` for all scheduled cores where a backed candidate was submitted. + 1. Invoke the `Inclusion::process_candidates` routine with the parameters `(backed_candidates, Scheduler::scheduled(), Scheduler::group_validators)`. + 1. Call `Scheduler::occupied` using the return value of the `Inclusion::process_candidates` call above, first sorting the list of assigned core indices. 1. If all of the above succeeds, set `Included` to `Some(())`. diff --git a/roadmap/implementors-guide/src/runtime/paras.md b/roadmap/implementors-guide/src/runtime/paras.md index d22021d68116..e80c2d102d2f 100644 --- a/roadmap/implementors-guide/src/runtime/paras.md +++ b/roadmap/implementors-guide/src/runtime/paras.md @@ -111,6 +111,8 @@ OutgoingParas: Vec; * `validation_code_at(ParaId, at: BlockNumber, assume_intermediate: Option)`: Fetches the validation code to be used when validating a block in the context of the given relay-chain height. A second block number parameter may be used to tell the lookup to proceed as if an intermediate parablock has been included at the given relay-chain height. This may return past, current, or (with certain choices of `assume_intermediate`) future code. `assume_intermediate`, if provided, must be before `at`. If the validation code has been pruned, this will return `None`. * `is_parathread(ParaId) -> bool`: Returns true if the para ID references any live parathread. +* `last_code_upgrade(id: ParaId, include_future: bool) -> Option`: The block number of the last scheduled upgrade of the requested para. Includes future upgrades if the flag is set. This is the `expected_at` number, not the `activated_at` number. + ## Finalization No finalization routine runs for this module. diff --git a/runtime/parachains/src/inclusion.rs b/runtime/parachains/src/inclusion.rs index 1f45de2df705..5fba4cf26ca3 100644 --- a/runtime/parachains/src/inclusion.rs +++ b/runtime/parachains/src/inclusion.rs @@ -13,3 +13,1590 @@ // 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 mut payload_encode_buf = Vec::new(); + + let signing_context = SigningContext { + parent_hash: >::parent_hash(), + session_index, + }; + + for signed_bitfield in &signed_bitfields.0 { + ensure!( + signed_bitfield.bitfield.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.bitfield.0.clone() == signed_bitfield.bitfield.0, + Error::::UnoccupiedBitInBitfield, + ); + + let validator_public = &validators[signed_bitfield.validator_index as usize]; + + if let Err(()) = primitives::parachain::check_availability_bitfield_signature( + &signed_bitfield.bitfield, + validator_public, + &signed_bitfield.signature, + &signing_context, + Some(&mut payload_encode_buf), + ) { + Err(Error::::InvalidBitfieldSignature)?; + } + + last_index = Some(signed_bitfield.validator_index); + payload_encode_buf.clear(); + } + } + + let now = >::block_number(); + for signed_bitfield in signed_bitfields.0 { + for (bit_idx, _) + in signed_bitfield.bitfield.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 record = AvailabilityBitfieldRecord { + bitfield: signed_bitfield.bitfield, + submitted_at: now, + }; + + >::insert(&signed_bitfield.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, Statement, ValidityAttestation, CollatorId, + CandidateCommitments, + }; + 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(); + let payload = Statement::Valid(candidate_hash).signing_payload(signing_context); + + 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; + + validity_votes.push(ValidityAttestation::Explicit(key.sign(&payload[..]).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 + { + let payload = bitfield.encode_signing_payload(signing_context); + + SignedAvailabilityBitfield { + validator_index, + bitfield: bitfield, + signature: key.sign(&payload[..]).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( + SignedAvailabilityBitfields(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( + SignedAvailabilityBitfields(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( + SignedAvailabilityBitfields(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( + SignedAvailabilityBitfields(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( + SignedAvailabilityBitfields(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( + SignedAvailabilityBitfields(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( + SignedAvailabilityBitfields(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()); + + }); + } +} diff --git a/runtime/parachains/src/inclusion_inherent.rs b/runtime/parachains/src/inclusion_inherent.rs new file mode 100644 index 000000000000..46fe1fee469e --- /dev/null +++ b/runtime/parachains/src/inclusion_inherent.rs @@ -0,0 +1,120 @@ +// 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 . + +//! Provides glue code over the scheduler and inclusion modules, and accepting +//! one inherent per block that can include new para candidates and bitfields. +//! +//! Unlike other modules in this crate, it does not need to be initialized by the initializer, +//! as it has no initialization logic and its finalization logic depends only on the details of +//! this module. + +use sp_std::prelude::*; +use primitives::{ + parachain::{BackedCandidate, SignedAvailabilityBitfields}, +}; +use frame_support::{ + decl_storage, decl_module, decl_error, ensure, + dispatch::DispatchResult, + weights::{DispatchClass, Weight}, + traits::Get, +}; +use system::ensure_none; +use crate::{inclusion, scheduler::{self, FreedReason}}; + +pub trait Trait: inclusion::Trait + scheduler::Trait { } + +decl_storage! { + trait Store for Module as ParaInclusionInherent { + /// Whether the inclusion inherent was included within this block. + /// + /// The `Option<()>` is effectively a bool, but it never hits storage in the `None` variant + /// due to the guarantees of FRAME's storage APIs. + /// + /// If this is `None` at the end of the block, we panic and render the block invalid. + Included: Option<()>; + } +} + +decl_error! { + pub enum Error for Module { + /// Inclusion inherent called more than once per block. + TooManyInclusionInherents, + } +} + +decl_module! { + /// The inclusion inherent module. + pub struct Module for enum Call where origin: ::Origin { + type Error = Error; + + fn on_initialize() -> Weight { + T::DbWeight::get().reads_writes(1, 1) // in on_finalize. + } + + fn on_finalize() { + if Included::take().is_none() { + panic!("Bitfields and heads must be included every block"); + } + } + + /// Include backed candidates and bitfields. + #[weight = (1_000_000_000, DispatchClass::Mandatory)] + pub fn inclusion( + origin, + signed_bitfields: SignedAvailabilityBitfields, + backed_candidates: Vec>, + ) -> DispatchResult { + ensure_none(origin)?; + ensure!(!::exists(), Error::::TooManyInclusionInherents); + + // Process new availability bitfields, yielding any availability cores whose + // work has now concluded. + let freed_concluded = >::process_bitfields( + signed_bitfields, + >::core_para, + )?; + + // Handle timeouts for any availability core work. + let availability_pred = >::availability_timeout_predicate(); + let freed_timeout = if let Some(pred) = availability_pred { + >::collect_pending(pred) + } else { + Vec::new() + }; + + // Schedule paras again, given freed cores, and reasons for freeing. + let freed = freed_concluded.into_iter().map(|c| (c, FreedReason::Concluded)) + .chain(freed_timeout.into_iter().map(|c| (c, FreedReason::TimedOut))); + + >::schedule(freed.collect()); + + // Process backed candidates according to scheduled cores. + let occupied = >::process_candidates( + backed_candidates, + >::scheduled(), + >::group_validators, + )?; + + // Note which of the scheduled cores were actually occupied by a backed candidate. + >::occupied(&occupied); + + // And track that we've finished processing the inherent for this block. + Included::set(Some(())); + + Ok(()) + } + } +} diff --git a/runtime/parachains/src/initializer.rs b/runtime/parachains/src/initializer.rs index 2b86d19654cd..5668a216f061 100644 --- a/runtime/parachains/src/initializer.rs +++ b/runtime/parachains/src/initializer.rs @@ -27,7 +27,7 @@ use primitives::{ use frame_support::{ decl_storage, decl_module, decl_error, traits::Randomness, }; -use crate::{configuration::{self, HostConfiguration}, paras, scheduler}; +use crate::{configuration::{self, HostConfiguration}, paras, scheduler, inclusion}; /// Information about a session change that has just occurred. #[derive(Default, Clone)] @@ -42,9 +42,13 @@ pub struct SessionChangeNotification { pub new_config: HostConfiguration, /// A secure random seed for the session, gathered from BABE. pub random_seed: [u8; 32], + /// New session index. + pub session_index: sp_staking::SessionIndex, } -pub trait Trait: system::Trait + configuration::Trait + paras::Trait + scheduler::Trait { +pub trait Trait: + system::Trait + configuration::Trait + paras::Trait + scheduler::Trait + inclusion::Trait +{ /// A randomness beacon. type Randomness: Randomness; } @@ -81,7 +85,8 @@ decl_module! { // - Validity let total_weight = configuration::Module::::initializer_initialize(now) + paras::Module::::initializer_initialize(now) + - scheduler::Module::::initializer_initialize(now); + scheduler::Module::::initializer_initialize(now) + + inclusion::Module::::initializer_initialize(now); HasInitialized::set(Some(())); @@ -91,6 +96,7 @@ decl_module! { fn on_finalize() { // reverse initialization order. + inclusion::Module::::initializer_finalize(); scheduler::Module::::initializer_finalize(); paras::Module::::initializer_finalize(); configuration::Module::::initializer_finalize(); @@ -101,16 +107,25 @@ decl_module! { impl Module { /// Should be called when a new session occurs. Forwards the session notification to all - /// wrapped modules. + /// wrapped modules. If `queued` is `None`, the `validators` are considered queued. /// /// Panics if the modules have already been initialized. - fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued: I) + fn on_new_session<'a, I: 'a>( + _changed: bool, + session_index: sp_staking::SessionIndex, + validators: I, + queued: Option, + ) where I: Iterator { assert!(HasInitialized::get().is_none()); let validators: Vec<_> = validators.map(|(_, v)| v).collect(); - let queued: Vec<_> = queued.map(|(_, v)| v).collect(); + let queued: Vec<_> = if let Some(queued) = queued { + queued.map(|(_, v)| v).collect() + } else { + validators.clone() + }; let prev_config = >::config(); @@ -134,10 +149,12 @@ impl Module { prev_config, new_config, random_seed, + session_index, }; paras::Module::::initializer_on_new_session(¬ification); scheduler::Module::::initializer_on_new_session(¬ification); + inclusion::Module::::initializer_on_new_session(¬ification); } } @@ -145,7 +162,7 @@ impl sp_runtime::BoundToRuntimeAppPublic for Module { type Public = ValidatorId; } -impl session::OneSessionHandler for Module { +impl session::OneSessionHandler for Module { type Key = ValidatorId; fn on_genesis_session<'a, I: 'a>(_validators: I) @@ -157,7 +174,8 @@ impl session::OneSessionHandler for Module { fn on_new_session<'a, I: 'a>(changed: bool, validators: I, queued: I) where I: Iterator { - >::on_new_session(changed, validators, queued); + let session_index = >::current_index(); + >::on_new_session(changed, session_index, validators, Some(queued)); } fn on_disabled(_i: usize) { } @@ -175,7 +193,12 @@ mod tests { fn panics_if_session_changes_after_on_initialize() { new_test_ext(Default::default()).execute_with(|| { Initializer::on_initialize(1); - Initializer::on_new_session(false, Vec::new().into_iter(), Vec::new().into_iter()); + Initializer::on_new_session( + false, + 1, + Vec::new().into_iter(), + Some(Vec::new().into_iter()), + ); }); } diff --git a/runtime/parachains/src/lib.rs b/runtime/parachains/src/lib.rs index 74c237fd2213..44554322e4a4 100644 --- a/runtime/parachains/src/lib.rs +++ b/runtime/parachains/src/lib.rs @@ -22,6 +22,7 @@ mod configuration; mod inclusion; +mod inclusion_inherent; mod initializer; mod paras; mod scheduler; diff --git a/runtime/parachains/src/mock.rs b/runtime/parachains/src/mock.rs index 362794a9d11c..fd0d8e75e20d 100644 --- a/runtime/parachains/src/mock.rs +++ b/runtime/parachains/src/mock.rs @@ -99,6 +99,8 @@ impl crate::paras::Trait for Test { } impl crate::scheduler::Trait for Test { } +impl crate::inclusion::Trait for Test { } + pub type System = system::Module; /// Mocked initializer. @@ -113,6 +115,9 @@ pub type Paras = crate::paras::Module; /// Mocked scheduler. pub type Scheduler = crate::scheduler::Module; +/// Mocked inclusion module. +pub type Inclusion = crate::inclusion::Module; + /// Create a new set of test externalities. pub fn new_test_ext(state: GenesisConfig) -> TestExternalities { let mut t = state.system.build_storage::().unwrap(); diff --git a/runtime/parachains/src/paras.rs b/runtime/parachains/src/paras.rs index 9d8c74ca0932..3bd55e277803 100644 --- a/runtime/parachains/src/paras.rs +++ b/runtime/parachains/src/paras.rs @@ -174,7 +174,7 @@ decl_storage! { /// All parathreads. Parathreads: map hasher(twox_64_concat) ParaId => Option<()>; /// The head-data of every registered para. - Heads get(fn parachain_head): map hasher(twox_64_concat) ParaId => Option; + Heads get(fn para_head): map hasher(twox_64_concat) ParaId => Option; /// The validation code of every live para. CurrentCode get(fn current_code): map hasher(twox_64_concat) ParaId => Option; /// Actual past code, indicated by the para id as well as the block number at which it became outdated. @@ -368,7 +368,7 @@ impl Module { ::PastCode::remove(&(para_id, pruned_repl_at)); } - meta.most_recent_change().is_none() && Self::parachain_head(¶_id).is_none() + meta.most_recent_change().is_none() && Self::para_head(¶_id).is_none() }); // This parachain has been removed and now the vestigial code @@ -428,7 +428,6 @@ impl Module { /// with number >= `expected_at` /// /// If there is already a scheduled code upgrade for the para, this is a no-op. - #[allow(unused)] pub(crate) fn schedule_code_upgrade( id: ParaId, new_code: ValidationCode, @@ -448,15 +447,14 @@ impl Module { /// Note that a para has progressed to a new head, where the new head was executed in the context /// of a relay-chain block with given number. This will apply pending code upgrades based /// on the block number provided. - #[allow(unused)] pub(crate) fn note_new_head( id: ParaId, new_head: HeadData, execution_context: T::BlockNumber, ) -> Weight { - if let Some(expected_at) = ::FutureCodeUpgrades::get(&id) { - Heads::insert(&id, new_head); + Heads::insert(&id, new_head); + if let Some(expected_at) = ::FutureCodeUpgrades::get(&id) { if expected_at <= execution_context { ::FutureCodeUpgrades::remove(&id); @@ -481,7 +479,7 @@ impl Module { T::DbWeight::get().reads_writes(1, 1 + 0) } } else { - T::DbWeight::get().reads_writes(1, 0) + T::DbWeight::get().reads_writes(1, 1) } } @@ -526,6 +524,18 @@ impl Module { pub(crate) fn is_parathread(id: ParaId) -> bool { Parathreads::get(&id).is_some() } + + /// The block number of the last scheduled upgrade of the requested para. Includes future upgrades + /// if the flag is set. This is the `expected_at` number, not the `activated_at` number. + pub(crate) fn last_code_upgrade(id: ParaId, include_future: bool) -> Option { + if include_future { + if let Some(at) = Self::future_code_upgrade_at(id) { + return Some(at); + } + } + + Self::past_code_meta(&id).most_recent_change() + } } #[cfg(test)] @@ -683,6 +693,40 @@ mod tests { }); } + #[test] + fn note_new_head_sets_head() { + let acceptance_period = 10; + let paras = vec![ + (0u32.into(), ParaGenesisArgs { + parachain: true, + genesis_head: Default::default(), + validation_code: Default::default(), + }), + ]; + + let genesis_config = MockGenesisConfig { + paras: GenesisConfig { paras, ..Default::default() }, + configuration: crate::configuration::GenesisConfig { + config: HostConfiguration { + acceptance_period, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + let id_a = ParaId::from(0u32); + + assert_eq!(Paras::para_head(&id_a), Some(Default::default())); + + Paras::note_new_head(id_a, vec![1, 2, 3].into(), 0); + + assert_eq!(Paras::para_head(&id_a), Some(vec![1, 2, 3].into())); + }); + } + #[test] fn note_past_code_sets_up_pruning_correctly() { let acceptance_period = 10; diff --git a/runtime/parachains/src/scheduler.rs b/runtime/parachains/src/scheduler.rs index 4f0554407774..6539915946c8 100644 --- a/runtime/parachains/src/scheduler.rs +++ b/runtime/parachains/src/scheduler.rs @@ -57,11 +57,23 @@ use crate::{configuration, paras, initializer::SessionChangeNotification}; #[cfg_attr(test, derive(Debug))] pub struct CoreIndex(u32); +impl From for CoreIndex { + fn from(i: u32) -> CoreIndex { + CoreIndex(i) + } +} + /// The unique (during session) index of a validator group. #[derive(Encode, Decode, Default, Clone, Copy)] #[cfg_attr(test, derive(PartialEq, Debug))] pub struct GroupIndex(u32); +impl From for GroupIndex { + fn from(i: u32) -> GroupIndex { + GroupIndex(i) + } +} + /// A claim on authoring the next block for a given parathread. #[derive(Clone, Encode, Decode, Default)] #[cfg_attr(test, derive(PartialEq, Debug))] @@ -130,7 +142,7 @@ pub enum AssignmentKind { } /// How a free core is scheduled to be assigned. -#[derive(Encode, Decode)] +#[derive(Clone, Encode, Decode)] #[cfg_attr(test, derive(PartialEq, Debug))] pub struct CoreAssignment { /// The core that is assigned. @@ -145,7 +157,6 @@ pub struct CoreAssignment { impl CoreAssignment { /// Get the ID of a collator who is required to collate this block. - #[allow(unused)] pub(crate) fn required_collator(&self) -> Option<&CollatorId> { match self.kind { AssignmentKind::Parachain => None, @@ -153,7 +164,6 @@ impl CoreAssignment { } } - #[allow(unused)] fn to_core_occupied(&self) -> CoreOccupied { match self.kind { AssignmentKind::Parachain => CoreOccupied::Parachain, @@ -168,7 +178,6 @@ impl CoreAssignment { } /// Reasons a core might be freed -#[allow(unused)] pub enum FreedReason { /// The core's work concluded and the parablock assigned to it is considered available. Concluded, @@ -525,7 +534,6 @@ impl Module { /// /// Complexity: O(n) in the number of scheduled cores, which is capped at the number of total cores. /// This is efficient in the case that most scheduled cores are occupied. - #[allow(unused)] pub(crate) fn occupied(now_occupied: &[CoreIndex]) { if now_occupied.is_empty() { return } @@ -557,7 +565,6 @@ impl Module { /// Get the para (chain or thread) ID assigned to a particular core or index, if any. Core indices /// out of bounds will return `None`, as will indices of unassigned cores. - #[allow(unused)] pub(crate) fn core_para(core_index: CoreIndex) -> Option { let cores = AvailabilityCores::get(); match cores.get(core_index.0 as usize).and_then(|c| c.as_ref()) { @@ -571,7 +578,6 @@ impl Module { } /// Get the validators in the given group, if the group index is valid for this session. - #[allow(unused)] pub(crate) fn group_validators(group_index: GroupIndex) -> Option> { ValidatorGroups::get().get(group_index.0 as usize).map(|g| g.clone()) } @@ -615,10 +621,9 @@ impl Module { /// timeouts, i.e. only within `max(config.chain_availability_period, config.thread_availability_period)` /// of the last rotation would this return `Some`. /// - /// This really should not be a box, but is working around a compiler limitation described here: - /// https://users.rust-lang.org/t/cannot-unify-associated-type-in-impl-fn-with-concrete-type/44129 + /// This really should not be a box, but is working around a compiler limitation filed here: + /// https://github.com/rust-lang/rust/issues/73226 /// which prevents us from testing the code if using `impl Trait`. - #[allow(unused)] pub(crate) fn availability_timeout_predicate() -> Option bool>> { let now = >::block_number(); let config = >::config();