mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 08:11:03 +00:00
Inclusion Module (#1242)
* add availability bitfield types to primitives * begin inclusion module * use GitHub issue link for limitation * fix some compiler errors * integrate validators into initializer * add generic signing context * make signing-context more generic * fix issues with inclusion module * add TODO * guide: add validators and session index to inclusion * guide: add session index to change notification * implement session change logic * add BackedCandidate type * guide: refine inclusion pipeline * guide: rename group_on to group_validators * guide: add check about collator for parathread * guide: add last_code_upgrade to paras and use in inclusion * implement Paras::last_code_upgrade * implement most checks in process_candidates * make candidate receipt structs more generic * make BackedCandidate struct more generic * use hash param, not block number * check that candidate is in context of the parent block * include inclusion module in initializer * implement enact-candidate * check that only occupied cores have bits set * finish implementing bitfield processing * restructure consistency checks on candidates * make some more primitives generic * signature checking logic for backed candidates * finish implementing process_candidates * implement collect_pending * add some trait implementations to primitives * implement InclusionInherent and squash warnings * test bitfield signing checks * rename parachain head to para_head * fix note_new_head bug in paras * test bitfield enactment in inclusion * helpers for candidate checks * add test for most candidate checks * add test for backing setting storage * test session change logic * remove extraneous type parameter * remove some allow(unused)s * extract threshold computation to const fn * remove some more allow(unused)s * improve doc * add debug assertion * fix primitive test compilation * tag unanimous variant as unused
This commit is contained in:
committed by
GitHub
parent
7accc6e499
commit
879892d3f9
@@ -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<N = BlockNumber> {
|
||||
/// 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<N = BlockNumber> {
|
||||
/// 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<BlockNumber>,
|
||||
pub code_upgrade_allowed: Option<N>,
|
||||
}
|
||||
|
||||
/// 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<H = Hash> {
|
||||
/// 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<UpwardMessage>,
|
||||
/// 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<ValidationCode>,
|
||||
}
|
||||
|
||||
/// 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<H: AsRef<[u8]>>(
|
||||
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<H: AsRef<[u8]>>(
|
||||
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<H = Hash, N = BlockNumber> {
|
||||
/// 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<N>,
|
||||
/// The local validation data.
|
||||
pub local_validation: LocalValidationData,
|
||||
pub local_validation: LocalValidationData<N>,
|
||||
/// Commitments made as a result of validation.
|
||||
pub commitments: CandidateCommitments,
|
||||
pub commitments: CandidateCommitments<H>,
|
||||
}
|
||||
|
||||
impl CandidateReceipt {
|
||||
impl<H: AsRef<[u8]>, N> CandidateReceipt<H, N> {
|
||||
/// 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<H>, OmittedValidationData<N>) {
|
||||
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<N = BlockNumber> {
|
||||
/// The global validation schedule.
|
||||
pub global_validation: GlobalValidationSchedule,
|
||||
pub global_validation: GlobalValidationSchedule<N>,
|
||||
/// The local validation data.
|
||||
pub local_validation: LocalValidationData,
|
||||
pub local_validation: LocalValidationData<N>,
|
||||
}
|
||||
|
||||
/// 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<H = Hash> {
|
||||
/// 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<H>,
|
||||
}
|
||||
|
||||
impl AbridgedCandidateReceipt {
|
||||
impl<H: AsRef<[u8]> + Encode> AbridgedCandidateReceipt<H> {
|
||||
/// 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<H: Encode>(
|
||||
&self,
|
||||
candidate_hash: Hash,
|
||||
signing_context: &SigningContext<H>,
|
||||
) -> Vec<u8> {
|
||||
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<H = Hash> {
|
||||
/// 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<BitVec<bitvec::order::Lsb0, u8>> for AvailabilityBitfield {
|
||||
|
||||
impl AvailabilityBitfield {
|
||||
/// Encodes the signing payload into the given buffer.
|
||||
pub fn encode_signing_payload_into(
|
||||
pub fn encode_signing_payload_into<H: Encode>(
|
||||
&self,
|
||||
signing_context: &SigningContext,
|
||||
signing_context: &SigningContext<H>,
|
||||
buf: &mut Vec<u8>,
|
||||
) {
|
||||
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<H: Encode>(
|
||||
&self,
|
||||
signing_context:
|
||||
&SigningContext,
|
||||
signing_context: &SigningContext<H>,
|
||||
) -> Vec<u8> {
|
||||
let mut v = Vec::new();
|
||||
self.encode_signing_payload_into(signing_context, &mut v);
|
||||
@@ -724,7 +765,7 @@ pub fn check_availability_bitfield_signature<H: Encode>(
|
||||
bitfield: &AvailabilityBitfield,
|
||||
validator: &ValidatorId,
|
||||
signature: &ValidatorSignature,
|
||||
signing_context: &SigningContext,
|
||||
signing_context: &SigningContext<H>,
|
||||
payload_encode_buf: Option<&mut Vec<u8>>,
|
||||
) -> Result<(),()> {
|
||||
use runtime_primitives::traits::AppVerify;
|
||||
@@ -750,15 +791,67 @@ pub struct SignedAvailabilityBitfields(pub Vec<SignedAvailabilityBitfield>);
|
||||
// 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<H = Hash> {
|
||||
/// The candidate referred to.
|
||||
pub candidate: AbridgedCandidateReceipt,
|
||||
pub candidate: AbridgedCandidateReceipt<H>,
|
||||
/// The validity votes themselves, expressed as signatures.
|
||||
pub validity_votes: Vec<ValidityAttestation>,
|
||||
/// The indices of the validators within the group, expressed as a bitfield.
|
||||
pub validator_indices: BitVec<bitvec::order::Lsb0, u8>,
|
||||
}
|
||||
|
||||
/// 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<H: AsRef<[u8]> + Encode>(
|
||||
backed: &BackedCandidate<H>,
|
||||
signing_context: &SigningContext<H>,
|
||||
group_len: usize,
|
||||
validator_lookup: impl Fn(usize) -> Option<ValidatorId>,
|
||||
) -> Result<usize, ()> {
|
||||
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]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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<ValidatorId>;
|
||||
|
||||
/// 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<CoreAssignment>)`:
|
||||
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<CoreAssignment>, group_validators: Fn(GroupIndex) -> Option<Vec<ValidatorIndex>>)`:
|
||||
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<CoreIndex>` 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)`:
|
||||
|
||||
@@ -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(())`.
|
||||
|
||||
@@ -111,6 +111,8 @@ OutgoingParas: Vec<ParaId>;
|
||||
* `validation_code_at(ParaId, at: BlockNumber, assume_intermediate: Option<BlockNumber>)`: 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<BlockNumber>`: 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.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<T: Trait> 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<T: Trait> {
|
||||
/// Inclusion inherent called more than once per block.
|
||||
TooManyInclusionInherents,
|
||||
}
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
/// The inclusion inherent module.
|
||||
pub struct Module<T: Trait> for enum Call where origin: <T as system::Trait>::Origin {
|
||||
type Error = Error<T>;
|
||||
|
||||
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<BackedCandidate<T::Hash>>,
|
||||
) -> DispatchResult {
|
||||
ensure_none(origin)?;
|
||||
ensure!(!<Included>::exists(), Error::<T>::TooManyInclusionInherents);
|
||||
|
||||
// Process new availability bitfields, yielding any availability cores whose
|
||||
// work has now concluded.
|
||||
let freed_concluded = <inclusion::Module<T>>::process_bitfields(
|
||||
signed_bitfields,
|
||||
<scheduler::Module<T>>::core_para,
|
||||
)?;
|
||||
|
||||
// Handle timeouts for any availability core work.
|
||||
let availability_pred = <scheduler::Module<T>>::availability_timeout_predicate();
|
||||
let freed_timeout = if let Some(pred) = availability_pred {
|
||||
<inclusion::Module<T>>::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)));
|
||||
|
||||
<scheduler::Module<T>>::schedule(freed.collect());
|
||||
|
||||
// Process backed candidates according to scheduled cores.
|
||||
let occupied = <inclusion::Module<T>>::process_candidates(
|
||||
backed_candidates,
|
||||
<scheduler::Module<T>>::scheduled(),
|
||||
<scheduler::Module<T>>::group_validators,
|
||||
)?;
|
||||
|
||||
// Note which of the scheduled cores were actually occupied by a backed candidate.
|
||||
<scheduler::Module<T>>::occupied(&occupied);
|
||||
|
||||
// And track that we've finished processing the inherent for this block.
|
||||
Included::set(Some(()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<BlockNumber> {
|
||||
pub new_config: HostConfiguration<BlockNumber>,
|
||||
/// 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<Self::Hash>;
|
||||
}
|
||||
@@ -81,7 +85,8 @@ decl_module! {
|
||||
// - Validity
|
||||
let total_weight = configuration::Module::<T>::initializer_initialize(now) +
|
||||
paras::Module::<T>::initializer_initialize(now) +
|
||||
scheduler::Module::<T>::initializer_initialize(now);
|
||||
scheduler::Module::<T>::initializer_initialize(now) +
|
||||
inclusion::Module::<T>::initializer_initialize(now);
|
||||
|
||||
HasInitialized::set(Some(()));
|
||||
|
||||
@@ -91,6 +96,7 @@ decl_module! {
|
||||
fn on_finalize() {
|
||||
// reverse initialization order.
|
||||
|
||||
inclusion::Module::<T>::initializer_finalize();
|
||||
scheduler::Module::<T>::initializer_finalize();
|
||||
paras::Module::<T>::initializer_finalize();
|
||||
configuration::Module::<T>::initializer_finalize();
|
||||
@@ -101,16 +107,25 @@ decl_module! {
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// 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<I>,
|
||||
)
|
||||
where I: Iterator<Item=(&'a T::AccountId, ValidatorId)>
|
||||
{
|
||||
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 = <configuration::Module<T>>::config();
|
||||
|
||||
@@ -134,10 +149,12 @@ impl<T: Trait> Module<T> {
|
||||
prev_config,
|
||||
new_config,
|
||||
random_seed,
|
||||
session_index,
|
||||
};
|
||||
|
||||
paras::Module::<T>::initializer_on_new_session(¬ification);
|
||||
scheduler::Module::<T>::initializer_on_new_session(¬ification);
|
||||
inclusion::Module::<T>::initializer_on_new_session(¬ification);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +162,7 @@ impl<T: Trait> sp_runtime::BoundToRuntimeAppPublic for Module<T> {
|
||||
type Public = ValidatorId;
|
||||
}
|
||||
|
||||
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||
impl<T: session::Trait + Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||
type Key = ValidatorId;
|
||||
|
||||
fn on_genesis_session<'a, I: 'a>(_validators: I)
|
||||
@@ -157,7 +174,8 @@ impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I, queued: I)
|
||||
where I: Iterator<Item=(&'a T::AccountId, Self::Key)>
|
||||
{
|
||||
<Module<T>>::on_new_session(changed, validators, queued);
|
||||
let session_index = <session::Module<T>>::current_index();
|
||||
<Module<T>>::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()),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
mod configuration;
|
||||
mod inclusion;
|
||||
mod inclusion_inherent;
|
||||
mod initializer;
|
||||
mod paras;
|
||||
mod scheduler;
|
||||
|
||||
@@ -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<Test>;
|
||||
|
||||
/// Mocked initializer.
|
||||
@@ -113,6 +115,9 @@ pub type Paras = crate::paras::Module<Test>;
|
||||
/// Mocked scheduler.
|
||||
pub type Scheduler = crate::scheduler::Module<Test>;
|
||||
|
||||
/// Mocked inclusion module.
|
||||
pub type Inclusion = crate::inclusion::Module<Test>;
|
||||
|
||||
/// Create a new set of test externalities.
|
||||
pub fn new_test_ext(state: GenesisConfig) -> TestExternalities {
|
||||
let mut t = state.system.build_storage::<Test>().unwrap();
|
||||
|
||||
@@ -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<HeadData>;
|
||||
Heads get(fn para_head): map hasher(twox_64_concat) ParaId => Option<HeadData>;
|
||||
/// The validation code of every live para.
|
||||
CurrentCode get(fn current_code): map hasher(twox_64_concat) ParaId => Option<ValidationCode>;
|
||||
/// Actual past code, indicated by the para id as well as the block number at which it became outdated.
|
||||
@@ -368,7 +368,7 @@ impl<T: Trait> Module<T> {
|
||||
<Self as Store>::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<T: Trait> Module<T> {
|
||||
/// 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<T: Trait> Module<T> {
|
||||
/// 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) = <Self as Store>::FutureCodeUpgrades::get(&id) {
|
||||
Heads::insert(&id, new_head);
|
||||
Heads::insert(&id, new_head);
|
||||
|
||||
if let Some(expected_at) = <Self as Store>::FutureCodeUpgrades::get(&id) {
|
||||
if expected_at <= execution_context {
|
||||
<Self as Store>::FutureCodeUpgrades::remove(&id);
|
||||
|
||||
@@ -481,7 +479,7 @@ impl<T: Trait> Module<T> {
|
||||
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<T: Trait> Module<T> {
|
||||
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<T::BlockNumber> {
|
||||
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;
|
||||
|
||||
@@ -57,11 +57,23 @@ use crate::{configuration, paras, initializer::SessionChangeNotification};
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub struct CoreIndex(u32);
|
||||
|
||||
impl From<u32> 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<u32> 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<T: Trait> Module<T> {
|
||||
///
|
||||
/// 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<T: Trait> Module<T> {
|
||||
|
||||
/// 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<ParaId> {
|
||||
let cores = AvailabilityCores::get();
|
||||
match cores.get(core_index.0 as usize).and_then(|c| c.as_ref()) {
|
||||
@@ -571,7 +578,6 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
|
||||
/// 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<Vec<ValidatorIndex>> {
|
||||
ValidatorGroups::get().get(group_index.0 as usize).map(|g| g.clone())
|
||||
}
|
||||
@@ -615,10 +621,9 @@ impl<T: Trait> Module<T> {
|
||||
/// 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<Box<dyn Fn(CoreIndex, T::BlockNumber) -> bool>> {
|
||||
let now = <system::Module<T>>::block_number();
|
||||
let config = <configuration::Module<T>>::config();
|
||||
|
||||
Reference in New Issue
Block a user