From 5d7142f19789c00c2b995328d813a48d1a6fbaaa Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Sat, 18 Jul 2020 16:01:51 -0400 Subject: [PATCH] Implement Runtime APIs (#1411) * create a README on Runtime APIs * add ParaId type * write up runtime APIs * more preamble * rename * rejig runtime APIs * add occupied_since to `BlockNumber` * skeleton crate for runtime API subsystem * improve group_for_core * improve docs on availability cores runtime API * guide: freed -> free * add primitives for runtime APIs * create a v1 ParachainHost API trait * guide: make validation code return `Option`al. * skeleton runtime API helpers * make parachain-host runtime-generic * skeleton for most runtime API implementation functions * guide: add runtime API helper methods * implement new helpers of the inclusion module * guide: remove retries check, as it is unneeded * implement helpers for scheduler module for Runtime APIs * clean up `validator_groups` implementation * implement next_rotation_at and last_rotation_at * guide: more helpers on GroupRotationInfo * almost finish implementing runtime APIs * add explicit block parameter to runtime API fns * guide: generalize number parameter * guide: add group_responsible to occupied-core * update primitives due to guide changes * finishing touches on runtime API implementation; squash warnings * break out runtime API impl to separate file * add tests for next_up logic * test group rotation info * point to filed TODO * remove unused TODO [now] * indentation * guide: para -> para_id * rename para field to para_id for core meta * remove reference to outdated AvailabilityCores type * add an event in `inclusion` for candidates being included or timing out * guide: candidate events * guide: adjust language * Candidate events type from guide and adjust inclusion event * implement `candidate_events` runtime API * fix runtime test compilation * max -> min * fix typos * guide: add `RuntimeAPIRequest::CandidateEvents` --- Cargo.lock | 2 + node/core/README.md | 2 +- node/core/runtime-api/Cargo.toml | 22 + node/core/runtime-api/src/lib.rs | 22 + node/network/pov-distribution/src/lib.rs | 1 - primitives/Cargo.toml | 2 + primitives/src/v1.rs | 235 ++++++++++- .../src/runtime-api/README.md | 39 +- .../src/runtime/inclusion.md | 3 + .../src/runtime/scheduler.md | 3 + .../src/types/overseer-protocol.md | 4 +- runtime/parachains/Cargo.toml | 2 + runtime/parachains/src/inclusion.rs | 126 +++++- runtime/parachains/src/lib.rs | 2 + runtime/parachains/src/mock.rs | 16 +- .../parachains/src/runtime_api_impl/mod.rs | 23 + runtime/parachains/src/runtime_api_impl/v1.rs | 287 +++++++++++++ runtime/parachains/src/scheduler.rs | 393 +++++++++++++++++- 18 files changed, 1151 insertions(+), 33 deletions(-) create mode 100644 node/core/runtime-api/Cargo.toml create mode 100644 node/core/runtime-api/src/lib.rs create mode 100644 runtime/parachains/src/runtime_api_impl/mod.rs create mode 100644 runtime/parachains/src/runtime_api_impl/v1.rs diff --git a/Cargo.lock b/Cargo.lock index 55b034e1c221..7128dd1e700f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4633,6 +4633,7 @@ dependencies = [ "serde", "sp-api", "sp-application-crypto", + "sp-arithmetic", "sp-core", "sp-inherents", "sp-runtime", @@ -4829,6 +4830,7 @@ dependencies = [ "sp-staking", "sp-std", "sp-trie", + "sp-version", ] [[package]] diff --git a/node/core/README.md b/node/core/README.md index a53faa966a73..1656bb569fe4 100644 --- a/node/core/README.md +++ b/node/core/README.md @@ -1 +1 @@ -Stub - This folder will hold core subsystem implementations, each with their own crate. +This folder contains core subsystems, each with their own crate. diff --git a/node/core/runtime-api/Cargo.toml b/node/core/runtime-api/Cargo.toml new file mode 100644 index 000000000000..14a0ce5540d3 --- /dev/null +++ b/node/core/runtime-api/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "polkadot-node-core-runtime-api" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +futures = "0.3.5" +log = "0.4.8" +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" } +primitives = { package = "sp-core", git = "https://github.com/paritytech/substrate", branch = "master" } + +polkadot-primitives = { path = "../../../primitives" } +polkadot-node-primitives = { path = "../../primitives" } +polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsystem" } + +[dev-dependencies] +futures = { version = "0.3.5", features = ["thread-pool"] } +subsystem-test = { package = "polkadot-subsystem-test-helpers", path = "../../test-helpers/subsystem" } +assert_matches = "1.3.0" diff --git a/node/core/runtime-api/src/lib.rs b/node/core/runtime-api/src/lib.rs new file mode 100644 index 000000000000..0e1023b8c4b4 --- /dev/null +++ b/node/core/runtime-api/src/lib.rs @@ -0,0 +1,22 @@ +// 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 . + +//! Implements the Runtime API Subsystem +//! +//! This provides a clean, ownerless wrapper around the parachain-related runtime APIs. This crate +//! can also be used to cache responses from heavy runtime APIs. +//! +//! TODO: https://github.com/paritytech/polkadot/issues/1419 implement this. diff --git a/node/network/pov-distribution/src/lib.rs b/node/network/pov-distribution/src/lib.rs index ea5365f8fc63..8a7c0fa7a5ee 100644 --- a/node/network/pov-distribution/src/lib.rs +++ b/node/network/pov-distribution/src/lib.rs @@ -1386,7 +1386,6 @@ mod tests { }); } - // TODO [now] awaiting peer sending us something is no longer awaiting. #[test] fn peer_completing_request_no_longer_awaiting() { let hash_a: Hash = [0; 32].into(); diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index b2f1373b05ae..877f03199d47 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -14,6 +14,7 @@ sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", d sp-version = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-std = { package = "sp-std", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-staking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } runtime_primitives = { package = "sp-runtime", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } polkadot-parachain = { path = "../parachain", default-features = false } polkadot-core-primitives = { path = "../core-primitives", default-features = false } @@ -37,6 +38,7 @@ std = [ "sp-std/std", "sp-version/std", "sp-staking/std", + "sp-arithmetic/std", "runtime_primitives/std", "serde", "polkadot-parachain/std", diff --git a/primitives/src/v1.rs b/primitives/src/v1.rs index 9fb3d1662ec7..796dd4fb3159 100644 --- a/primitives/src/v1.rs +++ b/primitives/src/v1.rs @@ -23,6 +23,7 @@ use bitvec::vec::BitVec; use primitives::RuntimeDebug; use runtime_primitives::traits::AppVerify; use inherents::InherentIdentifier; +use sp_arithmetic::traits::{BaseArithmetic, Saturating, Zero}; use runtime_primitives::traits::{BlakeTwo256, Hash as HashT}; @@ -50,6 +51,8 @@ pub use crate::v0::{ #[cfg(feature = "std")] pub use crate::v0::{ValidatorPair, CollatorPair}; +pub use sp_staking::SessionIndex; + /// Unique identifier for the Inclusion Inherent pub const INCLUSION_INHERENT_IDENTIFIER: InherentIdentifier = *b"inclusn0"; @@ -139,13 +142,13 @@ impl CandidateReceipt { /// All data pertaining to the execution of a para candidate. #[derive(PartialEq, Eq, Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Default))] -pub struct FullCandidateReceipt { +pub struct FullCandidateReceipt { /// The inner candidate receipt. pub inner: CandidateReceipt, /// The global validation schedule. - pub global_validation: GlobalValidationSchedule, + pub global_validation: GlobalValidationSchedule, /// The local validation data. - pub local_validation: LocalValidationData, + pub local_validation: LocalValidationData, } /// A candidate-receipt with commitments directly included. @@ -202,7 +205,7 @@ impl Ord for CommittedCandidateReceipt { /// 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. @@ -220,7 +223,7 @@ 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, } /// Extra data that is needed along with the other fields in a `CandidateReceipt` @@ -229,13 +232,13 @@ pub struct LocalValidationData { /// These are global parameters that apply to all 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, } /// Commitments made in a `CandidateReceipt`. Many of these are outputs of validation. @@ -476,3 +479,221 @@ pub struct AvailableData { /// The omitted validation data. pub omitted_validation: OmittedValidationData, } + +/// A helper data-type for tracking validator-group rotations. +#[derive(Clone, Encode, Decode)] +#[cfg_attr(feature = "std", derive(PartialEq, Debug))] +pub struct GroupRotationInfo { + /// The block number where the session started. + pub session_start_block: N, + /// How often groups rotate. 0 means never. + pub group_rotation_frequency: N, + /// The current block number. + pub now: N, +} + +impl GroupRotationInfo { + /// Returns the index of the group needed to validate the core at the given index, assuming + /// the given number of cores. + /// + /// `core_index` should be less than `cores`, which is capped at u32::max(). + pub fn group_for_core(&self, core_index: CoreIndex, cores: usize) -> GroupIndex { + if self.group_rotation_frequency == 0 { return GroupIndex(core_index.0) } + if cores == 0 { return GroupIndex(0) } + + let cores = sp_std::cmp::min(cores, u32::max_value() as usize); + let blocks_since_start = self.now.saturating_sub(self.session_start_block); + let rotations = blocks_since_start / self.group_rotation_frequency; + + let idx = (core_index.0 as usize + rotations as usize) % cores; + GroupIndex(idx as u32) + } +} + +impl GroupRotationInfo { + /// Returns the block number of the next rotation after the current block. If the current block + /// is 10 and the rotation frequency is 5, this should return 15. + /// + /// If the group rotation frequency is 0, returns 0. + pub fn next_rotation_at(&self) -> N { + if self.group_rotation_frequency.is_zero() { return Zero::zero() } + + let cycle_once = self.now + self.group_rotation_frequency; + cycle_once - ( + cycle_once.saturating_sub(self.session_start_block) % self.group_rotation_frequency + ) + } + + /// Returns the block number of the last rotation before or including the current block. If the + /// current block is 10 and the rotation frequency is 5, this should return 10. + /// + /// If the group rotation frequency is 0, returns 0. + pub fn last_rotation_at(&self) -> N { + if self.group_rotation_frequency.is_zero() { return Zero::zero() } + self.now - ( + self.now.saturating_sub(self.session_start_block) % self.group_rotation_frequency + ) + } +} + +/// Information about a core which is currently occupied. +#[derive(Clone, Encode, Decode)] +#[cfg_attr(feature = "std", derive(PartialEq, Debug))] +pub struct OccupiedCore { + /// The ID of the para occupying the core. + pub para_id: Id, + /// If this core is freed by availability, this is the assignment that is next up on this + /// core, if any. None if there is nothing queued for this core. + pub next_up_on_available: Option, + /// The relay-chain block number this began occupying the core at. + pub occupied_since: N, + /// The relay-chain block this will time-out at, if any. + pub time_out_at: N, + /// If this core is freed by being timed-out, this is the assignment that is next up on this + /// core. None if there is nothing queued for this core or there is no possibility of timing + /// out. + pub next_up_on_time_out: Option, + /// A bitfield with 1 bit for each validator in the set. `1` bits mean that the corresponding + /// validators has attested to availability on-chain. A 2/3+ majority of `1` bits means that + /// this will be available. + pub availability: BitVec, + /// The group assigned to distribute availability pieces of this candidate. + pub group_responsible: GroupIndex, +} + +/// Information about a core which is currently occupied. +#[derive(Clone, Encode, Decode)] +#[cfg_attr(feature = "std", derive(PartialEq, Debug))] +pub struct ScheduledCore { + /// The ID of a para scheduled. + pub para_id: Id, + /// The collator required to author the block, if any. + pub collator: Option, +} + +/// The state of a particular availability core. +#[derive(Clone, Encode, Decode)] +#[cfg_attr(feature = "std", derive(PartialEq, Debug))] +pub enum CoreState { + /// The core is currently occupied. + #[codec(index = "0")] + Occupied(OccupiedCore), + /// The core is currently free, with a para scheduled and given the opportunity + /// to occupy. + /// + /// If a particular Collator is required to author this block, that is also present in this + /// variant. + #[codec(index = "1")] + Scheduled(ScheduledCore), + /// The core is currently free and there is nothing scheduled. This can be the case for parathread + /// cores when there are no parathread blocks queued. Parachain cores will never be left idle. + #[codec(index = "2")] + Free, +} + +/// An assumption being made about the state of an occupied core. +#[derive(Clone, Encode, Decode)] +#[cfg_attr(feature = "std", derive(PartialEq, Debug))] +pub enum OccupiedCoreAssumption { + /// The candidate occupying the core was made available and included to free the core. + #[codec(index = "0")] + Included, + /// The candidate occupying the core timed out and freed the core without advancing the para. + #[codec(index = "1")] + TimedOut, + /// The core was not occupied to begin with. + #[codec(index = "2")] + Free, +} + +/// An even concerning a candidate. +#[derive(Clone, Encode, Decode)] +#[cfg_attr(feature = "std", derive(PartialEq, Debug))] +pub enum CandidateEvent { + /// This candidate receipt was backed in the most recent block. + #[codec(index = "0")] + CandidateBacked(CandidateReceipt, HeadData), + /// This candidate receipt was included and became a parablock at the most recent block. + #[codec(index = "1")] + CandidateIncluded(CandidateReceipt, HeadData), + /// This candidate receipt was not made available in time and timed out. + #[codec(index = "2")] + CandidateTimedOut(CandidateReceipt, HeadData), +} + +sp_api::decl_runtime_apis! { + /// The API for querying the state of parachains on-chain. + pub trait ParachainHost { + /// Get the current validators. + fn validators() -> Vec; + + /// Returns the validator groups and rotation info localized based on the block whose state + /// this is invoked on. Note that `now` in the `GroupRotationInfo` should be the successor of + /// the number of the block. + fn validator_groups() -> (Vec>, GroupRotationInfo); + + /// Yields information on all availability cores. Cores are either free or occupied. Free + /// cores can have paras assigned to them. + fn availability_cores() -> Vec>; + + /// Yields the GlobalValidationSchedule. This applies to all para candidates with the + /// relay-parent equal to the block in which context this is invoked in. + fn global_validation_schedule() -> GlobalValidationSchedule; + + /// Yields the LocalValidationData for the given ParaId along with an assumption that + /// should be used if the para currently occupies a core. + /// + /// Returns `None` if either the para is not registered or the assumption is `Freed` + /// and the para already occupies a core. + fn local_validation_data(para_id: Id, assumption: OccupiedCoreAssumption) + -> Option>; + + /// Returns the session index expected at a child of the block. + /// + /// This can be used to instantiate a `SigningContext`. + fn session_index_for_child() -> SessionIndex; + + /// Fetch the validation code used by a para, making the given `OccupiedCoreAssumption`. + /// + /// Returns `None` if either the para is not registered or the assumption is `Freed` + /// and the para already occupies a core. + fn validation_code(para_id: Id, assumption: OccupiedCoreAssumption) + -> Option; + + /// Get the receipt of a candidate pending availability. This returns `Some` for any paras + /// assigned to occupied cores in `availability_cores` and `None` otherwise. + fn candidate_pending_availability(para_id: Id) -> Option>; + + /// Get a vector of events concerning candidates that occurred within a block. + // NOTE: this needs to skip block initialization as events are wiped within block + // initialization. + #[skip_initialize_block] + fn candidate_events() -> Vec>; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn group_rotation_info_calculations() { + let info = GroupRotationInfo { + session_start_block: 10u32, + now: 15, + group_rotation_frequency: 5, + }; + + assert_eq!(info.next_rotation_at(), 20); + assert_eq!(info.last_rotation_at(), 15); + + let info = GroupRotationInfo { + session_start_block: 10u32, + now: 11, + group_rotation_frequency: 0, + }; + + assert_eq!(info.next_rotation_at(), 0); + assert_eq!(info.last_rotation_at(), 0); + } +} diff --git a/roadmap/implementers-guide/src/runtime-api/README.md b/roadmap/implementers-guide/src/runtime-api/README.md index 4631fab30b50..4f36ac5d108e 100644 --- a/roadmap/implementers-guide/src/runtime-api/README.md +++ b/roadmap/implementers-guide/src/runtime-api/README.md @@ -59,7 +59,19 @@ struct GroupRotationInfo { impl GroupRotationInfo { /// Returns the index of the group needed to validate the core at the given index, /// assuming the given amount of cores/groups. - fn group_for_core(core_index: usize, cores: usize) -> usize; + fn group_for_core(&self, core_index, cores) -> GroupIndex; + + /// Returns the block number of the next rotation after the current block. If the current block + /// is 10 and the rotation frequency is 5, this should return 15. + /// + /// If the group rotation frequency is 0, returns 0. + fn next_rotation_at(&self) -> BlockNumber; + + /// Returns the block number of the last rotation before or including the current block. If the + /// current block is 10 and the rotation frequency is 5, this should return 10. + /// + /// If the group rotation frequency is 0, returns 0. + fn last_rotation_at(&self) -> BlockNumber; } /// Returns the validator groups and rotation info localized based on the block whose state @@ -81,7 +93,7 @@ This is all the information that a validator needs about scheduling for the curr ```rust struct OccupiedCore { /// The ID of the para occupying the core. - para: ParaId, + para_id: ParaId, /// If this core is freed by availability, this is the assignment that is next up on this /// core, if any. None if there is nothing queued for this core. next_up_on_available: Option, @@ -97,11 +109,13 @@ struct OccupiedCore { /// validators has attested to availability on-chain. A 2/3+ majority of `1` bits means that /// this will be available. availability: Bitfield, + /// The group assigned to distribute availability pieces of this candidate. + group_responsible: GroupIndex, } struct ScheduledCore { /// The ID of a para scheduled. - para: ParaId, + para_id: ParaId, /// The collator required to author the block, if any. collator: Option, } @@ -171,7 +185,7 @@ fn session_index_for_child(at: Block) -> SessionIndex; Fetch the validation code used by a para, making the given `OccupiedCoreAssumption`. ```rust -fn validation_code(at: Block, ParaId, OccupiedCoreAssumption) -> ValidationCode; +fn validation_code(at: Block, ParaId, OccupiedCoreAssumption) -> Option; ``` ## Candidate Pending Availability @@ -181,3 +195,20 @@ Get the receipt of a candidate pending availability. This returns `Some` for any ```rust fn candidate_pending_availability(at: Block, ParaId) -> Option; ``` + +## Candidate Events + +Yields a vector of events concerning candidates that occurred within the given block. + +```rust +enum CandidateEvent { + /// This candidate receipt was backed in the most recent block. + CandidateBacked(CandidateReceipt, HeadData), + /// This candidate receipt was included and became a parablock at the most recent block. + CandidateIncluded(CandidateReceipt, HeadData), + /// This candidate receipt was not made available in time and timed out. + CandidateTimedOut(CandidateReceipt, HeadData), +} + +fn candidate_events(at: Block) -> Vec; +``` diff --git a/roadmap/implementers-guide/src/runtime/inclusion.md b/roadmap/implementers-guide/src/runtime/inclusion.md index 7f093261d304..2387a9244a57 100644 --- a/roadmap/implementers-guide/src/runtime/inclusion.md +++ b/roadmap/implementers-guide/src/runtime/inclusion.md @@ -84,3 +84,6 @@ All failed checks should lead to an unrecoverable error making the block invalid // return a vector of cleaned-up core IDs. } ``` +* `force_enact(ParaId)`: Forcibly enact the candidate with the given ID as though it had been deemed available by bitfields. Is a no-op if there is no candidate pending availability for this para-id. This should generally not be used but it is useful during execution of Runtime APIs, where the changes to the state are expected to be discarded directly after. +* `candidate_pending_availability(ParaId) -> Option`: returns the `CommittedCandidateReceipt` pending availability for the para provided, if any. +* `pending_availability(ParaId) -> Option`: returns the metadata around the candidate pending availability for the para, if any. diff --git a/roadmap/implementers-guide/src/runtime/scheduler.md b/roadmap/implementers-guide/src/runtime/scheduler.md index efa85a15c48b..85ad932b522e 100644 --- a/roadmap/implementers-guide/src/runtime/scheduler.md +++ b/roadmap/implementers-guide/src/runtime/scheduler.md @@ -198,3 +198,6 @@ Actions: - `core_para(CoreIndex) -> ParaId`: return the currently-scheduled or occupied ParaId for the given core. - `group_validators(GroupIndex) -> Option>`: return all validators in a given group, if the group index is valid for this session. - `availability_timeout_predicate() -> Option bool>`: returns an optional predicate that should be used for timing out occupied cores. if `None`, no timing-out should be done. The predicate accepts the index of the core, and the block number since which it has been occupied. The predicate should be implemented based on the time since the last validator group rotation, and the respective parachain and parathread timeouts, i.e. only within `max(config.chain_availability_period, config.thread_availability_period)` of the last rotation would this return `Some`. +- `group_rotation_info() -> GroupRotationInfo`: Returns a helper for determining group rotation. +- `next_up_on_available(CoreIndex) -> Option`: Return the next thing that will be scheduled on this core assuming it is currently occupied and the candidate occupying it became available. Returns in `ScheduledCore` format (todo: link to Runtime APIs page; linkcheck doesn't allow this right now). For parachains, this is always the ID of the parachain and no specified collator. For parathreads, this is based on the next item in the `ParathreadQueue` assigned to that core, and is `None` if there isn't one. +- `next_up_on_time_out(CoreIndex) -> Option`: Return the next thing that will be scheduled on this core assuming it is currently occupied and the candidate occupying it timed out. Returns in `ScheduledCore` format (todo: link to Runtime APIs page; linkcheck doesn't allow this right now). For parachains, this is always the ID of the parachain and no specified collator. For parathreads, this is based on the next item in the `ParathreadQueue` assigned to that core, or if there isn't one, the claim that is currently occupying the core. Otherwise `None`. diff --git a/roadmap/implementers-guide/src/types/overseer-protocol.md b/roadmap/implementers-guide/src/types/overseer-protocol.md index 3298d8f65f99..d5d5509dd461 100644 --- a/roadmap/implementers-guide/src/types/overseer-protocol.md +++ b/roadmap/implementers-guide/src/types/overseer-protocol.md @@ -252,9 +252,11 @@ enum RuntimeApiRequest { ResponseChannel>, ), /// Get information about all availability cores. - AvailabilityCores(ResponseChannel), + AvailabilityCores(ResponseChannel>), /// Get a committed candidate receipt for all candidates pending availability. CandidatePendingAvailability(ParaId, ResponseChannel>), + /// Get all events concerning candidates in the last block. + CandidateEvents(ResponseChannel>), } enum RuntimeApiMessage { diff --git a/runtime/parachains/Cargo.toml b/runtime/parachains/Cargo.toml index ab4ca5a78309..4563a05e108b 100644 --- a/runtime/parachains/Cargo.toml +++ b/runtime/parachains/Cargo.toml @@ -49,6 +49,8 @@ pallet-staking-reward-curve = { git = "https://github.com/paritytech/substrate", treasury = { package = "pallet-treasury", git = "https://github.com/paritytech/substrate", branch = "master" } serde_json = "1.0.41" libsecp256k1 = "0.3.2" +sp-version = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + [features] default = ["std"] diff --git a/runtime/parachains/src/inclusion.rs b/runtime/parachains/src/inclusion.rs index 10c019bbe1f1..435342cfb825 100644 --- a/runtime/parachains/src/inclusion.rs +++ b/runtime/parachains/src/inclusion.rs @@ -25,12 +25,11 @@ use primitives::v1::{ ValidatorId, CandidateCommitments, CandidateDescriptor, ValidatorIndex, Id as ParaId, AvailabilityBitfield as AvailabilityBitfield, SignedAvailabilityBitfields, SigningContext, BackedCandidate, CoreIndex, GroupIndex, CoreAssignment, CommittedCandidateReceipt, + CandidateReceipt, HeadData, }; use frame_support::{ - decl_storage, decl_module, decl_error, ensure, dispatch::DispatchResult, IterableStorageMap, - weights::Weight, - traits::Get, - debug, + decl_storage, decl_module, decl_error, decl_event, ensure, debug, + dispatch::DispatchResult, IterableStorageMap, weights::Weight, traits::Get, }; use codec::{Encode, Decode}; use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; @@ -69,7 +68,28 @@ pub struct CandidatePendingAvailability { backed_in_number: N, } -pub trait Trait: system::Trait + paras::Trait + configuration::Trait { } +impl CandidatePendingAvailability { + /// Get the availability votes on the candidate. + pub(crate) fn availability_votes(&self) -> &BitVec { + &self.availability_votes + } + + /// Get the relay-chain block number this was backed in. + pub(crate) fn backed_in_number(&self) -> &N { + &self.backed_in_number + } + + /// Get the core index. + pub(crate) fn core_occupied(&self)-> CoreIndex { + self.core.clone() + } +} + +pub trait Trait: + system::Trait + paras::Trait + configuration::Trait +{ + type Event: From> + Into<::Event>; +} decl_storage! { trait Store for Module as ParaInclusion { @@ -89,7 +109,7 @@ decl_storage! { Validators get(fn validators) config(validators): Vec; /// The current session index. - CurrentSessionIndex: SessionIndex; + CurrentSessionIndex get(fn session_index): SessionIndex; } } @@ -130,10 +150,25 @@ decl_error! { } } +decl_event! { + pub enum Event where ::Hash { + /// A candidate was backed. + CandidateBacked(CandidateReceipt, HeadData), + /// A candidate was included. + CandidateIncluded(CandidateReceipt, HeadData), + /// A candidate timed out. + CandidateTimedOut(CandidateReceipt, HeadData), + } +} + decl_module! { /// The parachain-candidate inclusion module. - pub struct Module for enum Call where origin: ::Origin, system = system { + pub struct Module + for enum Call where origin: ::Origin, system = system + { type Error = Error; + + fn deposit_event() = default; } } @@ -450,7 +485,17 @@ impl Module { // initialize all availability votes to 0. let availability_votes: BitVec = bitvec::bitvec![BitOrderLsb0, u8; 0; validators.len()]; - let (descriptor, commitments) = (candidate.candidate.descriptor, candidate.candidate.commitments); + + Self::deposit_event(Event::::CandidateBacked( + candidate.candidate.to_plain(), + candidate.candidate.commitments.head_data.clone(), + )); + + let (descriptor, commitments) = ( + candidate.candidate.descriptor, + candidate.candidate.commitments, + ); + >::insert(¶_id, CandidatePendingAvailability { core, descriptor, @@ -468,6 +513,7 @@ impl Module { relay_parent_number: T::BlockNumber, receipt: CommittedCandidateReceipt, ) -> Weight { + let plain = receipt.to_plain(); let commitments = receipt.commitments; let config = >::config(); @@ -481,6 +527,10 @@ impl Module { ); } + Self::deposit_event( + Event::::CandidateIncluded(plain, commitments.head_data.clone()) + ); + weight + >::note_new_head( receipt.descriptor.para_id, commitments.head_data, @@ -506,12 +556,66 @@ impl Module { } for para_id in cleaned_up_ids { - >::remove(¶_id); - ::remove(¶_id); + let pending = >::take(¶_id); + let commitments = ::take(¶_id); + + if let (Some(pending), Some(commitments)) = (pending, commitments) { + // defensive: this should always be true. + let candidate = CandidateReceipt { + descriptor: pending.descriptor, + commitments_hash: commitments.hash(), + }; + + Self::deposit_event(Event::::CandidateTimedOut( + candidate, + commitments.head_data, + )); + } } cleaned_up_cores } + + /// Forcibly enact the candidate with the given ID as though it had been deemed available + /// by bitfields. + /// + /// Is a no-op if there is no candidate pending availability for this para-id. + /// This should generally not be used but it is useful during execution of Runtime APIs, + /// where the changes to the state are expected to be discarded directly after. + pub(crate) fn force_enact(para: ParaId) { + let pending = >::take(¶); + let commitments = ::take(¶); + + if let (Some(pending), Some(commitments)) = (pending, commitments) { + let candidate = CommittedCandidateReceipt { + descriptor: pending.descriptor, + commitments, + }; + + Self::enact_candidate( + pending.relay_parent_number, + candidate, + ); + } + } + + /// Returns the CommittedCandidateReceipt pending availability for the para provided, if any. + pub(crate) fn candidate_pending_availability(para: ParaId) + -> Option> + { + >::get(¶) + .map(|p| p.descriptor) + .and_then(|d| ::get(¶).map(move |c| (d, c))) + .map(|(d, c)| CommittedCandidateReceipt { descriptor: d, commitments: c }) + } + + /// Returns the metadata around the candidate pending availability for the + /// para provided, if any. + pub(crate) fn pending_availability(para: ParaId) + -> Option> + { + >::get(¶) + } } const fn availability_threshold(n_validators: usize) -> usize { @@ -527,7 +631,7 @@ mod tests { use primitives::v1::{BlockNumber, Hash}; use primitives::v1::{ SignedAvailabilityBitfield, CompactStatement as Statement, ValidityAttestation, CollatorId, - CandidateCommitments, SignedStatement, CandidateDescriptor, HeadData, ValidationCode, + CandidateCommitments, SignedStatement, CandidateDescriptor, ValidationCode, AssignmentKind, }; use frame_support::traits::{OnFinalize, OnInitialize}; diff --git a/runtime/parachains/src/lib.rs b/runtime/parachains/src/lib.rs index 44554322e4a4..eb279ad19c2e 100644 --- a/runtime/parachains/src/lib.rs +++ b/runtime/parachains/src/lib.rs @@ -28,5 +28,7 @@ mod paras; mod scheduler; mod validity; +pub mod runtime_api_impl; + #[cfg(test)] mod mock; diff --git a/runtime/parachains/src/mock.rs b/runtime/parachains/src/mock.rs index 8eb45359da48..f7349b8f2515 100644 --- a/runtime/parachains/src/mock.rs +++ b/runtime/parachains/src/mock.rs @@ -26,9 +26,10 @@ use sp_runtime::{ }; use primitives::v1::{BlockNumber, Header}; use frame_support::{ - impl_outer_origin, impl_outer_dispatch, parameter_types, + impl_outer_origin, impl_outer_dispatch, impl_outer_event, parameter_types, weights::Weight, traits::Randomness as RandomnessT, }; +use crate::inclusion; /// A test runtime struct. #[derive(Clone, Eq, PartialEq)] @@ -44,6 +45,13 @@ impl_outer_dispatch! { } } +impl_outer_event! { + pub enum TestEvent for Test { + system, + inclusion, + } +} + pub struct TestRandomness; impl RandomnessT for TestRandomness { @@ -70,7 +78,7 @@ impl system::Trait for Test { type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; - type Event = (); + type Event = TestEvent; type BlockHashCount = BlockHashCount; type MaximumBlockWeight = MaximumBlockWeight; type DbWeight = (); @@ -97,7 +105,9 @@ impl crate::paras::Trait for Test { } impl crate::scheduler::Trait for Test { } -impl crate::inclusion::Trait for Test { } +impl crate::inclusion::Trait for Test { + type Event = TestEvent; +} pub type System = system::Module; diff --git a/runtime/parachains/src/runtime_api_impl/mod.rs b/runtime/parachains/src/runtime_api_impl/mod.rs new file mode 100644 index 000000000000..35c26fe35def --- /dev/null +++ b/runtime/parachains/src/runtime_api_impl/mod.rs @@ -0,0 +1,23 @@ +// 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 . + +//! Runtime API implementations for Parachains. +//! +//! These are exposed as different modules using different sets of primitives. +//! At the moment there is only a v1 module and it is not completely clear how migration +//! to a v2 would be done. + +pub mod v1; diff --git a/runtime/parachains/src/runtime_api_impl/v1.rs b/runtime/parachains/src/runtime_api_impl/v1.rs new file mode 100644 index 000000000000..fa5026dbada6 --- /dev/null +++ b/runtime/parachains/src/runtime_api_impl/v1.rs @@ -0,0 +1,287 @@ +// 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. + +//! A module exporting runtime API implementation functions for all runtime APIs using v1 +//! primitives. +//! +//! Runtimes implementing the v1 runtime API are recommended to forward directly to these +//! functions. + +use primitives::v1::{ + ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, GlobalValidationSchedule, + Id as ParaId, OccupiedCoreAssumption, LocalValidationData, SessionIndex, ValidationCode, + CommittedCandidateReceipt, ScheduledCore, OccupiedCore, CoreOccupied, CoreIndex, + GroupIndex, CandidateEvent, +}; +use sp_runtime::traits::{One, BlakeTwo256, Hash as HashT, Saturating, Zero}; +use frame_support::debug; +use crate::{initializer, inclusion, scheduler, configuration, paras}; + +/// Implementation for the `validators` function of the runtime API. +pub fn validators() -> Vec { + >::validators() +} + +/// Implementation for the `validator_groups` function of the runtime API. +pub fn validator_groups() -> ( + Vec>, + GroupRotationInfo, +) { + let groups = >::validator_groups(); + let rotation_info = >::group_rotation_info(); + + (groups, rotation_info) +} + +/// Implementation for the `availability_cores` function of the runtime API. +pub fn availability_cores() -> Vec> { + let cores = >::availability_cores(); + let parachains = >::parachains(); + let config = >::config(); + + let rotation_info = >::group_rotation_info(); + + let time_out_at = |backed_in_number, availability_period| { + let time_out_at = backed_in_number + availability_period; + + if rotation_info.group_rotation_frequency == Zero::zero() { + return time_out_at; + } + + let current_window = rotation_info.last_rotation_at() + availability_period; + let next_rotation = rotation_info.next_rotation_at(); + + // If we are within `period` blocks of rotation, timeouts are being checked + // actively. We could even time out this block. + if time_out_at < current_window { + time_out_at + } else if time_out_at <= next_rotation { + // Otherwise, it will time out at the sooner of the next rotation + next_rotation + } else { + // or the scheduled time-out. This is by definition within `period` blocks + // of `next_rotation` and is thus a valid timeout block. + time_out_at + } + }; + + let group_responsible_for = |backed_in_number, core_index| { + match >::group_assigned_to_core(core_index, backed_in_number) { + Some(g) => g, + None => { + debug::warn!("Could not determine the group responsible for core extracted \ + from list of cores for some prior block in same session"); + + GroupIndex(0) + } + } + }; + + let mut core_states: Vec<_> = cores.into_iter().enumerate().map(|(i, core)| match core { + Some(occupied) => { + CoreState::Occupied(match occupied { + CoreOccupied::Parachain => { + let para_id = parachains[i]; + let pending_availability = > + ::pending_availability(para_id) + .expect("Occupied core always has pending availability; qed"); + + let backed_in_number = pending_availability.backed_in_number().clone(); + OccupiedCore { + para_id, + next_up_on_available: >::next_up_on_available( + CoreIndex(i as u32) + ), + occupied_since: backed_in_number, + time_out_at: time_out_at( + backed_in_number, + config.chain_availability_period, + ), + next_up_on_time_out: >::next_up_on_time_out( + CoreIndex(i as u32) + ), + availability: pending_availability.availability_votes().clone(), + group_responsible: group_responsible_for( + backed_in_number, + pending_availability.core_occupied(), + ), + } + } + CoreOccupied::Parathread(p) => { + let para_id = p.claim.0; + let pending_availability = > + ::pending_availability(para_id) + .expect("Occupied core always has pending availability; qed"); + + let backed_in_number = pending_availability.backed_in_number().clone(); + OccupiedCore { + para_id, + next_up_on_available: >::next_up_on_available( + CoreIndex(i as u32) + ), + occupied_since: backed_in_number, + time_out_at: time_out_at( + backed_in_number, + config.thread_availability_period, + ), + next_up_on_time_out: >::next_up_on_time_out( + CoreIndex(i as u32) + ), + availability: pending_availability.availability_votes().clone(), + group_responsible: group_responsible_for( + backed_in_number, + pending_availability.core_occupied(), + ), + } + } + }) + } + None => CoreState::Free, + }).collect(); + + // This will overwrite only `Free` cores if the scheduler module is working as intended. + for scheduled in >::scheduled() { + core_states[scheduled.core.0 as usize] = CoreState::Scheduled(ScheduledCore { + para_id: scheduled.para_id, + collator: scheduled.required_collator().map(|c| c.clone()), + }); + } + + core_states +} + +/// Implementation for the `global_validation_schedule` function of the runtime API. +pub fn global_validation_schedule() + -> GlobalValidationSchedule +{ + let config = >::config(); + GlobalValidationSchedule { + max_code_size: config.max_code_size, + max_head_data_size: config.max_head_data_size, + block_number: >::block_number() - One::one(), + } +} + +/// Implementation for the `local_validation_data` function of the runtime API. +pub fn local_validation_data( + para_id: ParaId, + assumption: OccupiedCoreAssumption, +) -> Option> { + let construct = || { + let relay_parent_number = >::block_number() - One::one(); + + let config = >::config(); + let freq = config.validation_upgrade_frequency; + let delay = config.validation_upgrade_delay; + + let last_code_upgrade = >::last_code_upgrade(para_id, true)?; + let can_upgrade_code = last_code_upgrade <= relay_parent_number + && relay_parent_number.saturating_sub(last_code_upgrade) >= freq; + + let code_upgrade_allowed = if can_upgrade_code { + Some(relay_parent_number + delay) + } else { + None + }; + + Some(LocalValidationData { + parent_head: >::para_head(¶_id)?, + balance: 0, + validation_code_hash: BlakeTwo256::hash_of( + &>::current_code(¶_id)? + ), + code_upgrade_allowed, + }) + }; + + match assumption { + OccupiedCoreAssumption::Included => { + >::force_enact(para_id); + construct() + } + OccupiedCoreAssumption::TimedOut => { + construct() + } + OccupiedCoreAssumption::Free => { + if >::pending_availability(para_id).is_some() { + None + } else { + construct() + } + } + } +} + +/// Implementation for the `session_index_for_child` function of the runtime API. +pub fn session_index_for_child() -> SessionIndex { + // Just returns the session index from `inclusion`. Runtime APIs follow + // initialization so the initializer will have applied any pending session change + // which is expected at the child of the block whose context the runtime API was invoked + // in. + // + // Incidentally, this is also the rationale for why it is OK to query validators or + // occupied cores or etc. and expect the correct response "for child". + >::session_index() +} + +/// Implementation for the `validation_code` function of the runtime API. +pub fn validation_code( + para_id: ParaId, + assumption: OccupiedCoreAssumption, +) -> Option { + let fetch = || { + >::current_code(¶_id) + }; + + match assumption { + OccupiedCoreAssumption::Included => { + >::force_enact(para_id); + fetch() + } + OccupiedCoreAssumption::TimedOut => { + fetch() + } + OccupiedCoreAssumption::Free => { + if >::pending_availability(para_id).is_some() { + None + } else { + fetch() + } + } + } +} + +/// Implementation for the `candidate_pending_availability` function of the runtime API. +pub fn candidate_pending_availability(para_id: ParaId) + -> Option> +{ + >::candidate_pending_availability(para_id) +} + +/// Implementation for the `candidate_events` function of the runtime API. +// NOTE: this runs without block initialization, as it accesses events. +// this means it can run in a different session than other runtime APIs at the same block. +pub fn candidate_events( + extract_event: impl Fn(::Event) -> Option>, +) -> Vec> { + use inclusion::Event as RawEvent; + + >::events().into_iter() + .filter_map(|record| extract_event(record.event)) + .map(|event| match event { + RawEvent::::CandidateBacked(c, h) => CandidateEvent::CandidateBacked(c, h), + RawEvent::::CandidateIncluded(c, h) => CandidateEvent::CandidateIncluded(c, h), + RawEvent::::CandidateTimedOut(c, h) => CandidateEvent::CandidateTimedOut(c, h), + }) + .collect() +} diff --git a/runtime/parachains/src/scheduler.rs b/runtime/parachains/src/scheduler.rs index 9cc2bd72bd81..977fba582d01 100644 --- a/runtime/parachains/src/scheduler.rs +++ b/runtime/parachains/src/scheduler.rs @@ -39,7 +39,7 @@ use sp_std::prelude::*; use sp_std::convert::TryInto; use primitives::v1::{ Id as ParaId, ValidatorIndex, CoreAssignment, CoreOccupied, CoreIndex, AssignmentKind, - GroupIndex, ParathreadClaim, ParathreadEntry, + GroupIndex, ParathreadClaim, ParathreadEntry, GroupRotationInfo, ScheduledCore, }; use frame_support::{ decl_storage, decl_module, decl_error, @@ -84,11 +84,17 @@ impl ParathreadClaimQueue { }) } - // Take next queued entry with given core offset, if any. + /// Take next queued entry with given core offset, if any. fn take_next_on_core(&mut self, core_offset: u32) -> Option { let pos = self.queue.iter().position(|queued| queued.core_offset == core_offset); pos.map(|i| self.queue.remove(i).claim) } + + /// Get the next queued entry with given core offset, if any. + fn get_next_on_core(&self, core_offset: u32) -> Option<&ParathreadEntry> { + let pos = self.queue.iter().position(|queued| queued.core_offset == core_offset); + pos.map(|i| &self.queue[i].claim) + } } /// Reasons a core might be freed @@ -107,7 +113,7 @@ decl_storage! { /// /// Bound: The number of cores is the sum of the numbers of parachains and parathread multiplexers. /// Reasonably, 100-1000. The dominant factor is the number of validators: safe upper bound at 10k. - ValidatorGroups: Vec>; + ValidatorGroups get(fn validator_groups): Vec>; /// A queue of upcoming claims and which core they should be mapped onto. /// @@ -120,14 +126,14 @@ decl_storage! { /// parathread-multiplexers. /// /// Bounded by the number of cores: one for each parachain and parathread multiplexer. - AvailabilityCores: Vec>; + AvailabilityCores get(fn availability_cores): Vec>; /// An index used to ensure that only one claim on a parathread exists in the queue or is /// currently being handled by an occupied core. /// /// Bounded by the number of parathread cores and scheduling lookahead. Reasonably, 10 * 50 = 500. ParathreadClaimIndex: Vec; /// The block number where the session start occurred. Used to track how many group rotations have occurred. - SessionStartBlock: T::BlockNumber; + SessionStartBlock get(fn session_start_block): T::BlockNumber; /// Currently scheduled cores - free but up to be occupied. Ephemeral storage item that's wiped on finalization. /// /// Bounded by the number of cores: one for each parachain and parathread multiplexer. @@ -578,6 +584,86 @@ impl Module { })) } } + + /// Returns a helper for determining group rotation. + pub(crate) fn group_rotation_info() -> GroupRotationInfo { + let session_start_block = Self::session_start_block(); + let now = >::block_number(); + let group_rotation_frequency = >::config() + .parachain_rotation_frequency; + + GroupRotationInfo { + session_start_block, + now, + group_rotation_frequency, + } + } + + /// Return the next thing that will be scheduled on this core assuming it is currently + /// occupied and the candidate occupying it became available. + /// + /// For parachains, this is always the ID of the parachain and no specified collator. + /// For parathreads, this is based on the next item in the ParathreadQueue assigned to that + /// core, and is None if there isn't one. + pub(crate) fn next_up_on_available(core: CoreIndex) -> Option { + let parachains = >::parachains(); + if (core.0 as usize) < parachains.len() { + Some(ScheduledCore { + para_id: parachains[core.0 as usize], + collator: None, + }) + } else { + let queue = ParathreadQueue::get(); + let core_offset = (core.0 as usize - parachains.len()) as u32; + queue.get_next_on_core(core_offset).map(|entry| ScheduledCore { + para_id: entry.claim.0, + collator: Some(entry.claim.1.clone()), + }) + } + } + + /// Return the next thing that will be scheduled on this core assuming it is currently + /// occupied and the candidate occupying it became available. + /// + /// For parachains, this is always the ID of the parachain and no specified collator. + /// For parathreads, this is based on the next item in the ParathreadQueue assigned to that + /// core, or if there isn't one, the claim that is currently occupying the core, as long + /// as the claim's retries would not exceed the limit. Otherwise None. + pub(crate) fn next_up_on_time_out(core: CoreIndex) -> Option { + let parachains = >::parachains(); + if (core.0 as usize) < parachains.len() { + Some(ScheduledCore { + para_id: parachains[core.0 as usize], + collator: None, + }) + } else { + let queue = ParathreadQueue::get(); + + // This is the next scheduled para on this core. + let core_offset = (core.0 as usize - parachains.len()) as u32; + queue.get_next_on_core(core_offset) + .map(|entry| ScheduledCore { + para_id: entry.claim.0, + collator: Some(entry.claim.1.clone()), + }) + .or_else(|| { + // Or, if none, the claim currently occupying the core, + // as it would be put back on the queue after timing out. + let cores = AvailabilityCores::get(); + cores.get(core.0 as usize).and_then(|c| c.as_ref()).and_then(|o| { + match o { + CoreOccupied::Parathread(entry) => { + Some(ScheduledCore { + para_id: entry.claim.0, + collator: Some(entry.claim.1.clone()), + }) + } + CoreOccupied::Parachain => None, // defensive; not possible. + } + }) + }) + } + } } #[cfg(test)] @@ -1440,4 +1526,301 @@ mod tests { assert!(Scheduler::availability_timeout_predicate().is_none()); }); } + + #[test] + fn next_up_on_available_uses_next_scheduled_or_none_for_thread() { + let mut config = default_config(); + config.parathread_cores = 1; + + let genesis_config = MockGenesisConfig { + configuration: crate::configuration::GenesisConfig { + config: config.clone(), + ..Default::default() + }, + ..Default::default() + }; + + let thread_a = ParaId::from(1); + let thread_b = ParaId::from(2); + + let collator = CollatorId::from(Sr25519Keyring::Alice.public()); + + let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { + genesis_head: Vec::new().into(), + validation_code: Vec::new().into(), + parachain: is_chain, + }); + + new_test_ext(genesis_config).execute_with(|| { + schedule_blank_para(thread_a, false); + schedule_blank_para(thread_b, false); + + // start a new session to activate, 5 validators for 5 cores. + run_to_block(1, |number| match number { + 1 => Some(SessionChangeNotification { + new_config: config.clone(), + validators: vec![ + ValidatorId::from(Sr25519Keyring::Alice.public()), + ValidatorId::from(Sr25519Keyring::Eve.public()), + ], + ..Default::default() + }), + _ => None, + }); + + let thread_claim_a = ParathreadClaim(thread_a, collator.clone()); + let thread_claim_b = ParathreadClaim(thread_b, collator.clone()); + + Scheduler::add_parathread_claim(thread_claim_a.clone()); + + run_to_block(2, |_| None); + + { + assert_eq!(Scheduler::scheduled().len(), 1); + assert_eq!(Scheduler::availability_cores().len(), 1); + + Scheduler::occupied(&[CoreIndex(0)]); + + let cores = Scheduler::availability_cores(); + match cores[0].as_ref().unwrap() { + CoreOccupied::Parathread(entry) => assert_eq!(entry.claim, thread_claim_a), + _ => panic!("with no chains, only core should be a thread core"), + } + + assert!(Scheduler::next_up_on_available(CoreIndex(0)).is_none()); + + Scheduler::add_parathread_claim(thread_claim_b); + + let queue = ParathreadQueue::get(); + assert_eq!( + queue.get_next_on_core(0).unwrap().claim, + ParathreadClaim(thread_b, collator.clone()), + ); + + assert_eq!( + Scheduler::next_up_on_available(CoreIndex(0)).unwrap(), + ScheduledCore { + para_id: thread_b, + collator: Some(collator.clone()), + } + ); + } + }); + } + + #[test] + fn next_up_on_time_out_reuses_claim_if_nothing_queued() { + let mut config = default_config(); + config.parathread_cores = 1; + + let genesis_config = MockGenesisConfig { + configuration: crate::configuration::GenesisConfig { + config: config.clone(), + ..Default::default() + }, + ..Default::default() + }; + + let thread_a = ParaId::from(1); + let thread_b = ParaId::from(2); + + let collator = CollatorId::from(Sr25519Keyring::Alice.public()); + + let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { + genesis_head: Vec::new().into(), + validation_code: Vec::new().into(), + parachain: is_chain, + }); + + new_test_ext(genesis_config).execute_with(|| { + schedule_blank_para(thread_a, false); + schedule_blank_para(thread_b, false); + + // start a new session to activate, 5 validators for 5 cores. + run_to_block(1, |number| match number { + 1 => Some(SessionChangeNotification { + new_config: config.clone(), + validators: vec![ + ValidatorId::from(Sr25519Keyring::Alice.public()), + ValidatorId::from(Sr25519Keyring::Eve.public()), + ], + ..Default::default() + }), + _ => None, + }); + + let thread_claim_a = ParathreadClaim(thread_a, collator.clone()); + let thread_claim_b = ParathreadClaim(thread_b, collator.clone()); + + Scheduler::add_parathread_claim(thread_claim_a.clone()); + + run_to_block(2, |_| None); + + { + assert_eq!(Scheduler::scheduled().len(), 1); + assert_eq!(Scheduler::availability_cores().len(), 1); + + Scheduler::occupied(&[CoreIndex(0)]); + + let cores = Scheduler::availability_cores(); + match cores[0].as_ref().unwrap() { + CoreOccupied::Parathread(entry) => assert_eq!(entry.claim, thread_claim_a), + _ => panic!("with no chains, only core should be a thread core"), + } + + let queue = ParathreadQueue::get(); + assert!(queue.get_next_on_core(0).is_none()); + assert_eq!( + Scheduler::next_up_on_time_out(CoreIndex(0)).unwrap(), + ScheduledCore { + para_id: thread_a, + collator: Some(collator.clone()), + } + ); + + Scheduler::add_parathread_claim(thread_claim_b); + + let queue = ParathreadQueue::get(); + assert_eq!( + queue.get_next_on_core(0).unwrap().claim, + ParathreadClaim(thread_b, collator.clone()), + ); + + // Now that there is an earlier next-up, we use that. + assert_eq!( + Scheduler::next_up_on_available(CoreIndex(0)).unwrap(), + ScheduledCore { + para_id: thread_b, + collator: Some(collator.clone()), + } + ); + } + }); + } + + #[test] + fn next_up_on_available_is_parachain_always() { + let mut config = default_config(); + config.parathread_cores = 0; + + let genesis_config = MockGenesisConfig { + configuration: crate::configuration::GenesisConfig { + config: config.clone(), + ..Default::default() + }, + ..Default::default() + }; + + let chain_a = ParaId::from(1); + + let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { + genesis_head: Vec::new().into(), + validation_code: Vec::new().into(), + parachain: is_chain, + }); + + new_test_ext(genesis_config).execute_with(|| { + schedule_blank_para(chain_a, true); + + // start a new session to activate, 5 validators for 5 cores. + run_to_block(1, |number| match number { + 1 => Some(SessionChangeNotification { + new_config: config.clone(), + validators: vec![ + ValidatorId::from(Sr25519Keyring::Alice.public()), + ValidatorId::from(Sr25519Keyring::Eve.public()), + ], + ..Default::default() + }), + _ => None, + }); + + run_to_block(2, |_| None); + + { + assert_eq!(Scheduler::scheduled().len(), 1); + assert_eq!(Scheduler::availability_cores().len(), 1); + + Scheduler::occupied(&[CoreIndex(0)]); + + let cores = Scheduler::availability_cores(); + match cores[0].as_ref().unwrap() { + CoreOccupied::Parachain => {}, + _ => panic!("with no threads, only core should be a chain core"), + } + + // Now that there is an earlier next-up, we use that. + assert_eq!( + Scheduler::next_up_on_available(CoreIndex(0)).unwrap(), + ScheduledCore { + para_id: chain_a, + collator: None, + } + ); + } + }); + } + + #[test] + fn next_up_on_time_out_is_parachain_always() { + let mut config = default_config(); + config.parathread_cores = 0; + + let genesis_config = MockGenesisConfig { + configuration: crate::configuration::GenesisConfig { + config: config.clone(), + ..Default::default() + }, + ..Default::default() + }; + + let chain_a = ParaId::from(1); + + let schedule_blank_para = |id, is_chain| Paras::schedule_para_initialize(id, ParaGenesisArgs { + genesis_head: Vec::new().into(), + validation_code: Vec::new().into(), + parachain: is_chain, + }); + + new_test_ext(genesis_config).execute_with(|| { + schedule_blank_para(chain_a, true); + + // start a new session to activate, 5 validators for 5 cores. + run_to_block(1, |number| match number { + 1 => Some(SessionChangeNotification { + new_config: config.clone(), + validators: vec![ + ValidatorId::from(Sr25519Keyring::Alice.public()), + ValidatorId::from(Sr25519Keyring::Eve.public()), + ], + ..Default::default() + }), + _ => None, + }); + + run_to_block(2, |_| None); + + { + assert_eq!(Scheduler::scheduled().len(), 1); + assert_eq!(Scheduler::availability_cores().len(), 1); + + Scheduler::occupied(&[CoreIndex(0)]); + + let cores = Scheduler::availability_cores(); + match cores[0].as_ref().unwrap() { + CoreOccupied::Parachain => {}, + _ => panic!("with no threads, only core should be a chain core"), + } + + // Now that there is an earlier next-up, we use that. + assert_eq!( + Scheduler::next_up_on_available(CoreIndex(0)).unwrap(), + ScheduledCore { + para_id: chain_a, + collator: None, + } + ); + } + }); + } }