mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-25 09:25:46 +00:00
Blocks carry full attestations for candidates (#42)
* statement table yields fully-attested candidates * attestation types in polkadot-primitives * propose block with fully-attested candidates in consensus * some signature-checking logic in the runtime * fix runtime compilation * ensure attestations are full and without duplicate when checking * fix consensus-service compilation * add some tests * use bitvec from crates.io now that it's published * sign statements based on primitive statement's encoding * remove some serialize bounds * Fix error message with duplicate availability attestations Co-Authored-By: rphmeier <rphmeier@gmail.com>
This commit is contained in:
committed by
GitHub
parent
c31f8168df
commit
7b46856159
@@ -177,7 +177,7 @@ enum ValidityVote<S: Eq + Clone> {
|
||||
pub struct Summary<D, G> {
|
||||
/// The digest of the candidate referenced.
|
||||
pub candidate: D,
|
||||
/// The group that candidate is in.
|
||||
/// The group that the candidate is in.
|
||||
pub group_id: G,
|
||||
/// How many validity votes are currently witnessed.
|
||||
pub validity_votes: usize,
|
||||
@@ -187,6 +187,30 @@ pub struct Summary<D, G> {
|
||||
pub signalled_bad: bool,
|
||||
}
|
||||
|
||||
/// A validity attestation.
|
||||
#[derive(Clone, PartialEq, Decode, Encode)]
|
||||
pub enum ValidityAttestation<S> {
|
||||
/// implicit validity attestation by issuing.
|
||||
/// This corresponds to issuance of a `Candidate` statement.
|
||||
Implicit(S),
|
||||
/// An explicit attestation. This corresponds to issuance of a
|
||||
/// `Valid` statement.
|
||||
Explicit(S),
|
||||
}
|
||||
|
||||
/// An attested-to candidate.
|
||||
#[derive(Clone, PartialEq, Decode, Encode)]
|
||||
pub struct AttestedCandidate<Group, Candidate, AuthorityId, Signature> {
|
||||
/// The group ID that the candidate is in.
|
||||
pub group_id: Group,
|
||||
/// The candidate data.
|
||||
pub candidate: Candidate,
|
||||
/// Validity attestations.
|
||||
pub validity_votes: Vec<(AuthorityId, ValidityAttestation<Signature>)>,
|
||||
/// Availability attestations.
|
||||
pub availability_votes: Vec<(AuthorityId, Signature)>
|
||||
}
|
||||
|
||||
/// Stores votes and data about a candidate.
|
||||
pub struct CandidateData<C: Context> {
|
||||
group_id: C::GroupId,
|
||||
@@ -202,6 +226,53 @@ impl<C: Context> CandidateData<C> {
|
||||
!self.indicated_bad_by.is_empty()
|
||||
}
|
||||
|
||||
/// Yield a full attestation for a candidate.
|
||||
/// If the candidate can be included, it will return `Some`.
|
||||
pub fn attested(&self, validity_threshold: usize, availability_threshold: usize)
|
||||
-> Option<AttestedCandidate<
|
||||
C::GroupId, C::Candidate, C::AuthorityId, C::Signature,
|
||||
>>
|
||||
{
|
||||
if self.can_be_included(validity_threshold, availability_threshold) {
|
||||
let validity_votes: Vec<_> = self.validity_votes.iter()
|
||||
.filter_map(|(a, v)| match *v {
|
||||
ValidityVote::Invalid(_) => None,
|
||||
|
||||
ValidityVote::Valid(ref s) =>
|
||||
Some((a, ValidityAttestation::Explicit(s.clone()))),
|
||||
ValidityVote::Issued(ref s) =>
|
||||
Some((a, ValidityAttestation::Implicit(s.clone()))),
|
||||
})
|
||||
.take(validity_threshold)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
validity_votes.len() == validity_threshold,
|
||||
"candidate is includable; therefore there are enough validity votes; qed",
|
||||
);
|
||||
|
||||
let availability_votes: Vec<_> = self.availability_votes.iter()
|
||||
.take(availability_threshold)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
availability_votes.len() == availability_threshold,
|
||||
"candidate is includable; therefore there are enough availability votes; qed",
|
||||
);
|
||||
|
||||
Some(AttestedCandidate {
|
||||
group_id: self.group_id.clone(),
|
||||
candidate: self.candidate.clone(),
|
||||
validity_votes,
|
||||
availability_votes,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Candidate data can be included in a proposal
|
||||
// if it has enough validity and availability votes
|
||||
// and no authorities have called it bad.
|
||||
@@ -267,7 +338,9 @@ impl<C: Context> Table<C> {
|
||||
/// best candidate for each group with requisite votes for inclusion.
|
||||
///
|
||||
/// The vector is sorted in ascending order by group id.
|
||||
pub fn proposed_candidates<'a>(&'a self, context: &C) -> Vec<&'a C::Candidate> {
|
||||
pub fn proposed_candidates(&self, context: &C) -> Vec<AttestedCandidate<
|
||||
C::GroupId, C::Candidate, C::AuthorityId, C::Signature,
|
||||
>> {
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::btree_map::Entry as BTreeEntry;
|
||||
|
||||
@@ -282,19 +355,26 @@ impl<C: Context> Table<C> {
|
||||
let (validity_t, availability_t) = context.requisite_votes(group_id);
|
||||
|
||||
if !candidate_data.can_be_included(validity_t, availability_t) { continue }
|
||||
let candidate = &candidate_data.candidate;
|
||||
match best_candidates.entry(group_id.clone()) {
|
||||
BTreeEntry::Vacant(vacant) => {
|
||||
vacant.insert((candidate_data, validity_t, availability_t));
|
||||
},
|
||||
BTreeEntry::Occupied(mut occ) => {
|
||||
let candidate_ref = occ.get_mut();
|
||||
if *candidate_ref > candidate {
|
||||
*candidate_ref = candidate;
|
||||
if candidate_ref.0.candidate > candidate_data.candidate {
|
||||
candidate_ref.0 = candidate_data;
|
||||
}
|
||||
}
|
||||
BTreeEntry::Vacant(vacant) => { vacant.insert(candidate); },
|
||||
}
|
||||
}
|
||||
|
||||
best_candidates.values().cloned().collect::<Vec<_>>()
|
||||
best_candidates.values()
|
||||
.map(|&(candidate_data, validity_t, availability_t)|
|
||||
candidate_data.attested(validity_t, availability_t)
|
||||
.expect("candidate has been checked includable; \
|
||||
therefore an attestation can be constructed; qed")
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Whether a candidate can be included.
|
||||
|
||||
@@ -25,7 +25,9 @@ pub mod generic;
|
||||
|
||||
pub use generic::Table;
|
||||
|
||||
use primitives::parachain::{Id, CandidateReceipt, CandidateSignature as Signature};
|
||||
use primitives::parachain::{
|
||||
Id, CandidateReceipt, CandidateSignature as Signature, Statement as PrimitiveStatement,
|
||||
};
|
||||
use primitives::{SessionKey, Hash};
|
||||
|
||||
/// Statements about candidates on the network.
|
||||
@@ -86,3 +88,14 @@ impl<C: Context> generic::Context for C {
|
||||
Context::requisite_votes(self, group)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Statement> for PrimitiveStatement {
|
||||
fn from(s: Statement) -> PrimitiveStatement {
|
||||
match s {
|
||||
generic::Statement::Valid(s) => PrimitiveStatement::Valid(s),
|
||||
generic::Statement::Invalid(s) => PrimitiveStatement::Invalid(s),
|
||||
generic::Statement::Candidate(s) => PrimitiveStatement::Candidate(s),
|
||||
generic::Statement::Available(s) => PrimitiveStatement::Available(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user