CandidateBackingSubsystem (#1312)

* Updates guide for CandidateBacking

* Move assignment types to primitives

* Initial implementation.

* More functionality

* use assert_matches

* Changes to report misbehaviors

* Some fixes after a review

* Remove a blank line

* Update guide and some types

* Adds run_job function

* Some comments and refactorings

* Fix review

* Remove warnings

* Use summary in kicking off validation

* Parallelize requests

* Validation provides local and global validation params

* Test issued validity tracking

* Nits from review
This commit is contained in:
Fedor Sakharov
2020-07-09 23:23:58 +03:00
committed by GitHub
parent 54bace2b5d
commit c119627835
14 changed files with 2638 additions and 453 deletions
+464 -312
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -50,6 +50,7 @@ members = [
"node/overseer", "node/overseer",
"node/primitives", "node/primitives",
"node/service", "node/service",
"node/core/backing",
"node/subsystem", "node/subsystem",
"node/test-helpers/subsystem", "node/test-helpers/subsystem",
"node/test-service", "node/test-service",
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "polkadot-node-core-backing"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
futures = "0.3.5"
log = "0.4.8"
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
keystore = { package = "sc-keystore", git = "https://github.com/paritytech/substrate", branch = "master" }
primitives = { package = "sp-core", git = "https://github.com/paritytech/substrate", branch = "master" }
polkadot-primitives = { path = "../../../primitives" }
polkadot-node-primitives = { path = "../../primitives" }
polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsystem" }
erasure-coding = { package = "polkadot-erasure-coding", path = "../../../erasure-coding" }
statement-table = { package = "polkadot-statement-table", path = "../../../statement-table" }
futures-timer = "3.0.2"
streamunordered = "0.5.1"
derive_more = "0.99.9"
bitvec = { version = "0.17.4", default-features = false, features = ["alloc"] }
[dev-dependencies]
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
futures = { version = "0.3.5", features = ["thread-pool"] }
subsystem-test = { package = "polkadot-subsystem-test-helpers", path = "../../test-helpers/subsystem" }
assert_matches = "1.3.0"
File diff suppressed because it is too large Load Diff
@@ -59,6 +59,7 @@ impl Subsystem1 {
ctx.send_message(AllMessages::CandidateValidation( ctx.send_message(AllMessages::CandidateValidation(
CandidateValidationMessage::Validate( CandidateValidationMessage::Validate(
Default::default(),
Default::default(), Default::default(),
Default::default(), Default::default(),
PoVBlock { PoVBlock {
+1
View File
@@ -726,6 +726,7 @@ mod tests {
ctx.send_message( ctx.send_message(
AllMessages::CandidateValidation( AllMessages::CandidateValidation(
CandidateValidationMessage::Validate( CandidateValidationMessage::Validate(
Default::default(),
Default::default(), Default::default(),
Default::default(), Default::default(),
PoVBlock { PoVBlock {
+129 -5
View File
@@ -23,10 +23,17 @@
use parity_scale_codec::{Decode, Encode}; use parity_scale_codec::{Decode, Encode};
use polkadot_primitives::{Hash, use polkadot_primitives::{Hash,
parachain::{ parachain::{
AbridgedCandidateReceipt, CandidateReceipt, CompactStatement, AbridgedCandidateReceipt, CompactStatement,
EncodeAs, Signed, EncodeAs, Signed, SigningContext, ValidatorIndex, ValidatorId,
} }
}; };
use polkadot_statement_table::{
generic::{
ValidityDoubleVote as TableValidityDoubleVote,
MultipleCandidates as TableMultipleCandidates,
},
Misbehavior as TableMisbehavior,
};
/// A statement, where the candidate receipt is included in the `Seconded` variant. /// A statement, where the candidate receipt is included in the `Seconded` variant.
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
@@ -77,11 +84,128 @@ pub enum MisbehaviorReport {
/// this message should be dispatched with all of them, in arbitrary order. /// this message should be dispatched with all of them, in arbitrary order.
/// ///
/// This variant is also used when our own validity checks disagree with others'. /// This variant is also used when our own validity checks disagree with others'.
CandidateValidityDisagreement(CandidateReceipt, Vec<SignedFullStatement>), CandidateValidityDisagreement(AbridgedCandidateReceipt, Vec<SignedFullStatement>),
/// I've noticed a peer contradicting itself about a particular candidate /// I've noticed a peer contradicting itself about a particular candidate
SelfContradiction(CandidateReceipt, SignedFullStatement, SignedFullStatement), SelfContradiction(AbridgedCandidateReceipt, SignedFullStatement, SignedFullStatement),
/// This peer has seconded more than one parachain candidate for this relay parent head /// This peer has seconded more than one parachain candidate for this relay parent head
DoubleVote(CandidateReceipt, SignedFullStatement, SignedFullStatement), DoubleVote(SignedFullStatement, SignedFullStatement),
}
/// A utility struct used to convert `TableMisbehavior` to `MisbehaviorReport`s.
pub struct FromTableMisbehavior {
/// Index of the validator.
pub id: ValidatorIndex,
/// The misbehavior reported by the table.
pub report: TableMisbehavior,
/// Signing context.
pub signing_context: SigningContext,
/// Misbehaving validator's public key.
pub key: ValidatorId,
}
/// Result of the validation of the candidate.
#[derive(Debug)]
pub enum ValidationResult {
/// Candidate is valid.
Valid,
/// Candidate is invalid.
Invalid,
}
impl std::convert::TryFrom<FromTableMisbehavior> for MisbehaviorReport {
type Error = ();
fn try_from(f: FromTableMisbehavior) -> Result<Self, Self::Error> {
match f.report {
TableMisbehavior::ValidityDoubleVote(
TableValidityDoubleVote::IssuedAndValidity((c, s1), (d, s2))
) => {
let receipt = c.clone();
let signed_1 = SignedFullStatement::new(
Statement::Seconded(c),
f.id,
s1,
&f.signing_context,
&f.key,
).ok_or(())?;
let signed_2 = SignedFullStatement::new(
Statement::Valid(d),
f.id,
s2,
&f.signing_context,
&f.key,
).ok_or(())?;
Ok(MisbehaviorReport::SelfContradiction(receipt, signed_1, signed_2))
}
TableMisbehavior::ValidityDoubleVote(
TableValidityDoubleVote::IssuedAndInvalidity((c, s1), (d, s2))
) => {
let receipt = c.clone();
let signed_1 = SignedFullStatement::new(
Statement::Seconded(c),
f.id,
s1,
&f.signing_context,
&f.key,
).ok_or(())?;
let signed_2 = SignedFullStatement::new(
Statement::Invalid(d),
f.id,
s2,
&f.signing_context,
&f.key,
).ok_or(())?;
Ok(MisbehaviorReport::SelfContradiction(receipt, signed_1, signed_2))
}
TableMisbehavior::ValidityDoubleVote(
TableValidityDoubleVote::ValidityAndInvalidity(c, s1, s2)
) => {
let signed_1 = SignedFullStatement::new(
Statement::Valid(c.hash()),
f.id,
s1,
&f.signing_context,
&f.key,
).ok_or(())?;
let signed_2 = SignedFullStatement::new(
Statement::Invalid(c.hash()),
f.id,
s2,
&f.signing_context,
&f.key,
).ok_or(())?;
Ok(MisbehaviorReport::SelfContradiction(c, signed_1, signed_2))
}
TableMisbehavior::MultipleCandidates(
TableMultipleCandidates {
first,
second,
}
) => {
let signed_1 = SignedFullStatement::new(
Statement::Seconded(first.0),
f.id,
first.1,
&f.signing_context,
&f.key,
).ok_or(())?;
let signed_2 = SignedFullStatement::new(
Statement::Seconded(second.0),
f.id,
second.1,
&f.signing_context,
&f.key,
).ok_or(())?;
Ok(MisbehaviorReport::DoubleVote(signed_1, signed_2))
}
_ => Err(()),
}
}
} }
/// A unique identifier for a network protocol. /// A unique identifier for a network protocol.
+29 -7
View File
@@ -28,10 +28,11 @@ use polkadot_primitives::{BlockNumber, Hash, Signature};
use polkadot_primitives::parachain::{ use polkadot_primitives::parachain::{
AbridgedCandidateReceipt, PoVBlock, ErasureChunk, BackedCandidate, Id as ParaId, AbridgedCandidateReceipt, PoVBlock, ErasureChunk, BackedCandidate, Id as ParaId,
SignedAvailabilityBitfield, SigningContext, ValidatorId, ValidationCode, ValidatorIndex, SignedAvailabilityBitfield, SigningContext, ValidatorId, ValidationCode, ValidatorIndex,
CandidateDescriptor, CoreAssignment, CoreOccupied, HeadData, CandidateDescriptor, GlobalValidationSchedule,
LocalValidationData,
}; };
use polkadot_node_primitives::{ use polkadot_node_primitives::{
MisbehaviorReport, SignedFullStatement, View, ProtocolId, MisbehaviorReport, SignedFullStatement, View, ProtocolId, ValidationResult,
}; };
use std::sync::Arc; use std::sync::Arc;
@@ -53,12 +54,12 @@ pub enum CandidateSelectionMessage {
/// Messages received by the Candidate Backing subsystem. /// Messages received by the Candidate Backing subsystem.
#[derive(Debug)] #[derive(Debug)]
pub enum CandidateBackingMessage { pub enum CandidateBackingMessage {
/// Registers a stream listener for updates to the set of backable candidates that could be backed /// Requests a set of backable candidates that could be backed in a child of the given
/// in a child of the given relay-parent, referenced by its hash. /// relay-parent, referenced by its hash.
RegisterBackingWatcher(Hash, mpsc::Sender<NewBackedCandidate>), GetBackedCandidates(Hash, oneshot::Sender<Vec<NewBackedCandidate>>),
/// Note that the Candidate Backing subsystem should second the given candidate in the context of the /// Note that the Candidate Backing subsystem should second the given candidate in the context of the
/// given relay-parent (ref. by hash). This candidate must be validated. /// given relay-parent (ref. by hash). This candidate must be validated.
Second(Hash, AbridgedCandidateReceipt), Second(Hash, AbridgedCandidateReceipt, PoVBlock),
/// Note a validator's statement about a particular candidate. Disagreements about validity must be escalated /// Note a validator's statement about a particular candidate. Disagreements about validity must be escalated
/// to a broader check by Misbehavior Arbitration. Agreements are simply tallied until a quorum is reached. /// to a broader check by Misbehavior Arbitration. Agreements are simply tallied until a quorum is reached.
Statement(Hash, SignedFullStatement), Statement(Hash, SignedFullStatement),
@@ -78,8 +79,12 @@ pub enum CandidateValidationMessage {
Validate( Validate(
Hash, Hash,
AbridgedCandidateReceipt, AbridgedCandidateReceipt,
HeadData,
PoVBlock, PoVBlock,
oneshot::Sender<Result<(), ValidationFailed>>, oneshot::Sender<Result<
(ValidationResult, GlobalValidationSchedule, LocalValidationData),
ValidationFailed,
>>,
), ),
} }
@@ -151,17 +156,34 @@ pub enum AvailabilityStoreMessage {
StoreChunk(Hash, ValidatorIndex, ErasureChunk), StoreChunk(Hash, ValidatorIndex, ErasureChunk),
} }
/// The information on scheduler assignments that some somesystems may be querying.
#[derive(Debug, Clone)]
pub struct SchedulerRoster {
/// Validator-to-groups assignments.
pub validator_groups: Vec<Vec<ValidatorIndex>>,
/// All scheduled paras.
pub scheduled: Vec<CoreAssignment>,
/// Upcoming paras (chains and threads).
pub upcoming: Vec<ParaId>,
/// Occupied cores.
pub availability_cores: Vec<Option<CoreOccupied>>,
}
/// A request to the Runtime API subsystem. /// A request to the Runtime API subsystem.
#[derive(Debug)] #[derive(Debug)]
pub enum RuntimeApiRequest { pub enum RuntimeApiRequest {
/// Get the current validator set. /// Get the current validator set.
Validators(oneshot::Sender<Vec<ValidatorId>>), Validators(oneshot::Sender<Vec<ValidatorId>>),
/// Get the assignments of validators to cores.
ValidatorGroups(oneshot::Sender<SchedulerRoster>),
/// Get a signing context for bitfields and statements. /// Get a signing context for bitfields and statements.
SigningContext(oneshot::Sender<SigningContext>), SigningContext(oneshot::Sender<SigningContext>),
/// Get the validation code for a specific para, assuming execution under given block number, and /// Get the validation code for a specific para, assuming execution under given block number, and
/// an optional block number representing an intermediate parablock executed in the context of /// an optional block number representing an intermediate parablock executed in the context of
/// that block. /// that block.
ValidationCode(ParaId, BlockNumber, Option<BlockNumber>, oneshot::Sender<ValidationCode>), ValidationCode(ParaId, BlockNumber, Option<BlockNumber>, oneshot::Sender<ValidationCode>),
/// Get head data for a specific para.
HeadData(ParaId, oneshot::Sender<HeadData>),
} }
/// A message to the Runtime API subsystem. /// A message to the Runtime API subsystem.
+124 -1
View File
@@ -171,6 +171,100 @@ pub struct DutyRoster {
pub validator_duty: Vec<Chain>, pub validator_duty: Vec<Chain>,
} }
/// The unique (during session) index of a core.
#[derive(Encode, Decode, Default, PartialOrd, Ord, Eq, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct CoreIndex(pub u32);
impl From<u32> for CoreIndex {
fn from(i: u32) -> CoreIndex {
CoreIndex(i)
}
}
/// The unique (during session) index of a validator group.
#[derive(Encode, Decode, Default, Clone, Copy)]
#[cfg_attr(feature = "std", derive(Eq, Hash, PartialEq, Debug))]
pub struct GroupIndex(pub u32);
impl From<u32> for GroupIndex {
fn from(i: u32) -> GroupIndex {
GroupIndex(i)
}
}
/// A claim on authoring the next block for a given parathread.
#[derive(Clone, Encode, Decode, Default)]
#[cfg_attr(feature = "std", derive(PartialEq, Debug))]
pub struct ParathreadClaim(pub Id, pub CollatorId);
/// An entry tracking a claim to ensure it does not pass the maximum number of retries.
#[derive(Clone, Encode, Decode, Default)]
#[cfg_attr(feature = "std", derive(PartialEq, Debug))]
pub struct ParathreadEntry {
/// The claim.
pub claim: ParathreadClaim,
/// Number of retries.
pub retries: u32,
}
/// What is occupying a specific availability core.
#[derive(Clone, Encode, Decode)]
#[cfg_attr(feature = "std", derive(PartialEq, Debug))]
pub enum CoreOccupied {
/// A parathread.
Parathread(ParathreadEntry),
/// A parachain.
Parachain,
}
/// The assignment type.
#[derive(Clone, Encode, Decode)]
#[cfg_attr(feature = "std", derive(PartialEq, Debug))]
pub enum AssignmentKind {
/// A parachain.
Parachain,
/// A parathread.
Parathread(CollatorId, u32),
}
/// How a free core is scheduled to be assigned.
#[derive(Clone, Encode, Decode)]
#[cfg_attr(feature = "std", derive(PartialEq, Debug))]
pub struct CoreAssignment {
/// The core that is assigned.
pub core: CoreIndex,
/// The unique ID of the para that is assigned to the core.
pub para_id: Id,
/// The kind of the assignment.
pub kind: AssignmentKind,
/// The index of the validator group assigned to the core.
pub group_idx: GroupIndex,
}
impl CoreAssignment {
/// Get the ID of a collator who is required to collate this block.
pub fn required_collator(&self) -> Option<&CollatorId> {
match self.kind {
AssignmentKind::Parachain => None,
AssignmentKind::Parathread(ref id, _) => Some(id),
}
}
/// Get the `CoreOccupied` from this.
pub fn to_core_occupied(&self) -> CoreOccupied {
match self.kind {
AssignmentKind::Parachain => CoreOccupied::Parachain,
AssignmentKind::Parathread(ref collator, retries) => CoreOccupied::Parathread(
ParathreadEntry {
claim: ParathreadClaim(self.para_id, collator.clone()),
retries,
}
),
}
}
}
/// Extra data that is needed along with the other fields in a `CandidateReceipt` /// Extra data that is needed along with the other fields in a `CandidateReceipt`
/// to fully validate the candidate. /// to fully validate the candidate.
/// ///
@@ -382,6 +476,15 @@ pub struct AbridgedCandidateReceipt<H = Hash> {
pub commitments: CandidateCommitments<H>, pub commitments: CandidateCommitments<H>,
} }
/// A candidate-receipt with commitments directly included.
pub struct CommitedCandidateReceipt<H = Hash> {
/// The descriptor of the candidae.
pub descriptor: CandidateDescriptor,
/// The commitments of the candidate receipt.
pub commitments: CandidateCommitments<H>
}
impl<H: AsRef<[u8]> + Encode> AbridgedCandidateReceipt<H> { impl<H: AsRef<[u8]> + Encode> AbridgedCandidateReceipt<H> {
/// Check integrity vs. provided block data. /// Check integrity vs. provided block data.
pub fn check_signature(&self) -> Result<(), ()> { pub fn check_signature(&self) -> Result<(), ()> {
@@ -473,7 +576,6 @@ impl AbridgedCandidateReceipt {
} }
} }
impl PartialOrd for AbridgedCandidateReceipt { impl PartialOrd for AbridgedCandidateReceipt {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
@@ -923,6 +1025,27 @@ impl<Payload: EncodeAs<RealPayload>, RealPayload: Encode> Signed<Payload, RealPa
out out
} }
/// Used to create a `Signed` from already existing parts.
#[cfg(feature = "std")]
pub fn new<H: Encode>(
payload: Payload,
validator_index: ValidatorIndex,
signature: ValidatorSignature,
context: &SigningContext<H>,
key: &ValidatorId,
) -> Option<Self> {
let s = Self {
payload,
validator_index,
signature,
real_payload: std::marker::PhantomData,
};
s.check_signature(context, key).ok()?;
Some(s)
}
/// Sign this payload with the given context and key, storing the validator index. /// Sign this payload with the given context and key, storing the validator index.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn sign<H: Encode>( pub fn sign<H: Encode>(
@@ -2,30 +2,43 @@
The Candidate Backing subsystem ensures every parablock considered for relay block inclusion has been seconded by at least one validator, and approved by a quorum. Parablocks for which no validator will assert correctness are discarded. If the block later proves invalid, the initial backers are slashable; this gives polkadot a rational threat model during subsequent stages. The Candidate Backing subsystem ensures every parablock considered for relay block inclusion has been seconded by at least one validator, and approved by a quorum. Parablocks for which no validator will assert correctness are discarded. If the block later proves invalid, the initial backers are slashable; this gives polkadot a rational threat model during subsequent stages.
Its role is to produce backable candidates for inclusion in new relay-chain blocks. It does so by issuing signed [`Statement`s](../../types/backing.md#statement-type) and tracking received statements signed by other validators. Once enough statements are received, they can be combined into backing for specific candidates. Its role is to produce backable candidates for inclusion in new relay-chain blocks. It does so by issuing signed [`Statement`s][Statement] and tracking received statements signed by other validators. Once enough statements are received, they can be combined into backing for specific candidates.
Note that though the candidate backing subsystem attempts to produce as many backable candidates as possible, it does _not_ attempt to choose a single authoritative one. The choice of which actually gets included is ultimately up to the block author, by whatever metrics it may use; those are opaque to this subsystem. Note that though the candidate backing subsystem attempts to produce as many backable candidates as possible, it does _not_ attempt to choose a single authoritative one. The choice of which actually gets included is ultimately up to the block author, by whatever metrics it may use; those are opaque to this subsystem.
Once a sufficient quorum has agreed that a candidate is valid, this subsystem notifies the [Provisioner](../utility/provisioner.md), which in turn engages block production mechanisms to include the parablock. Once a sufficient quorum has agreed that a candidate is valid, this subsystem notifies the [Provisioner][PV], which in turn engages block production mechanisms to include the parablock.
## Protocol ## Protocol
The [Candidate Selection subsystem](candidate-selection.md) is the primary source of non-overseer messages into this subsystem. That subsystem generates appropriate [`CandidateBackingMessage`s](../../types/overseer-protocol.md#candidate-backing-message), and passes them to this subsystem. Input: [`CandidateBackingMessage`][CBM]
This subsystem validates the candidates and generates an appropriate [`SignedStatement`](../../types/backing.md#signed-statement-type). All `SignedStatement`s are then passed on to the [Statement Distribution subsystem](statement-distribution.md) to be gossiped to peers. All [Proofs of Validity](../../types/availability.md#proof-of-validity) should be distributed via the [PoV Distribution](pov-distribution.md) subsystem. When this subsystem decides that a candidate is invalid, and it was recommended to us to second by our own Candidate Selection subsystem, a message is sent to the Candidate Selection subsystem with the candidate's hash so that the collator which recommended it can be penalized. Output:
- [`CandidateValidationMessage`][CVM]
- [`RuntimeApiMessage`][RAM]
- [`CandidateSelectionMessage`][CSM]
- [`ProvisionerMessage`][PM]
- [`PoVDistributionMessage`][PDM]
- [`StatementDistributionMessage`][SDM]
## Functionality ## Functionality
The [Candidate Selection][CS] subsystem is the primary source of non-overseer messages into this subsystem. That subsystem generates appropriate [`CandidateBackingMessage`s][CBM] and passes them to this subsystem.
This subsystem requests validation from the [Candidate Validation][CV] and generates an appropriate [`Statement`][Statement]. All `Statement`s are then passed on to the [Statement Distribution][SD] subsystem to be gossiped to peers. When [Candidate Validation][CV] decides that a candidate is invalid, and it was recommended to us to second by our own [Candidate Selection][CS] subsystem, a message is sent to the [Candidate Selection][CS] subsystem with the candidate's hash so that the collator which recommended it can be penalized.
The subsystem should maintain a set of handles to Candidate Backing Jobs that are currently live, as well as the relay-parent to which they correspond. The subsystem should maintain a set of handles to Candidate Backing Jobs that are currently live, as well as the relay-parent to which they correspond.
### On Overseer Signal ### On Overseer Signal
* If the signal is an [`OverseerSignal`](../../types/overseer-protocol.md#overseer-signal)`::StartWork(relay_parent)`, spawn a Candidate Backing Job with the given relay parent, storing a bidirectional channel with the Candidate Backing Job in the set of handles. * If the signal is an [`OverseerSignal`][OverseerSignal]`::StartWork(relay_parent)`, spawn a Candidate Backing Job with the given relay parent, storing a bidirectional channel with the Candidate Backing Job in the set of handles.
* If the signal is an [`OverseerSignal`](../../types/overseer-protocol.md#overseer-signal)`::StopWork(relay_parent)`, cease the Candidate Backing Job under that relay parent, if any. * If the signal is an [`OverseerSignal`][OverseerSignal]`::StopWork(relay_parent)`, cease the Candidate Backing Job under that relay parent, if any.
### On `CandidateBackingMessage` ### On Receiving `CandidateBackingMessage`
* If the message corresponds to a particular relay-parent, forward the message to the Candidate Backing Job for that relay-parent, if any is live. * If the message is a [`CandidateBackingMessage`][CBM]`::GetBackedCandidates`, get all backable candidates from the statement table and send them back.
* If the message is a [`CandidateBackingMessage`][CBM]`::Second`, sign and dispatch a `Seconded` statement only if we have not seconded any other candidate and have not signed a `Valid` statement for the requested candidate. Signing both a `Seconded` and `Valid` message is a double-voting misbehavior with a heavy penalty, and this could occur if another validator has seconded the same candidate and we've received their message before the internal seconding request.
* If the message is a [`CandidateBackingMessage`][CBM]`::Statement`, count the statement to the quorum. If the statement in the message is `Seconded` and it contains a candidate that belongs to our assignment, request the corresponding `PoV` from the `PoVDistribution` and launch validation. Issue our own `Valid` or `Invalid` statement as a result.
> big TODO: "contextual execution" > big TODO: "contextual execution"
> >
@@ -39,36 +52,49 @@ The subsystem should maintain a set of handles to Candidate Backing Jobs that ar
The Candidate Backing Job represents the work a node does for backing candidates with respect to a particular relay-parent. The Candidate Backing Job represents the work a node does for backing candidates with respect to a particular relay-parent.
The goal of a Candidate Backing Job is to produce as many backable candidates as possible. This is done via signed [`Statement`s](../../types/backing.md#statement-type) by validators. If a candidate receives a majority of supporting Statements from the Parachain Validators currently assigned, then that candidate is considered backable. The goal of a Candidate Backing Job is to produce as many backable candidates as possible. This is done via signed [`Statement`s][STMT] by validators. If a candidate receives a majority of supporting Statements from the Parachain Validators currently assigned, then that candidate is considered backable.
### On Startup ### On Startup
* Fetch current validator set, validator -> parachain assignments from runtime API. * Fetch current validator set, validator -> parachain assignments from [`Runtime API`][RA] subsystem using [`RuntimeApiRequest::Validators`][RAM] and [`RuntimeApiRequest::ValidatorGroups`][RAM]
* Determine if the node controls a key in the current validator set. Call this the local key if so. * Determine if the node controls a key in the current validator set. Call this the local key if so.
* If the local key exists, extract the parachain head and validation function for the parachain the local key is assigned to. * If the local key exists, extract the parachain head and validation function from the [`Runtime API`][RA] for the parachain the local key is assigned to by issuing a [`RuntimeApiRequest::Validators`][RAM]
* Issue a [`RuntimeApiRequest::SigningContext`][RAM] message to get a context that will later be used upon signing.
### On Receiving New Signed Statement ### On Receiving New Candidate Backing Message
```rust ```rust
if let Statement::Seconded(candidate) = signed.statement { match msg {
if candidate is unknown and in local assignment { CetBackedCandidates(hash, tx) => {
spawn_validation_work(candidate, parachain head, validation function) // Send back a set of backable candidates.
}
CandidateBackingMessage::Second(hash, candidate) => {
if candidate is unknown and in local assignment {
spawn_validation_work(candidate, parachain head, validation function)
}
}
CandidateBackingMessage::Statement(hash, statement) => {
// count to the votes on this candidate
if let Statement::Seconded(candidate) = statement {
if candidate.parachain_id == our_assignment {
spawn_validation_work(candidate, parachain head, validation function)
}
}
} }
} }
// add `Seconded` statements and `Valid` statements to a quorum. If quorum reaches validator-group
// majority, send a `BlockAuthorshipProvisioning::BackableCandidate(relay_parent, Candidate, Backing)` message.
``` ```
### Spawning Validation Work Add `Seconded` statements and `Valid` statements to a quorum. If quorum reaches validator-group majority, send a [`ProvisionerMessage`][PM]`::ProvisionableData(ProvisionableData::BackedCandidate(BackedCandidate))` message.
`Invalid` statements that conflict with already witnessed `Seconded` and `Valid` statements for the given candidate, statements that are double-votes, self-contradictions and so on, should result in issuing a [`ProvisionerMessage`][PM]`::MisbehaviorReport` message for each newly detected case of this kind.
### Validating Candidates.
```rust ```rust
fn spawn_validation_work(candidate, parachain head, validation function) { fn spawn_validation_work(candidate, parachain head, validation function) {
asynchronously { asynchronously {
let pov = (fetch pov block).await let pov = (fetch pov block).await
// dispatched to sub-process (OS process) pool. let valid = (validate pov block).await;
let valid = validate_candidate(candidate, validation function, parachain head, pov).await;
if valid { if valid {
// make PoV available for later distribution. Send data to the availability store to keep. // make PoV available for later distribution. Send data to the availability store to keep.
// sign and dispatch `valid` statement to network if we have not seconded the given candidate. // sign and dispatch `valid` statement to network if we have not seconded the given candidate.
@@ -82,11 +108,30 @@ fn spawn_validation_work(candidate, parachain head, validation function) {
### Fetch Pov Block ### Fetch Pov Block
Create a `(sender, receiver)` pair. Create a `(sender, receiver)` pair.
Dispatch a `PovFetchSubsystemMessage(relay_parent, candidate_hash, sender)` and listen on the receiver for a response. Dispatch a [`PoVDistributionMessage`][PDM]`::FecthPoV(relay_parent, candidate_hash, sender)` and listen on the receiver for a response.
### On Receiving `CandidateBackingMessage` ### Validate PoV Block
* If the message is a `CandidateBackingMessage::RegisterBackingWatcher`, register the watcher and trigger it each time a new candidate is backable. Also trigger it once initially if there are any backable candidates at the time of receipt. Create a `(sender, receiver)` pair.
* If the message is a `CandidateBackingMessage::Second`, sign and dispatch a `Seconded` statement only if we have not seconded any other candidate and have not signed a `Valid` statement for the requested candidate. Signing both a `Seconded` and `Valid` message is a double-voting misbehavior with a heavy penalty, and this could occur if another validator has seconded the same candidate and we've received their message before the internal seconding request. Dispatch a `CandidateValidationMessage::Validate(validation function, candidate, pov, sender)` and listen on the receiver for a response.
> TODO: send statements to Statement Distribution subsystem, handle shutdown signal from candidate backing subsystem ### Distribute Signed Statemnet
Dispatch a [`StatementDistributionMessage`][PDM]`::Share(relay_parent, SignedFullStatement)`.
[OverseerSignal]: ../../types/overseer-protocol.md#overseer-signal
[Statement]: ../../types/backing.md#statement-type
[STMT]: ../../types/backing.md#statement-type
[CSM]: ../../types/overseer-protocol.md#candidate-selection-message
[RAM]: ../../types/overseer-protocol.md#runtime-api-message
[CVM]: ../../types/overseer-protocol.md#validation-request-type
[PM]: ../../types/overseer-protocol.md#provisioner-message
[CBM]: ../../types/overseer-protocol.md#candidate-backing-message
[PDM]: ../../types/overseer-protocol.md#pov-distribution-message
[SDM]: ../../types/overseer-protocol.md#statement-distribution-message
[CS]: candidate-selection.md
[CV]: ../utility/candidate-validation.md
[SD]: statement-distribution.md
[RA]: ../utility/runtime-api.md
[PV]: ../utility/provisioner.md
@@ -86,9 +86,9 @@ enum BitfieldSigningMessage { }
```rust ```rust
enum CandidateBackingMessage { enum CandidateBackingMessage {
/// Registers a stream listener for updates to the set of backable candidates that could be backed /// Requests a set of backable candidates that could be backed in a child of the given
/// in a child of the given relay-parent, referenced by its hash. /// relay-parent, referenced by its hash.
RegisterBackingWatcher(Hash, TODO), GetBackedCandidates(Hash, ResponseChannel<Vec<NewBackedCandidate>>),
/// Note that the Candidate Backing subsystem should second the given candidate in the context of the /// Note that the Candidate Backing subsystem should second the given candidate in the context of the
/// given relay-parent (ref. by hash). This candidate must be validated using the provided PoV. /// given relay-parent (ref. by hash). This candidate must be validated using the provided PoV.
Second(Hash, CandidateReceipt, PoV), Second(Hash, CandidateReceipt, PoV),
@@ -230,9 +230,24 @@ The Runtime API subsystem is responsible for providing an interface to the state
Other subsystems query this data by sending these messages. Other subsystems query this data by sending these messages.
```rust ```rust
/// The information on validator groups, core assignments,
/// upcoming paras and availability cores.
struct SchedulerRoster {
/// Validator-to-groups assignments.
validator_groups: Vec<Vec<ValidatorIndex>>,
/// All scheduled paras.
scheduled: Vec<CoreAssignment>,
/// Upcoming paras (chains and threads).
upcoming: Vec<ParaId>,
/// Occupied cores.
availability_cores: Vec<Option<CoreOccupied>>,
}
enum RuntimeApiRequest { enum RuntimeApiRequest {
/// Get the current validator set. /// Get the current validator set.
Validators(ResponseChannel<Vec<ValidatorId>>), Validators(ResponseChannel<Vec<ValidatorId>>),
/// Get the assignments of validators to cores, upcoming parachains.
SchedulerRoster(ResponseChannel<SchedulerRoster>),
/// Get a signing context for bitfields and statements. /// Get a signing context for bitfields and statements.
SigningContext(ResponseChannel<SigningContext>), SigningContext(ResponseChannel<SigningContext>),
/// Get the validation code for a specific para, assuming execution under given block number, and /// Get the validation code for a specific para, assuming execution under given block number, and
@@ -270,10 +285,26 @@ enum StatementDistributionMessage {
Various modules request that the [Candidate Validation subsystem](../node/utility/candidate-validation.md) validate a block with this message Various modules request that the [Candidate Validation subsystem](../node/utility/candidate-validation.md) validate a block with this message
```rust ```rust
/// Result of the validation of the candidate.
enum ValidationResult {
/// Candidate is valid.
Valid,
/// Candidate is invalid.
Invalid,
}
enum CandidateValidationMessage { enum CandidateValidationMessage {
/// Validate a candidate with provided parameters. Returns `Err` if an only if an internal /// Validate a candidate with provided parameters. Returns `Err` if an only if an internal
/// error is encountered. A bad candidate will return `Ok(false)`, while a good one will /// error is encountered.
/// return `Ok(true)`. /// In case no internal error was encontered it returns a tuple containing the result of
Validate(ValidationCode, CandidateReceipt, PoV, ResponseChannel<Result<bool>>), /// validation and `GlobalValidationSchedule` and `LocalValidationData` structures that
/// may be used by the caller to make the candidate available.
/// A bad candidate will return `Ok((ValidationResult::Invalid, _, _)`, while a good one will
/// return `Ok((ValidationResult::Valid, _, _))`.
Validate(
Hash, CandidateReceipt, HeadData, PoV, ResponseChannel<
Result<(ValidationResult, GlobalValidationSchedule, LocalValidationData)>
>),
} }
``` ```
+3 -4
View File
@@ -25,7 +25,7 @@ use primitives::{
parachain::{ parachain::{
ValidatorId, AbridgedCandidateReceipt, ValidatorIndex, Id as ParaId, ValidatorId, AbridgedCandidateReceipt, ValidatorIndex, Id as ParaId,
AvailabilityBitfield as AvailabilityBitfield, SignedAvailabilityBitfields, SigningContext, AvailabilityBitfield as AvailabilityBitfield, SignedAvailabilityBitfields, SigningContext,
BackedCandidate, BackedCandidate, CoreIndex, GroupIndex, CoreAssignment,
}, },
}; };
use frame_support::{ use frame_support::{
@@ -38,7 +38,7 @@ use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
use sp_staking::SessionIndex; use sp_staking::SessionIndex;
use sp_runtime::{DispatchError, traits::{One, Saturating}}; use sp_runtime::{DispatchError, traits::{One, Saturating}};
use crate::{configuration, paras, scheduler::{CoreIndex, GroupIndex, CoreAssignment}}; use crate::{configuration, paras};
/// A bitfield signed by a validator indicating that it is keeping its piece of the erasure-coding /// A bitfield signed by a validator indicating that it is keeping its piece of the erasure-coding
/// for any backed candidates referred to by a `1` bit available. /// for any backed candidates referred to by a `1` bit available.
@@ -498,7 +498,7 @@ mod tests {
use primitives::{BlockNumber, Hash}; use primitives::{BlockNumber, Hash};
use primitives::parachain::{ use primitives::parachain::{
SignedAvailabilityBitfield, CompactStatement as Statement, ValidityAttestation, CollatorId, SignedAvailabilityBitfield, CompactStatement as Statement, ValidityAttestation, CollatorId,
CandidateCommitments, SignedStatement, CandidateCommitments, SignedStatement, AssignmentKind,
}; };
use frame_support::traits::{OnFinalize, OnInitialize}; use frame_support::traits::{OnFinalize, OnInitialize};
use keyring::Sr25519Keyring; use keyring::Sr25519Keyring;
@@ -510,7 +510,6 @@ mod tests {
use crate::initializer::SessionChangeNotification; use crate::initializer::SessionChangeNotification;
use crate::configuration::HostConfiguration; use crate::configuration::HostConfiguration;
use crate::paras::ParaGenesisArgs; use crate::paras::ParaGenesisArgs;
use crate::scheduler::AssignmentKind;
fn default_config() -> HostConfiguration<BlockNumber> { fn default_config() -> HostConfiguration<BlockNumber> {
let mut config = HostConfiguration::default(); let mut config = HostConfiguration::default();
+5 -89
View File
@@ -38,7 +38,10 @@
use sp_std::prelude::*; use sp_std::prelude::*;
use sp_std::convert::TryInto; use sp_std::convert::TryInto;
use primitives::{ use primitives::{
parachain::{Id as ParaId, CollatorId, ValidatorIndex}, parachain::{
Id as ParaId, ValidatorIndex, CoreAssignment, CoreOccupied, CoreIndex, AssignmentKind,
GroupIndex, ParathreadClaim, ParathreadEntry,
},
}; };
use frame_support::{ use frame_support::{
decl_storage, decl_module, decl_error, decl_storage, decl_module, decl_error,
@@ -52,41 +55,6 @@ use rand_chacha::ChaCha20Rng;
use crate::{configuration, paras, initializer::SessionChangeNotification}; use crate::{configuration, paras, initializer::SessionChangeNotification};
/// The unique (during session) index of a core.
#[derive(Encode, Decode, Default, PartialOrd, Ord, Eq, PartialEq, Clone, Copy)]
#[cfg_attr(test, derive(Debug))]
pub struct CoreIndex(u32);
impl From<u32> for CoreIndex {
fn from(i: u32) -> CoreIndex {
CoreIndex(i)
}
}
/// The unique (during session) index of a validator group.
#[derive(Encode, Decode, Default, Clone, Copy)]
#[cfg_attr(test, derive(PartialEq, Debug))]
pub struct GroupIndex(u32);
impl From<u32> for GroupIndex {
fn from(i: u32) -> GroupIndex {
GroupIndex(i)
}
}
/// A claim on authoring the next block for a given parathread.
#[derive(Clone, Encode, Decode, Default)]
#[cfg_attr(test, derive(PartialEq, Debug))]
pub struct ParathreadClaim(pub ParaId, pub CollatorId);
/// An entry tracking a claim to ensure it does not pass the maximum number of retries.
#[derive(Clone, Encode, Decode, Default)]
#[cfg_attr(test, derive(PartialEq, Debug))]
pub struct ParathreadEntry {
claim: ParathreadClaim,
retries: u32,
}
/// A queued parathread entry, pre-assigned to a core. /// A queued parathread entry, pre-assigned to a core.
#[derive(Encode, Decode, Default)] #[derive(Encode, Decode, Default)]
#[cfg_attr(test, derive(PartialEq, Debug))] #[cfg_attr(test, derive(PartialEq, Debug))]
@@ -125,58 +93,6 @@ impl ParathreadClaimQueue {
} }
} }
/// What is occupying a specific availability core.
#[derive(Clone, Encode, Decode)]
#[cfg_attr(test, derive(PartialEq, Debug))]
pub(crate) enum CoreOccupied {
Parathread(ParathreadEntry),
Parachain,
}
/// The assignment type.
#[derive(Clone, Encode, Decode)]
#[cfg_attr(test, derive(PartialEq, Debug))]
pub enum AssignmentKind {
Parachain,
Parathread(CollatorId, u32),
}
/// How a free core is scheduled to be assigned.
#[derive(Clone, Encode, Decode)]
#[cfg_attr(test, derive(PartialEq, Debug))]
pub struct CoreAssignment {
/// The core that is assigned.
pub core: CoreIndex,
/// The unique ID of the para that is assigned to the core.
pub para_id: ParaId,
/// The kind of the assignment.
pub kind: AssignmentKind,
/// The index of the validator group assigned to the core.
pub group_idx: GroupIndex,
}
impl CoreAssignment {
/// Get the ID of a collator who is required to collate this block.
pub(crate) fn required_collator(&self) -> Option<&CollatorId> {
match self.kind {
AssignmentKind::Parachain => None,
AssignmentKind::Parathread(ref id, _) => Some(id),
}
}
fn to_core_occupied(&self) -> CoreOccupied {
match self.kind {
AssignmentKind::Parachain => CoreOccupied::Parachain,
AssignmentKind::Parathread(ref collator, retries) => CoreOccupied::Parathread(
ParathreadEntry {
claim: ParathreadClaim(self.para_id, collator.clone()),
retries,
}
),
}
}
}
/// Reasons a core might be freed /// Reasons a core might be freed
pub enum FreedReason { pub enum FreedReason {
/// The core's work concluded and the parablock assigned to it is considered available. /// The core's work concluded and the parablock assigned to it is considered available.
@@ -670,7 +586,7 @@ impl<T: Trait> Module<T> {
mod tests { mod tests {
use super::*; use super::*;
use primitives::{BlockNumber, parachain::ValidatorId}; use primitives::{BlockNumber, parachain::{CollatorId, ValidatorId}};
use frame_support::traits::{OnFinalize, OnInitialize}; use frame_support::traits::{OnFinalize, OnInitialize};
use keyring::Sr25519Keyring; use keyring::Sr25519Keyring;
+14 -3
View File
@@ -28,6 +28,8 @@ use std::collections::hash_map::{HashMap, Entry};
use std::hash::Hash; use std::hash::Hash;
use std::fmt::Debug; use std::fmt::Debug;
use primitives::parachain::{ValidityAttestation as PrimitiveValidityAttestation, ValidatorSignature};
use codec::{Encode, Decode}; use codec::{Encode, Decode};
/// Context for the statement table. /// Context for the statement table.
@@ -98,7 +100,7 @@ pub enum ValidityDoubleVote<C, D, S> {
/// Implicit vote by issuing and explicitly voting invalidity /// Implicit vote by issuing and explicitly voting invalidity
IssuedAndInvalidity((C, S), (D, S)), IssuedAndInvalidity((C, S), (D, S)),
/// Direct votes for validity and invalidity /// Direct votes for validity and invalidity
ValidityAndInvalidity(D, S, S), ValidityAndInvalidity(C, S, S),
} }
/// Misbehavior: multiple signatures on same statement. /// Misbehavior: multiple signatures on same statement.
@@ -180,6 +182,15 @@ pub enum ValidityAttestation<S> {
Explicit(S), Explicit(S),
} }
impl Into<PrimitiveValidityAttestation> for ValidityAttestation<ValidatorSignature> {
fn into(self) -> PrimitiveValidityAttestation {
match self {
Self::Implicit(s) => PrimitiveValidityAttestation::Implicit(s),
Self::Explicit(s) => PrimitiveValidityAttestation::Explicit(s),
}
}
}
/// An attested-to candidate. /// An attested-to candidate.
#[derive(Clone, PartialEq, Decode, Encode)] #[derive(Clone, PartialEq, Decode, Encode)]
pub struct AttestedCandidate<Group, Candidate, AuthorityId, Signature> { pub struct AttestedCandidate<Group, Candidate, AuthorityId, Signature> {
@@ -550,7 +561,7 @@ impl<C: Context> Table<C> {
// valid vote conflicting with invalid vote // valid vote conflicting with invalid vote
(ValidityVote::Valid(good), ValidityVote::Invalid(bad)) | (ValidityVote::Valid(good), ValidityVote::Invalid(bad)) |
(ValidityVote::Invalid(bad), ValidityVote::Valid(good)) => (ValidityVote::Invalid(bad), ValidityVote::Valid(good)) =>
make_vdv(ValidityDoubleVote::ValidityAndInvalidity(digest, good, bad)), make_vdv(ValidityDoubleVote::ValidityAndInvalidity(votes.candidate.clone(), good, bad)),
// two signatures on same candidate // two signatures on same candidate
(ValidityVote::Issued(a), ValidityVote::Issued(b)) => (ValidityVote::Issued(a), ValidityVote::Issued(b)) =>
@@ -817,7 +828,7 @@ mod tests {
assert_eq!( assert_eq!(
table.detected_misbehavior.get(&AuthorityId(2)).unwrap(), table.detected_misbehavior.get(&AuthorityId(2)).unwrap(),
&Misbehavior::ValidityDoubleVote(ValidityDoubleVote::ValidityAndInvalidity( &Misbehavior::ValidityDoubleVote(ValidityDoubleVote::ValidityAndInvalidity(
candidate_digest, Candidate(2, 100),
Signature(2), Signature(2),
Signature(2), Signature(2),
)) ))