mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 10:31:04 +00:00
introduce errors with info (#1834)
This commit is contained in:
committed by
GitHub
parent
40ea09389c
commit
f345123748
@@ -5,12 +5,12 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
derive_more = "0.99.9"
|
||||
futures = "0.3.5"
|
||||
futures-timer = "3.0.2"
|
||||
kvdb = "0.7.0"
|
||||
kvdb-rocksdb = "0.9.1"
|
||||
log = "0.4.8"
|
||||
log = "0.4.11"
|
||||
thiserror = "1.0.21"
|
||||
|
||||
codec = { package = "parity-scale-codec", version = "1.3.1", features = ["derive"] }
|
||||
erasure = { package = "polkadot-erasure-coding", path = "../../../erasure-coding" }
|
||||
|
||||
@@ -44,6 +44,7 @@ use polkadot_node_subsystem_util::metrics::{self, prometheus};
|
||||
use polkadot_subsystem::messages::{
|
||||
AllMessages, AvailabilityStoreMessage, ChainApiMessage, RuntimeApiMessage, RuntimeApiRequest,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
const LOG_TARGET: &str = "availability";
|
||||
|
||||
@@ -53,22 +54,22 @@ mod columns {
|
||||
pub const NUM_COLUMNS: u32 = 2;
|
||||
}
|
||||
|
||||
#[derive(Debug, derive_more::From)]
|
||||
#[derive(Debug, Error)]
|
||||
enum Error {
|
||||
#[from]
|
||||
Chain(ChainApiError),
|
||||
#[from]
|
||||
Erasure(erasure::Error),
|
||||
#[from]
|
||||
Io(io::Error),
|
||||
#[from]
|
||||
Oneshot(oneshot::Canceled),
|
||||
#[from]
|
||||
Runtime(RuntimeApiError),
|
||||
#[from]
|
||||
Subsystem(SubsystemError),
|
||||
#[from]
|
||||
Time(SystemTimeError),
|
||||
#[error(transparent)]
|
||||
RuntimeAPI(#[from] RuntimeApiError),
|
||||
#[error(transparent)]
|
||||
ChainAPI(#[from] ChainApiError),
|
||||
#[error(transparent)]
|
||||
Erasure(#[from] erasure::Error),
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
#[error(transparent)]
|
||||
Oneshot(#[from] oneshot::Canceled),
|
||||
#[error(transparent)]
|
||||
Subsystem(#[from] SubsystemError),
|
||||
#[error(transparent)]
|
||||
Time(#[from] SystemTimeError),
|
||||
}
|
||||
|
||||
/// A wrapper type for delays.
|
||||
|
||||
@@ -6,19 +6,16 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.5"
|
||||
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-keystore = { 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" }
|
||||
polkadot-primitives = { path = "../../../primitives" }
|
||||
polkadot-node-primitives = { path = "../../primitives" }
|
||||
polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsystem" }
|
||||
polkadot-node-subsystem-util = { path = "../../subsystem-util" }
|
||||
erasure-coding = { package = "polkadot-erasure-coding", path = "../../../erasure-coding" }
|
||||
statement-table = { package = "polkadot-statement-table", path = "../../../statement-table" }
|
||||
derive_more = "0.99.9"
|
||||
bitvec = { version = "0.17.4", default-features = false, features = ["alloc"] }
|
||||
log = "0.4.8"
|
||||
log = "0.4.11"
|
||||
thiserror = "1.0.21"
|
||||
|
||||
[dev-dependencies]
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
//! Implements a `CandidateBackingSubsystem`.
|
||||
|
||||
#![deny(unused_crate_dependencies)]
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::convert::TryFrom;
|
||||
use std::pin::Pin;
|
||||
@@ -64,22 +66,26 @@ use statement_table::{
|
||||
SignedStatement as TableSignedStatement, Summary as TableSummary,
|
||||
},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, derive_more::From)]
|
||||
#[derive(Debug, Error)]
|
||||
enum Error {
|
||||
#[error("Candidate is not found")]
|
||||
CandidateNotFound,
|
||||
#[error("Signature is invalid")]
|
||||
InvalidSignature,
|
||||
StoreFailed,
|
||||
#[from]
|
||||
Erasure(erasure_coding::Error),
|
||||
#[from]
|
||||
ValidationFailed(ValidationFailed),
|
||||
#[from]
|
||||
Oneshot(oneshot::Canceled),
|
||||
#[from]
|
||||
Mpsc(mpsc::SendError),
|
||||
#[from]
|
||||
UtilError(util::Error),
|
||||
#[error("Failed to send candidates {0:?}")]
|
||||
Send(Vec<NewBackedCandidate>),
|
||||
#[error("Oneshot never resolved")]
|
||||
Oneshot(#[from] #[source] oneshot::Canceled),
|
||||
#[error("Obtaining erasure chunks failed")]
|
||||
ObtainErasureChunks(#[from] #[source] erasure_coding::Error),
|
||||
#[error(transparent)]
|
||||
ValidationFailed(#[from] ValidationFailed),
|
||||
#[error(transparent)]
|
||||
Mpsc(#[from] mpsc::SendError),
|
||||
#[error(transparent)]
|
||||
UtilError(#[from] util::Error),
|
||||
}
|
||||
|
||||
/// Holds all data needed for candidate backing job operation.
|
||||
@@ -468,7 +474,7 @@ impl CandidateBackingJob {
|
||||
CandidateBackingMessage::GetBackedCandidates(_, tx) => {
|
||||
let backed = self.get_backed();
|
||||
|
||||
tx.send(backed).map_err(|_| oneshot::Canceled)?;
|
||||
tx.send(backed).map_err(|data| Error::Send(data))?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,7 +646,7 @@ impl CandidateBackingJob {
|
||||
)
|
||||
).await?;
|
||||
|
||||
rx.await?.map_err(|_| Error::StoreFailed)?;
|
||||
let _ = rx.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bitvec = "0.17.4"
|
||||
derive_more = "0.99.9"
|
||||
futures = "0.3.5"
|
||||
log = "0.4.8"
|
||||
log = "0.4.11"
|
||||
polkadot-primitives = { path = "../../../primitives" }
|
||||
polkadot-node-subsystem = { path = "../../subsystem" }
|
||||
polkadot-node-subsystem-util = { path = "../../subsystem-util" }
|
||||
sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
wasm-timer = "0.2.4"
|
||||
thiserror = "1.0.21"
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
//! The bitfield signing subsystem produces `SignedAvailabilityBitfield`s once per block.
|
||||
|
||||
#![deny(unused_crate_dependencies, unused_results)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use bitvec::bitvec;
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
@@ -37,6 +40,7 @@ use polkadot_node_subsystem_util::{
|
||||
use polkadot_primitives::v1::{AvailabilityBitfield, CoreState, Hash, ValidatorIndex};
|
||||
use std::{convert::TryFrom, pin::Pin, time::Duration};
|
||||
use wasm_timer::{Delay, Instant};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Delay between starting a bitfield signing job and its attempting to create a bitfield.
|
||||
const JOB_DELAY: Duration = Duration::from_millis(1500);
|
||||
@@ -45,6 +49,7 @@ const JOB_DELAY: Duration = Duration::from_millis(1500);
|
||||
pub struct BitfieldSigningJob;
|
||||
|
||||
/// Messages which a `BitfieldSigningJob` is prepared to receive.
|
||||
#[allow(missing_docs)]
|
||||
pub enum ToJob {
|
||||
BitfieldSigning(BitfieldSigningMessage),
|
||||
Stop,
|
||||
@@ -79,6 +84,7 @@ impl From<BitfieldSigningMessage> for ToJob {
|
||||
}
|
||||
|
||||
/// Messages which may be sent from a `BitfieldSigningJob`.
|
||||
#[allow(missing_docs)]
|
||||
pub enum FromJob {
|
||||
AvailabilityStore(AvailabilityStoreMessage),
|
||||
BitfieldDistribution(BitfieldDistributionMessage),
|
||||
@@ -112,28 +118,28 @@ impl TryFrom<AllMessages> for FromJob {
|
||||
}
|
||||
|
||||
/// Errors we may encounter in the course of executing the `BitfieldSigningSubsystem`.
|
||||
#[derive(Debug, derive_more::From)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// error propagated from the utility subsystem
|
||||
#[from]
|
||||
Util(util::Error),
|
||||
#[error(transparent)]
|
||||
Util(#[from] util::Error),
|
||||
/// io error
|
||||
#[from]
|
||||
Io(std::io::Error),
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
/// a one shot channel was canceled
|
||||
#[from]
|
||||
Oneshot(oneshot::Canceled),
|
||||
#[error(transparent)]
|
||||
Oneshot(#[from] oneshot::Canceled),
|
||||
/// a mspc channel failed to send
|
||||
#[from]
|
||||
MpscSend(mpsc::SendError),
|
||||
#[error(transparent)]
|
||||
MpscSend(#[from] mpsc::SendError),
|
||||
/// several errors collected into one
|
||||
#[from]
|
||||
#[error("Multiple errours occured: {0:?}")]
|
||||
Multiple(Vec<Error>),
|
||||
/// the runtime API failed to return what we wanted
|
||||
#[from]
|
||||
Runtime(RuntimeApiError),
|
||||
#[error(transparent)]
|
||||
Runtime(#[from] RuntimeApiError),
|
||||
/// the keystore failed to process signing request
|
||||
#[from]
|
||||
#[error("Keystore failed: {0:?}")]
|
||||
Keystore(KeystoreError),
|
||||
}
|
||||
|
||||
@@ -252,7 +258,7 @@ async fn construct_availability_bitfield(
|
||||
if errs.is_empty() {
|
||||
Ok(out.into_inner().into())
|
||||
} else {
|
||||
Err(errs.into())
|
||||
Err(Error::Multiple(errs.into()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
derive_more = "0.99.9"
|
||||
futures = "0.3.5"
|
||||
log = "0.4.8"
|
||||
log = "0.4.11"
|
||||
thiserror = "1.0.21"
|
||||
polkadot-primitives = { path = "../../../primitives" }
|
||||
polkadot-node-primitives = { path = "../../primitives" }
|
||||
polkadot-node-subsystem = { path = "../../subsystem" }
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
//! The provisioner is responsible for assembling a relay chain block
|
||||
//! from a set of available parachain candidates of its choice.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_docs, unused_crate_dependencies, unused_results)]
|
||||
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
@@ -39,6 +39,7 @@ use polkadot_primitives::v1::{
|
||||
CandidateDescriptor, CandidateReceipt, CollatorId, Hash, Id as ParaId, PoV,
|
||||
};
|
||||
use std::{convert::TryFrom, pin::Pin, sync::Arc};
|
||||
use thiserror::Error;
|
||||
|
||||
const TARGET: &'static str = "candidate_selection";
|
||||
|
||||
@@ -116,18 +117,18 @@ impl TryFrom<AllMessages> for FromJob {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, derive_more::From)]
|
||||
#[derive(Debug, Error)]
|
||||
enum Error {
|
||||
#[from]
|
||||
Sending(mpsc::SendError),
|
||||
#[from]
|
||||
Util(util::Error),
|
||||
#[from]
|
||||
OneshotRecv(oneshot::Canceled),
|
||||
#[from]
|
||||
ChainApi(ChainApiError),
|
||||
#[from]
|
||||
Runtime(RuntimeApiError),
|
||||
#[error(transparent)]
|
||||
Sending(#[from] mpsc::SendError),
|
||||
#[error(transparent)]
|
||||
Util(#[from] util::Error),
|
||||
#[error(transparent)]
|
||||
OneshotRecv(#[from] oneshot::Canceled),
|
||||
#[error(transparent)]
|
||||
ChainApi(#[from] ChainApiError),
|
||||
#[error(transparent)]
|
||||
Runtime(#[from] RuntimeApiError),
|
||||
}
|
||||
|
||||
impl JobTrait for CandidateSelectionJob {
|
||||
@@ -149,14 +150,13 @@ impl JobTrait for CandidateSelectionJob {
|
||||
receiver: mpsc::Receiver<ToJob>,
|
||||
sender: mpsc::Sender<FromJob>,
|
||||
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send>> {
|
||||
async move {
|
||||
Box::pin(async move {
|
||||
let job = CandidateSelectionJob::new(metrics, sender, receiver);
|
||||
|
||||
// it isn't necessary to break run_loop into its own function,
|
||||
// but it's convenient to separate the concerns in this way
|
||||
job.run_loop().await
|
||||
}
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,9 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
derive_more = "0.99.9"
|
||||
futures = "0.3.5"
|
||||
log = "0.4.8"
|
||||
log = "0.4.11"
|
||||
|
||||
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-core = { package = "sp-core", git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
parity-scale-codec = { version = "1.3.0", default-features = false, features = ["bit-vec", "derive"] }
|
||||
|
||||
|
||||
@@ -20,8 +20,11 @@
|
||||
//! according to a validation function. This delegates validation to an underlying
|
||||
//! pool of processes used for execution of the Wasm.
|
||||
|
||||
#![deny(unused_crate_dependencies, unused_results)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use polkadot_subsystem::{
|
||||
Subsystem, SubsystemContext, SpawnedSubsystem, SubsystemResult,
|
||||
Subsystem, SubsystemContext, SpawnedSubsystem, SubsystemResult, SubsystemError,
|
||||
FromOverseer, OverseerSignal,
|
||||
messages::{
|
||||
AllMessages, CandidateValidationMessage, RuntimeApiMessage,
|
||||
@@ -116,9 +119,13 @@ impl<S, C> Subsystem<C> for CandidateValidationSubsystem<S> where
|
||||
S: SpawnNamed + Clone + 'static,
|
||||
{
|
||||
fn start(self, ctx: C) -> SpawnedSubsystem {
|
||||
let future = run(ctx, self.spawn, self.metrics)
|
||||
.map_err(|e| SubsystemError::with_origin("candidate-validation", e))
|
||||
.map(|_| ())
|
||||
.boxed();
|
||||
SpawnedSubsystem {
|
||||
name: "candidate-validation-subsystem",
|
||||
future: run(ctx, self.spawn, self.metrics).map(|_| ()).boxed(),
|
||||
future,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,12 @@
|
||||
//! * Last finalized block number
|
||||
//! * Ancestors
|
||||
|
||||
#![deny(unused_crate_dependencies, unused_results)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use polkadot_subsystem::{
|
||||
FromOverseer, OverseerSignal,
|
||||
SpawnedSubsystem, Subsystem, SubsystemResult, SubsystemContext,
|
||||
SpawnedSubsystem, Subsystem, SubsystemResult, SubsystemError, SubsystemContext,
|
||||
messages::ChainApiMessage,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{
|
||||
@@ -61,8 +64,12 @@ impl<Client, Context> Subsystem<Context> for ChainApiSubsystem<Client> where
|
||||
Context: SubsystemContext<Message = ChainApiMessage>
|
||||
{
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let future = run(ctx, self)
|
||||
.map_err(|e| SubsystemError::with_origin("chain-api", e))
|
||||
.map(|_| ())
|
||||
.boxed();
|
||||
SpawnedSubsystem {
|
||||
future: run(ctx, self).map(|_| ()).boxed(),
|
||||
future,
|
||||
name: "chain-api-subsystem",
|
||||
}
|
||||
}
|
||||
@@ -112,7 +119,10 @@ where
|
||||
let maybe_header = subsystem.client.header(BlockId::Hash(hash));
|
||||
match maybe_header {
|
||||
// propagate the error
|
||||
Err(e) => Some(Err(e.to_string().into())),
|
||||
Err(e) => {
|
||||
let e = e.to_string().into();
|
||||
Some(Err(e))
|
||||
},
|
||||
// fewer than `k` ancestors are available
|
||||
Ok(None) => None,
|
||||
Ok(Some(header)) => {
|
||||
|
||||
@@ -6,16 +6,13 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.4"
|
||||
futures-timer = "3.0.1"
|
||||
log = "0.4.8"
|
||||
parity-scale-codec = "1.3.4"
|
||||
polkadot-node-subsystem = { path = "../../subsystem" }
|
||||
polkadot-overseer = { path = "../../overseer" }
|
||||
polkadot-primitives = { path = "../../../primitives" }
|
||||
sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-telemetry = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
@@ -23,5 +20,4 @@ sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
tokio-executor = { version = "0.2.0-alpha.6", features = ["blocking"] }
|
||||
wasm-timer = "0.2.4"
|
||||
|
||||
@@ -1,3 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The proposer proposes new blocks to include
|
||||
|
||||
#![deny(unused_crate_dependencies, unused_results)]
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::select;
|
||||
use polkadot_node_subsystem::{messages::{AllMessages, ProvisionerInherentData, ProvisionerMessage}, SubsystemError};
|
||||
@@ -123,7 +143,7 @@ where
|
||||
let (sender, receiver) = futures::channel::oneshot::channel();
|
||||
|
||||
overseer.wait_for_activation(parent_header_hash, sender).await?;
|
||||
receiver.await.map_err(Error::ClosedChannelFromProvisioner)?;
|
||||
receiver.await.map_err(Error::ClosedChannelFromProvisioner)??;
|
||||
|
||||
let (sender, receiver) = futures::channel::oneshot::channel();
|
||||
// strictly speaking, we don't _have_ to .await this send_msg before opening the
|
||||
@@ -206,7 +226,7 @@ where
|
||||
|
||||
// It would have been more ergonomic to use thiserror to derive the
|
||||
// From implementations, Display, and std::error::Error, but unfortunately
|
||||
// two of the wrapped errors (sp_inherents::Error, SubsystemError) also
|
||||
// one of the wrapped errors (sp_inherents::Error) also
|
||||
// don't impl std::error::Error, which breaks the thiserror derive.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -261,6 +281,7 @@ impl std::error::Error for Error {
|
||||
Self::Consensus(err) => Some(err),
|
||||
Self::Blockchain(err) => Some(err),
|
||||
Self::ClosedChannelFromProvisioner(err) => Some(err),
|
||||
Self::Subsystem(err) => Some(err),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,14 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bitvec = { version = "0.17.4", default-features = false, features = ["alloc"] }
|
||||
derive_more = "0.99.9"
|
||||
futures = "0.3.5"
|
||||
log = "0.4.8"
|
||||
log = "0.4.11"
|
||||
thiserror = "1.0.21"
|
||||
polkadot-primitives = { path = "../../../primitives" }
|
||||
polkadot-node-subsystem = { path = "../../subsystem" }
|
||||
polkadot-node-subsystem-util = { path = "../../subsystem-util" }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.4"
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
//! The provisioner is responsible for assembling a relay chain block
|
||||
//! from a set of available parachain candidates of its choice.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_docs, unused_crate_dependencies, unused_results)]
|
||||
|
||||
use bitvec::vec::BitVec;
|
||||
use futures::{
|
||||
@@ -42,6 +42,7 @@ use polkadot_primitives::v1::{
|
||||
SignedAvailabilityBitfield,
|
||||
};
|
||||
use std::{collections::HashMap, convert::TryFrom, pin::Pin};
|
||||
use thiserror::Error;
|
||||
|
||||
struct ProvisioningJob {
|
||||
relay_parent: Hash,
|
||||
@@ -115,19 +116,25 @@ impl TryFrom<AllMessages> for FromJob {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, derive_more::From)]
|
||||
#[derive(Debug, Error)]
|
||||
enum Error {
|
||||
#[from]
|
||||
Sending(mpsc::SendError),
|
||||
#[from]
|
||||
Util(util::Error),
|
||||
#[from]
|
||||
OneshotRecv(oneshot::Canceled),
|
||||
#[from]
|
||||
ChainApi(ChainApiError),
|
||||
#[from]
|
||||
Runtime(RuntimeApiError),
|
||||
OneshotSend,
|
||||
#[error(transparent)]
|
||||
Util(#[from] util::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
OneshotRecv(#[from] oneshot::Canceled),
|
||||
|
||||
#[error(transparent)]
|
||||
ChainApi(#[from] ChainApiError),
|
||||
|
||||
#[error(transparent)]
|
||||
Runtime(#[from] RuntimeApiError),
|
||||
|
||||
#[error("Failed to send message to ChainAPI")]
|
||||
ChainApiMessageSend(#[source] mpsc::SendError),
|
||||
|
||||
#[error("Failed to send return message with Inherents")]
|
||||
InherentDataReturnChannel,
|
||||
}
|
||||
|
||||
impl JobTrait for ProvisioningJob {
|
||||
@@ -230,7 +237,7 @@ impl ProvisioningJob {
|
||||
let tail = bad_indices[bad_indices.len() - 1];
|
||||
let retain = *idx != tail;
|
||||
if *idx >= tail {
|
||||
bad_indices.pop();
|
||||
let _ = bad_indices.pop();
|
||||
}
|
||||
retain
|
||||
})
|
||||
@@ -299,7 +306,7 @@ async fn send_inherent_data(
|
||||
|
||||
return_sender
|
||||
.send((bitfields, candidates))
|
||||
.map_err(|_| Error::OneshotSend)?;
|
||||
.map_err(|_data| Error::InherentDataReturnChannel)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -423,7 +430,7 @@ async fn get_block_number_under_construction(
|
||||
tx,
|
||||
)))
|
||||
.await
|
||||
.map_err(|_| Error::OneshotSend)?;
|
||||
.map_err(|e| Error::ChainApiMessageSend(e))?;
|
||||
match rx.await? {
|
||||
Ok(Some(n)) => Ok(n + 1),
|
||||
Ok(None) => Ok(0),
|
||||
@@ -504,382 +511,4 @@ impl metrics::Metrics for Metrics {
|
||||
delegated_subsystem!(ProvisioningJob((), Metrics) <- ToJob as ProvisioningSubsystem);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bitvec::bitvec;
|
||||
use polkadot_primitives::v1::{OccupiedCore, ScheduledCore};
|
||||
|
||||
pub fn occupied_core(para_id: u32) -> CoreState {
|
||||
CoreState::Occupied(OccupiedCore {
|
||||
para_id: para_id.into(),
|
||||
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: default_bitvec(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_occupied_core<Builder>(para_id: u32, builder: Builder) -> CoreState
|
||||
where
|
||||
Builder: FnOnce(&mut OccupiedCore),
|
||||
{
|
||||
let mut core = match occupied_core(para_id) {
|
||||
CoreState::Occupied(core) => core,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
builder(&mut core);
|
||||
|
||||
CoreState::Occupied(core)
|
||||
}
|
||||
|
||||
pub fn default_bitvec() -> CoreAvailability {
|
||||
bitvec![bitvec::order::Lsb0, u8; 0; 32]
|
||||
}
|
||||
|
||||
pub fn scheduled_core(id: u32) -> ScheduledCore {
|
||||
ScheduledCore {
|
||||
para_id: id.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
mod select_availability_bitfields {
|
||||
use super::super::*;
|
||||
use super::{default_bitvec, occupied_core};
|
||||
use futures::executor::block_on;
|
||||
use std::sync::Arc;
|
||||
use polkadot_primitives::v1::{SigningContext, ValidatorIndex, ValidatorId};
|
||||
use sp_application_crypto::AppKey;
|
||||
use sp_keystore::{CryptoStore, SyncCryptoStorePtr};
|
||||
use sc_keystore::LocalKeystore;
|
||||
|
||||
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.expect("Should be signed")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_more_than_one_per_validator() {
|
||||
// Configure filesystem-based keystore as generating keys without seed
|
||||
// would trigger the key to be generated on the filesystem.
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore : SyncCryptoStorePtr = Arc::new(LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore"));
|
||||
let bitvec = default_bitvec();
|
||||
|
||||
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(), 0)),
|
||||
block_on(signed_bitfield(&keystore, bitvec.clone(), 1)),
|
||||
block_on(signed_bitfield(&keystore, bitvec, 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() {
|
||||
// Configure filesystem-based keystore as generating keys without seed
|
||||
// would trigger the key to be generated on the filesystem.
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore : SyncCryptoStorePtr = Arc::new(LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore"));
|
||||
let bitvec = default_bitvec();
|
||||
|
||||
let cores = vec![CoreState::Free, CoreState::Scheduled(Default::default())];
|
||||
|
||||
let bitfields = vec![
|
||||
block_on(signed_bitfield(&keystore, bitvec.clone(), 0)),
|
||||
block_on(signed_bitfield(&keystore, bitvec.clone(), 1)),
|
||||
block_on(signed_bitfield(&keystore, bitvec, 1)),
|
||||
];
|
||||
|
||||
let mut selected_bitfields = select_availability_bitfields(&cores, &bitfields);
|
||||
selected_bitfields.sort_by_key(|bitfield| bitfield.validator_index());
|
||||
|
||||
// bitfields not corresponding to occupied cores are not selected
|
||||
assert!(selected_bitfields.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn more_set_bits_win_conflicts() {
|
||||
// Configure filesystem-based keystore as generating keys without seed
|
||||
// would trigger the key to be generated on the filesystem.
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore : SyncCryptoStorePtr = Arc::new(LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore"));
|
||||
let bitvec_zero = default_bitvec();
|
||||
let bitvec_one = {
|
||||
let mut bitvec = bitvec_zero.clone();
|
||||
bitvec.set(0, true);
|
||||
bitvec
|
||||
};
|
||||
|
||||
let cores = vec![occupied_core(0)];
|
||||
|
||||
let bitfields = vec![
|
||||
block_on(signed_bitfield(&keystore, bitvec_zero, 0)),
|
||||
block_on(signed_bitfield(&keystore, bitvec_one.clone(), 0)),
|
||||
];
|
||||
|
||||
// this test is probablistic: chances are excellent that it does what it claims to.
|
||||
// it cannot fail unless things are broken.
|
||||
// however, there is a (very small) chance that it passes when things are broken.
|
||||
for _ in 0..64 {
|
||||
let selected_bitfields = select_availability_bitfields(&cores, &bitfields);
|
||||
assert_eq!(selected_bitfields.len(), 1);
|
||||
assert_eq!(selected_bitfields[0].payload().0, bitvec_one);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod select_candidates {
|
||||
use futures_timer::Delay;
|
||||
use super::super::*;
|
||||
use super::{build_occupied_core, default_bitvec, occupied_core, scheduled_core};
|
||||
use polkadot_node_subsystem::messages::RuntimeApiRequest::{
|
||||
AvailabilityCores, PersistedValidationData as PersistedValidationDataReq,
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
BlockNumber, CandidateDescriptor, CommittedCandidateReceipt, PersistedValidationData,
|
||||
};
|
||||
use FromJob::{ChainApi, Runtime};
|
||||
|
||||
const BLOCK_UNDER_PRODUCTION: BlockNumber = 128;
|
||||
|
||||
fn test_harness<OverseerFactory, Overseer, TestFactory, Test>(
|
||||
overseer_factory: OverseerFactory,
|
||||
test_factory: TestFactory,
|
||||
) where
|
||||
OverseerFactory: FnOnce(mpsc::Receiver<FromJob>) -> Overseer,
|
||||
Overseer: Future<Output = ()>,
|
||||
TestFactory: FnOnce(mpsc::Sender<FromJob>) -> Test,
|
||||
Test: Future<Output = ()>,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel(64);
|
||||
let overseer = overseer_factory(rx);
|
||||
let test = test_factory(tx);
|
||||
|
||||
futures::pin_mut!(overseer, test);
|
||||
|
||||
futures::executor::block_on(future::select(overseer, test));
|
||||
}
|
||||
|
||||
// For test purposes, we always return this set of availability cores:
|
||||
//
|
||||
// [
|
||||
// 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::Receiver<FromJob>) {
|
||||
use ChainApiMessage::BlockNumber;
|
||||
use RuntimeApiMessage::Request;
|
||||
|
||||
while let Some(from_job) = receiver.next().await {
|
||||
match from_job {
|
||||
ChainApi(BlockNumber(_relay_parent, tx)) => {
|
||||
tx.send(Ok(Some(BLOCK_UNDER_PRODUCTION - 1))).unwrap()
|
||||
}
|
||||
Runtime(Request(
|
||||
_parent_hash,
|
||||
PersistedValidationDataReq(_para_id, _assumption, tx),
|
||||
)) => tx.send(Ok(Some(Default::default()))).unwrap(),
|
||||
Runtime(Request(_parent_hash, AvailabilityCores(tx))) => {
|
||||
tx.send(Ok(mock_availability_cores())).unwrap()
|
||||
}
|
||||
// non-exhaustive matches are fine for testing
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_overseer_failure() {
|
||||
let overseer = |rx: mpsc::Receiver<FromJob>| async move {
|
||||
// drop the receiver so it closes and the sender can't send, then just sleep long enough that
|
||||
// this is almost certainly not the first of the two futures to complete
|
||||
std::mem::drop(rx);
|
||||
Delay::new(std::time::Duration::from_secs(1)).await;
|
||||
};
|
||||
|
||||
let test = |mut tx: mpsc::Sender<FromJob>| async move {
|
||||
// wait so that the overseer can drop the rx before we attempt to send
|
||||
Delay::new(std::time::Duration::from_millis(50)).await;
|
||||
let result = select_candidates(&[], &[], &[], Default::default(), &mut tx).await;
|
||||
println!("{:?}", result);
|
||||
assert!(std::matches!(result, Err(Error::OneshotSend)));
|
||||
};
|
||||
|
||||
test_harness(overseer, test);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_succeed() {
|
||||
test_harness(mock_overseer, |mut tx: mpsc::Sender<FromJob>| async move {
|
||||
let result = select_candidates(&[], &[], &[], Default::default(), &mut tx).await;
|
||||
println!("{:?}", result);
|
||||
assert!(result.is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
// 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 empty_hash = PersistedValidationData::<BlockNumber>::default().hash();
|
||||
|
||||
let candidate_template = BackedCandidate {
|
||||
candidate: CommittedCandidateReceipt {
|
||||
descriptor: CandidateDescriptor {
|
||||
persisted_validation_data_hash: empty_hash,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
validity_votes: Vec::new(),
|
||||
validator_indices: default_bitvec(),
|
||||
};
|
||||
|
||||
let candidates: Vec<_> = std::iter::repeat(candidate_template)
|
||||
.take(mock_cores.len())
|
||||
.enumerate()
|
||||
.map(|(idx, mut candidate)| {
|
||||
candidate.candidate.descriptor.para_id = idx.into();
|
||||
candidate
|
||||
})
|
||||
.cycle()
|
||||
.take(mock_cores.len() * 3)
|
||||
.enumerate()
|
||||
.map(|(idx, mut candidate)| {
|
||||
if idx < mock_cores.len() {
|
||||
// first go-around: use candidates which should work
|
||||
candidate
|
||||
} else if idx < mock_cores.len() * 2 {
|
||||
// for the second repetition of the candidates, give them the wrong hash
|
||||
candidate.candidate.descriptor.persisted_validation_data_hash
|
||||
= Default::default();
|
||||
candidate
|
||||
} else {
|
||||
// third go-around: right hash, wrong para_id
|
||||
candidate.candidate.descriptor.para_id = idx.into();
|
||||
candidate
|
||||
}
|
||||
})
|
||||
.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();
|
||||
|
||||
test_harness(mock_overseer, |mut tx: mpsc::Sender<FromJob>| async move {
|
||||
let result =
|
||||
select_candidates(&mock_cores, &[], &candidates, Default::default(), &mut tx)
|
||||
.await;
|
||||
|
||||
if result.is_err() {
|
||||
println!("{:?}", result);
|
||||
}
|
||||
assert_eq!(result.unwrap(), expected_candidates);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
mod tests;
|
||||
@@ -0,0 +1,377 @@
|
||||
use super::*;
|
||||
use bitvec::bitvec;
|
||||
use polkadot_primitives::v1::{OccupiedCore, ScheduledCore};
|
||||
|
||||
pub fn occupied_core(para_id: u32) -> CoreState {
|
||||
CoreState::Occupied(OccupiedCore {
|
||||
para_id: para_id.into(),
|
||||
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: default_bitvec(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_occupied_core<Builder>(para_id: u32, builder: Builder) -> CoreState
|
||||
where
|
||||
Builder: FnOnce(&mut OccupiedCore),
|
||||
{
|
||||
let mut core = match occupied_core(para_id) {
|
||||
CoreState::Occupied(core) => core,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
builder(&mut core);
|
||||
|
||||
CoreState::Occupied(core)
|
||||
}
|
||||
|
||||
pub fn default_bitvec() -> CoreAvailability {
|
||||
bitvec![bitvec::order::Lsb0, u8; 0; 32]
|
||||
}
|
||||
|
||||
pub fn scheduled_core(id: u32) -> ScheduledCore {
|
||||
ScheduledCore {
|
||||
para_id: id.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
mod select_availability_bitfields {
|
||||
use super::super::*;
|
||||
use super::{default_bitvec, occupied_core};
|
||||
use futures::executor::block_on;
|
||||
use std::sync::Arc;
|
||||
use polkadot_primitives::v1::{SigningContext, ValidatorIndex, ValidatorId};
|
||||
use sp_application_crypto::AppKey;
|
||||
use sp_keystore::{CryptoStore, SyncCryptoStorePtr};
|
||||
use sc_keystore::LocalKeystore;
|
||||
|
||||
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.expect("Should be signed")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_more_than_one_per_validator() {
|
||||
// Configure filesystem-based keystore as generating keys without seed
|
||||
// would trigger the key to be generated on the filesystem.
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore : SyncCryptoStorePtr = Arc::new(LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore"));
|
||||
let bitvec = default_bitvec();
|
||||
|
||||
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(), 0)),
|
||||
block_on(signed_bitfield(&keystore, bitvec.clone(), 1)),
|
||||
block_on(signed_bitfield(&keystore, bitvec, 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() {
|
||||
// Configure filesystem-based keystore as generating keys without seed
|
||||
// would trigger the key to be generated on the filesystem.
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore : SyncCryptoStorePtr = Arc::new(LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore"));
|
||||
let bitvec = default_bitvec();
|
||||
|
||||
let cores = vec![CoreState::Free, CoreState::Scheduled(Default::default())];
|
||||
|
||||
let bitfields = vec![
|
||||
block_on(signed_bitfield(&keystore, bitvec.clone(), 0)),
|
||||
block_on(signed_bitfield(&keystore, bitvec.clone(), 1)),
|
||||
block_on(signed_bitfield(&keystore, bitvec, 1)),
|
||||
];
|
||||
|
||||
let mut selected_bitfields = select_availability_bitfields(&cores, &bitfields);
|
||||
selected_bitfields.sort_by_key(|bitfield| bitfield.validator_index());
|
||||
|
||||
// bitfields not corresponding to occupied cores are not selected
|
||||
assert!(selected_bitfields.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn more_set_bits_win_conflicts() {
|
||||
// Configure filesystem-based keystore as generating keys without seed
|
||||
// would trigger the key to be generated on the filesystem.
|
||||
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
||||
let keystore : SyncCryptoStorePtr = Arc::new(LocalKeystore::open(keystore_path.path(), None)
|
||||
.expect("Creates keystore"));
|
||||
let bitvec_zero = default_bitvec();
|
||||
let bitvec_one = {
|
||||
let mut bitvec = bitvec_zero.clone();
|
||||
bitvec.set(0, true);
|
||||
bitvec
|
||||
};
|
||||
|
||||
let cores = vec![occupied_core(0)];
|
||||
|
||||
let bitfields = vec![
|
||||
block_on(signed_bitfield(&keystore, bitvec_zero, 0)),
|
||||
block_on(signed_bitfield(&keystore, bitvec_one.clone(), 0)),
|
||||
];
|
||||
|
||||
// this test is probablistic: chances are excellent that it does what it claims to.
|
||||
// it cannot fail unless things are broken.
|
||||
// however, there is a (very small) chance that it passes when things are broken.
|
||||
for _ in 0..64 {
|
||||
let selected_bitfields = select_availability_bitfields(&cores, &bitfields);
|
||||
assert_eq!(selected_bitfields.len(), 1);
|
||||
assert_eq!(selected_bitfields[0].payload().0, bitvec_one);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod select_candidates {
|
||||
use futures_timer::Delay;
|
||||
use super::super::*;
|
||||
use super::{build_occupied_core, default_bitvec, occupied_core, scheduled_core};
|
||||
use polkadot_node_subsystem::messages::RuntimeApiRequest::{
|
||||
AvailabilityCores, PersistedValidationData as PersistedValidationDataReq,
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
BlockNumber, CandidateDescriptor, CommittedCandidateReceipt, PersistedValidationData,
|
||||
};
|
||||
use FromJob::{ChainApi, Runtime};
|
||||
|
||||
const BLOCK_UNDER_PRODUCTION: BlockNumber = 128;
|
||||
|
||||
fn test_harness<OverseerFactory, Overseer, TestFactory, Test>(
|
||||
overseer_factory: OverseerFactory,
|
||||
test_factory: TestFactory,
|
||||
) where
|
||||
OverseerFactory: FnOnce(mpsc::Receiver<FromJob>) -> Overseer,
|
||||
Overseer: Future<Output = ()>,
|
||||
TestFactory: FnOnce(mpsc::Sender<FromJob>) -> Test,
|
||||
Test: Future<Output = ()>,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel(64);
|
||||
let overseer = overseer_factory(rx);
|
||||
let test = test_factory(tx);
|
||||
|
||||
futures::pin_mut!(overseer, test);
|
||||
|
||||
let _ = futures::executor::block_on(future::select(overseer, test));
|
||||
}
|
||||
|
||||
// For test purposes, we always return this set of availability cores:
|
||||
//
|
||||
// [
|
||||
// 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::Receiver<FromJob>) {
|
||||
use ChainApiMessage::BlockNumber;
|
||||
use RuntimeApiMessage::Request;
|
||||
|
||||
while let Some(from_job) = receiver.next().await {
|
||||
match from_job {
|
||||
ChainApi(BlockNumber(_relay_parent, tx)) => {
|
||||
tx.send(Ok(Some(BLOCK_UNDER_PRODUCTION - 1))).unwrap()
|
||||
}
|
||||
Runtime(Request(
|
||||
_parent_hash,
|
||||
PersistedValidationDataReq(_para_id, _assumption, tx),
|
||||
)) => tx.send(Ok(Some(Default::default()))).unwrap(),
|
||||
Runtime(Request(_parent_hash, AvailabilityCores(tx))) => {
|
||||
tx.send(Ok(mock_availability_cores())).unwrap()
|
||||
}
|
||||
// non-exhaustive matches are fine for testing
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_overseer_failure() {
|
||||
let overseer = |rx: mpsc::Receiver<FromJob>| async move {
|
||||
// drop the receiver so it closes and the sender can't send, then just sleep long enough that
|
||||
// this is almost certainly not the first of the two futures to complete
|
||||
std::mem::drop(rx);
|
||||
Delay::new(std::time::Duration::from_secs(1)).await;
|
||||
};
|
||||
|
||||
let test = |mut tx: mpsc::Sender<FromJob>| async move {
|
||||
// wait so that the overseer can drop the rx before we attempt to send
|
||||
Delay::new(std::time::Duration::from_millis(50)).await;
|
||||
let result = select_candidates(&[], &[], &[], Default::default(), &mut tx).await;
|
||||
println!("{:?}", result);
|
||||
assert!(std::matches!(result, Err(Error::ChainApiMessageSend(_))));
|
||||
};
|
||||
|
||||
test_harness(overseer, test);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_succeed() {
|
||||
test_harness(mock_overseer, |mut tx: mpsc::Sender<FromJob>| async move {
|
||||
let result = select_candidates(&[], &[], &[], Default::default(), &mut tx).await;
|
||||
println!("{:?}", result);
|
||||
assert!(result.is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
// 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 empty_hash = PersistedValidationData::<BlockNumber>::default().hash();
|
||||
|
||||
let candidate_template = BackedCandidate {
|
||||
candidate: CommittedCandidateReceipt {
|
||||
descriptor: CandidateDescriptor {
|
||||
persisted_validation_data_hash: empty_hash,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
validity_votes: Vec::new(),
|
||||
validator_indices: default_bitvec(),
|
||||
};
|
||||
|
||||
let candidates: Vec<_> = std::iter::repeat(candidate_template)
|
||||
.take(mock_cores.len())
|
||||
.enumerate()
|
||||
.map(|(idx, mut candidate)| {
|
||||
candidate.candidate.descriptor.para_id = idx.into();
|
||||
candidate
|
||||
})
|
||||
.cycle()
|
||||
.take(mock_cores.len() * 3)
|
||||
.enumerate()
|
||||
.map(|(idx, mut candidate)| {
|
||||
if idx < mock_cores.len() {
|
||||
// first go-around: use candidates which should work
|
||||
candidate
|
||||
} else if idx < mock_cores.len() * 2 {
|
||||
// for the second repetition of the candidates, give them the wrong hash
|
||||
candidate.candidate.descriptor.persisted_validation_data_hash
|
||||
= Default::default();
|
||||
candidate
|
||||
} else {
|
||||
// third go-around: right hash, wrong para_id
|
||||
candidate.candidate.descriptor.para_id = idx.into();
|
||||
candidate
|
||||
}
|
||||
})
|
||||
.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();
|
||||
|
||||
test_harness(mock_overseer, |mut tx: mpsc::Sender<FromJob>| async move {
|
||||
let result =
|
||||
select_candidates(&mock_cores, &[], &candidates, Default::default(), &mut tx)
|
||||
.await;
|
||||
|
||||
if result.is_err() {
|
||||
println!("{:?}", result);
|
||||
}
|
||||
assert_eq!(result.unwrap(), expected_candidates);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,8 @@ edition = "2018"
|
||||
[dependencies]
|
||||
futures = "0.3.5"
|
||||
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-blockchain = { 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" }
|
||||
polkadot-node-subsystem-util = { path = "../../subsystem-util" }
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
//! 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.
|
||||
|
||||
#![deny(unused_crate_dependencies)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use polkadot_subsystem::{
|
||||
Subsystem, SpawnedSubsystem, SubsystemResult, SubsystemContext,
|
||||
FromOverseer, OverseerSignal,
|
||||
|
||||
Reference in New Issue
Block a user