mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 03:31:05 +00:00
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:
Generated
+464
-312
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,7 @@ members = [
|
||||
"node/overseer",
|
||||
"node/primitives",
|
||||
"node/service",
|
||||
"node/core/backing",
|
||||
"node/subsystem",
|
||||
"node/test-helpers/subsystem",
|
||||
"node/test-service",
|
||||
|
||||
@@ -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(
|
||||
CandidateValidationMessage::Validate(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
PoVBlock {
|
||||
|
||||
@@ -726,6 +726,7 @@ mod tests {
|
||||
ctx.send_message(
|
||||
AllMessages::CandidateValidation(
|
||||
CandidateValidationMessage::Validate(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
PoVBlock {
|
||||
|
||||
@@ -23,10 +23,17 @@
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use polkadot_primitives::{Hash,
|
||||
parachain::{
|
||||
AbridgedCandidateReceipt, CandidateReceipt, CompactStatement,
|
||||
EncodeAs, Signed,
|
||||
AbridgedCandidateReceipt, CompactStatement,
|
||||
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.
|
||||
#[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 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
|
||||
SelfContradiction(CandidateReceipt, SignedFullStatement, SignedFullStatement),
|
||||
SelfContradiction(AbridgedCandidateReceipt, SignedFullStatement, SignedFullStatement),
|
||||
/// 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.
|
||||
|
||||
@@ -28,10 +28,11 @@ use polkadot_primitives::{BlockNumber, Hash, Signature};
|
||||
use polkadot_primitives::parachain::{
|
||||
AbridgedCandidateReceipt, PoVBlock, ErasureChunk, BackedCandidate, Id as ParaId,
|
||||
SignedAvailabilityBitfield, SigningContext, ValidatorId, ValidationCode, ValidatorIndex,
|
||||
CandidateDescriptor,
|
||||
CoreAssignment, CoreOccupied, HeadData, CandidateDescriptor, GlobalValidationSchedule,
|
||||
LocalValidationData,
|
||||
};
|
||||
use polkadot_node_primitives::{
|
||||
MisbehaviorReport, SignedFullStatement, View, ProtocolId,
|
||||
MisbehaviorReport, SignedFullStatement, View, ProtocolId, ValidationResult,
|
||||
};
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -53,12 +54,12 @@ pub enum CandidateSelectionMessage {
|
||||
/// Messages received by the Candidate Backing subsystem.
|
||||
#[derive(Debug)]
|
||||
pub enum CandidateBackingMessage {
|
||||
/// Registers a stream listener for updates to the set of backable candidates that could be backed
|
||||
/// in a child of the given relay-parent, referenced by its hash.
|
||||
RegisterBackingWatcher(Hash, mpsc::Sender<NewBackedCandidate>),
|
||||
/// Requests a set of backable candidates that could be backed in a child of the given
|
||||
/// relay-parent, referenced by its hash.
|
||||
GetBackedCandidates(Hash, oneshot::Sender<Vec<NewBackedCandidate>>),
|
||||
/// 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.
|
||||
Second(Hash, AbridgedCandidateReceipt),
|
||||
Second(Hash, AbridgedCandidateReceipt, PoVBlock),
|
||||
/// 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.
|
||||
Statement(Hash, SignedFullStatement),
|
||||
@@ -78,8 +79,12 @@ pub enum CandidateValidationMessage {
|
||||
Validate(
|
||||
Hash,
|
||||
AbridgedCandidateReceipt,
|
||||
HeadData,
|
||||
PoVBlock,
|
||||
oneshot::Sender<Result<(), ValidationFailed>>,
|
||||
oneshot::Sender<Result<
|
||||
(ValidationResult, GlobalValidationSchedule, LocalValidationData),
|
||||
ValidationFailed,
|
||||
>>,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -151,17 +156,34 @@ pub enum AvailabilityStoreMessage {
|
||||
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.
|
||||
#[derive(Debug)]
|
||||
pub enum RuntimeApiRequest {
|
||||
/// Get the current validator set.
|
||||
Validators(oneshot::Sender<Vec<ValidatorId>>),
|
||||
/// Get the assignments of validators to cores.
|
||||
ValidatorGroups(oneshot::Sender<SchedulerRoster>),
|
||||
/// Get a signing context for bitfields and statements.
|
||||
SigningContext(oneshot::Sender<SigningContext>),
|
||||
/// 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
|
||||
/// that block.
|
||||
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.
|
||||
|
||||
@@ -171,6 +171,100 @@ pub struct DutyRoster {
|
||||
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`
|
||||
/// to fully validate the candidate.
|
||||
///
|
||||
@@ -382,6 +476,15 @@ pub struct AbridgedCandidateReceipt<H = Hash> {
|
||||
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> {
|
||||
/// Check integrity vs. provided block data.
|
||||
pub fn check_signature(&self) -> Result<(), ()> {
|
||||
@@ -473,7 +576,6 @@ impl AbridgedCandidateReceipt {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl PartialOrd for AbridgedCandidateReceipt {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
@@ -923,6 +1025,27 @@ impl<Payload: EncodeAs<RealPayload>, RealPayload: Encode> Signed<Payload, RealPa
|
||||
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.
|
||||
#[cfg(feature = "std")]
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
### 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`](../../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]`::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]`::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"
|
||||
>
|
||||
@@ -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 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
|
||||
|
||||
* 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.
|
||||
* 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
|
||||
if let Statement::Seconded(candidate) = signed.statement {
|
||||
match msg {
|
||||
CetBackedCandidates(hash, tx) => {
|
||||
// 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
|
||||
fn spawn_validation_work(candidate, parachain head, validation function) {
|
||||
asynchronously {
|
||||
let pov = (fetch pov block).await
|
||||
|
||||
// dispatched to sub-process (OS process) pool.
|
||||
let valid = validate_candidate(candidate, validation function, parachain head, pov).await;
|
||||
let valid = (validate pov block).await;
|
||||
if valid {
|
||||
// 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.
|
||||
@@ -82,11 +108,30 @@ fn spawn_validation_work(candidate, parachain head, validation function) {
|
||||
### Fetch Pov Block
|
||||
|
||||
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.
|
||||
* 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.
|
||||
Create a `(sender, receiver)` pair.
|
||||
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
|
||||
enum CandidateBackingMessage {
|
||||
/// Registers a stream listener for updates to the set of backable candidates that could be backed
|
||||
/// in a child of the given relay-parent, referenced by its hash.
|
||||
RegisterBackingWatcher(Hash, TODO),
|
||||
/// Requests a set of backable candidates that could be backed in a child of the given
|
||||
/// relay-parent, referenced by its hash.
|
||||
GetBackedCandidates(Hash, ResponseChannel<Vec<NewBackedCandidate>>),
|
||||
/// 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.
|
||||
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.
|
||||
|
||||
```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 {
|
||||
/// Get the current validator set.
|
||||
Validators(ResponseChannel<Vec<ValidatorId>>),
|
||||
/// Get the assignments of validators to cores, upcoming parachains.
|
||||
SchedulerRoster(ResponseChannel<SchedulerRoster>),
|
||||
/// Get a signing context for bitfields and statements.
|
||||
SigningContext(ResponseChannel<SigningContext>),
|
||||
/// 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
|
||||
|
||||
```rust
|
||||
|
||||
/// Result of the validation of the candidate.
|
||||
enum ValidationResult {
|
||||
/// Candidate is valid.
|
||||
Valid,
|
||||
/// Candidate is invalid.
|
||||
Invalid,
|
||||
}
|
||||
|
||||
enum CandidateValidationMessage {
|
||||
/// 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
|
||||
/// return `Ok(true)`.
|
||||
Validate(ValidationCode, CandidateReceipt, PoV, ResponseChannel<Result<bool>>),
|
||||
/// error is encountered.
|
||||
/// In case no internal error was encontered it returns a tuple containing the result of
|
||||
/// 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)>
|
||||
>),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -25,7 +25,7 @@ use primitives::{
|
||||
parachain::{
|
||||
ValidatorId, AbridgedCandidateReceipt, ValidatorIndex, Id as ParaId,
|
||||
AvailabilityBitfield as AvailabilityBitfield, SignedAvailabilityBitfields, SigningContext,
|
||||
BackedCandidate,
|
||||
BackedCandidate, CoreIndex, GroupIndex, CoreAssignment,
|
||||
},
|
||||
};
|
||||
use frame_support::{
|
||||
@@ -38,7 +38,7 @@ use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
|
||||
use sp_staking::SessionIndex;
|
||||
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
|
||||
/// for any backed candidates referred to by a `1` bit available.
|
||||
@@ -498,7 +498,7 @@ mod tests {
|
||||
use primitives::{BlockNumber, Hash};
|
||||
use primitives::parachain::{
|
||||
SignedAvailabilityBitfield, CompactStatement as Statement, ValidityAttestation, CollatorId,
|
||||
CandidateCommitments, SignedStatement,
|
||||
CandidateCommitments, SignedStatement, AssignmentKind,
|
||||
};
|
||||
use frame_support::traits::{OnFinalize, OnInitialize};
|
||||
use keyring::Sr25519Keyring;
|
||||
@@ -510,7 +510,6 @@ mod tests {
|
||||
use crate::initializer::SessionChangeNotification;
|
||||
use crate::configuration::HostConfiguration;
|
||||
use crate::paras::ParaGenesisArgs;
|
||||
use crate::scheduler::AssignmentKind;
|
||||
|
||||
fn default_config() -> HostConfiguration<BlockNumber> {
|
||||
let mut config = HostConfiguration::default();
|
||||
|
||||
@@ -38,7 +38,10 @@
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::convert::TryInto;
|
||||
use primitives::{
|
||||
parachain::{Id as ParaId, CollatorId, ValidatorIndex},
|
||||
parachain::{
|
||||
Id as ParaId, ValidatorIndex, CoreAssignment, CoreOccupied, CoreIndex, AssignmentKind,
|
||||
GroupIndex, ParathreadClaim, ParathreadEntry,
|
||||
},
|
||||
};
|
||||
use frame_support::{
|
||||
decl_storage, decl_module, decl_error,
|
||||
@@ -52,41 +55,6 @@ use rand_chacha::ChaCha20Rng;
|
||||
|
||||
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.
|
||||
#[derive(Encode, Decode, Default)]
|
||||
#[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
|
||||
pub enum FreedReason {
|
||||
/// 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 {
|
||||
use super::*;
|
||||
|
||||
use primitives::{BlockNumber, parachain::ValidatorId};
|
||||
use primitives::{BlockNumber, parachain::{CollatorId, ValidatorId}};
|
||||
use frame_support::traits::{OnFinalize, OnInitialize};
|
||||
use keyring::Sr25519Keyring;
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ use std::collections::hash_map::{HashMap, Entry};
|
||||
use std::hash::Hash;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use primitives::parachain::{ValidityAttestation as PrimitiveValidityAttestation, ValidatorSignature};
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
/// Context for the statement table.
|
||||
@@ -98,7 +100,7 @@ pub enum ValidityDoubleVote<C, D, S> {
|
||||
/// Implicit vote by issuing and explicitly voting invalidity
|
||||
IssuedAndInvalidity((C, S), (D, S)),
|
||||
/// Direct votes for validity and invalidity
|
||||
ValidityAndInvalidity(D, S, S),
|
||||
ValidityAndInvalidity(C, S, S),
|
||||
}
|
||||
|
||||
/// Misbehavior: multiple signatures on same statement.
|
||||
@@ -180,6 +182,15 @@ pub enum ValidityAttestation<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.
|
||||
#[derive(Clone, PartialEq, Decode, Encode)]
|
||||
pub struct AttestedCandidate<Group, Candidate, AuthorityId, Signature> {
|
||||
@@ -550,7 +561,7 @@ impl<C: Context> Table<C> {
|
||||
// valid vote conflicting with invalid vote
|
||||
(ValidityVote::Valid(good), ValidityVote::Invalid(bad)) |
|
||||
(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
|
||||
(ValidityVote::Issued(a), ValidityVote::Issued(b)) =>
|
||||
@@ -817,7 +828,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
table.detected_misbehavior.get(&AuthorityId(2)).unwrap(),
|
||||
&Misbehavior::ValidityDoubleVote(ValidityDoubleVote::ValidityAndInvalidity(
|
||||
candidate_digest,
|
||||
Candidate(2, 100),
|
||||
Signature(2),
|
||||
Signature(2),
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user