mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
misbehavior: report multiple offenses per validator as necessary (#2222)
* use proper descriptive generic type names * cleanup * Table stores a list of detected misbehavior per authority * add Table::drain_misbehaviors_for * WIP: unify misbehavior types; report multiple misbehaviors per validator Code checks, but tests don't yet pass. * update drain_misbehaviors: return authority id as well as specific misbehavior * enable unchecked construction of Signed structs in tests * remove test-features feature & unnecessary generic * fix backing tests This took a while to figure out, because where we'd previously been passing around `SignedFullStatement`s, we now needed to construct those on the fly within the test, to take advantage of the signature- checking in the constructor. That, in turn, necessitated changing the iterable type of `drain_misbehaviors` to return the validator index, and passing that validator index along within the misbehavior report. Once that was sorted, however, it became relatively straightforward: just needed to add appropriate methods to deconstruct the misbehavior reports, and then we could construct the signed statements directly. * fix bad merge
This commit is contained in:
committed by
GitHub
parent
a66801bd0f
commit
25cfb884af
@@ -19,7 +19,6 @@
|
||||
#![deny(unused_crate_dependencies)]
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::convert::TryFrom;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -28,13 +27,12 @@ use futures::{channel::{mpsc, oneshot}, Future, FutureExt, SinkExt, StreamExt};
|
||||
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
use polkadot_primitives::v1::{
|
||||
CommittedCandidateReceipt, BackedCandidate, Id as ParaId, ValidatorId,
|
||||
ValidatorIndex, SigningContext, PoV, CandidateHash,
|
||||
CandidateDescriptor, AvailableData, ValidatorSignature, Hash, CandidateReceipt,
|
||||
CoreState, CoreIndex, CollatorId, ValidityAttestation, CandidateCommitments,
|
||||
AvailableData, BackedCandidate, CandidateCommitments, CandidateDescriptor, CandidateHash,
|
||||
CandidateReceipt, CollatorId, CommittedCandidateReceipt, CoreIndex, CoreState, Hash, Id as ParaId,
|
||||
PoV, SigningContext, ValidatorId, ValidatorIndex, ValidatorSignature, ValidityAttestation,
|
||||
};
|
||||
use polkadot_node_primitives::{
|
||||
FromTableMisbehavior, Statement, SignedFullStatement, MisbehaviorReport, ValidationResult,
|
||||
Statement, SignedFullStatement, ValidationResult,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
JaegerSpan, PerLeafSpan,
|
||||
@@ -60,8 +58,9 @@ use statement_table::{
|
||||
Context as TableContextTrait,
|
||||
Table,
|
||||
v1::{
|
||||
SignedStatement as TableSignedStatement,
|
||||
Statement as TableStatement,
|
||||
SignedStatement as TableSignedStatement, Summary as TableSummary,
|
||||
Summary as TableSummary,
|
||||
},
|
||||
};
|
||||
use thiserror::Error;
|
||||
@@ -145,8 +144,6 @@ struct CandidateBackingJob {
|
||||
/// The candidates that are includable, by hash. Each entry here indicates
|
||||
/// that we've sent the provisioner the backed candidate.
|
||||
backed: HashSet<CandidateHash>,
|
||||
/// We have already reported misbehaviors for these validators.
|
||||
reported_misbehavior_for: HashSet<ValidatorIndex>,
|
||||
keystore: SyncCryptoStorePtr,
|
||||
table: Table<TableContext>,
|
||||
table_context: TableContext,
|
||||
@@ -644,36 +641,17 @@ impl CandidateBackingJob {
|
||||
}
|
||||
|
||||
/// Check if there have happened any new misbehaviors and issue necessary messages.
|
||||
///
|
||||
/// TODO: Report multiple misbehaviors (https://github.com/paritytech/polkadot/issues/1387)
|
||||
#[tracing::instrument(level = "trace", skip(self), fields(subsystem = LOG_TARGET))]
|
||||
async fn issue_new_misbehaviors(&mut self) -> Result<(), Error> {
|
||||
let mut reports = Vec::new();
|
||||
|
||||
for (k, v) in self.table.get_misbehavior().iter() {
|
||||
if !self.reported_misbehavior_for.contains(k) {
|
||||
self.reported_misbehavior_for.insert(*k);
|
||||
|
||||
let f = FromTableMisbehavior {
|
||||
id: *k,
|
||||
report: v.clone(),
|
||||
signing_context: self.table_context.signing_context.clone(),
|
||||
key: self.table_context.validators[*k as usize].clone(),
|
||||
};
|
||||
|
||||
if let Ok(report) = MisbehaviorReport::try_from(f) {
|
||||
let message = ProvisionerMessage::ProvisionableData(
|
||||
self.parent,
|
||||
ProvisionableData::MisbehaviorReport(self.parent, report),
|
||||
);
|
||||
|
||||
reports.push(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for report in reports.drain(..) {
|
||||
self.send_to_provisioner(report).await?
|
||||
// collect the misbehaviors to avoid double mutable self borrow issues
|
||||
let misbehaviors: Vec<_> = self.table.drain_misbehaviors().collect();
|
||||
for (validator_id, report) in misbehaviors {
|
||||
self.send_to_provisioner(
|
||||
ProvisionerMessage::ProvisionableData(
|
||||
self.parent,
|
||||
ProvisionableData::MisbehaviorReport(self.parent, validator_id, report)
|
||||
)
|
||||
).await?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1086,7 +1064,6 @@ impl util::JobTrait for CandidateBackingJob {
|
||||
seconded: None,
|
||||
unbacked_candidates: HashMap::new(),
|
||||
backed: HashSet::new(),
|
||||
reported_misbehavior_for: HashSet::new(),
|
||||
keystore,
|
||||
table: Table::default(),
|
||||
table_context,
|
||||
@@ -1199,9 +1176,7 @@ mod tests {
|
||||
use super::*;
|
||||
use assert_matches::assert_matches;
|
||||
use futures::{future, Future};
|
||||
use polkadot_primitives::v1::{
|
||||
ScheduledCore, BlockData, PersistedValidationData, HeadData, GroupRotationInfo,
|
||||
};
|
||||
use polkadot_primitives::v1::{BlockData, GroupRotationInfo, HeadData, PersistedValidationData, ScheduledCore};
|
||||
use polkadot_subsystem::{
|
||||
messages::{RuntimeApiRequest, RuntimeApiMessage},
|
||||
ActiveLeavesUpdate, FromOverseer, OverseerSignal,
|
||||
@@ -1210,12 +1185,23 @@ mod tests {
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use sp_application_crypto::AppKey;
|
||||
use sp_keystore::{CryptoStore, SyncCryptoStore};
|
||||
use statement_table::v1::Misbehavior;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec<ValidatorId> {
|
||||
val_ids.iter().map(|v| v.public().into()).collect()
|
||||
}
|
||||
|
||||
fn table_statement_to_primitive(
|
||||
statement: TableStatement,
|
||||
) -> Statement {
|
||||
match statement {
|
||||
TableStatement::Candidate(committed_candidate_receipt) => Statement::Seconded(committed_candidate_receipt),
|
||||
TableStatement::Valid(candidate_hash) => Statement::Valid(candidate_hash),
|
||||
TableStatement::Invalid(candidate_hash) => Statement::Invalid(candidate_hash),
|
||||
}
|
||||
}
|
||||
|
||||
struct TestState {
|
||||
chain_ids: Vec<ParaId>,
|
||||
keystore: SyncCryptoStorePtr,
|
||||
@@ -1950,19 +1936,30 @@ mod tests {
|
||||
_,
|
||||
ProvisionableData::MisbehaviorReport(
|
||||
relay_parent,
|
||||
MisbehaviorReport::SelfContradiction(_, s1, s2),
|
||||
validator_index,
|
||||
Misbehavior::ValidityDoubleVote(vdv),
|
||||
)
|
||||
)
|
||||
) if relay_parent == test_state.relay_parent => {
|
||||
s1.check_signature(
|
||||
&test_state.signing_context,
|
||||
&test_state.validator_public[s1.validator_index() as usize],
|
||||
).unwrap();
|
||||
let ((t1, s1), (t2, s2)) = vdv.deconstruct::<TableContext>();
|
||||
let t1 = table_statement_to_primitive(t1);
|
||||
let t2 = table_statement_to_primitive(t2);
|
||||
|
||||
s2.check_signature(
|
||||
SignedFullStatement::new(
|
||||
t1,
|
||||
validator_index,
|
||||
s1,
|
||||
&test_state.signing_context,
|
||||
&test_state.validator_public[s2.validator_index() as usize],
|
||||
).unwrap();
|
||||
&test_state.validator_public[validator_index as usize],
|
||||
).expect("signature must be valid");
|
||||
|
||||
SignedFullStatement::new(
|
||||
t2,
|
||||
validator_index,
|
||||
s2,
|
||||
&test_state.signing_context,
|
||||
&test_state.validator_public[validator_index as usize],
|
||||
).expect("signature must be valid");
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1979,19 +1976,30 @@ mod tests {
|
||||
_,
|
||||
ProvisionableData::MisbehaviorReport(
|
||||
relay_parent,
|
||||
MisbehaviorReport::SelfContradiction(_, s1, s2),
|
||||
validator_index,
|
||||
Misbehavior::ValidityDoubleVote(vdv),
|
||||
)
|
||||
)
|
||||
) if relay_parent == test_state.relay_parent => {
|
||||
s1.check_signature(
|
||||
&test_state.signing_context,
|
||||
&test_state.validator_public[s1.validator_index() as usize],
|
||||
).unwrap();
|
||||
let ((t1, s1), (t2, s2)) = vdv.deconstruct::<TableContext>();
|
||||
let t1 = table_statement_to_primitive(t1);
|
||||
let t2 = table_statement_to_primitive(t2);
|
||||
|
||||
s2.check_signature(
|
||||
SignedFullStatement::new(
|
||||
t1,
|
||||
validator_index,
|
||||
s1,
|
||||
&test_state.signing_context,
|
||||
&test_state.validator_public[s2.validator_index() as usize],
|
||||
).unwrap();
|
||||
&test_state.validator_public[validator_index as usize],
|
||||
).expect("signature must be valid");
|
||||
|
||||
SignedFullStatement::new(
|
||||
t2,
|
||||
validator_index,
|
||||
s2,
|
||||
&test_state.signing_context,
|
||||
&test_state.validator_public[validator_index as usize],
|
||||
).expect("signature must be valid");
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -2464,6 +2472,7 @@ mod tests {
|
||||
#[test]
|
||||
fn candidate_backing_reorders_votes() {
|
||||
use sp_core::Encode;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
let relay_parent = [1; 32].into();
|
||||
let para_id = ParaId::from(10);
|
||||
|
||||
@@ -25,17 +25,9 @@
|
||||
use futures::Future;
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use polkadot_primitives::v1::{
|
||||
Hash, CommittedCandidateReceipt, CandidateReceipt, CompactStatement,
|
||||
EncodeAs, Signed, SigningContext, ValidatorIndex, ValidatorId,
|
||||
UpwardMessage, ValidationCode, PersistedValidationData,
|
||||
HeadData, PoV, CollatorPair, Id as ParaId, OutboundHrmpMessage, CandidateCommitments, CandidateHash,
|
||||
};
|
||||
use polkadot_statement_table::{
|
||||
generic::{
|
||||
ValidityDoubleVote as TableValidityDoubleVote,
|
||||
MultipleCandidates as TableMultipleCandidates,
|
||||
},
|
||||
v1::Misbehavior as TableMisbehavior,
|
||||
CandidateCommitments, CandidateHash, CollatorPair, CommittedCandidateReceipt, CompactStatement,
|
||||
EncodeAs, Hash, HeadData, Id as ParaId, OutboundHrmpMessage, PersistedValidationData, PoV,
|
||||
Signed, UpwardMessage, ValidationCode,
|
||||
};
|
||||
use std::pin::Pin;
|
||||
|
||||
@@ -105,36 +97,6 @@ impl EncodeAs<CompactStatement> for Statement {
|
||||
/// Only the compact `SignedStatement` is suitable for submission to the chain.
|
||||
pub type SignedFullStatement = Signed<Statement, CompactStatement>;
|
||||
|
||||
/// A misbehaviour report.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MisbehaviorReport {
|
||||
/// These validator nodes disagree on this candidate's validity, please figure it out
|
||||
///
|
||||
/// Most likely, the list of statments all agree except for the final one. That's not
|
||||
/// guaranteed, though; if somehow we become aware of lots of
|
||||
/// statements disagreeing about the validity of a candidate before taking action,
|
||||
/// 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>),
|
||||
/// I've noticed a peer contradicting itself about a particular candidate
|
||||
SelfContradiction(CandidateReceipt, SignedFullStatement, SignedFullStatement),
|
||||
/// This peer has seconded more than one parachain candidate for this relay parent head
|
||||
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,
|
||||
}
|
||||
|
||||
/// Candidate invalidity details
|
||||
#[derive(Debug)]
|
||||
pub enum InvalidCandidate {
|
||||
@@ -170,102 +132,6 @@ pub enum ValidationResult {
|
||||
Invalid(InvalidCandidate),
|
||||
}
|
||||
|
||||
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.to_plain(), 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.to_plain(), 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.to_plain(), 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(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The output of a collator.
|
||||
///
|
||||
/// This differs from `CandidateCommitments` in two ways:
|
||||
|
||||
@@ -28,7 +28,7 @@ use polkadot_node_network_protocol::{
|
||||
v1 as protocol_v1, NetworkBridgeEvent, ReputationChange, PeerId,
|
||||
};
|
||||
use polkadot_node_primitives::{
|
||||
CollationGenerationConfig, MisbehaviorReport, SignedFullStatement, ValidationResult,
|
||||
CollationGenerationConfig, SignedFullStatement, ValidationResult,
|
||||
approval::{BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote},
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
@@ -41,6 +41,7 @@ use polkadot_primitives::v1::{
|
||||
ValidatorIndex, ValidatorSignature, InboundDownwardMessage, InboundHrmpMessage,
|
||||
CandidateIndex,
|
||||
};
|
||||
use polkadot_statement_table::v1::Misbehavior;
|
||||
use std::{sync::Arc, collections::btree_map::BTreeMap};
|
||||
|
||||
/// Subsystem messages where each message is always bound to a relay parent.
|
||||
@@ -508,7 +509,7 @@ pub enum ProvisionableData {
|
||||
/// The Candidate Backing subsystem believes that this candidate is valid, pending availability.
|
||||
BackedCandidate(CandidateReceipt),
|
||||
/// Misbehavior reports are self-contained proofs of validator misbehavior.
|
||||
MisbehaviorReport(Hash, MisbehaviorReport),
|
||||
MisbehaviorReport(Hash, ValidatorIndex, Misbehavior),
|
||||
/// Disputes trigger a broad dispute resolution process.
|
||||
Dispute(Hash, ValidatorSignature),
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ Indicates a change in active leaves. Activated leaves should have jobs, whereas
|
||||
|
||||
```rust
|
||||
struct ActiveLeavesUpdate {
|
||||
activated: [Hash], // in practice, these should probably be a SmallVec
|
||||
deactivated: [Hash],
|
||||
activated: [Hash], // in practice, these should probably be a SmallVec
|
||||
deactivated: [Hash],
|
||||
}
|
||||
```
|
||||
|
||||
@@ -41,46 +41,46 @@ Messages received by the approval voting subsystem.
|
||||
|
||||
```rust
|
||||
enum AssignmentCheckResult {
|
||||
// The vote was accepted and should be propagated onwards.
|
||||
Accepted,
|
||||
// The vote was valid but duplicate and should not be propagated onwards.
|
||||
AcceptedDuplicate,
|
||||
// The vote was valid but too far in the future to accept right now.
|
||||
TooFarInFuture,
|
||||
// The vote was bad and should be ignored, reporting the peer who propagated it.
|
||||
Bad,
|
||||
// The vote was accepted and should be propagated onwards.
|
||||
Accepted,
|
||||
// The vote was valid but duplicate and should not be propagated onwards.
|
||||
AcceptedDuplicate,
|
||||
// The vote was valid but too far in the future to accept right now.
|
||||
TooFarInFuture,
|
||||
// The vote was bad and should be ignored, reporting the peer who propagated it.
|
||||
Bad,
|
||||
}
|
||||
|
||||
enum ApprovalCheckResult {
|
||||
// The vote was accepted and should be propagated onwards.
|
||||
Accepted,
|
||||
// The vote was bad and should be ignored, reporting the peer who propagated it.
|
||||
Bad,
|
||||
// The vote was accepted and should be propagated onwards.
|
||||
Accepted,
|
||||
// The vote was bad and should be ignored, reporting the peer who propagated it.
|
||||
Bad,
|
||||
}
|
||||
|
||||
enum ApprovalVotingMessage {
|
||||
/// Check if the assignment is valid and can be accepted by our view of the protocol.
|
||||
/// Should not be sent unless the block hash is known.
|
||||
CheckAndImportAssignment(
|
||||
IndirectAssignmentCert,
|
||||
ResponseChannel<AssignmentCheckResult>,
|
||||
),
|
||||
/// Check if the approval vote is valid and can be accepted by our view of the
|
||||
/// protocol.
|
||||
///
|
||||
/// Should not be sent unless the block hash within the indirect vote is known.
|
||||
CheckAndImportApproval(
|
||||
IndirectSignedApprovalVote,
|
||||
ResponseChannel<ApprovalCheckResult>,
|
||||
),
|
||||
/// Returns the highest possible ancestor hash of the provided block hash which is
|
||||
/// acceptable to vote on finality for.
|
||||
/// The `BlockNumber` provided is the number of the block's ancestor which is the
|
||||
/// earliest possible vote.
|
||||
///
|
||||
/// It can also return the same block hash, if that is acceptable to vote upon.
|
||||
/// Return `None` if the input hash is unrecognized.
|
||||
ApprovedAncestor(Hash, BlockNumber, ResponseChannel<Option<Hash>>),
|
||||
/// Check if the assignment is valid and can be accepted by our view of the protocol.
|
||||
/// Should not be sent unless the block hash is known.
|
||||
CheckAndImportAssignment(
|
||||
IndirectAssignmentCert,
|
||||
ResponseChannel<AssignmentCheckResult>,
|
||||
),
|
||||
/// Check if the approval vote is valid and can be accepted by our view of the
|
||||
/// protocol.
|
||||
///
|
||||
/// Should not be sent unless the block hash within the indirect vote is known.
|
||||
CheckAndImportApproval(
|
||||
IndirectSignedApprovalVote,
|
||||
ResponseChannel<ApprovalCheckResult>,
|
||||
),
|
||||
/// Returns the highest possible ancestor hash of the provided block hash which is
|
||||
/// acceptable to vote on finality for.
|
||||
/// The `BlockNumber` provided is the number of the block's ancestor which is the
|
||||
/// earliest possible vote.
|
||||
///
|
||||
/// It can also return the same block hash, if that is acceptable to vote upon.
|
||||
/// Return `None` if the input hash is unrecognized.
|
||||
ApprovedAncestor(Hash, BlockNumber, ResponseChannel<Option<Hash>>),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -91,32 +91,35 @@ Messages received by the approval Distribution subsystem.
|
||||
```rust
|
||||
/// Metadata about a block which is now live in the approval protocol.
|
||||
struct BlockApprovalMeta {
|
||||
/// The hash of the block.
|
||||
hash: Hash,
|
||||
/// The number of the block.
|
||||
number: BlockNumber,
|
||||
/// The hash of the parent block.
|
||||
parent_hash: Hash,
|
||||
/// The candidates included by the block. Note that these are not the same as the candidates that appear within the
|
||||
/// block body.
|
||||
candidates: Vec<CandidateHash>,
|
||||
/// The consensus slot number of the block.
|
||||
slot_number: SlotNumber,
|
||||
/// The hash of the block.
|
||||
hash: Hash,
|
||||
/// The number of the block.
|
||||
number: BlockNumber,
|
||||
/// The candidates included by the block. Note that these are not the same as the candidates that appear within the
|
||||
/// block body.
|
||||
parent_hash: Hash,
|
||||
/// The candidates included by the block. Note that these are not the same as the candidates that appear within the
|
||||
/// block body.
|
||||
candidates: Vec<CandidateHash>,
|
||||
/// The consensus slot number of the block.
|
||||
slot_number: SlotNumber,
|
||||
}
|
||||
|
||||
enum ApprovalDistributionMessage {
|
||||
/// Notify the `ApprovalDistribution` subsystem about new blocks and the candidates contained within
|
||||
/// them.
|
||||
NewBlocks(Vec<BlockApprovalMeta>),
|
||||
/// Distribute an assignment cert from the local validator. The cert is assumed
|
||||
/// to be valid, relevant, and for the given relay-parent and validator index.
|
||||
DistributeAssignment(IndirectAssignmentCert, CandidateIndex),
|
||||
/// Distribute an approval vote for the local validator. The approval vote is assumed to be
|
||||
/// valid, relevant, and the corresponding approval already issued. If not, the subsystem is free to drop
|
||||
/// the message.
|
||||
DistributeApproval(IndirectSignedApprovalVote),
|
||||
/// An update from the network bridge.
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<ApprovalDistributionV1Message>),
|
||||
/// Notify the `ApprovalDistribution` subsystem about new blocks and the candidates contained within
|
||||
/// them.
|
||||
NewBlocks(Vec<BlockApprovalMeta>),
|
||||
/// Distribute an assignment cert from the local validator. The cert is assumed
|
||||
/// to be valid, relevant, and for the given relay-parent and validator index.
|
||||
///
|
||||
/// The `u32` param is the candidate index in the fully-included list.
|
||||
DistributeAssignment(IndirectAssignmentCert, u32),
|
||||
/// Distribute an approval vote for the local validator. The approval vote is assumed to be
|
||||
/// valid, relevant, and the corresponding approval already issued. If not, the subsystem is free to drop
|
||||
/// the message.
|
||||
DistributeApproval(IndirectSignedApprovalVote),
|
||||
/// An update from the network bridge.
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<ApprovalDistributionV1Message>),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -132,13 +135,13 @@ This is a network protocol that receives messages of type [`AvailabilityDistribu
|
||||
|
||||
```rust
|
||||
enum AvailabilityDistributionMessage {
|
||||
/// Distribute an availability chunk to other validators.
|
||||
DistributeChunk(Hash, ErasureChunk),
|
||||
/// Fetch an erasure chunk from network by candidate hash and chunk index.
|
||||
FetchChunk(Hash, u32),
|
||||
/// Event from the network.
|
||||
/// An update on network state from the network bridge.
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<AvailabilityDistributionV1Message>),
|
||||
/// Distribute an availability chunk to other validators.
|
||||
DistributeChunk(Hash, ErasureChunk),
|
||||
/// Fetch an erasure chunk from network by candidate hash and chunk index.
|
||||
FetchChunk(Hash, u32),
|
||||
/// Event from the network.
|
||||
/// An update on network state from the network bridge.
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<AvailabilityDistributionV1Message>),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -148,16 +151,16 @@ Messages received by the availability recovery subsystem.
|
||||
|
||||
```rust
|
||||
enum RecoveryError {
|
||||
Invalid,
|
||||
Unavailable,
|
||||
Invalid,
|
||||
Unavailable,
|
||||
}
|
||||
enum AvailabilityRecoveryMessage {
|
||||
/// Recover available data from validators on the network.
|
||||
RecoverAvailableData(
|
||||
CandidateReceipt,
|
||||
SessionIndex,
|
||||
ResponseChannel<Result<AvailableData, RecoveryError>>,
|
||||
),
|
||||
/// Recover available data from validators on the network.
|
||||
RecoverAvailableData(
|
||||
CandidateReceipt,
|
||||
SessionIndex,
|
||||
ResponseChannel<Result<AvailableData, RecoveryError>>,
|
||||
),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -167,19 +170,19 @@ Messages to and from the availability store.
|
||||
|
||||
```rust
|
||||
enum AvailabilityStoreMessage {
|
||||
/// Query the `AvailableData` of a candidate by hash.
|
||||
QueryAvailableData(CandidateHash, ResponseChannel<Option<AvailableData>>),
|
||||
/// Query whether an `AvailableData` exists within the AV Store.
|
||||
QueryDataAvailability(CandidateHash, ResponseChannel<bool>),
|
||||
/// Query a specific availability chunk of the candidate's erasure-coding by validator index.
|
||||
/// Returns the chunk and its inclusion proof against the candidate's erasure-root.
|
||||
QueryChunk(CandidateHash, ValidatorIndex, ResponseChannel<Option<ErasureChunk>>),
|
||||
/// Store a specific chunk of the candidate's erasure-coding, with an
|
||||
/// accompanying proof.
|
||||
StoreChunk(CandidateHash, ErasureChunk, ResponseChannel<Result<()>>),
|
||||
/// Store `AvailableData`. If `ValidatorIndex` is provided, also store this validator's
|
||||
/// `ErasureChunk`.
|
||||
StoreAvailableData(CandidateHash, Option<ValidatorIndex>, u32, AvailableData, ResponseChannel<Result<()>>),
|
||||
/// Query the `AvailableData` of a candidate by hash.
|
||||
QueryAvailableData(CandidateHash, ResponseChannel<Option<AvailableData>>),
|
||||
/// Query whether an `AvailableData` exists within the AV Store.
|
||||
QueryDataAvailability(CandidateHash, ResponseChannel<bool>),
|
||||
/// Query a specific availability chunk of the candidate's erasure-coding by validator index.
|
||||
/// Returns the chunk and its inclusion proof against the candidate's erasure-root.
|
||||
QueryChunk(CandidateHash, ValidatorIndex, ResponseChannel<Option<AvailabilityChunkAndProof>>),
|
||||
/// Store a specific chunk of the candidate's erasure-coding by validator index, with an
|
||||
/// accompanying proof.
|
||||
StoreChunk(CandidateHash, ValidatorIndex, AvailabilityChunkAndProof, ResponseChannel<Result<()>>),
|
||||
/// Store `AvailableData`. If `ValidatorIndex` is provided, also store this validator's
|
||||
/// `AvailabilityChunkAndProof`.
|
||||
StoreAvailableData(CandidateHash, Option<ValidatorIndex>, u32, AvailableData, ResponseChannel<Result<()>>),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -190,11 +193,11 @@ This is a network protocol that receives messages of type [`BitfieldDistribution
|
||||
|
||||
```rust
|
||||
enum BitfieldDistributionMessage {
|
||||
/// Distribute a bitfield signed by a validator to other validators.
|
||||
/// The bitfield distribution subsystem will assume this is indeed correctly signed.
|
||||
DistributeBitfield(relay_parent, SignedAvailabilityBitfield),
|
||||
/// Receive a network bridge update.
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<BitfieldDistributionV1Message>),
|
||||
/// Distribute a bitfield signed by a validator to other validators.
|
||||
/// The bitfield distribution subsystem will assume this is indeed correctly signed.
|
||||
DistributeBitfield(relay_parent, SignedAvailabilityBitfield),
|
||||
/// Receive a network bridge update.
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<BitfieldDistributionV1Message>),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -243,31 +246,31 @@ The Chain API subsystem is responsible for providing an interface to chain data.
|
||||
|
||||
```rust
|
||||
enum ChainApiMessage {
|
||||
/// Get the block number by hash.
|
||||
/// Returns `None` if a block with the given hash is not present in the db.
|
||||
BlockNumber(Hash, ResponseChannel<Result<Option<BlockNumber>, Error>>),
|
||||
/// Request the block header by hash.
|
||||
/// Returns `None` if a block with the given hash is not present in the db.
|
||||
BlockHeader(Hash, ResponseChannel<Result<Option<BlockHeader>, Error>>),
|
||||
/// Get the finalized block hash by number.
|
||||
/// Returns `None` if a block with the given number is not present in the db.
|
||||
/// Note: the caller must ensure the block is finalized.
|
||||
FinalizedBlockHash(BlockNumber, ResponseChannel<Result<Option<Hash>, Error>>),
|
||||
/// Get the last finalized block number.
|
||||
/// This request always succeeds.
|
||||
FinalizedBlockNumber(ResponseChannel<Result<BlockNumber, Error>>),
|
||||
/// Request the `k` ancestors block hashes of a block with the given hash.
|
||||
/// The response channel may return a `Vec` of size up to `k`
|
||||
/// filled with ancestors hashes with the following order:
|
||||
/// `parent`, `grandparent`, ...
|
||||
Ancestors {
|
||||
/// The hash of the block in question.
|
||||
hash: Hash,
|
||||
/// The number of ancestors to request.
|
||||
k: usize,
|
||||
/// The response channel.
|
||||
response_channel: ResponseChannel<Result<Vec<Hash>, Error>>,
|
||||
}
|
||||
/// Get the block number by hash.
|
||||
/// Returns `None` if a block with the given hash is not present in the db.
|
||||
BlockNumber(Hash, ResponseChannel<Result<Option<BlockNumber>, Error>>),
|
||||
/// Request the block header by hash.
|
||||
/// Returns `None` if a block with the given hash is not present in the db.
|
||||
BlockHeader(Hash, ResponseChannel<Result<Option<BlockHeader>, Error>>),
|
||||
/// Get the finalized block hash by number.
|
||||
/// Returns `None` if a block with the given number is not present in the db.
|
||||
/// Note: the caller must ensure the block is finalized.
|
||||
FinalizedBlockHash(BlockNumber, ResponseChannel<Result<Option<Hash>, Error>>),
|
||||
/// Get the last finalized block number.
|
||||
/// This request always succeeds.
|
||||
FinalizedBlockNumber(ResponseChannel<Result<BlockNumber, Error>>),
|
||||
/// Request the `k` ancestors block hashes of a block with the given hash.
|
||||
/// The response channel may return a `Vec` of size up to `k`
|
||||
/// filled with ancestors hashes with the following order:
|
||||
/// `parent`, `grandparent`, ...
|
||||
Ancestors {
|
||||
/// The hash of the block in question.
|
||||
hash: Hash,
|
||||
/// The number of ancestors to request.
|
||||
k: usize,
|
||||
/// The response channel.
|
||||
response_channel: ResponseChannel<Result<Vec<Hash>, Error>>,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -279,22 +282,22 @@ This is a network protocol that receives messages of type [`CollatorProtocolV1Me
|
||||
|
||||
```rust
|
||||
enum CollatorProtocolMessage {
|
||||
/// Signal to the collator protocol that it should connect to validators with the expectation
|
||||
/// of collating on the given para. This is only expected to be called once, early on, if at all,
|
||||
/// and only by the Collation Generation subsystem. As such, it will overwrite the value of
|
||||
/// the previous signal.
|
||||
///
|
||||
/// This should be sent before any `DistributeCollation` message.
|
||||
CollateOn(ParaId),
|
||||
/// Provide a collation to distribute to validators.
|
||||
DistributeCollation(CandidateReceipt, PoV),
|
||||
/// Fetch a collation under the given relay-parent for the given ParaId.
|
||||
FetchCollation(Hash, ParaId, ResponseChannel<(CandidateReceipt, PoV)>),
|
||||
/// Report a collator as having provided an invalid collation. This should lead to disconnect
|
||||
/// and blacklist of the collator.
|
||||
ReportCollator(CollatorId),
|
||||
/// Note a collator as having provided a good collation.
|
||||
NoteGoodCollation(CollatorId),
|
||||
/// Signal to the collator protocol that it should connect to validators with the expectation
|
||||
/// of collating on the given para. This is only expected to be called once, early on, if at all,
|
||||
/// and only by the Collation Generation subsystem. As such, it will overwrite the value of
|
||||
/// the previous signal.
|
||||
///
|
||||
/// This should be sent before any `DistributeCollation` message.
|
||||
CollateOn(ParaId),
|
||||
/// Provide a collation to distribute to validators.
|
||||
DistributeCollation(CandidateReceipt, PoV),
|
||||
/// Fetch a collation under the given relay-parent for the given ParaId.
|
||||
FetchCollation(Hash, ParaId, ResponseChannel<(CandidateReceipt, PoV)>),
|
||||
/// Report a collator as having provided an invalid collation. This should lead to disconnect
|
||||
/// and blacklist of the collator.
|
||||
ReportCollator(CollatorId),
|
||||
/// Note a collator as having provided a good collation.
|
||||
NoteGoodCollation(CollatorId),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -306,79 +309,118 @@ to the low-level networking code.
|
||||
```rust
|
||||
/// Peer-sets handled by the network bridge.
|
||||
enum PeerSet {
|
||||
/// The collation peer-set is used to distribute collations from collators to validators.
|
||||
Collation,
|
||||
/// The validation peer-set is used to distribute information relevant to parachain
|
||||
/// validation among validators. This may include nodes which are not validators,
|
||||
/// as some protocols on this peer-set are expected to be gossip.
|
||||
Validation,
|
||||
/// The collation peer-set is used to distribute collations from collators to validators.
|
||||
Collation,
|
||||
/// The validation peer-set is used to distribute information relevant to parachain
|
||||
/// validation among validators. This may include nodes which are not validators,
|
||||
/// as some protocols on this peer-set are expected to be gossip.
|
||||
Validation,
|
||||
}
|
||||
|
||||
enum NetworkBridgeMessage {
|
||||
/// Report a cost or benefit of a peer. Negative values are costs, positive are benefits.
|
||||
ReportPeer(PeerSet, PeerId, cost_benefit: i32),
|
||||
/// Send a message to one or more peers on the validation peerset.
|
||||
SendValidationMessage([PeerId], ValidationProtocolV1),
|
||||
/// Send a message to one or more peers on the collation peerset.
|
||||
SendCollationMessage([PeerId], CollationProtocolV1),
|
||||
/// Send multiple validation messages.
|
||||
SendValidationMessages([([PeerId, ValidationProtocolV1])]),
|
||||
/// Send multiple collation messages.
|
||||
SendCollationMessages([([PeerId, ValidationProtocolV1])]),
|
||||
/// Connect to peers who represent the given `validator_ids`.
|
||||
///
|
||||
/// Also ask the network to stay connected to these peers at least
|
||||
/// until the request is revoked.
|
||||
/// This can be done by dropping the receiver.
|
||||
ConnectToValidators {
|
||||
/// Ids of the validators to connect to.
|
||||
validator_ids: Vec<AuthorityDiscoveryId>,
|
||||
/// Response sender by which the issuer can learn the `PeerId`s of
|
||||
/// the validators as they are connected.
|
||||
/// The response is sent immediately for already connected peers.
|
||||
connected: ResponseStream<(AuthorityDiscoveryId, PeerId)>,
|
||||
},
|
||||
/// Report a cost or benefit of a peer. Negative values are costs, positive are benefits.
|
||||
ReportPeer(PeerSet, PeerId, cost_benefit: i32),
|
||||
/// Send a message to one or more peers on the validation peerset.
|
||||
SendValidationMessage([PeerId], ValidationProtocolV1),
|
||||
/// Send a message to one or more peers on the collation peerset.
|
||||
SendCollationMessage([PeerId], ValidationProtocolV1),
|
||||
/// Send multiple validation messages.
|
||||
SendValidationMessages([([PeerId, ValidationProtocolV1])]),
|
||||
/// Send multiple collation messages.
|
||||
SendCollationMessages([([PeerId, ValidationProtocolV1])]),
|
||||
/// Connect to peers who represent the given `validator_ids`.
|
||||
///
|
||||
/// Also ask the network to stay connected to these peers at least
|
||||
/// until the request is revoked.
|
||||
/// This can be done by dropping the receiver.
|
||||
ConnectToValidators {
|
||||
/// Ids of the validators to connect to.
|
||||
validator_ids: Vec<AuthorityDiscoveryId>,
|
||||
/// Response sender by which the issuer can learn the `PeerId`s of
|
||||
/// the validators as they are connected.
|
||||
/// The response is sent immediately for already connected peers.
|
||||
connected: ResponseStream<(AuthorityDiscoveryId, PeerId)>,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Misbehavior Report
|
||||
|
||||
```rust
|
||||
enum MisbehaviorReport {
|
||||
/// These validator nodes disagree on this candidate's validity, please figure it out
|
||||
///
|
||||
/// Most likely, the list of statments all agree except for the final one. That's not
|
||||
/// guaranteed, though; if somehow we become aware of lots of
|
||||
/// statements disagreeing about the validity of a candidate before taking action,
|
||||
/// 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>),
|
||||
/// I've noticed a peer contradicting itself about a particular candidate
|
||||
SelfContradiction(CandidateReceipt, SignedFullStatement, SignedFullStatement),
|
||||
/// This peer has seconded more than one parachain candidate for this relay parent head
|
||||
DoubleVote(CandidateReceipt, SignedFullStatement, SignedFullStatement),
|
||||
pub type Misbehavior = generic::Misbehavior<
|
||||
CommittedCandidateReceipt,
|
||||
CandidateHash,
|
||||
ValidatorIndex,
|
||||
ValidatorSignature,
|
||||
>;
|
||||
|
||||
mod generic {
|
||||
/// Misbehavior: voting more than one way on candidate validity.
|
||||
///
|
||||
/// Since there are three possible ways to vote, a double vote is possible in
|
||||
/// three possible combinations (unordered)
|
||||
pub enum ValidityDoubleVote<Candidate, Digest, Signature> {
|
||||
/// Implicit vote by issuing and explicitly voting validity.
|
||||
IssuedAndValidity((Candidate, Signature), (Digest, Signature)),
|
||||
/// Implicit vote by issuing and explicitly voting invalidity
|
||||
IssuedAndInvalidity((Candidate, Signature), (Digest, Signature)),
|
||||
/// Direct votes for validity and invalidity
|
||||
ValidityAndInvalidity(Candidate, Signature, Signature),
|
||||
}
|
||||
|
||||
/// Misbehavior: multiple signatures on same statement.
|
||||
pub enum DoubleSign<Candidate, Digest, Signature> {
|
||||
/// On candidate.
|
||||
Candidate(Candidate, Signature, Signature),
|
||||
/// On validity.
|
||||
Validity(Digest, Signature, Signature),
|
||||
/// On invalidity.
|
||||
Invalidity(Digest, Signature, Signature),
|
||||
}
|
||||
|
||||
/// Misbehavior: declaring multiple candidates.
|
||||
pub struct MultipleCandidates<Candidate, Signature> {
|
||||
/// The first candidate seen.
|
||||
pub first: (Candidate, Signature),
|
||||
/// The second candidate seen.
|
||||
pub second: (Candidate, Signature),
|
||||
}
|
||||
|
||||
/// Misbehavior: submitted statement for wrong group.
|
||||
pub struct UnauthorizedStatement<Candidate, Digest, AuthorityId, Signature> {
|
||||
/// A signed statement which was submitted without proper authority.
|
||||
pub statement: SignedStatement<Candidate, Digest, AuthorityId, Signature>,
|
||||
}
|
||||
|
||||
pub enum Misbehavior<Candidate, Digest, AuthorityId, Signature> {
|
||||
/// Voted invalid and valid on validity.
|
||||
ValidityDoubleVote(ValidityDoubleVote<Candidate, Digest, Signature>),
|
||||
/// Submitted multiple candidates.
|
||||
MultipleCandidates(MultipleCandidates<Candidate, Signature>),
|
||||
/// Submitted a message that was unauthorized.
|
||||
UnauthorizedStatement(UnauthorizedStatement<Candidate, Digest, AuthorityId, Signature>),
|
||||
/// Submitted two valid signatures for the same message.
|
||||
DoubleSign(DoubleSign<Candidate, Digest, Signature>),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If this subsystem chooses to second a parachain block, it dispatches a `CandidateBackingSubsystemMessage`.
|
||||
|
||||
## PoV Distribution Message
|
||||
|
||||
This is a network protocol that receives messages of type [`PoVDistributionV1Message`][PoVDistributionV1NetworkMessage].
|
||||
|
||||
```rust
|
||||
enum PoVDistributionMessage {
|
||||
/// Fetch a PoV from the network.
|
||||
///
|
||||
/// This `CandidateDescriptor` should correspond to a candidate seconded under the provided
|
||||
/// relay-parent hash.
|
||||
FetchPoV(Hash, CandidateDescriptor, ResponseChannel<PoV>),
|
||||
/// Distribute a PoV for the given relay-parent and CandidateDescriptor.
|
||||
/// The PoV should correctly hash to the PoV hash mentioned in the CandidateDescriptor
|
||||
DistributePoV(Hash, CandidateDescriptor, PoV),
|
||||
/// An update from the network bridge.
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<PoVDistributionV1Message>),
|
||||
/// Fetch a PoV from the network.
|
||||
///
|
||||
/// This `CandidateDescriptor` should correspond to a candidate seconded under the provided
|
||||
/// relay-parent hash.
|
||||
FetchPoV(Hash, CandidateDescriptor, ResponseChannel<PoV>),
|
||||
/// Distribute a PoV for the given relay-parent and CandidateDescriptor.
|
||||
/// The PoV should correctly hash to the PoV hash mentioned in the CandidateDescriptor
|
||||
DistributePoV(Hash, CandidateDescriptor, PoV),
|
||||
/// An update from the network bridge.
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<PoVDistributionV1Message>),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -430,48 +472,48 @@ This is fueled by an auxiliary type encapsulating all request types defined in t
|
||||
|
||||
```rust
|
||||
enum RuntimeApiRequest {
|
||||
/// Get the current validator set.
|
||||
Validators(ResponseChannel<Vec<ValidatorId>>),
|
||||
/// Get the validator groups and rotation info.
|
||||
ValidatorGroups(ResponseChannel<(Vec<Vec<ValidatorIndex>>, GroupRotationInfo)>),
|
||||
/// Get information about all availability cores.
|
||||
AvailabilityCores(ResponseChannel<Vec<CoreState>>),
|
||||
/// with the given occupied core assumption.
|
||||
PersistedValidationData(
|
||||
ParaId,
|
||||
OccupiedCoreAssumption,
|
||||
ResponseChannel<Option<PersistedValidationData>>,
|
||||
),
|
||||
/// Sends back `true` if the commitments pass all acceptance criteria checks.
|
||||
CheckValidationOutputs(
|
||||
ParaId,
|
||||
CandidateCommitments,
|
||||
RuntimeApiSender<bool>,
|
||||
),
|
||||
/// Get the session index for children of the block. This can be used to construct a signing
|
||||
/// context.
|
||||
SessionIndexForChild(ResponseChannel<SessionIndex>),
|
||||
/// Get the validation code for a specific para, using the given occupied core assumption.
|
||||
ValidationCode(ParaId, OccupiedCoreAssumption, ResponseChannel<Option<ValidationCode>>),
|
||||
/// Fetch the historical validation code used by a para for candidates executed in
|
||||
/// the context of a given block height in the current chain.
|
||||
HistoricalValidationCode(ParaId, BlockNumber, ResponseChannel<Option<ValidationCode>>),
|
||||
/// Get a committed candidate receipt for all candidates pending availability.
|
||||
CandidatePendingAvailability(ParaId, ResponseChannel<Option<CommittedCandidateReceipt>>),
|
||||
/// Get all events concerning candidates in the last block.
|
||||
CandidateEvents(ResponseChannel<Vec<CandidateEvent>>),
|
||||
/// Get the session info for the given session, if stored.
|
||||
SessionInfo(SessionIndex, ResponseChannel<Option<SessionInfo>>),
|
||||
/// Get all the pending inbound messages in the downward message queue for a para.
|
||||
DmqContents(ParaId, ResponseChannel<Vec<InboundDownwardMessage<BlockNumber>>>),
|
||||
/// Get the contents of all channels addressed to the given recipient. Channels that have no
|
||||
/// messages in them are also included.
|
||||
InboundHrmpChannelsContents(ParaId, ResponseChannel<BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>>),
|
||||
/// Get the current validator set.
|
||||
Validators(ResponseChannel<Vec<ValidatorId>>),
|
||||
/// Get the validator groups and rotation info.
|
||||
ValidatorGroups(ResponseChannel<(Vec<Vec<ValidatorIndex>>, GroupRotationInfo)>),
|
||||
/// Get information about all availability cores.
|
||||
AvailabilityCores(ResponseChannel<Vec<CoreState>>),
|
||||
/// with the given occupied core assumption.
|
||||
PersistedValidationData(
|
||||
ParaId,
|
||||
OccupiedCoreAssumption,
|
||||
ResponseChannel<Option<PersistedValidationData>>,
|
||||
),
|
||||
/// Sends back `true` if the commitments pass all acceptance criteria checks.
|
||||
CheckValidationOutputs(
|
||||
ParaId,
|
||||
CandidateCommitments,
|
||||
RuntimeApiSender<bool>,
|
||||
),
|
||||
/// Get the session index for children of the block. This can be used to construct a signing
|
||||
/// context.
|
||||
SessionIndexForChild(ResponseChannel<SessionIndex>),
|
||||
/// Get the validation code for a specific para, using the given occupied core assumption.
|
||||
ValidationCode(ParaId, OccupiedCoreAssumption, ResponseChannel<Option<ValidationCode>>),
|
||||
/// Fetch the historical validation code used by a para for candidates executed in
|
||||
/// the context of a given block height in the current chain.
|
||||
HistoricalValidationCode(ParaId, BlockNumber, ResponseChannel<Option<ValidationCode>>),
|
||||
/// Get a committed candidate receipt for all candidates pending availability.
|
||||
CandidatePendingAvailability(ParaId, ResponseChannel<Option<CommittedCandidateReceipt>>),
|
||||
/// Get all events concerning candidates in the last block.
|
||||
CandidateEvents(ResponseChannel<Vec<CandidateEvent>>),
|
||||
/// Get the session info for the given session, if stored.
|
||||
SessionInfo(SessionIndex, ResponseChannel<Option<SessionInfo>>),
|
||||
/// Get all the pending inbound messages in the downward message queue for a para.
|
||||
DmqContents(ParaId, ResponseChannel<Vec<InboundDownwardMessage<BlockNumber>>>),
|
||||
/// Get the contents of all channels addressed to the given recipient. Channels that have no
|
||||
/// messages in them are also included.
|
||||
InboundHrmpChannelsContents(ParaId, ResponseChannel<BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>>),
|
||||
}
|
||||
|
||||
enum RuntimeApiMessage {
|
||||
/// Make a request of the runtime API against the post-state of the given relay-parent.
|
||||
Request(Hash, RuntimeApiRequest),
|
||||
/// Make a request of the runtime API against the post-state of the given relay-parent.
|
||||
Request(Hash, RuntimeApiRequest),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -484,16 +526,16 @@ This is a network protocol that receives messages of type [`StatementDistributio
|
||||
|
||||
```rust
|
||||
enum StatementDistributionMessage {
|
||||
/// An update from the network bridge.
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<StatementDistributionV1Message>),
|
||||
/// We have validated a candidate and want to share our judgment with our peers.
|
||||
/// The hash is the relay parent.
|
||||
///
|
||||
/// The statement distribution subsystem assumes that the statement should be correctly
|
||||
/// signed.
|
||||
Share(Hash, SignedFullStatement),
|
||||
/// Register a listener to be notified on any new statements.
|
||||
RegisterStatementListener(ResponseChannel<SignedFullStatement>),
|
||||
/// An update from the network bridge.
|
||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<StatementDistributionV1Message>),
|
||||
/// We have validated a candidate and want to share our judgment with our peers.
|
||||
/// The hash is the relay parent.
|
||||
///
|
||||
/// The statement distribution subsystem assumes that the statement should be correctly
|
||||
/// signed.
|
||||
Share(Hash, SignedFullStatement),
|
||||
/// Register a listener to be notified on any new statements.
|
||||
RegisterStatementListener(ResponseChannel<SignedFullStatement>),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -505,11 +547,11 @@ Various modules request that the [Candidate Validation subsystem](../node/utilit
|
||||
|
||||
/// Result of the validation of the candidate.
|
||||
enum ValidationResult {
|
||||
/// Candidate is valid, and here are the outputs and the validation data used to form inputs.
|
||||
/// In practice, this should be a shared type so that validation caching can be done.
|
||||
Valid(CandidateCommitments, PersistedValidationData),
|
||||
/// Candidate is invalid.
|
||||
Invalid,
|
||||
/// Candidate is valid, and here are the outputs and the validation data used to form inputs.
|
||||
/// In practice, this should be a shared type so that validation caching can be done.
|
||||
Valid(CandidateCommitments, PersistedValidationData),
|
||||
/// Candidate is invalid.
|
||||
Invalid,
|
||||
}
|
||||
|
||||
/// Messages received by the Validation subsystem.
|
||||
@@ -521,37 +563,37 @@ enum ValidationResult {
|
||||
/// or `Ok(ValidationResult::Invalid)`.
|
||||
#[derive(Debug)]
|
||||
pub enum CandidateValidationMessage {
|
||||
/// Validate a candidate with provided parameters using relay-chain state.
|
||||
///
|
||||
/// This will implicitly attempt to gather the `PersistedValidationData` and `ValidationCode`
|
||||
/// from the runtime API of the chain, based on the `relay_parent`
|
||||
/// of the `CandidateDescriptor`.
|
||||
///
|
||||
/// This will also perform checking of validation outputs against the acceptance criteria.
|
||||
///
|
||||
/// If there is no state available which can provide this data or the core for
|
||||
/// the para is not free at the relay-parent, an error is returned.
|
||||
ValidateFromChainState(
|
||||
CandidateDescriptor,
|
||||
Arc<PoV>,
|
||||
oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
|
||||
),
|
||||
/// Validate a candidate with provided, exhaustive parameters for validation.
|
||||
///
|
||||
/// Explicitly provide the `PersistedValidationData` and `ValidationCode` so this can do full
|
||||
/// validation without needing to access the state of the relay-chain.
|
||||
///
|
||||
/// This request doesn't involve acceptance criteria checking, therefore only useful for the
|
||||
/// cases where the validity of the candidate is established. This is the case for the typical
|
||||
/// use-case: secondary checkers would use this request relying on the full prior checks
|
||||
/// performed by the relay-chain.
|
||||
ValidateFromExhaustive(
|
||||
PersistedValidationData,
|
||||
ValidationCode,
|
||||
CandidateDescriptor,
|
||||
Arc<PoV>,
|
||||
oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
|
||||
),
|
||||
/// Validate a candidate with provided parameters using relay-chain state.
|
||||
///
|
||||
/// This will implicitly attempt to gather the `PersistedValidationData` and `ValidationCode`
|
||||
/// from the runtime API of the chain, based on the `relay_parent`
|
||||
/// of the `CandidateDescriptor`.
|
||||
///
|
||||
/// This will also perform checking of validation outputs against the acceptance criteria.
|
||||
///
|
||||
/// If there is no state available which can provide this data or the core for
|
||||
/// the para is not free at the relay-parent, an error is returned.
|
||||
ValidateFromChainState(
|
||||
CandidateDescriptor,
|
||||
Arc<PoV>,
|
||||
oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
|
||||
),
|
||||
/// Validate a candidate with provided, exhaustive parameters for validation.
|
||||
///
|
||||
/// Explicitly provide the `PersistedValidationData` and `ValidationCode` so this can do full
|
||||
/// validation without needing to access the state of the relay-chain.
|
||||
///
|
||||
/// This request doesn't involve acceptance criteria checking, therefore only useful for the
|
||||
/// cases where the validity of the candidate is established. This is the case for the typical
|
||||
/// use-case: secondary checkers would use this request relying on the full prior checks
|
||||
/// performed by the relay-chain.
|
||||
ValidateFromExhaustive(
|
||||
PersistedValidationData,
|
||||
ValidationCode,
|
||||
CandidateDescriptor,
|
||||
Arc<PoV>,
|
||||
oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
|
||||
),
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
//! indicating whether the candidate is valid or invalid. Once a threshold of the committee
|
||||
//! has signed validity statements, the candidate may be marked includable.
|
||||
|
||||
use std::collections::hash_map::{HashMap, Entry};
|
||||
use std::collections::hash_map::{self, Entry, HashMap};
|
||||
use std::hash::Hash;
|
||||
use std::fmt::Debug;
|
||||
|
||||
@@ -34,7 +34,7 @@ use parity_scale_codec::{Encode, Decode};
|
||||
|
||||
/// Context for the statement table.
|
||||
pub trait Context {
|
||||
/// A authority ID
|
||||
/// An authority ID
|
||||
type AuthorityId: Debug + Hash + Eq + Clone;
|
||||
/// The digest (hash or other unique attribute) of a candidate.
|
||||
type Digest: Debug + Hash + Eq + Clone;
|
||||
@@ -61,32 +61,29 @@ pub trait Context {
|
||||
|
||||
/// Statements circulated among peers.
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Encode, Decode)]
|
||||
pub enum Statement<C, D> {
|
||||
/// Broadcast by an authority to indicate that this is his candidate for
|
||||
/// inclusion.
|
||||
pub enum Statement<Candidate, Digest> {
|
||||
/// Broadcast by an authority to indicate that this is its candidate for inclusion.
|
||||
///
|
||||
/// Broadcasting two different candidate messages per round is not allowed.
|
||||
#[codec(index = "1")]
|
||||
Candidate(C),
|
||||
/// Broadcast by a authority to attest that the candidate with given digest
|
||||
/// is valid.
|
||||
Candidate(Candidate),
|
||||
/// Broadcast by a authority to attest that the candidate with given digest is valid.
|
||||
#[codec(index = "2")]
|
||||
Valid(D),
|
||||
/// Broadcast by a authority to attest that the candidate with given digest
|
||||
/// is invalid.
|
||||
Valid(Digest),
|
||||
/// Broadcast by a authority to attest that the candidate with given digest is invalid.
|
||||
#[codec(index = "3")]
|
||||
Invalid(D),
|
||||
Invalid(Digest),
|
||||
}
|
||||
|
||||
/// A signed statement.
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Encode, Decode)]
|
||||
pub struct SignedStatement<C, D, V, S> {
|
||||
pub struct SignedStatement<Candidate, Digest, AuthorityId, Signature> {
|
||||
/// The statement.
|
||||
pub statement: Statement<C, D>,
|
||||
pub statement: Statement<Candidate, Digest>,
|
||||
/// The signature.
|
||||
pub signature: S,
|
||||
pub signature: Signature,
|
||||
/// The sender.
|
||||
pub sender: V,
|
||||
pub sender: AuthorityId,
|
||||
}
|
||||
|
||||
/// Misbehavior: voting more than one way on candidate validity.
|
||||
@@ -94,77 +91,124 @@ pub struct SignedStatement<C, D, V, S> {
|
||||
/// Since there are three possible ways to vote, a double vote is possible in
|
||||
/// three possible combinations (unordered)
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum ValidityDoubleVote<C, D, S> {
|
||||
pub enum ValidityDoubleVote<Candidate, Digest, Signature> {
|
||||
/// Implicit vote by issuing and explicitly voting validity.
|
||||
IssuedAndValidity((C, S), (D, S)),
|
||||
IssuedAndValidity((Candidate, Signature), (Digest, Signature)),
|
||||
/// Implicit vote by issuing and explicitly voting invalidity
|
||||
IssuedAndInvalidity((C, S), (D, S)),
|
||||
IssuedAndInvalidity((Candidate, Signature), (Digest, Signature)),
|
||||
/// Direct votes for validity and invalidity
|
||||
ValidityAndInvalidity(C, S, S),
|
||||
ValidityAndInvalidity(Candidate, Signature, Signature),
|
||||
}
|
||||
|
||||
impl<Candidate, Digest, Signature> ValidityDoubleVote<Candidate, Digest, Signature> {
|
||||
/// Deconstruct this misbehavior into two `(Statement, Signature)` pairs, erasing the information
|
||||
/// about precisely what the problem was.
|
||||
pub fn deconstruct<Ctx>(self) -> (
|
||||
(Statement<Candidate, Digest>, Signature),
|
||||
(Statement<Candidate, Digest>, Signature),
|
||||
)
|
||||
where
|
||||
Ctx: Context<Candidate=Candidate, Digest=Digest, Signature=Signature>,
|
||||
Candidate: Debug + Ord + Eq + Clone,
|
||||
Digest: Debug + Hash + Eq + Clone,
|
||||
Signature: Debug + Eq + Clone,
|
||||
{
|
||||
match self {
|
||||
Self::IssuedAndValidity((c, s1), (d, s2)) => {
|
||||
((Statement::Candidate(c), s1), (Statement::Valid(d), s2))
|
||||
}
|
||||
Self::IssuedAndInvalidity((c, s1), (d, s2)) => {
|
||||
((Statement::Candidate(c), s1), (Statement::Invalid(d), s2))
|
||||
}
|
||||
Self::ValidityAndInvalidity(c, s1, s2) => {
|
||||
(
|
||||
(Statement::Valid(Ctx::candidate_digest(&c)), s1),
|
||||
(Statement::Invalid(Ctx::candidate_digest(&c)), s2),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Misbehavior: multiple signatures on same statement.
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum DoubleSign<C, D, S> {
|
||||
pub enum DoubleSign<Candidate, Digest, Signature> {
|
||||
/// On candidate.
|
||||
Candidate(C, S, S),
|
||||
Candidate(Candidate, Signature, Signature),
|
||||
/// On validity.
|
||||
Validity(D, S, S),
|
||||
Validity(Digest, Signature, Signature),
|
||||
/// On invalidity.
|
||||
Invalidity(D, S, S),
|
||||
Invalidity(Digest, Signature, Signature),
|
||||
}
|
||||
|
||||
impl<Candidate, Digest, Signature> DoubleSign<Candidate, Digest, Signature> {
|
||||
/// Deconstruct this misbehavior into a statement with two signatures, erasing the information about
|
||||
/// precisely where in the process the issue was detected.
|
||||
pub fn deconstruct(self) -> (Statement<Candidate, Digest>, Signature, Signature) {
|
||||
match self {
|
||||
Self::Candidate(candidate, a, b) => (Statement::Candidate(candidate), a, b),
|
||||
Self::Validity(digest, a, b) => (Statement::Valid(digest), a, b),
|
||||
Self::Invalidity(digest, a, b) => (Statement::Invalid(digest), a, b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Misbehavior: declaring multiple candidates.
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub struct MultipleCandidates<C, S> {
|
||||
pub struct MultipleCandidates<Candidate, Signature> {
|
||||
/// The first candidate seen.
|
||||
pub first: (C, S),
|
||||
pub first: (Candidate, Signature),
|
||||
/// The second candidate seen.
|
||||
pub second: (C, S),
|
||||
pub second: (Candidate, Signature),
|
||||
}
|
||||
|
||||
/// Misbehavior: submitted statement for wrong group.
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub struct UnauthorizedStatement<C, D, V, S> {
|
||||
pub struct UnauthorizedStatement<Candidate, Digest, AuthorityId, Signature> {
|
||||
/// A signed statement which was submitted without proper authority.
|
||||
pub statement: SignedStatement<C, D, V, S>,
|
||||
pub statement: SignedStatement<Candidate, Digest, AuthorityId, Signature>,
|
||||
}
|
||||
|
||||
/// Different kinds of misbehavior. All of these kinds of malicious misbehavior
|
||||
/// are easily provable and extremely disincentivized.
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Misbehavior<C, D, V, S> {
|
||||
pub enum Misbehavior<Candidate, Digest, AuthorityId, Signature> {
|
||||
/// Voted invalid and valid on validity.
|
||||
ValidityDoubleVote(ValidityDoubleVote<C, D, S>),
|
||||
ValidityDoubleVote(ValidityDoubleVote<Candidate, Digest, Signature>),
|
||||
/// Submitted multiple candidates.
|
||||
MultipleCandidates(MultipleCandidates<C, S>),
|
||||
MultipleCandidates(MultipleCandidates<Candidate, Signature>),
|
||||
/// Submitted a message that was unauthorized.
|
||||
UnauthorizedStatement(UnauthorizedStatement<C, D, V, S>),
|
||||
UnauthorizedStatement(UnauthorizedStatement<Candidate, Digest, AuthorityId, Signature>),
|
||||
/// Submitted two valid signatures for the same message.
|
||||
DoubleSign(DoubleSign<C, D, S>),
|
||||
DoubleSign(DoubleSign<Candidate, Digest, Signature>),
|
||||
}
|
||||
|
||||
/// Type alias for misbehavior corresponding to context type.
|
||||
pub type MisbehaviorFor<C> = Misbehavior<<C as Context>::Candidate, <C as Context>::Digest, <C as Context>::AuthorityId, <C as Context>::Signature>;
|
||||
pub type MisbehaviorFor<Ctx> = Misbehavior<
|
||||
<Ctx as Context>::Candidate,
|
||||
<Ctx as Context>::Digest,
|
||||
<Ctx as Context>::AuthorityId,
|
||||
<Ctx as Context>::Signature,
|
||||
>;
|
||||
|
||||
// kinds of votes for validity
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
enum ValidityVote<S: Eq + Clone> {
|
||||
enum ValidityVote<Signature: Eq + Clone> {
|
||||
// implicit validity vote by issuing
|
||||
Issued(S),
|
||||
Issued(Signature),
|
||||
// direct validity vote
|
||||
Valid(S),
|
||||
Valid(Signature),
|
||||
// direct invalidity vote
|
||||
Invalid(S),
|
||||
Invalid(Signature),
|
||||
}
|
||||
|
||||
/// A summary of import of a statement.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Summary<D, G> {
|
||||
pub struct Summary<Digest, Group> {
|
||||
/// The digest of the candidate referenced.
|
||||
pub candidate: D,
|
||||
pub candidate: Digest,
|
||||
/// The group that the candidate is in.
|
||||
pub group_id: G,
|
||||
pub group_id: Group,
|
||||
/// How many validity votes are currently witnessed.
|
||||
pub validity_votes: usize,
|
||||
/// Whether this has been signalled bad by at least one participant.
|
||||
@@ -173,13 +217,13 @@ pub struct Summary<D, G> {
|
||||
|
||||
/// A validity attestation.
|
||||
#[derive(Clone, PartialEq, Decode, Encode)]
|
||||
pub enum ValidityAttestation<S> {
|
||||
pub enum ValidityAttestation<Signature> {
|
||||
/// implicit validity attestation by issuing.
|
||||
/// This corresponds to issuance of a `Candidate` statement.
|
||||
Implicit(S),
|
||||
Implicit(Signature),
|
||||
/// An explicit attestation. This corresponds to issuance of a
|
||||
/// `Valid` statement.
|
||||
Explicit(S),
|
||||
Explicit(Signature),
|
||||
}
|
||||
|
||||
impl Into<PrimitiveValidityAttestation> for ValidityAttestation<ValidatorSignature> {
|
||||
@@ -203,14 +247,14 @@ pub struct AttestedCandidate<Group, Candidate, AuthorityId, Signature> {
|
||||
}
|
||||
|
||||
/// Stores votes and data about a candidate.
|
||||
pub struct CandidateData<C: Context> {
|
||||
group_id: C::GroupId,
|
||||
candidate: C::Candidate,
|
||||
validity_votes: HashMap<C::AuthorityId, ValidityVote<C::Signature>>,
|
||||
indicated_bad_by: Vec<C::AuthorityId>,
|
||||
pub struct CandidateData<Ctx: Context> {
|
||||
group_id: Ctx::GroupId,
|
||||
candidate: Ctx::Candidate,
|
||||
validity_votes: HashMap<Ctx::AuthorityId, ValidityVote<Ctx::Signature>>,
|
||||
indicated_bad_by: Vec<Ctx::AuthorityId>,
|
||||
}
|
||||
|
||||
impl<C: Context> CandidateData<C> {
|
||||
impl<Ctx: Context> CandidateData<Ctx> {
|
||||
/// whether this has been indicated bad by anyone.
|
||||
pub fn indicated_bad(&self) -> bool {
|
||||
!self.indicated_bad_by.is_empty()
|
||||
@@ -220,7 +264,7 @@ impl<C: Context> CandidateData<C> {
|
||||
/// If the candidate can be included, it will return `Some`.
|
||||
pub fn attested(&self, validity_threshold: usize)
|
||||
-> Option<AttestedCandidate<
|
||||
C::GroupId, C::Candidate, C::AuthorityId, C::Signature,
|
||||
Ctx::GroupId, Ctx::Candidate, Ctx::AuthorityId, Ctx::Signature,
|
||||
>>
|
||||
{
|
||||
if self.can_be_included(validity_threshold) {
|
||||
@@ -259,7 +303,7 @@ impl<C: Context> CandidateData<C> {
|
||||
self.validity_votes.len() >= validity_threshold
|
||||
}
|
||||
|
||||
fn summary(&self, digest: C::Digest) -> Summary<C::Digest, C::GroupId> {
|
||||
fn summary(&self, digest: Ctx::Digest) -> Summary<Ctx::Digest, Ctx::GroupId> {
|
||||
Summary {
|
||||
candidate: digest,
|
||||
group_id: self.group_id.clone(),
|
||||
@@ -270,11 +314,11 @@ impl<C: Context> CandidateData<C> {
|
||||
}
|
||||
|
||||
// authority metadata
|
||||
struct AuthorityData<C: Context> {
|
||||
proposal: Option<(C::Digest, C::Signature)>,
|
||||
struct AuthorityData<Ctx: Context> {
|
||||
proposal: Option<(Ctx::Digest, Ctx::Signature)>,
|
||||
}
|
||||
|
||||
impl<C: Context> Default for AuthorityData<C> {
|
||||
impl<Ctx: Context> Default for AuthorityData<Ctx> {
|
||||
fn default() -> Self {
|
||||
AuthorityData {
|
||||
proposal: None,
|
||||
@@ -283,20 +327,20 @@ impl<C: Context> Default for AuthorityData<C> {
|
||||
}
|
||||
|
||||
/// Type alias for the result of a statement import.
|
||||
pub type ImportResult<C> = Result<
|
||||
Option<Summary<<C as Context>::Digest, <C as Context>::GroupId>>,
|
||||
MisbehaviorFor<C>
|
||||
pub type ImportResult<Ctx> = Result<
|
||||
Option<Summary<<Ctx as Context>::Digest, <Ctx as Context>::GroupId>>,
|
||||
MisbehaviorFor<Ctx>
|
||||
>;
|
||||
|
||||
/// Stores votes
|
||||
pub struct Table<C: Context> {
|
||||
authority_data: HashMap<C::AuthorityId, AuthorityData<C>>,
|
||||
detected_misbehavior: HashMap<C::AuthorityId, MisbehaviorFor<C>>,
|
||||
candidate_votes: HashMap<C::Digest, CandidateData<C>>,
|
||||
includable_count: HashMap<C::GroupId, usize>,
|
||||
pub struct Table<Ctx: Context> {
|
||||
authority_data: HashMap<Ctx::AuthorityId, AuthorityData<Ctx>>,
|
||||
detected_misbehavior: HashMap<Ctx::AuthorityId, Vec<MisbehaviorFor<Ctx>>>,
|
||||
candidate_votes: HashMap<Ctx::Digest, CandidateData<Ctx>>,
|
||||
includable_count: HashMap<Ctx::GroupId, usize>,
|
||||
}
|
||||
|
||||
impl<C: Context> Default for Table<C> {
|
||||
impl<Ctx: Context> Default for Table<Ctx> {
|
||||
fn default() -> Self {
|
||||
Table {
|
||||
authority_data: HashMap::new(),
|
||||
@@ -307,15 +351,15 @@ impl<C: Context> Default for Table<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Context> Table<C> {
|
||||
impl<Ctx: Context> Table<Ctx> {
|
||||
/// Produce a set of proposed candidates.
|
||||
///
|
||||
/// This will be at most one per group, consisting of the
|
||||
/// best candidate for each group with requisite votes for inclusion.
|
||||
///
|
||||
/// The vector is sorted in ascending order by group id.
|
||||
pub fn proposed_candidates(&self, context: &C) -> Vec<AttestedCandidate<
|
||||
C::GroupId, C::Candidate, C::AuthorityId, C::Signature,
|
||||
pub fn proposed_candidates(&self, context: &Ctx) -> Vec<AttestedCandidate<
|
||||
Ctx::GroupId, Ctx::Candidate, Ctx::AuthorityId, Ctx::Signature,
|
||||
>> {
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::btree_map::Entry as BTreeEntry;
|
||||
@@ -354,7 +398,7 @@ impl<C: Context> Table<C> {
|
||||
}
|
||||
|
||||
/// Whether a candidate can be included.
|
||||
pub fn candidate_includable(&self, digest: &C::Digest, context: &C) -> bool {
|
||||
pub fn candidate_includable(&self, digest: &Ctx::Digest, context: &Ctx) -> bool {
|
||||
self.candidate_votes.get(digest).map_or(false, |data| {
|
||||
let v_threshold = context.requisite_votes(&data.group_id);
|
||||
data.can_be_included(v_threshold)
|
||||
@@ -364,9 +408,9 @@ impl<C: Context> Table<C> {
|
||||
/// Get the attested candidate for `digest`.
|
||||
///
|
||||
/// Returns `Some(_)` if the candidate exists and is includable.
|
||||
pub fn attested_candidate(&self, digest: &C::Digest, context: &C)
|
||||
pub fn attested_candidate(&self, digest: &Ctx::Digest, context: &Ctx)
|
||||
-> Option<AttestedCandidate<
|
||||
C::GroupId, C::Candidate, C::AuthorityId, C::Signature,
|
||||
Ctx::GroupId, Ctx::Candidate, Ctx::AuthorityId, Ctx::Signature,
|
||||
>>
|
||||
{
|
||||
self.candidate_votes.get(digest).and_then(|data| {
|
||||
@@ -384,9 +428,9 @@ impl<C: Context> Table<C> {
|
||||
/// If this returns `None`, the statement was either duplicate or invalid.
|
||||
pub fn import_statement(
|
||||
&mut self,
|
||||
context: &C,
|
||||
statement: SignedStatement<C::Candidate, C::Digest, C::AuthorityId, C::Signature>,
|
||||
) -> Option<Summary<C::Digest, C::GroupId>> {
|
||||
context: &Ctx,
|
||||
statement: SignedStatement<Ctx::Candidate, Ctx::Digest, Ctx::AuthorityId, Ctx::Signature>,
|
||||
) -> Option<Summary<Ctx::Digest, Ctx::GroupId>> {
|
||||
let SignedStatement { statement, signature, sender: signer } = statement;
|
||||
|
||||
let res = match statement {
|
||||
@@ -414,25 +458,34 @@ impl<C: Context> Table<C> {
|
||||
Ok(maybe_summary) => maybe_summary,
|
||||
Err(misbehavior) => {
|
||||
// all misbehavior in agreement is provable and actively malicious.
|
||||
// punishments are not cumulative.
|
||||
self.detected_misbehavior.insert(signer, misbehavior);
|
||||
// punishments may be cumulative.
|
||||
self.detected_misbehavior.entry(signer).or_default().push(misbehavior);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a candidate by digest.
|
||||
pub fn get_candidate(&self, digest: &C::Digest) -> Option<&C::Candidate> {
|
||||
pub fn get_candidate(&self, digest: &Ctx::Digest) -> Option<&Ctx::Candidate> {
|
||||
self.candidate_votes.get(digest).map(|d| &d.candidate)
|
||||
}
|
||||
|
||||
/// Access all witnessed misbehavior.
|
||||
pub fn get_misbehavior(&self)
|
||||
-> &HashMap<C::AuthorityId, MisbehaviorFor<C>>
|
||||
-> &HashMap<Ctx::AuthorityId, Vec<MisbehaviorFor<Ctx>>>
|
||||
{
|
||||
&self.detected_misbehavior
|
||||
}
|
||||
|
||||
/// Create a draining iterator of misbehaviors.
|
||||
///
|
||||
/// This consumes all detected misbehaviors, even if the iterator is not completely consumed.
|
||||
pub fn drain_misbehaviors(&mut self) -> DrainMisbehaviors<'_, Ctx> {
|
||||
self.detected_misbehavior
|
||||
.drain()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Get the current number of parachains with includable candidates.
|
||||
pub fn includable_count(&self) -> usize {
|
||||
self.includable_count.len()
|
||||
@@ -440,26 +493,26 @@ impl<C: Context> Table<C> {
|
||||
|
||||
fn import_candidate(
|
||||
&mut self,
|
||||
context: &C,
|
||||
from: C::AuthorityId,
|
||||
candidate: C::Candidate,
|
||||
signature: C::Signature,
|
||||
) -> ImportResult<C> {
|
||||
let group = C::candidate_group(&candidate);
|
||||
if !context.is_member_of(&from, &group) {
|
||||
context: &Ctx,
|
||||
authority: Ctx::AuthorityId,
|
||||
candidate: Ctx::Candidate,
|
||||
signature: Ctx::Signature,
|
||||
) -> ImportResult<Ctx> {
|
||||
let group = Ctx::candidate_group(&candidate);
|
||||
if !context.is_member_of(&authority, &group) {
|
||||
return Err(Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
|
||||
statement: SignedStatement {
|
||||
signature,
|
||||
statement: Statement::Candidate(candidate),
|
||||
sender: from,
|
||||
sender: authority,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
// check that authority hasn't already specified another candidate.
|
||||
let digest = C::candidate_digest(&candidate);
|
||||
let digest = Ctx::candidate_digest(&candidate);
|
||||
|
||||
let new_proposal = match self.authority_data.entry(from.clone()) {
|
||||
let new_proposal = match self.authority_data.entry(authority.clone()) {
|
||||
Entry::Occupied(mut occ) => {
|
||||
// if digest is different, fetch candidate and
|
||||
// note misbehavior.
|
||||
@@ -510,7 +563,7 @@ impl<C: Context> Table<C> {
|
||||
|
||||
self.validity_vote(
|
||||
context,
|
||||
from,
|
||||
authority,
|
||||
digest,
|
||||
ValidityVote::Issued(signature),
|
||||
)
|
||||
@@ -518,11 +571,11 @@ impl<C: Context> Table<C> {
|
||||
|
||||
fn validity_vote(
|
||||
&mut self,
|
||||
context: &C,
|
||||
from: C::AuthorityId,
|
||||
digest: C::Digest,
|
||||
vote: ValidityVote<C::Signature>,
|
||||
) -> ImportResult<C> {
|
||||
context: &Ctx,
|
||||
from: Ctx::AuthorityId,
|
||||
digest: Ctx::Digest,
|
||||
vote: ValidityVote<Ctx::Signature>,
|
||||
) -> ImportResult<Ctx> {
|
||||
let votes = match self.candidate_votes.get_mut(&digest) {
|
||||
None => return Ok(None),
|
||||
Some(votes) => votes,
|
||||
@@ -608,9 +661,68 @@ impl<C: Context> Table<C> {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_includable_count<G: Hash + Eq + Clone>(
|
||||
map: &mut HashMap<G, usize>,
|
||||
group_id: &G,
|
||||
type Drain<'a, Ctx> = hash_map::Drain<'a, <Ctx as Context>::AuthorityId, Vec<MisbehaviorFor<Ctx>>>;
|
||||
|
||||
struct MisbehaviorForAuthority<Ctx: Context> {
|
||||
id: Ctx::AuthorityId,
|
||||
misbehaviors: Vec<MisbehaviorFor<Ctx>>,
|
||||
}
|
||||
|
||||
impl<Ctx: Context> From<(Ctx::AuthorityId, Vec<MisbehaviorFor<Ctx>>)> for MisbehaviorForAuthority<Ctx> {
|
||||
fn from((id, mut misbehaviors): (Ctx::AuthorityId, Vec<MisbehaviorFor<Ctx>>)) -> Self {
|
||||
// we're going to be popping items off this list in the iterator, so reverse it now to
|
||||
// preserve the original ordering.
|
||||
misbehaviors.reverse();
|
||||
Self { id, misbehaviors }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: Context> Iterator for MisbehaviorForAuthority<Ctx> {
|
||||
type Item = (Ctx::AuthorityId, MisbehaviorFor<Ctx>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.misbehaviors.pop().map(|misbehavior| (self.id.clone(), misbehavior))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DrainMisbehaviors<'a, Ctx: Context> {
|
||||
drain: Drain<'a, Ctx>,
|
||||
in_progress: Option<MisbehaviorForAuthority<Ctx>>,
|
||||
}
|
||||
|
||||
impl<'a, Ctx: Context> From<Drain<'a, Ctx>> for DrainMisbehaviors<'a, Ctx> {
|
||||
fn from(drain: Drain<'a, Ctx>) -> Self {
|
||||
Self {
|
||||
drain,
|
||||
in_progress: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Ctx: Context> DrainMisbehaviors<'a, Ctx> {
|
||||
fn maybe_item(&mut self) -> Option<(Ctx::AuthorityId, MisbehaviorFor<Ctx>)> {
|
||||
self.in_progress.as_mut().and_then(Iterator::next)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Ctx: Context> Iterator for DrainMisbehaviors<'a, Ctx> {
|
||||
type Item = (Ctx::AuthorityId, MisbehaviorFor<Ctx>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// Note: this implementation will prematurely return `None` if `self.drain.next()` ever returns a
|
||||
// tuple whose vector is empty. That will never currently happen, as the only modification
|
||||
// to the backing map is currently via `drain` and `entry(...).or_default().push(...)`.
|
||||
// However, future code changes might change that property.
|
||||
self.maybe_item().or_else(|| {
|
||||
self.in_progress = self.drain.next().map(Into::into);
|
||||
self.maybe_item()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn update_includable_count<Group: Hash + Eq + Clone>(
|
||||
map: &mut HashMap<Group, usize>,
|
||||
group_id: &Group,
|
||||
was_includable: bool,
|
||||
is_includable: bool,
|
||||
) {
|
||||
@@ -633,7 +745,7 @@ mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn create<C: Context>() -> Table<C> {
|
||||
fn create<Candidate: Context>() -> Table<Candidate> {
|
||||
Table::default()
|
||||
}
|
||||
|
||||
@@ -721,8 +833,8 @@ mod tests {
|
||||
|
||||
table.import_statement(&context, statement_b);
|
||||
assert_eq!(
|
||||
table.detected_misbehavior.get(&AuthorityId(1)).unwrap(),
|
||||
&Misbehavior::MultipleCandidates(MultipleCandidates {
|
||||
table.detected_misbehavior[&AuthorityId(1)][0],
|
||||
Misbehavior::MultipleCandidates(MultipleCandidates {
|
||||
first: (Candidate(2, 100), Signature(1)),
|
||||
second: (Candidate(2, 999), Signature(1)),
|
||||
})
|
||||
@@ -749,8 +861,8 @@ mod tests {
|
||||
table.import_statement(&context, statement);
|
||||
|
||||
assert_eq!(
|
||||
table.detected_misbehavior.get(&AuthorityId(1)).unwrap(),
|
||||
&Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
|
||||
table.detected_misbehavior[&AuthorityId(1)][0],
|
||||
Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
|
||||
statement: SignedStatement {
|
||||
statement: Statement::Candidate(Candidate(2, 100)),
|
||||
signature: Signature(1),
|
||||
@@ -793,8 +905,8 @@ mod tests {
|
||||
table.import_statement(&context, bad_validity_vote);
|
||||
|
||||
assert_eq!(
|
||||
table.detected_misbehavior.get(&AuthorityId(2)).unwrap(),
|
||||
&Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
|
||||
table.detected_misbehavior[&AuthorityId(2)][0],
|
||||
Misbehavior::UnauthorizedStatement(UnauthorizedStatement {
|
||||
statement: SignedStatement {
|
||||
statement: Statement::Valid(candidate_a_digest),
|
||||
signature: Signature(2),
|
||||
@@ -844,8 +956,8 @@ mod tests {
|
||||
table.import_statement(&context, invalid_statement);
|
||||
|
||||
assert_eq!(
|
||||
table.detected_misbehavior.get(&AuthorityId(2)).unwrap(),
|
||||
&Misbehavior::ValidityDoubleVote(ValidityDoubleVote::ValidityAndInvalidity(
|
||||
table.detected_misbehavior[&AuthorityId(2)][0],
|
||||
Misbehavior::ValidityDoubleVote(ValidityDoubleVote::ValidityAndInvalidity(
|
||||
Candidate(2, 100),
|
||||
Signature(2),
|
||||
Signature(2),
|
||||
@@ -978,8 +1090,8 @@ mod tests {
|
||||
|
||||
table.import_statement(&context, extra_vote);
|
||||
assert_eq!(
|
||||
table.detected_misbehavior.get(&AuthorityId(1)).unwrap(),
|
||||
&Misbehavior::ValidityDoubleVote(ValidityDoubleVote::IssuedAndValidity(
|
||||
table.detected_misbehavior[&AuthorityId(1)][0],
|
||||
Misbehavior::ValidityDoubleVote(ValidityDoubleVote::IssuedAndValidity(
|
||||
(Candidate(2, 100), Signature(1)),
|
||||
(Digest(100), Signature(1)),
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user