remove provisioner checks (#4254)

* chore/provisioner: move metrics to a separate module

* avoid the duplicate names

* reduce all checks

* fixup tests

* Update node/core/provisioner/src/lib.rs

Co-authored-by: Zeke Mostov <z.mostov@gmail.com>

* chore: fmt

* chore: spellcheck

* doc

* remove the enum anti-pattern

* guide update - remove all the responsibilities

* add another trivial check

* Update node/core/provisioner/src/metrics.rs

Co-authored-by: Andronik Ordian <write@reusable.software>

* Update roadmap/implementers-guide/src/node/utility/provisioner.md

Co-authored-by: Andronik Ordian <write@reusable.software>

* Update node/core/provisioner/src/metrics.rs

Co-authored-by: Andronik Ordian <write@reusable.software>

Co-authored-by: Zeke Mostov <z.mostov@gmail.com>
Co-authored-by: Andronik Ordian <write@reusable.software>
This commit is contained in:
Bernhard Schuster
2021-11-19 19:15:59 +01:00
committed by GitHub
parent eee4bb2577
commit d5d916a915
8 changed files with 321 additions and 886 deletions
+19 -21
View File
@@ -567,9 +567,9 @@ dependencies = [
[[package]] [[package]]
name = "bitvec" name = "bitvec"
version = "0.20.1" version = "0.20.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5011ffc90248764d7005b0e10c7294f5aa1bd87d9dd7248f4ad475b347c294d" checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848"
dependencies = [ dependencies = [
"funty", "funty",
"radium 0.6.2", "radium 0.6.2",
@@ -729,7 +729,7 @@ dependencies = [
name = "bp-messages" name = "bp-messages"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bitvec 0.20.1", "bitvec 0.20.4",
"bp-runtime", "bp-runtime",
"frame-support", "frame-support",
"frame-system", "frame-system",
@@ -3159,7 +3159,7 @@ name = "kusama-runtime"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"beefy-primitives", "beefy-primitives",
"bitvec 0.20.1", "bitvec 0.20.4",
"frame-benchmarking", "frame-benchmarking",
"frame-election-provider-support", "frame-election-provider-support",
"frame-executive", "frame-executive",
@@ -4884,7 +4884,7 @@ dependencies = [
name = "pallet-bridge-messages" name = "pallet-bridge-messages"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bitvec 0.20.1", "bitvec 0.20.4",
"bp-message-dispatch", "bp-message-dispatch",
"bp-messages", "bp-messages",
"bp-rialto", "bp-rialto",
@@ -5565,7 +5565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909" checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909"
dependencies = [ dependencies = [
"arrayvec 0.7.2", "arrayvec 0.7.2",
"bitvec 0.20.1", "bitvec 0.20.4",
"byte-slice-cast", "byte-slice-cast",
"impl-trait-for-tuples", "impl-trait-for-tuples",
"parity-scale-codec-derive", "parity-scale-codec-derive",
@@ -5912,7 +5912,7 @@ name = "polkadot-availability-bitfield-distribution"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"assert_matches", "assert_matches",
"bitvec 0.20.1", "bitvec 0.20.4",
"env_logger 0.9.0", "env_logger 0.9.0",
"futures 0.3.17", "futures 0.3.17",
"log", "log",
@@ -6186,7 +6186,7 @@ name = "polkadot-node-core-approval-voting"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"assert_matches", "assert_matches",
"bitvec 0.20.1", "bitvec 0.20.4",
"derive_more", "derive_more",
"futures 0.3.17", "futures 0.3.17",
"futures-timer 3.0.2", "futures-timer 3.0.2",
@@ -6222,7 +6222,7 @@ name = "polkadot-node-core-av-store"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"assert_matches", "assert_matches",
"bitvec 0.20.1", "bitvec 0.20.4",
"env_logger 0.9.0", "env_logger 0.9.0",
"futures 0.3.17", "futures 0.3.17",
"futures-timer 3.0.2", "futures-timer 3.0.2",
@@ -6249,7 +6249,7 @@ name = "polkadot-node-core-backing"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"assert_matches", "assert_matches",
"bitvec 0.20.1", "bitvec 0.20.4",
"futures 0.3.17", "futures 0.3.17",
"polkadot-erasure-coding", "polkadot-erasure-coding",
"polkadot-node-primitives", "polkadot-node-primitives",
@@ -6349,7 +6349,7 @@ name = "polkadot-node-core-dispute-coordinator"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"assert_matches", "assert_matches",
"bitvec 0.20.1", "bitvec 0.20.4",
"derive_more", "derive_more",
"futures 0.3.17", "futures 0.3.17",
"kvdb", "kvdb",
@@ -6388,15 +6388,13 @@ dependencies = [
name = "polkadot-node-core-provisioner" name = "polkadot-node-core-provisioner"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"bitvec 0.20.1", "bitvec 0.20.4",
"futures 0.3.17", "futures 0.3.17",
"futures-timer 3.0.2", "futures-timer 3.0.2",
"polkadot-node-subsystem", "polkadot-node-subsystem",
"polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-test-helpers",
"polkadot-node-subsystem-util", "polkadot-node-subsystem-util",
"polkadot-primitives", "polkadot-primitives",
"sp-application-crypto",
"sp-keystore",
"thiserror", "thiserror",
"tracing", "tracing",
] ]
@@ -6669,7 +6667,7 @@ dependencies = [
name = "polkadot-primitives" name = "polkadot-primitives"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"bitvec 0.20.1", "bitvec 0.20.4",
"frame-system", "frame-system",
"hex-literal", "hex-literal",
"parity-scale-codec", "parity-scale-codec",
@@ -6729,7 +6727,7 @@ name = "polkadot-runtime"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"beefy-primitives", "beefy-primitives",
"bitvec 0.20.1", "bitvec 0.20.4",
"frame-benchmarking", "frame-benchmarking",
"frame-election-provider-support", "frame-election-provider-support",
"frame-executive", "frame-executive",
@@ -6812,7 +6810,7 @@ name = "polkadot-runtime-common"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"beefy-primitives", "beefy-primitives",
"bitvec 0.20.1", "bitvec 0.20.4",
"frame-benchmarking", "frame-benchmarking",
"frame-election-provider-support", "frame-election-provider-support",
"frame-support", "frame-support",
@@ -6862,7 +6860,7 @@ name = "polkadot-runtime-parachains"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bitvec 0.20.1", "bitvec 0.20.4",
"derive_more", "derive_more",
"frame-benchmarking", "frame-benchmarking",
"frame-support", "frame-support",
@@ -7144,7 +7142,7 @@ name = "polkadot-test-runtime"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"beefy-primitives", "beefy-primitives",
"bitvec 0.20.1", "bitvec 0.20.4",
"frame-election-provider-support", "frame-election-provider-support",
"frame-executive", "frame-executive",
"frame-support", "frame-support",
@@ -9106,7 +9104,7 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c55b744399c25532d63a0d2789b109df8d46fc93752d46b0782991a931a782f" checksum = "5c55b744399c25532d63a0d2789b109df8d46fc93752d46b0782991a931a782f"
dependencies = [ dependencies = [
"bitvec 0.20.1", "bitvec 0.20.4",
"cfg-if 1.0.0", "cfg-if 1.0.0",
"derive_more", "derive_more",
"parity-scale-codec", "parity-scale-codec",
@@ -11666,7 +11664,7 @@ name = "westend-runtime"
version = "0.9.13" version = "0.9.13"
dependencies = [ dependencies = [
"beefy-primitives", "beefy-primitives",
"bitvec 0.20.1", "bitvec 0.20.4",
"frame-benchmarking", "frame-benchmarking",
"frame-election-provider-support", "frame-election-provider-support",
"frame-executive", "frame-executive",
+1 -3
View File
@@ -5,7 +5,6 @@ authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
bitvec = { version = "0.20.1", default-features = false, features = ["alloc"] }
futures = "0.3.17" futures = "0.3.17"
tracing = "0.1.29" tracing = "0.1.29"
thiserror = "1.0.30" thiserror = "1.0.30"
@@ -15,6 +14,5 @@ polkadot-node-subsystem-util = { path = "../../subsystem-util" }
futures-timer = "3.0.2" futures-timer = "3.0.2"
[dev-dependencies] [dev-dependencies]
sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" }
bitvec = { version = "0.20.1", default-features = false, features = [] }
+56 -342
View File
@@ -14,12 +14,11 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. // along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! The provisioner is responsible for assembling a relay chain block //! The provisioner is responsible for assembling a set of items, from which the
//! from a set of available parachain candidates of its choice. //! runtime will pick a subset and create a relay chain block.
#![deny(missing_docs, unused_crate_dependencies)] #![deny(missing_docs, unused_crate_dependencies)]
use bitvec::vec::BitVec;
use futures::{ use futures::{
channel::{mpsc, oneshot}, channel::{mpsc, oneshot},
prelude::*, prelude::*,
@@ -29,25 +28,24 @@ use polkadot_node_subsystem::{
errors::{ChainApiError, RuntimeApiError}, errors::{ChainApiError, RuntimeApiError},
jaeger, jaeger,
messages::{ messages::{
CandidateBackingMessage, ChainApiMessage, DisputeCoordinatorMessage, ProvisionableData, CandidateBackingMessage, DisputeCoordinatorMessage, ProvisionableData,
ProvisionerInherentData, ProvisionerMessage, ProvisionerInherentData, ProvisionerMessage,
}, },
PerLeafSpan, SubsystemSender, PerLeafSpan, SubsystemSender,
}; };
use polkadot_node_subsystem_util::{ use polkadot_node_subsystem_util::{self as util, JobSender, JobSubsystem, JobTrait};
self as util,
metrics::{self, prometheus},
request_availability_cores, request_persisted_validation_data, JobSender, JobSubsystem,
JobTrait,
};
use polkadot_primitives::v1::{ use polkadot_primitives::v1::{
BackedCandidate, BlockNumber, CandidateReceipt, CoreState, DisputeStatement, BackedCandidate, CandidateHash, CandidateReceipt, DisputeStatement, DisputeStatementSet, Hash,
DisputeStatementSet, Hash, MultiDisputeStatementSet, OccupiedCoreAssumption, Id as ParaId, MultiDisputeStatementSet, SignedAvailabilityBitfield,
SignedAvailabilityBitfield, ValidatorIndex, SignedAvailabilityBitfields,
}; };
use std::{collections::BTreeMap, pin::Pin, sync::Arc}; use std::{collections::HashSet, pin::Pin, sync::Arc};
use thiserror::Error; use thiserror::Error;
mod metrics;
pub use self::metrics::*;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@@ -106,40 +104,17 @@ pub enum Error {
#[error(transparent)] #[error(transparent)]
Util(#[from] util::Error), Util(#[from] util::Error),
#[error("failed to get availability cores")]
CanceledAvailabilityCores(#[source] oneshot::Canceled),
#[error("failed to get persisted validation data")]
CanceledPersistedValidationData(#[source] oneshot::Canceled),
#[error("failed to get block number")]
CanceledBlockNumber(#[source] oneshot::Canceled),
#[error("failed to get backed candidates")] #[error("failed to get backed candidates")]
CanceledBackedCandidates(#[source] oneshot::Canceled), CanceledBackedCandidates(#[source] oneshot::Canceled),
#[error("failed to get votes on dispute")]
CanceledCandidateVotes(#[source] oneshot::Canceled),
#[error(transparent)] #[error(transparent)]
ChainApi(#[from] ChainApiError), ChainApi(#[from] ChainApiError),
#[error(transparent)] #[error(transparent)]
Runtime(#[from] RuntimeApiError), Runtime(#[from] RuntimeApiError),
#[error("failed to send message to ChainAPI")]
ChainApiMessageSend(#[source] mpsc::SendError),
#[error("failed to send message to CandidateBacking to get backed candidates")]
GetBackedCandidatesSend(#[source] mpsc::SendError),
#[error("failed to send return message with Inherents")] #[error("failed to send return message with Inherents")]
InherentDataReturnChannel, InherentDataReturnChannel,
#[error(
"backed candidate does not correspond to selected candidate; check logic in provisioner"
)]
BackedCandidateOrderingProblem,
} }
impl JobTrait for ProvisioningJob { impl JobTrait for ProvisioningJob {
@@ -193,11 +168,10 @@ impl ProvisioningJob {
sender: &mut impl SubsystemSender, sender: &mut impl SubsystemSender,
span: PerLeafSpan, span: PerLeafSpan,
) -> Result<(), Error> { ) -> Result<(), Error> {
use ProvisionerMessage::{ProvisionableData, RequestInherentData};
loop { loop {
futures::select! { futures::select! {
msg = self.receiver.next() => match msg { msg = self.receiver.next() => match msg {
Some(RequestInherentData(_, return_sender)) => { Some(ProvisionerMessage::RequestInherentData(_, return_sender)) => {
let _span = span.child("req-inherent-data"); let _span = span.child("req-inherent-data");
let _timer = self.metrics.time_request_inherent_data(); let _timer = self.metrics.time_request_inherent_data();
@@ -207,7 +181,7 @@ impl ProvisioningJob {
self.awaiting_inherent.push(return_sender); self.awaiting_inherent.push(return_sender);
} }
} }
Some(ProvisionableData(_, data)) => { Some(ProvisionerMessage::ProvisionableData(_, data)) => {
let span = span.child("provisionable-data"); let span = span.child("provisionable-data");
let _timer = self.metrics.time_provisionable_data(); let _timer = self.metrics.time_provisionable_data();
@@ -235,8 +209,8 @@ impl ProvisioningJob {
) { ) {
if let Err(err) = send_inherent_data( if let Err(err) = send_inherent_data(
self.relay_parent, self.relay_parent,
&self.signed_bitfields, self.signed_bitfields.clone(),
&self.backed_candidates, self.backed_candidates.clone(),
return_senders, return_senders,
sender, sender,
) )
@@ -268,46 +242,25 @@ impl ProvisioningJob {
} }
} }
type CoreAvailability = BitVec<bitvec::order::Lsb0, u8>; /// The provisioner is the subsystem best suited on the node side,
/// yet it lacks sufficient information to do weight based inherents limiting.
/// The provisioner is the subsystem best suited to choosing which specific /// This does the minimalistic checks and forwards a most likely
/// backed candidates and availability bitfields should be assembled into the /// too large set of bitfields, candidates, and dispute votes to
/// block. To engage this functionality, a /// the runtime. The `fn create_inherent` in the runtime is responsible
/// `ProvisionerMessage::RequestInherentData` is sent; the response is a set of /// to use a subset of these.
/// non-conflicting candidates and the appropriate bitfields. Non-conflicting
/// means that there are never two distinct parachain candidates included for
/// the same parachain and that new parachain candidates cannot be included
/// until the previous one either gets declared available or expired.
///
/// The main complication here is going to be around handling
/// occupied-core-assumptions. We might have candidates that are only
/// includable when some bitfields are included. And we might have candidates
/// that are not includable when certain bitfields are included.
///
/// When we're choosing bitfields to include, the rule should be simple:
/// maximize availability. So basically, include all bitfields. And then
/// choose a coherent set of candidates along with that.
async fn send_inherent_data( async fn send_inherent_data(
relay_parent: Hash, relay_parent: Hash,
bitfields: &[SignedAvailabilityBitfield], bitfields: SignedAvailabilityBitfields,
candidates: &[CandidateReceipt], candidate_receipts: Vec<CandidateReceipt>,
return_senders: Vec<oneshot::Sender<ProvisionerInherentData>>, return_senders: Vec<oneshot::Sender<ProvisionerInherentData>>,
from_job: &mut impl SubsystemSender, from_job: &mut impl SubsystemSender,
) -> Result<(), Error> { ) -> Result<(), Error> {
let availability_cores = request_availability_cores(relay_parent, from_job) let backed_candidates =
.await collect_backed_candidates(candidate_receipts, relay_parent, from_job).await?;
.await
.map_err(|err| Error::CanceledAvailabilityCores(err))??;
let bitfields = select_availability_bitfields(&availability_cores, bitfields); let disputes = collect_disputes(from_job).await?;
let candidates =
select_candidates(&availability_cores, &bitfields, candidates, relay_parent, from_job)
.await?;
let disputes = select_disputes(from_job).await?; let inherent_data = ProvisionerInherentData { bitfields, backed_candidates, disputes };
let inherent_data =
ProvisionerInherentData { bitfields, backed_candidates: candidates, disputes };
for return_sender in return_senders { for return_sender in return_senders {
return_sender return_sender
@@ -318,120 +271,33 @@ async fn send_inherent_data(
Ok(()) Ok(())
} }
/// In general, we want to pick all the bitfields. However, we have the following constraints: /// Collect backed candidates with a matching `relay_parent`.
/// async fn collect_backed_candidates(
/// - not more than one per validator candidate_receipts: Vec<CandidateReceipt>,
/// - each 1 bit must correspond to an occupied core
///
/// If we have too many, an arbitrary selection policy is fine. For purposes of maximizing availability,
/// we pick the one with the greatest number of 1 bits.
///
/// Note: This does not enforce any sorting precondition on the output; the ordering there will be unrelated
/// to the sorting of the input.
fn select_availability_bitfields(
cores: &[CoreState],
bitfields: &[SignedAvailabilityBitfield],
) -> Vec<SignedAvailabilityBitfield> {
let mut selected: BTreeMap<ValidatorIndex, SignedAvailabilityBitfield> = BTreeMap::new();
'a: for bitfield in bitfields.iter().cloned() {
if bitfield.payload().0.len() != cores.len() {
continue
}
let is_better = selected
.get(&bitfield.validator_index())
.map_or(true, |b| b.payload().0.count_ones() < bitfield.payload().0.count_ones());
if !is_better {
continue
}
for (idx, _) in cores.iter().enumerate().filter(|v| !v.1.is_occupied()) {
// Bit is set for an unoccupied core - invalid
if *bitfield.payload().0.get(idx).as_deref().unwrap_or(&false) {
continue 'a
}
}
let _ = selected.insert(bitfield.validator_index(), bitfield);
}
selected.into_iter().map(|(_, b)| b).collect()
}
/// Determine which cores are free, and then to the degree possible, pick a candidate appropriate to each free core.
async fn select_candidates(
availability_cores: &[CoreState],
bitfields: &[SignedAvailabilityBitfield],
candidates: &[CandidateReceipt],
relay_parent: Hash, relay_parent: Hash,
sender: &mut impl SubsystemSender, sender: &mut impl SubsystemSender,
) -> Result<Vec<BackedCandidate>, Error> { ) -> Result<Vec<BackedCandidate>, Error> {
let block_number = get_block_number_under_construction(relay_parent, sender).await?; let max_one_candidate_per_para = HashSet::<ParaId>::with_capacity(candidate_receipts.len());
let selected_candidates = candidate_receipts
let mut selected_candidates = .into_iter()
Vec::with_capacity(candidates.len().min(availability_cores.len())); .filter(|candidate_receipt| {
// assure the follow up query `GetBackedCandidate` succeeds
for (core_idx, core) in availability_cores.iter().enumerate() { candidate_receipt.descriptor().relay_parent == relay_parent
let (scheduled_core, assumption) = match core { })
CoreState::Scheduled(scheduled_core) => (scheduled_core, OccupiedCoreAssumption::Free), .scan(max_one_candidate_per_para, |unique, candidate_receipt| {
CoreState::Occupied(occupied_core) => { let para_id = candidate_receipt.descriptor().para_id;
if bitfields_indicate_availability(core_idx, bitfields, &occupied_core.availability) if unique.insert(para_id) {
{ Some(candidate_receipt.hash())
if let Some(ref scheduled_core) = occupied_core.next_up_on_available {
(scheduled_core, OccupiedCoreAssumption::Included)
} else { } else {
continue tracing::debug!(
}
} else {
if occupied_core.time_out_at != block_number {
continue
}
if let Some(ref scheduled_core) = occupied_core.next_up_on_time_out {
(scheduled_core, OccupiedCoreAssumption::TimedOut)
} else {
continue
}
}
},
CoreState::Free => continue,
};
let validation_data = match request_persisted_validation_data(
relay_parent,
scheduled_core.para_id,
assumption,
sender,
)
.await
.await
.map_err(|err| Error::CanceledPersistedValidationData(err))??
{
Some(v) => v,
None => continue,
};
let computed_validation_data_hash = validation_data.hash();
// we arbitrarily pick the first of the backed candidates which match the appropriate selection criteria
if let Some(candidate) = candidates.iter().find(|backed_candidate| {
let descriptor = &backed_candidate.descriptor;
descriptor.para_id == scheduled_core.para_id &&
descriptor.persisted_validation_data_hash == computed_validation_data_hash
}) {
let candidate_hash = candidate.hash();
tracing::trace!(
target: LOG_TARGET, target: LOG_TARGET,
"Selecting candidate {}. para_id={} core={}", ?para_id,
candidate_hash, "Duplicate candidate detected for para, only submitting one",
candidate.descriptor.para_id,
core_idx,
); );
None
selected_candidates.push(candidate_hash);
}
} }
})
.collect::<Vec<CandidateHash>>();
// now get the backed candidates corresponding to these candidate receipts // now get the backed candidates corresponding to these candidate receipts
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
@@ -445,106 +311,18 @@ async fn select_candidates(
.into(), .into(),
) )
.await; .await;
let mut candidates = rx.await.map_err(|err| Error::CanceledBackedCandidates(err))?; let backed_candidates = rx.await.map_err(|err| Error::CanceledBackedCandidates(err))?;
// `selected_candidates` is generated in ascending order by core index, and `GetBackedCandidates`
// _should_ preserve that property, but let's just make sure.
//
// We can't easily map from `BackedCandidate` to `core_idx`, but we know that every selected candidate
// maps to either 0 or 1 backed candidate, and the hashes correspond. Therefore, by checking them
// in order, we can ensure that the backed candidates are also in order.
let mut backed_idx = 0;
for selected in selected_candidates {
if selected ==
candidates.get(backed_idx).ok_or(Error::BackedCandidateOrderingProblem)?.hash()
{
backed_idx += 1;
}
}
if candidates.len() != backed_idx {
Err(Error::BackedCandidateOrderingProblem)?;
}
// keep only one candidate with validation code.
let mut with_validation_code = false;
candidates.retain(|c| {
if c.candidate.commitments.new_validation_code.is_some() {
if with_validation_code {
return false
}
with_validation_code = true;
}
true
});
tracing::debug!( tracing::debug!(
target: LOG_TARGET, target: LOG_TARGET,
"Selected {} candidates for {} cores", "Selected {} backed candidates ready to be sanitized by the runtime",
candidates.len(), backed_candidates.len(),
availability_cores.len(),
); );
Ok(candidates) Ok(backed_candidates)
} }
/// Produces a block number 1 higher than that of the relay parent async fn collect_disputes(
/// in the event of an invalid `relay_parent`, returns `Ok(0)`
async fn get_block_number_under_construction(
relay_parent: Hash,
sender: &mut impl SubsystemSender,
) -> Result<BlockNumber, Error> {
let (tx, rx) = oneshot::channel();
sender.send_message(ChainApiMessage::BlockNumber(relay_parent, tx).into()).await;
match rx.await.map_err(|err| Error::CanceledBlockNumber(err))? {
Ok(Some(n)) => Ok(n + 1),
Ok(None) => Ok(0),
Err(err) => Err(err.into()),
}
}
/// The availability bitfield for a given core is the transpose
/// of a set of signed availability bitfields. It goes like this:
///
/// - construct a transverse slice along `core_idx`
/// - bitwise-or it with the availability slice
/// - count the 1 bits, compare to the total length; true on 2/3+
fn bitfields_indicate_availability(
core_idx: usize,
bitfields: &[SignedAvailabilityBitfield],
availability: &CoreAvailability,
) -> bool {
let mut availability = availability.clone();
let availability_len = availability.len();
for bitfield in bitfields {
let validator_idx = bitfield.validator_index().0 as usize;
match availability.get_mut(validator_idx) {
None => {
// in principle, this function might return a `Result<bool, Error>` so that we can more clearly express this error condition
// however, in practice, that would just push off an error-handling routine which would look a whole lot like this one.
// simpler to just handle the error internally here.
tracing::warn!(
target: LOG_TARGET,
validator_idx = %validator_idx,
availability_len = %availability_len,
"attempted to set a transverse bit at idx {} which is greater than bitfield size {}",
validator_idx,
availability_len,
);
return false
},
Some(mut bit_mut) => *bit_mut |= bitfield.payload().0[core_idx],
}
}
3 * availability.count_ones() >= 2 * availability.len()
}
async fn select_disputes(
sender: &mut impl SubsystemSender, sender: &mut impl SubsystemSender,
) -> Result<MultiDisputeStatementSet, Error> { ) -> Result<MultiDisputeStatementSet, Error> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
@@ -560,6 +338,8 @@ async fn select_disputes(
// 2. Disputes are expected to be rare because they come with heavy slashing. // 2. Disputes are expected to be rare because they come with heavy slashing.
sender.send_message(DisputeCoordinatorMessage::RecentDisputes(tx).into()).await; sender.send_message(DisputeCoordinatorMessage::RecentDisputes(tx).into()).await;
// TODO: scrape concluded disputes from on-chain to limit the number of disputes
// TODO: <https://github.com/paritytech/polkadot/issues/4329>
let recent_disputes = match rx.await { let recent_disputes = match rx.await {
Ok(r) => r, Ok(r) => r,
Err(oneshot::Canceled) => { Err(oneshot::Canceled) => {
@@ -614,71 +394,5 @@ async fn select_disputes(
.collect()) .collect())
} }
#[derive(Clone)]
struct MetricsInner {
inherent_data_requests: prometheus::CounterVec<prometheus::U64>,
request_inherent_data: prometheus::Histogram,
provisionable_data: prometheus::Histogram,
}
/// Provisioner metrics.
#[derive(Default, Clone)]
pub struct Metrics(Option<MetricsInner>);
impl Metrics {
fn on_inherent_data_request(&self, response: Result<(), ()>) {
if let Some(metrics) = &self.0 {
match response {
Ok(()) => metrics.inherent_data_requests.with_label_values(&["succeeded"]).inc(),
Err(()) => metrics.inherent_data_requests.with_label_values(&["failed"]).inc(),
}
}
}
/// Provide a timer for `request_inherent_data` which observes on drop.
fn time_request_inherent_data(
&self,
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
self.0.as_ref().map(|metrics| metrics.request_inherent_data.start_timer())
}
/// Provide a timer for `provisionable_data` which observes on drop.
fn time_provisionable_data(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
self.0.as_ref().map(|metrics| metrics.provisionable_data.start_timer())
}
}
impl metrics::Metrics for Metrics {
fn try_register(registry: &prometheus::Registry) -> Result<Self, prometheus::PrometheusError> {
let metrics = MetricsInner {
inherent_data_requests: prometheus::register(
prometheus::CounterVec::new(
prometheus::Opts::new(
"parachain_inherent_data_requests_total",
"Number of InherentData requests served by provisioner.",
),
&["success"],
)?,
registry,
)?,
request_inherent_data: prometheus::register(
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
"parachain_provisioner_request_inherent_data",
"Time spent within `provisioner::request_inherent_data`",
))?,
registry,
)?,
provisionable_data: prometheus::register(
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
"parachain_provisioner_provisionable_data",
"Time spent within `provisioner::provisionable_data`",
))?,
registry,
)?,
};
Ok(Metrics(Some(metrics)))
}
}
/// The provisioning subsystem. /// The provisioning subsystem.
pub type ProvisioningSubsystem<Spawner> = JobSubsystem<ProvisioningJob, Spawner>; pub type ProvisionerSubsystem<Spawner> = JobSubsystem<ProvisioningJob, Spawner>;
@@ -0,0 +1,85 @@
// 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/>.
use polkadot_node_subsystem_util::metrics::{self, prometheus};
#[derive(Clone)]
struct MetricsInner {
inherent_data_requests: prometheus::CounterVec<prometheus::U64>,
request_inherent_data: prometheus::Histogram,
provisionable_data: prometheus::Histogram,
}
/// Provisioner metrics.
#[derive(Default, Clone)]
pub struct Metrics(Option<MetricsInner>);
impl Metrics {
pub(crate) fn on_inherent_data_request(&self, response: Result<(), ()>) {
if let Some(metrics) = &self.0 {
match response {
Ok(()) => metrics.inherent_data_requests.with_label_values(&["succeeded"]).inc(),
Err(()) => metrics.inherent_data_requests.with_label_values(&["failed"]).inc(),
}
}
}
/// Provide a timer for `request_inherent_data` which observes on drop.
pub(crate) fn time_request_inherent_data(
&self,
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
self.0.as_ref().map(|metrics| metrics.request_inherent_data.start_timer())
}
/// Provide a timer for `provisionable_data` which observes on drop.
pub(crate) fn time_provisionable_data(
&self,
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
self.0.as_ref().map(|metrics| metrics.provisionable_data.start_timer())
}
}
impl metrics::Metrics for Metrics {
fn try_register(registry: &prometheus::Registry) -> Result<Self, prometheus::PrometheusError> {
let metrics = MetricsInner {
inherent_data_requests: prometheus::register(
prometheus::CounterVec::new(
prometheus::Opts::new(
"parachain_inherent_data_requests_total",
"Number of InherentData requests served by provisioner.",
),
&["success"],
)?,
registry,
)?,
request_inherent_data: prometheus::register(
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
"parachain_provisioner_request_inherent_data_time",
"Time spent within `provisioner::request_inherent_data`",
))?,
registry,
)?,
provisionable_data: prometheus::register(
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
"parachain_provisioner_provisionable_data_time",
"Time spent within `provisioner::provisionable_data`",
))?,
registry,
)?,
};
Ok(Metrics(Some(metrics)))
}
}
+72 -395
View File
@@ -1,217 +1,43 @@
// 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/>.
use super::*; use super::*;
use bitvec::bitvec; use futures::{channel::mpsc, future};
use polkadot_primitives::v1::{OccupiedCore, ScheduledCore};
pub fn occupied_core(para_id: u32) -> CoreState { fn default_bitvec(n_cores: usize) -> bitvec::vec::BitVec<bitvec::order::Lsb0, u8> {
CoreState::Occupied(OccupiedCore { bitvec::vec::BitVec::repeat(false, n_cores)
group_responsible: para_id.into(),
next_up_on_available: None,
occupied_since: 100_u32,
time_out_at: 200_u32,
next_up_on_time_out: None,
availability: bitvec![bitvec::order::Lsb0, u8; 0; 32],
candidate_descriptor: Default::default(),
candidate_hash: Default::default(),
})
} }
pub fn build_occupied_core<Builder>(para_id: u32, builder: Builder) -> CoreState use crate::collect_backed_candidates;
where use polkadot_node_subsystem::messages::AllMessages;
Builder: FnOnce(&mut OccupiedCore), use polkadot_node_subsystem_test_helpers::TestSubsystemSender;
{ use polkadot_primitives::v1::{
let mut core = match occupied_core(para_id) { BlockNumber, CandidateCommitments, CandidateDescriptor, CommittedCandidateReceipt, Hash,
CoreState::Occupied(core) => core,
_ => unreachable!(),
};
builder(&mut core);
CoreState::Occupied(core)
}
pub fn default_bitvec(n_cores: usize) -> CoreAvailability {
bitvec![bitvec::order::Lsb0, u8; 0; n_cores]
}
pub fn scheduled_core(id: u32) -> ScheduledCore {
ScheduledCore { para_id: id.into(), ..Default::default() }
}
mod select_availability_bitfields {
use super::{super::*, default_bitvec, occupied_core};
use futures::executor::block_on;
use polkadot_primitives::v1::{SigningContext, ValidatorId, ValidatorIndex};
use sp_application_crypto::AppKey;
use sp_keystore::{testing::KeyStore, CryptoStore, SyncCryptoStorePtr};
use std::sync::Arc;
async fn signed_bitfield(
keystore: &SyncCryptoStorePtr,
field: CoreAvailability,
validator_idx: ValidatorIndex,
) -> SignedAvailabilityBitfield {
let public = CryptoStore::sr25519_generate_new(&**keystore, ValidatorId::ID, None)
.await
.expect("generated sr25519 key");
SignedAvailabilityBitfield::sign(
&keystore,
field.into(),
&<SigningContext<Hash>>::default(),
validator_idx,
&public.into(),
)
.await
.ok()
.flatten()
.expect("Should be signed")
}
#[test]
fn not_more_than_one_per_validator() {
let keystore: SyncCryptoStorePtr = Arc::new(KeyStore::new());
let mut bitvec = default_bitvec(2);
bitvec.set(0, true);
bitvec.set(1, true);
let cores = vec![occupied_core(0), occupied_core(1)];
// we pass in three bitfields with two validators
// this helps us check the postcondition that we get two bitfields back, for which the validators differ
let bitfields = vec![
block_on(signed_bitfield(&keystore, bitvec.clone(), ValidatorIndex(0))),
block_on(signed_bitfield(&keystore, bitvec.clone(), ValidatorIndex(1))),
block_on(signed_bitfield(&keystore, bitvec, ValidatorIndex(1))),
];
let mut selected_bitfields = select_availability_bitfields(&cores, &bitfields);
selected_bitfields.sort_by_key(|bitfield| bitfield.validator_index());
assert_eq!(selected_bitfields.len(), 2);
assert_eq!(selected_bitfields[0], bitfields[0]);
// we don't know which of the (otherwise equal) bitfields will be selected
assert!(selected_bitfields[1] == bitfields[1] || selected_bitfields[1] == bitfields[2]);
}
#[test]
fn each_corresponds_to_an_occupied_core() {
let keystore: SyncCryptoStorePtr = Arc::new(KeyStore::new());
let bitvec = default_bitvec(3);
// invalid: bit on free core
let mut bitvec0 = bitvec.clone();
bitvec0.set(0, true);
// invalid: bit on scheduled core
let mut bitvec1 = bitvec.clone();
bitvec1.set(1, true);
// valid: bit on occupied core.
let mut bitvec2 = bitvec.clone();
bitvec2.set(2, true);
let cores =
vec![CoreState::Free, CoreState::Scheduled(Default::default()), occupied_core(2)];
let bitfields = vec![
block_on(signed_bitfield(&keystore, bitvec0, ValidatorIndex(0))),
block_on(signed_bitfield(&keystore, bitvec1, ValidatorIndex(1))),
block_on(signed_bitfield(&keystore, bitvec2.clone(), ValidatorIndex(2))),
];
let selected_bitfields = select_availability_bitfields(&cores, &bitfields);
// selects only the valid bitfield
assert_eq!(selected_bitfields.len(), 1);
assert_eq!(selected_bitfields[0].payload().0, bitvec2);
}
#[test]
fn more_set_bits_win_conflicts() {
let keystore: SyncCryptoStorePtr = Arc::new(KeyStore::new());
let mut bitvec = default_bitvec(2);
bitvec.set(0, true);
let mut bitvec1 = bitvec.clone();
bitvec1.set(1, true);
let cores = vec![occupied_core(0), occupied_core(1)];
let bitfields = vec![
block_on(signed_bitfield(&keystore, bitvec, ValidatorIndex(1))),
block_on(signed_bitfield(&keystore, bitvec1.clone(), ValidatorIndex(1))),
];
let selected_bitfields = select_availability_bitfields(&cores, &bitfields);
assert_eq!(selected_bitfields.len(), 1);
assert_eq!(selected_bitfields[0].payload().0, bitvec1.clone());
}
#[test]
fn more_complex_bitfields() {
let keystore: SyncCryptoStorePtr = Arc::new(KeyStore::new());
let cores = vec![occupied_core(0), occupied_core(1), occupied_core(2), occupied_core(3)];
let mut bitvec0 = default_bitvec(4);
bitvec0.set(0, true);
bitvec0.set(2, true);
let mut bitvec1 = default_bitvec(4);
bitvec1.set(1, true);
let mut bitvec2 = default_bitvec(4);
bitvec2.set(2, true);
let mut bitvec3 = default_bitvec(4);
bitvec3.set(0, true);
bitvec3.set(1, true);
bitvec3.set(2, true);
bitvec3.set(3, true);
// these are out of order but will be selected in order. The better
// bitfield for 3 will be selected.
let bitfields = vec![
block_on(signed_bitfield(&keystore, bitvec2.clone(), ValidatorIndex(3))),
block_on(signed_bitfield(&keystore, bitvec3.clone(), ValidatorIndex(3))),
block_on(signed_bitfield(&keystore, bitvec0.clone(), ValidatorIndex(0))),
block_on(signed_bitfield(&keystore, bitvec2.clone(), ValidatorIndex(2))),
block_on(signed_bitfield(&keystore, bitvec1.clone(), ValidatorIndex(1))),
];
let selected_bitfields = select_availability_bitfields(&cores, &bitfields);
assert_eq!(selected_bitfields.len(), 4);
assert_eq!(selected_bitfields[0].payload().0, bitvec0);
assert_eq!(selected_bitfields[1].payload().0, bitvec1);
assert_eq!(selected_bitfields[2].payload().0, bitvec2);
assert_eq!(selected_bitfields[3].payload().0, bitvec3);
}
}
mod select_candidates {
use super::{super::*, build_occupied_core, default_bitvec, occupied_core, scheduled_core};
use polkadot_node_subsystem::messages::{
AllMessages, RuntimeApiMessage,
RuntimeApiRequest::{
AvailabilityCores, PersistedValidationData as PersistedValidationDataReq,
},
};
use polkadot_node_subsystem_test_helpers::TestSubsystemSender;
use polkadot_primitives::v1::{
BlockNumber, CandidateCommitments, CandidateDescriptor, CommittedCandidateReceipt,
PersistedValidationData, PersistedValidationData,
}; };
const BLOCK_UNDER_PRODUCTION: BlockNumber = 128; fn test_harness<OverseerFactory, Overseer, TestFactory, Test>(
fn test_harness<OverseerFactory, Overseer, TestFactory, Test>(
overseer_factory: OverseerFactory, overseer_factory: OverseerFactory,
test_factory: TestFactory, test_factory: TestFactory,
) where ) where
OverseerFactory: FnOnce(mpsc::UnboundedReceiver<AllMessages>) -> Overseer, OverseerFactory: FnOnce(mpsc::UnboundedReceiver<AllMessages>) -> Overseer,
Overseer: Future<Output = ()>, Overseer: Future<Output = ()>,
TestFactory: FnOnce(TestSubsystemSender) -> Test, TestFactory: FnOnce(TestSubsystemSender) -> Test,
Test: Future<Output = ()>, Test: Future<Output = ()>,
{ {
let (tx, rx) = polkadot_node_subsystem_test_helpers::sender_receiver(); let (tx, rx) = polkadot_node_subsystem_test_helpers::sender_receiver();
let overseer = overseer_factory(rx); let overseer = overseer_factory(rx);
let test = test_factory(tx); let test = test_factory(tx);
@@ -219,101 +45,14 @@ mod select_candidates {
futures::pin_mut!(overseer, test); futures::pin_mut!(overseer, test);
let _ = futures::executor::block_on(future::join(overseer, test)); let _ = futures::executor::block_on(future::join(overseer, test));
} }
// For test purposes, we always return this set of availability cores: async fn mock_overseer(
//
// [
// 0: Free,
// 1: Scheduled(default),
// 2: Occupied(no next_up set),
// 3: Occupied(next_up_on_available set but not available),
// 4: Occupied(next_up_on_available set and available),
// 5: Occupied(next_up_on_time_out set but not timeout),
// 6: Occupied(next_up_on_time_out set and timeout but available),
// 7: Occupied(next_up_on_time_out set and timeout and not available),
// 8: Occupied(both next_up set, available),
// 9: Occupied(both next_up set, not available, no timeout),
// 10: Occupied(both next_up set, not available, timeout),
// 11: Occupied(next_up_on_available and available, but different successor para_id)
// ]
fn mock_availability_cores() -> Vec<CoreState> {
use std::ops::Not;
use CoreState::{Free, Scheduled};
vec![
// 0: Free,
Free,
// 1: Scheduled(default),
Scheduled(scheduled_core(1)),
// 2: Occupied(no next_up set),
occupied_core(2),
// 3: Occupied(next_up_on_available set but not available),
build_occupied_core(3, |core| {
core.next_up_on_available = Some(scheduled_core(3));
}),
// 4: Occupied(next_up_on_available set and available),
build_occupied_core(4, |core| {
core.next_up_on_available = Some(scheduled_core(4));
core.availability = core.availability.clone().not();
}),
// 5: Occupied(next_up_on_time_out set but not timeout),
build_occupied_core(5, |core| {
core.next_up_on_time_out = Some(scheduled_core(5));
}),
// 6: Occupied(next_up_on_time_out set and timeout but available),
build_occupied_core(6, |core| {
core.next_up_on_time_out = Some(scheduled_core(6));
core.time_out_at = BLOCK_UNDER_PRODUCTION;
core.availability = core.availability.clone().not();
}),
// 7: Occupied(next_up_on_time_out set and timeout and not available),
build_occupied_core(7, |core| {
core.next_up_on_time_out = Some(scheduled_core(7));
core.time_out_at = BLOCK_UNDER_PRODUCTION;
}),
// 8: Occupied(both next_up set, available),
build_occupied_core(8, |core| {
core.next_up_on_available = Some(scheduled_core(8));
core.next_up_on_time_out = Some(scheduled_core(8));
core.availability = core.availability.clone().not();
}),
// 9: Occupied(both next_up set, not available, no timeout),
build_occupied_core(9, |core| {
core.next_up_on_available = Some(scheduled_core(9));
core.next_up_on_time_out = Some(scheduled_core(9));
}),
// 10: Occupied(both next_up set, not available, timeout),
build_occupied_core(10, |core| {
core.next_up_on_available = Some(scheduled_core(10));
core.next_up_on_time_out = Some(scheduled_core(10));
core.time_out_at = BLOCK_UNDER_PRODUCTION;
}),
// 11: Occupied(next_up_on_available and available, but different successor para_id)
build_occupied_core(11, |core| {
core.next_up_on_available = Some(scheduled_core(12));
core.availability = core.availability.clone().not();
}),
]
}
async fn mock_overseer(
mut receiver: mpsc::UnboundedReceiver<AllMessages>, mut receiver: mpsc::UnboundedReceiver<AllMessages>,
expected: Vec<BackedCandidate>, expected: Vec<BackedCandidate>,
) { ) {
use ChainApiMessage::BlockNumber;
use RuntimeApiMessage::Request;
while let Some(from_job) = receiver.next().await { while let Some(from_job) = receiver.next().await {
match from_job { match from_job {
AllMessages::ChainApi(BlockNumber(_relay_parent, tx)) =>
tx.send(Ok(Some(BLOCK_UNDER_PRODUCTION - 1))).unwrap(),
AllMessages::RuntimeApi(Request(
_parent_hash,
PersistedValidationDataReq(_para_id, _assumption, tx),
)) => tx.send(Ok(Some(Default::default()))).unwrap(),
AllMessages::RuntimeApi(Request(_parent_hash, AvailabilityCores(tx))) =>
tx.send(Ok(mock_availability_cores())).unwrap(),
AllMessages::CandidateBacking(CandidateBackingMessage::GetBackedCandidates( AllMessages::CandidateBacking(CandidateBackingMessage::GetBackedCandidates(
_, _,
_, _,
@@ -324,26 +63,25 @@ mod select_candidates {
_ => panic!("Unexpected message: {:?}", from_job), _ => panic!("Unexpected message: {:?}", from_job),
} }
} }
} }
#[test] #[test]
fn can_succeed() { fn can_succeed() {
test_harness( test_harness(
|r| mock_overseer(r, Vec::new()), |r| mock_overseer(r, Vec::new()),
|mut tx: TestSubsystemSender| async move { |mut tx: TestSubsystemSender| async move {
select_candidates(&[], &[], &[], Default::default(), &mut tx).await.unwrap(); collect_backed_candidates(Vec::new(), Default::default(), &mut tx)
.await
.unwrap();
}, },
) )
} }
// this tests that only the appropriate candidates get selected.
// To accomplish this, we supply a candidate list containing one candidate per possible core;
// the candidate selection algorithm must filter them to the appropriate set
#[test]
fn selects_correct_candidates() {
let mock_cores = mock_availability_cores();
let n_cores = mock_cores.len();
// this tests that only the appropriate candidates get selected.
// To accomplish this, we supply a candidate list containing one candidate per possible core;
// the candidate selection algorithm must filter them to the appropriate set
#[test]
fn selects_correct_candidates() {
let empty_hash = PersistedValidationData::<Hash, BlockNumber>::default().hash(); let empty_hash = PersistedValidationData::<Hash, BlockNumber>::default().hash();
let candidate_template = CandidateReceipt { let candidate_template = CandidateReceipt {
@@ -353,108 +91,49 @@ mod select_candidates {
}, },
commitments_hash: CandidateCommitments::default().hash(), commitments_hash: CandidateCommitments::default().hash(),
}; };
let n_cores = 5;
let candidates: Vec<_> = std::iter::repeat(candidate_template) let candidate_receipts: Vec<_> = std::iter::repeat(candidate_template)
.take(mock_cores.len()) .take(n_cores)
.enumerate() .enumerate()
.map(|(idx, mut candidate)| { .map(|(idx, mut candidate)| {
candidate.descriptor.para_id = idx.into(); candidate.descriptor.para_id = idx.into();
candidate candidate
}) })
.cycle() .cycle()
.take(mock_cores.len() * 3) .take(n_cores * 4)
.enumerate() .enumerate()
.map(|(idx, mut candidate)| { .map(|(idx, mut candidate_receipt)| {
if idx < mock_cores.len() { if idx < n_cores {
// first go-around: use candidates which should work // first go-around: use candidates which should work
candidate candidate_receipt
} else if idx < mock_cores.len() * 2 { } else if idx < n_cores * 2 {
// for the second repetition of the candidates, give them the wrong hash // for the second repetition of the candidates, give them the wrong hash
candidate.descriptor.persisted_validation_data_hash = Default::default(); candidate_receipt.descriptor.persisted_validation_data_hash = Default::default();
candidate candidate_receipt
} else { } else if idx < n_cores * 3 {
// third go-around: right hash, wrong para_id // third go-around: right hash, wrong para_id
candidate.descriptor.para_id = idx.into(); candidate_receipt.descriptor.para_id = idx.into();
candidate candidate_receipt
}
})
.collect();
// why those particular indices? see the comments on mock_availability_cores()
let expected_candidates: Vec<_> =
[1, 4, 7, 8, 10].iter().map(|&idx| candidates[idx].clone()).collect();
let expected_backed = expected_candidates
.iter()
.map(|c| BackedCandidate {
candidate: CommittedCandidateReceipt {
descriptor: c.descriptor.clone(),
..Default::default()
},
validity_votes: Vec::new(),
validator_indices: default_bitvec(n_cores),
})
.collect();
test_harness(
|r| mock_overseer(r, expected_backed),
|mut tx: TestSubsystemSender| async move {
let result =
select_candidates(&mock_cores, &[], &candidates, Default::default(), &mut tx)
.await
.unwrap();
result.into_iter().for_each(|c| {
assert!(
expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)),
"Failed to find candidate: {:?}",
c,
)
});
},
)
}
#[test]
fn selects_max_one_code_upgrade() {
let mock_cores = mock_availability_cores();
let n_cores = mock_cores.len();
let empty_hash = PersistedValidationData::<Hash, BlockNumber>::default().hash();
// why those particular indices? see the comments on mock_availability_cores()
// the first candidate with code is included out of [1, 4, 7, 8, 10].
let cores = [1, 7, 10];
let cores_with_code = [1, 4, 8];
let committed_receipts: Vec<_> = (0..mock_cores.len())
.map(|i| CommittedCandidateReceipt {
descriptor: CandidateDescriptor {
para_id: i.into(),
persisted_validation_data_hash: empty_hash,
..Default::default()
},
commitments: CandidateCommitments {
new_validation_code: if cores_with_code.contains(&i) {
Some(vec![].into())
} else { } else {
None // fourth go-around: wrong relay parent, this is the only thing that is checked
}, candidate_receipt.descriptor.relay_parent = Hash::repeat_byte(0xFF);
..Default::default() candidate_receipt
}, }
..Default::default()
}) })
.collect(); .collect();
// candidates now contains 1/3 valid canidates, and 2/3 invalid
// but we don't check them in them here, so they should be passed alright
let candidates: Vec<_> = committed_receipts.iter().map(|r| r.to_plain()).collect(); let expected_candidate_receipts =
candidate_receipts.iter().take(n_cores * 3).cloned().collect::<Vec<_>>();
let expected_candidates: Vec<_> = let expected_backed = expected_candidate_receipts
cores.iter().map(|&idx| candidates[idx].clone()).collect();
let expected_backed: Vec<_> = cores
.iter() .iter()
.map(|&idx| BackedCandidate { .map(|candidate_receipt| BackedCandidate {
candidate: committed_receipts[idx].clone(), candidate: CommittedCandidateReceipt {
descriptor: candidate_receipt.descriptor.clone(),
..Default::default()
},
validity_votes: Vec::new(), validity_votes: Vec::new(),
validator_indices: default_bitvec(n_cores), validator_indices: default_bitvec(n_cores),
}) })
@@ -463,19 +142,17 @@ mod select_candidates {
test_harness( test_harness(
|r| mock_overseer(r, expected_backed), |r| mock_overseer(r, expected_backed),
|mut tx: TestSubsystemSender| async move { |mut tx: TestSubsystemSender| async move {
let result = let result = collect_backed_candidates(candidate_receipts, Default::default(), &mut tx)
select_candidates(&mock_cores, &[], &candidates, Default::default(), &mut tx)
.await .await
.unwrap(); .unwrap();
result.into_iter().for_each(|c| { result.into_iter().for_each(|c| {
assert!( assert!(
expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)), expected_candidate_receipts.iter().any(|c2| c.candidate.corresponds_to(c2)),
"Failed to find candidate: {:?}", "Failed to find candidate: {:?}",
c, c,
) )
}); });
}, },
) )
}
} }
+1 -1
View File
@@ -59,7 +59,7 @@ pub use polkadot_node_core_candidate_validation::CandidateValidationSubsystem;
pub use polkadot_node_core_chain_api::ChainApiSubsystem; pub use polkadot_node_core_chain_api::ChainApiSubsystem;
pub use polkadot_node_core_chain_selection::ChainSelectionSubsystem; pub use polkadot_node_core_chain_selection::ChainSelectionSubsystem;
pub use polkadot_node_core_dispute_coordinator::DisputeCoordinatorSubsystem; pub use polkadot_node_core_dispute_coordinator::DisputeCoordinatorSubsystem;
pub use polkadot_node_core_provisioner::ProvisioningSubsystem as ProvisionerSubsystem; pub use polkadot_node_core_provisioner::ProvisionerSubsystem;
pub use polkadot_node_core_runtime_api::RuntimeApiSubsystem; pub use polkadot_node_core_runtime_api::RuntimeApiSubsystem;
pub use polkadot_statement_distribution::StatementDistribution as StatementDistributionSubsystem; pub use polkadot_statement_distribution::StatementDistribution as StatementDistributionSubsystem;
@@ -28,8 +28,6 @@ The dispute inherent is similar to a misbehavior report in that it is an attesta
Dispute resolution is complex and is explained in substantially more detail [here](../../runtime/disputes.md). Dispute resolution is complex and is explained in substantially more detail [here](../../runtime/disputes.md).
> TODO: The provisioner is responsible for selecting remote disputes to replay. Let's figure out the details.
## Protocol ## Protocol
Input: [`ProvisionerMessage`](../../types/overseer-protocol.md#provisioner-message). Backed candidates come from the [Candidate Backing subsystem](../backing/candidate-backing.md), signed bitfields come from the [Bitfield Distribution subsystem](../availability/bitfield-distribution.md), and misbehavior reports and disputes come from the [Misbehavior Arbitration subsystem](misbehavior-arbitration.md). Input: [`ProvisionerMessage`](../../types/overseer-protocol.md#provisioner-message). Backed candidates come from the [Candidate Backing subsystem](../backing/candidate-backing.md), signed bitfields come from the [Bitfield Distribution subsystem](../availability/bitfield-distribution.md), and misbehavior reports and disputes come from the [Misbehavior Arbitration subsystem](misbehavior-arbitration.md).
@@ -42,62 +40,26 @@ Block authors request the inherent data they should use for constructing the inh
When a validator is selected by BABE to author a block, it becomes a block producer. The provisioner is the subsystem best suited to choosing which specific backed candidates and availability bitfields should be assembled into the block. To engage this functionality, a `ProvisionerMessage::RequestInherentData` is sent; the response is a [`ParaInherentData`](../../types/runtime.md#parainherentdata). There are never two distinct parachain candidates included for the same parachain and that new parachain candidates cannot be backed until the previous one either gets declared available or expired. Appropriate bitfields, as outlined in the section on [bitfield selection](#bitfield-selection), and any dispute statements should be attached as well. When a validator is selected by BABE to author a block, it becomes a block producer. The provisioner is the subsystem best suited to choosing which specific backed candidates and availability bitfields should be assembled into the block. To engage this functionality, a `ProvisionerMessage::RequestInherentData` is sent; the response is a [`ParaInherentData`](../../types/runtime.md#parainherentdata). There are never two distinct parachain candidates included for the same parachain and that new parachain candidates cannot be backed until the previous one either gets declared available or expired. Appropriate bitfields, as outlined in the section on [bitfield selection](#bitfield-selection), and any dispute statements should be attached as well.
### Bitfield Selection ### Bitfield Collection
Our goal with respect to bitfields is simple: maximize availability. However, it's not quite as simple as always including all bitfields; there are constraints which still need to be met: Collects all provided bitfields and forwards them to the runtimes `fn create_inherent` which does both sanity (one bitfield per validator) as well as weight limiting logic.
- We cannot choose more than one bitfield per validator. ### Backed Candidate Collection
- Each bitfield must correspond to an occupied core.
Beyond that, a semi-arbitrary selection policy is fine. In order to meet the goal of maximizing availability, a heuristic of picking the bitfield with the greatest number of 1 bits set in the event of conflict is useful. Selects all referenced candidates the correspond to the
correct relay chain parent block and assures only one candidate per block is submitted.
### Candidate Selection The full backed candidate are obtained by issuing a `CandidateBackingMessage::GetBackedCandidates` which yields a `Vec<BackedCandidate>` in response.
The goal of candidate selection is to determine which cores are free, and then to the degree possible, pick a candidate appropriate to each free core. The end result of this process is a vector of `BackedCandidate`s, sorted in the order of their `CandidateReceipt`s.
To determine availability:
- Get the list of core states from the runtime API
- For each core state:
- On `CoreState::Scheduled`, then we can make an `OccupiedCoreAssumption::Free`.
- On `CoreState::Occupied`, then we may be able to make an assumption:
- If the bitfields indicate availability and there is a scheduled `next_up_on_available`, then we can make an `OccupiedCoreAssumption::Included`.
- If the bitfields do not indicate availability, and there is a scheduled `next_up_on_time_out`, and `occupied_core.time_out_at == block_number_under_production`, then we can make an `OccupiedCoreAssumption::TimedOut`.
- If we did not make an `OccupiedCoreAssumption`, then continue on to the next core.
- Now compute the core's `validation_data_hash`: get the `PersistedValidationData` from the runtime, given the known `ParaId` and `OccupiedCoreAssumption`;
- Find an appropriate candidate for the core.
- There are two constraints: `backed_candidate.candidate.descriptor.para_id == scheduled_core.para_id && candidate.candidate.descriptor.validation_data_hash == computed_validation_data_hash`.
- In the event that more than one candidate meets the constraints, selection between the candidates is arbitrary. However, not more than one candidate can be selected per core.
The end result of this process is a vector of `BackedCandidate`s, sorted in order of their core index. Furthermore, this process should select at maximum one candidate which upgrades the runtime validation code.
### Dispute Statement Selection ### Dispute Statement Selection
This is the point at which the block author provides further votes to active disputes or initiates new disputes in the runtime state. This is the point at which the block author provides further votes to active disputes or initiates new disputes in the runtime state.
The block-authoring logic of the runtime has an extra step between handling the inherent-data and producing the actual inherent call, which we assume performs the work of filtering out disputes which are not relevant to the on-chain state.
To select disputes: To select disputes:
- Issue a `DisputeCoordinatorMessage::RecentDisputes` message and wait for the response. This is a set of all disputes in recent sessions which we are aware of. - Issue a `DisputeCoordinatorMessage::RecentDisputes` message and wait for the response. This is a set of all disputes in recent sessions which we are aware of. They are passed on unaltered to the `fn create_inherent` which is run off-chain.
### Determining Bitfield Availability
An occupied core has a `CoreAvailability` bitfield. We also have a list of `SignedAvailabilityBitfield`s. We need to determine from these whether or not a core at a particular index has become available.
The key insight required is that `CoreAvailability` is transverse to the `SignedAvailabilityBitfield`s: if we conceptualize the list of bitfields as many rows, each bit of which is its own column, then `CoreAvailability` for a given core index is the vertical slice of bits in the set at that index.
To compute bitfield availability, then:
- Start with a copy of `OccupiedCore.availability`
- For each bitfield in the list of `SignedAvailabilityBitfield`s:
- Get the bitfield's `validator_index`
- Update the availability. Conceptually, assuming bit vectors: `availability[validator_index] |= bitfield[core_idx]`
- Availability has a 2/3 threshold. Therefore: `3 * availability.count_ones() >= 2 * availability.len()`
### Notes
See also: [Scheduler Module: Availability Cores](../../runtime/scheduler.md#availability-cores).
## Functionality ## Functionality
+1
View File
@@ -143,6 +143,7 @@ Merklized
metadata/M metadata/M
middleware/MS middleware/MS
Millau Millau
minimalistic
misbehavior/SM misbehavior/SM
misbehaviors misbehaviors
misvalidate/D misvalidate/D